fix margin calls

This commit is contained in:
Daniel Larimer 2015-06-19 09:07:23 -04:00
parent 2c055d2320
commit d5fb32a839
4 changed files with 50 additions and 112 deletions

View file

@ -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();

View file

@ -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

View file

@ -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;