update black swan implementation

This commit is contained in:
Daniel Larimer 2015-06-19 09:57:23 -04:00
parent d5fb32a839
commit be5a8c6365
5 changed files with 77 additions and 98 deletions

View file

@ -247,6 +247,7 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita
FC_ASSERT(a.is_market_issued(), "Cannot update BitAsset-specific settings on a non-BitAsset.");
const asset_bitasset_data_object& b = a.bitasset_data(d);
FC_ASSERT( !b.has_settlement(), "Cannot update a bitasset after a settlement has executed" );
if( o.new_options.short_backing_asset != b.options.short_backing_asset )
{
FC_ASSERT(a.dynamic_asset_data_id(d).current_supply == 0);
@ -345,21 +346,40 @@ object_id_type asset_settle_evaluator::do_evaluate(const asset_settle_evaluator:
const database& d = db();
asset_to_settle = &op.amount.asset_id(d);
FC_ASSERT(asset_to_settle->is_market_issued());
FC_ASSERT(asset_to_settle->can_force_settle());
const auto& bitasset = asset_to_settle->bitasset_data(d);
FC_ASSERT(asset_to_settle->can_force_settle() || bitasset.has_settlement() );
FC_ASSERT(d.get_balance(d.get(op.account), *asset_to_settle) >= op.amount);
return d.get_index_type<force_settlement_index>().get_next_id();
}
object_id_type asset_settle_evaluator::do_apply(const asset_settle_evaluator::operation_type& op)
operation_result asset_settle_evaluator::do_apply(const asset_settle_evaluator::operation_type& op)
{
database& d = db();
d.adjust_balance(op.account, -op.amount);
return d.create<force_settlement_object>([&](force_settlement_object& s) {
s.owner = op.account;
s.balance = op.amount;
s.settlement_date = d.head_block_time() + asset_to_settle->bitasset_data(d).options.force_settlement_delay_sec;
}).id;
const auto& bitasset = asset_to_settle->bitasset_data(d);
if( bitasset.has_settlement() )
{
auto settled_amount = op.amount * bitasset.settlement_price;
FC_ASSERT( settled_amount.amount <= bitasset.settlement_fund );
d.modify( bitasset, [&]( asset_bitasset_data_object& obj ){
obj.settlement_fund -= settled_amount.amount;
});
d.adjust_balance(op.account, settled_amount);
return settled_amount;
}
else
{
return d.create<force_settlement_object>([&](force_settlement_object& s) {
s.owner = op.account;
s.balance = op.amount;
s.settlement_date = d.head_block_time() + asset_to_settle->bitasset_data(d).options.force_settlement_delay_sec;
}).id;
}
}
void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_operation& o)
@ -371,6 +391,7 @@ void_result asset_publish_feeds_evaluator::do_evaluate(const asset_publish_feed_
FC_ASSERT(base.is_market_issued());
const asset_bitasset_data_object& bitasset = base.bitasset_data(d);
FC_ASSERT( !bitasset.has_settlement(), "No further feeds may be published after a settlement event" );
FC_ASSERT(o.feed.settlement_price.quote.asset_id == bitasset.options.short_backing_asset);
//Verify that the publisher is authoritative to publish a feed
if( base.issuer == account_id_type() )

View file

@ -35,6 +35,10 @@ void_result call_order_update_evaluator::do_evaluate(const call_order_update_ope
_bitasset_data = &_debt_asset->bitasset_data(d);
/// if there is a settlement for this asset, then no further margin positions may be taken and
/// all existing margin positions should have been closed va database::globally_settle_asset
FC_ASSERT( !_bitasset_data->has_settlement() );
FC_ASSERT( o.delta_collateral.asset_id == _bitasset_data->options.short_backing_asset );
if( _bitasset_data->is_prediction_market )

View file

@ -28,14 +28,12 @@
namespace graphene { namespace chain {
/**
for each short order, fill it at settlement price and place funds received into a total
calculate the USD->CORE price and convert all USD balances to CORE at that price and subtract CORE from total
- any fees accumulated by the issuer in the bitasset are forfeit / not redeemed
- cancel all open orders with bitasset in it
- any bitassets that use this bitasset as collateral are immediately settled at their feed price
- convert all balances in bitasset to CORE and subtract from total
- any prediction markets with usd as the backing get converted to CORE as the backing
any CORE left over due to rounding errors is paid to accumulated fees
* All margin positions are force closed at the swan price
* Collateral received goes into a force-settlement fund
* No new margin positions can be created for this asset
* No more price feed updates
* Force settlement happens without delay at the swan price, deducting from force-settlement fund
* No more asset updates may be issued.
*/
void database::globally_settle_asset( const asset_object& mia, const price& settlement_price )
{ try {
@ -45,6 +43,8 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett
edump( (mia.symbol)(settlement_price) );
const asset_bitasset_data_object& bitasset = mia.bitasset_data(*this);
FC_ASSERT( !bitasset.has_settlement(), "black swan already occurred, it should not happen again" );
const asset_object& backing_asset = bitasset.options.short_backing_asset(*this);
asset collateral_gathered = backing_asset.amount(0);
@ -54,93 +54,30 @@ void database::globally_settle_asset( const asset_object& mia, const price& sett
const call_order_index& call_index = get_index_type<call_order_index>();
const auto& call_price_index = call_index.indices().get<by_price>();
auto call_itr = call_price_index.lower_bound( price::min( bitasset.options.short_backing_asset, mia.id ) );
auto call_end = call_price_index.upper_bound( price::max( bitasset.options.short_backing_asset, mia.id ) );
while( call_itr != call_end )
{
auto pays = call_itr->get_debt() * settlement_price;
wdump( (call_itr->get_debt() ) );
collateral_gathered += pays;
const auto& order = *call_itr;
++call_itr;
FC_ASSERT( fill_order( order, pays, order.get_debt() ) );
}
const limit_order_index& limit_index = get_index_type<limit_order_index>();
const auto& limit_price_index = limit_index.indices().get<by_price>();
auto max_short_squeeze = bitasset.current_feed.max_short_squeeze_price();
// cancel all orders selling the market issued asset
auto limit_itr = limit_price_index.lower_bound(price::max(mia.id, bitasset.options.short_backing_asset));
auto limit_end = limit_price_index.upper_bound(~max_short_squeeze);
while( limit_itr != limit_end )
{
const auto& order = *limit_itr;
ilog( "CANCEL LIMIT ORDER" );
idump((order));
++limit_itr;
cancel_order( order );
}
limit_itr = limit_price_index.begin();
while( limit_itr != limit_end )
{
if( limit_itr->amount_for_sale().asset_id == mia.id )
{
const auto& order = *limit_itr;
ilog( "CANCEL_AGAIN" );
edump((order));
++limit_itr;
cancel_order( order );
}
}
limit_itr = limit_price_index.begin();
while( limit_itr != limit_end )
// cancel all call orders and accumulate it into collateral_gathered
auto call_itr = call_price_index.lower_bound( price::min( bitasset.options.short_backing_asset, mia.id ) );
auto call_end = call_price_index.upper_bound( price::max( bitasset.options.short_backing_asset, mia.id ) );
while( call_itr != call_end )
{
if( limit_itr->amount_for_sale().asset_id == mia.id )
{
const auto& order = *limit_itr;
edump((order));
++limit_itr;
cancel_order( order );
}
auto pays = call_itr->get_debt() * settlement_price;
wdump( (call_itr->get_debt() ) );
collateral_gathered += pays;
const auto& order = *call_itr;
++call_itr;
FC_ASSERT( fill_order( order, pays, order.get_debt() ) );
}
// settle all balances
asset total_mia_settled = mia.amount(0);
modify( bitasset, [&]( asset_bitasset_data_object& obj ){
obj.settlement_price = settlement_price;
obj.settlement_fund = collateral_gathered.amount;
});
const auto& index = get_index_type<account_balance_index>().indices().get<by_asset>();
auto range = index.equal_range(mia.get_id());
for( auto itr = range.first; itr != range.second; ++itr )
{
auto mia_balance = itr->get_balance();
if( mia_balance.amount > 0 )
{
adjust_balance(itr->owner, -mia_balance);
auto settled_amount = mia_balance * settlement_price;
idump( (mia_balance)(settled_amount)(settlement_price) );
adjust_balance(itr->owner, settled_amount);
total_mia_settled += mia_balance;
collateral_gathered -= settled_amount;
}
}
/// TODO: after all margin positions are closed, the current supply will be reported as 0, but
/// that is a lie, the supply didn't change. We need to capture the current supply before
/// filling all call orders and then restore it afterward. Then in the force settlement
/// evaluator reduce the supply
// TODO: convert payments held in escrow
modify( mia_dyn, [&]( asset_dynamic_data_object& obj ){
total_mia_settled.amount += obj.accumulated_fees;
obj.accumulated_fees = 0;
});
wlog( "====================== AFTER SETTLE BLACK SWAN UNCLAIMED SETTLEMENT FUNDS ==============\n" );
wdump((collateral_gathered)(total_mia_settled)(original_mia_supply)(mia_dyn.current_supply));
modify( bitasset.options.short_backing_asset(*this).dynamic_asset_data_id(*this), [&]( asset_dynamic_data_object& obj ){
obj.accumulated_fees += collateral_gathered.amount;
});
FC_ASSERT( total_mia_settled.amount == original_mia_supply, "", ("total_settled",total_mia_settled)("original",original_mia_supply) );
} FC_CAPTURE_AND_RETHROW( (mia)(settlement_price) ) }
} FC_CAPTURE_AND_RETHROW( (mia)(settlement_price) ) }
void database::cancel_order(const force_settlement_object& order, bool create_virtual_op)
{

View file

@ -114,7 +114,7 @@ namespace graphene { namespace chain {
typedef asset_settle_operation operation_type;
object_id_type do_evaluate(const operation_type& op);
object_id_type do_apply(const operation_type& op);
operation_result do_apply(const operation_type& op);
const asset_object* asset_to_settle = nullptr;
};

View file

@ -250,6 +250,21 @@ namespace graphene { namespace chain {
/// Calculate the maximum force settlement volume per maintenance interval, given the current share supply
share_type max_force_settlement_volume(share_type current_supply)const;
/** return true if there has been a black swan, false otherwise */
bool has_settlement()const { return !settlement_price.is_null(); }
/**
* In the event of a black swan, the swan price is saved in the settlement price,
* and all margin positions are settled at the same price with the siezed collateral
* being moved into the settlement fund. From this point on no further updates to
* the asset are permitted (no feeds, etc) and forced settlement occurs using
* the settlement price and fund.
*/
///@{
price settlement_price;
share_type settlement_fund;
///@}
time_point_sec feed_expiration_time()const
{ return current_feed_publication_time + options.feed_lifetime_sec; }
bool feed_is_expired(time_point_sec current_time)const
@ -292,6 +307,8 @@ FC_REFLECT_DERIVED( graphene::chain::asset_bitasset_data_object, (graphene::db::
(options)
(force_settled_volume)
(is_prediction_market)
(settlement_price)
(settlement_fund)
)
FC_REFLECT( graphene::chain::asset_object::asset_options,