fix margin calls
This commit is contained in:
parent
2c055d2320
commit
d5fb32a839
4 changed files with 50 additions and 112 deletions
|
|
@ -130,7 +130,7 @@ namespace graphene { namespace chain {
|
|||
{
|
||||
asset collateral = settlement_price.quote;
|
||||
fc::uint128 tmp( collateral.amount.value );
|
||||
tmp *= maximum_short_squeeze_ratio - 1000;
|
||||
tmp *= maximum_short_squeeze_ratio;
|
||||
tmp /= 1000;
|
||||
FC_ASSERT( tmp <= GRAPHENE_MAX_SHARE_SUPPLY );
|
||||
collateral.amount = tmp.to_uint64();
|
||||
|
|
@ -140,7 +140,7 @@ namespace graphene { namespace chain {
|
|||
{
|
||||
asset collateral = settlement_price.quote;
|
||||
fc::uint128 tmp( collateral.amount.value );
|
||||
tmp *= maintenance_collateral_ratio - 1000;
|
||||
tmp *= maintenance_collateral_ratio;
|
||||
tmp /= 1000;
|
||||
FC_ASSERT( tmp <= GRAPHENE_MAX_SHARE_SUPPLY );
|
||||
collateral.amount = tmp.to_uint64();
|
||||
|
|
|
|||
|
|
@ -290,7 +290,7 @@ bool database::fill_order( const limit_order_object& order, const asset& pays, c
|
|||
|
||||
bool database::fill_order( const call_order_object& order, const asset& pays, const asset& receives )
|
||||
{ try {
|
||||
idump((pays)(receives)(order));
|
||||
//idump((pays)(receives)(order));
|
||||
assert( order.get_debt().asset_id == receives.asset_id );
|
||||
assert( order.get_collateral().asset_id == pays.asset_id );
|
||||
assert( order.get_collateral() >= pays );
|
||||
|
|
@ -311,7 +311,7 @@ bool database::fill_order( const call_order_object& order, const asset& pays, co
|
|||
const asset_dynamic_data_object& mia_ddo = mia.dynamic_asset_data_id(*this);
|
||||
|
||||
modify( mia_ddo, [&]( asset_dynamic_data_object& ao ){
|
||||
idump((receives));
|
||||
//idump((receives));
|
||||
ao.current_supply -= receives.amount;
|
||||
});
|
||||
|
||||
|
|
@ -391,8 +391,11 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa
|
|||
// looking for limit orders selling the most USD for the least CORE
|
||||
auto max_price = price::max( mia.id, bitasset.options.short_backing_asset );
|
||||
// stop when limit orders are selling too little USD for too much CORE
|
||||
//auto min_price = bitasset.current_feed.max_short_squeeze_price();
|
||||
auto min_price = price::min( mia.id, bitasset.options.short_backing_asset );
|
||||
auto min_price = bitasset.current_feed.max_short_squeeze_price();
|
||||
// edump((bitasset.current_feed));
|
||||
// edump((min_price.to_real())(min_price));
|
||||
//auto min_price = price::min( mia.id, bitasset.options.short_backing_asset );
|
||||
/*
|
||||
idump((bitasset.current_feed.settlement_price)(bitasset.current_feed.settlement_price.to_real()));
|
||||
{
|
||||
for( const auto& order : limit_price_index )
|
||||
|
|
@ -406,6 +409,7 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa
|
|||
wdump((max_price)(max_price.to_real()));
|
||||
wdump((min_price)(min_price.to_real()));
|
||||
}
|
||||
*/
|
||||
|
||||
assert( max_price.base.asset_id == min_price.base.asset_id );
|
||||
// wlog( "from ${a} Debt/Col to ${b} Debt/Col ", ("a", max_price.to_real())("b",min_price.to_real()) );
|
||||
|
|
@ -413,21 +417,23 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa
|
|||
auto limit_itr = limit_price_index.lower_bound( max_price );
|
||||
auto limit_end = limit_price_index.upper_bound( min_price );
|
||||
|
||||
/*
|
||||
if( limit_itr != limit_price_index.end() )
|
||||
wdump((*limit_itr)(limit_itr->sell_price.to_real()));
|
||||
if( limit_end != limit_price_index.end() )
|
||||
wdump((*limit_end)(limit_end->sell_price.to_real()));
|
||||
*/
|
||||
|
||||
if( limit_itr == limit_end )
|
||||
{
|
||||
wlog( "no orders available to fill margin calls" );
|
||||
// wlog( "no orders available to fill margin calls" );
|
||||
return false;
|
||||
}
|
||||
|
||||
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 ) );
|
||||
|
||||
bool filled_short_or_limit = false;
|
||||
bool filled_limit = false;
|
||||
|
||||
while( call_itr != call_end )
|
||||
{
|
||||
|
|
@ -440,16 +446,20 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa
|
|||
match_price = limit_itr->sell_price;
|
||||
usd_for_sale = limit_itr->amount_for_sale();
|
||||
}
|
||||
else return filled_short_or_limit;
|
||||
else return filled_limit;
|
||||
// wdump((match_price));
|
||||
// edump((usd_for_sale));
|
||||
|
||||
match_price.validate();
|
||||
|
||||
// wdump((match_price)(~call_itr->call_price) );
|
||||
if( match_price > ~call_itr->call_price )
|
||||
{
|
||||
return filled_short_or_limit;
|
||||
return filled_limit;
|
||||
}
|
||||
|
||||
auto usd_to_buy = call_itr->get_debt();
|
||||
// edump((usd_to_buy));
|
||||
|
||||
if( usd_to_buy * match_price > call_itr->get_collateral() )
|
||||
{
|
||||
|
|
@ -462,12 +472,13 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa
|
|||
asset call_pays, call_receives, order_pays, order_receives;
|
||||
if( usd_to_buy >= usd_for_sale )
|
||||
{ // fill order
|
||||
//ilog( "filling all of limit order" );
|
||||
call_receives = usd_for_sale;
|
||||
order_receives = usd_for_sale * match_price;
|
||||
call_pays = order_receives;
|
||||
order_pays = usd_for_sale;
|
||||
|
||||
filled_short_or_limit = true;
|
||||
filled_limit = true;
|
||||
filled_call = (usd_to_buy == usd_for_sale);
|
||||
}
|
||||
else // fill call
|
||||
|
|
@ -484,11 +495,11 @@ bool database::check_call_orders( const asset_object& mia, bool enable_black_swa
|
|||
if( filled_call ) ++call_itr;
|
||||
fill_order( *old_call_itr, call_pays, call_receives );
|
||||
|
||||
auto old_limit_itr = !filled_call ? limit_itr++ : limit_itr;
|
||||
auto old_limit_itr = filled_limit ? limit_itr++ : limit_itr;
|
||||
fill_order( *old_limit_itr, order_pays, order_receives );
|
||||
} // whlie call_itr != call_end
|
||||
|
||||
return filled_short_or_limit;
|
||||
return filled_limit;
|
||||
} FC_CAPTURE_AND_RETHROW() }
|
||||
|
||||
void database::pay_order( const account_object& receiver, const asset& receives, const asset& pays )
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit dd1c77b327c6eba807168856c3c12e90173468c4
|
||||
Subproject commit dde8ed9d7ab49807f2556488c0815f3741b11e00
|
||||
|
|
@ -145,6 +145,10 @@ BOOST_AUTO_TEST_CASE( call_order_update_test )
|
|||
* 1. highest bid is lower than the call price of an order
|
||||
* 2. the asset is not a prediction market
|
||||
* 3. there is a valid price feed
|
||||
*
|
||||
* This test creates two scenarios:
|
||||
* a) when the bids are above the short squeese limit (should execute)
|
||||
* b) when the bids are below the short squeeze limit (should not execute)
|
||||
*/
|
||||
BOOST_AUTO_TEST_CASE( margin_call_limit_test )
|
||||
{ try {
|
||||
|
|
@ -168,111 +172,34 @@ BOOST_AUTO_TEST_CASE( margin_call_limit_test )
|
|||
publish_feed( bitusd, feedproducer, current_feed );
|
||||
|
||||
// start out with 2:1 collateral
|
||||
borrow( borrower, bitusd.amount(5000), asset(10000), default_call_price );
|
||||
borrow( borrower2, bitusd.amount(5000), asset(30000), default_call_price );
|
||||
elog( "selling USD at 1:1, should be ok because we have 2:1 collateral" );
|
||||
create_sell_order( borrower, bitusd.amount(5000), core.amount(5000) );
|
||||
elog( "buying USD at 1:1" );
|
||||
create_sell_order( buyer, core.amount(5000), bitusd.amount(5000) );
|
||||
borrow( borrower, bitusd.amount(1000), asset(2000), default_call_price );
|
||||
borrow( borrower2, bitusd.amount(1000), asset(4000), default_call_price );
|
||||
|
||||
BOOST_REQUIRE_EQUAL( get_balance( borrower, bitusd ), 0 );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( buyer, bitusd ), 4950 ); // 1% market fee
|
||||
BOOST_REQUIRE_EQUAL( get_balance( borrower, core ), init_balance - 10000 + 5000 );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( buyer, core ), init_balance - 5000 );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( borrower, bitusd ), 1000 );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( borrower2, bitusd ), 1000 );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( borrower , core ), init_balance - 2000 );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( borrower2, core ), init_balance - 4000 );
|
||||
|
||||
ilog( "print call orders..." );
|
||||
print_call_orders();
|
||||
ilog( "print market..." );
|
||||
print_market( "", "" );
|
||||
// this should trigger margin call that is below the call limit, but above the
|
||||
// protection threshold.
|
||||
BOOST_TEST_MESSAGE( "Creating a margin call that is NOT protected by the max short squeeze price" );
|
||||
auto order = create_sell_order( borrower2, bitusd.amount(1000), core.amount(1400) );
|
||||
BOOST_REQUIRE( order == nullptr );
|
||||
|
||||
// sell the BitUSD for 50% more which represents a 33% fall in the value of the collateral
|
||||
const auto order = create_sell_order( borrower2, bitusd.amount(1000), core.amount(1400) );
|
||||
const auto order2 = create_sell_order( borrower2, bitusd.amount(1000), core.amount(1600) );
|
||||
const auto order3 = create_sell_order( borrower2, bitusd.amount(1000), core.amount(1700) );
|
||||
const auto order4 = create_sell_order( borrower2, bitusd.amount(1000), core.amount(1800) );
|
||||
const auto order5 = create_sell_order( borrower2, bitusd.amount(950), core.amount(1850) );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( borrower2, core ), init_balance - 4000 + 1400 );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( borrower2, bitusd ), 0 );
|
||||
|
||||
ilog( "print call orders..." );
|
||||
print_call_orders();
|
||||
ilog( "print market..." );
|
||||
print_market( "", "" );
|
||||
|
||||
FC_ASSERT( order, "order should not match because sell price is below short squeeze protection" );
|
||||
wdump((bitusd.bitasset_data(db).current_feed.max_short_squeeze_price().to_real()));
|
||||
wdump((bitusd.bitasset_data(db).current_feed.maintenance_price().to_real()));
|
||||
wdump(((order->sell_price).to_real()));
|
||||
|
||||
// update the feed to indicate a 33% drop in value
|
||||
BOOST_TEST_MESSAGE( "Update feed to indicate CORE fell in value by 33%" );
|
||||
current_feed.settlement_price = bitusd.amount( 100 ) / core.amount(200);
|
||||
publish_feed( bitusd, feedproducer, current_feed );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( borrower, core ), init_balance - 2000 + 600 );
|
||||
BOOST_REQUIRE_EQUAL( get_balance( borrower, bitusd ), 1000 );
|
||||
|
||||
|
||||
/*
|
||||
const asset_object& bitusd = create_bitasset( "BITUSD" );
|
||||
const asset_object& core = get_asset( GRAPHENE_SYMBOL );
|
||||
BOOST_TEST_MESSAGE( "Creating a margin call that is protected by the max short squeeze price" );
|
||||
borrow( borrower, bitusd.amount(1000), asset(2000), default_call_price );
|
||||
borrow( borrower2, bitusd.amount(1000), asset(4000), default_call_price );
|
||||
|
||||
db.modify( bitusd.bitasset_data(db), [&]( asset_bitasset_data_object& usd ){
|
||||
usd.current_feed.call_limit = core.amount(3) / bitusd.amount(1);
|
||||
});
|
||||
|
||||
const account_object& shorter1 = create_account( "shorter1" );
|
||||
const account_object& shorter2 = create_account( "shorter2" );
|
||||
const account_object& buyer1 = create_account( "buyer1" );
|
||||
const account_object& buyer2 = create_account( "buyer2" );
|
||||
|
||||
transfer( genesis_account(db), shorter1, asset( 10000 ) );
|
||||
transfer( genesis_account(db), shorter2, asset( 10000 ) );
|
||||
transfer( genesis_account(db), buyer1, asset( 10000 ) );
|
||||
transfer( genesis_account(db), buyer2, asset( 10000 ) );
|
||||
|
||||
BOOST_REQUIRE( create_sell_order( buyer1, asset(1000), bitusd.amount(1000) ) );
|
||||
BOOST_REQUIRE( !create_short( shorter1, bitusd.amount(1000), asset(1000) ) );
|
||||
BOOST_REQUIRE_EQUAL( get_balance(buyer1, bitusd), 990 ); // 1000 - 1% fee
|
||||
|
||||
const auto& call_index = db.get_index_type<call_order_index>().indices().get<by_account>();
|
||||
const auto call_itr = call_index.find(boost::make_tuple(shorter1.id, bitusd.id));
|
||||
BOOST_REQUIRE( call_itr != call_index.end() );
|
||||
const call_order_object& call = *call_itr;
|
||||
BOOST_CHECK(call.get_collateral() == core.amount(2000));
|
||||
BOOST_CHECK(call.get_debt() == bitusd.amount(1000));
|
||||
BOOST_CHECK(call.call_price == price(core.amount(1500), bitusd.amount(1000)));
|
||||
BOOST_CHECK_EQUAL(get_balance(shorter1, core), 9000);
|
||||
|
||||
ilog( "=================================== START===================================\n\n");
|
||||
// this should cause the highest bid to below the margin call threshold
|
||||
// which means it should be filled by the cover
|
||||
auto unmatched = create_sell_order( buyer1, bitusd.amount(495), core.amount(750) );
|
||||
if( unmatched ) edump((*unmatched));
|
||||
BOOST_CHECK( !unmatched );
|
||||
BOOST_CHECK(call.get_debt() == bitusd.amount(505));
|
||||
BOOST_CHECK(call.get_collateral() == core.amount(1250));
|
||||
|
||||
auto below_call_price = create_sell_order(buyer1, bitusd.amount(200), core.amount(1));
|
||||
BOOST_REQUIRE(below_call_price);
|
||||
auto above_call_price = create_sell_order(buyer1, bitusd.amount(200), core.amount(303));
|
||||
BOOST_REQUIRE(above_call_price);
|
||||
auto above_id = above_call_price->id;
|
||||
|
||||
cancel_limit_order(*below_call_price);
|
||||
BOOST_CHECK_THROW(db.get_object(above_id), fc::exception);
|
||||
BOOST_CHECK(call.get_debt() == bitusd.amount(305));
|
||||
BOOST_CHECK(call.get_collateral() == core.amount(947));
|
||||
|
||||
below_call_price = create_sell_order(buyer1, bitusd.amount(200), core.amount(1));
|
||||
BOOST_REQUIRE(below_call_price);
|
||||
auto below_id = below_call_price->id;
|
||||
above_call_price = create_sell_order(buyer1, bitusd.amount(95), core.amount(144));
|
||||
BOOST_REQUIRE(above_call_price);
|
||||
above_id = above_call_price->id;
|
||||
auto match_below_call = create_sell_order(buyer2, core.amount(1), bitusd.amount(200));
|
||||
BOOST_CHECK(!match_below_call);
|
||||
|
||||
BOOST_CHECK_THROW(db.get_object(above_id), fc::exception);
|
||||
BOOST_CHECK_THROW(db.get_object(below_id), fc::exception);
|
||||
BOOST_CHECK(call.get_debt() == bitusd.amount(210));
|
||||
BOOST_CHECK(call.get_collateral() == core.amount(803));
|
||||
*/
|
||||
// this should trigger margin call without protection from the price feed.
|
||||
order = create_sell_order( borrower2, bitusd.amount(1000), core.amount(1800) );
|
||||
BOOST_REQUIRE( order != nullptr );
|
||||
} catch( const fc::exception& e) {
|
||||
edump((e.to_detail_string()));
|
||||
throw;
|
||||
|
|
|
|||
Loading…
Reference in a new issue