void cancel_all_shorts() { for( auto short_itr = _db_impl._short_db.begin(); short_itr.valid(); ++short_itr ) { const market_index_key market_idx = short_itr.key(); const order_record order_rec = short_itr.value(); _current_bid = market_order( short_order, market_idx, order_rec ); // Initialize the market transaction market_transaction mtrx; mtrx.bid_owner = _current_bid->get_owner(); mtrx.bid_type = short_order; cancel_current_short( mtrx, market_idx.order_price.quote_asset_id ); push_market_transaction( mtrx ); } }
void market_engine_v7::cancel_high_apr_shorts() { static const fc::uint128 max_apr = fc::uint128( BTS_BLOCKCHAIN_MAX_SHORT_APR_PCT ) * FC_REAL128_PRECISION / 100; for( auto short_itr = _db_impl._short_db.begin(); short_itr.valid(); ++short_itr ) { const market_index_key market_idx = short_itr.key(); if( market_idx.order_price.ratio <= max_apr ) continue; const order_record order_rec = short_itr.value(); _current_bid = market_order( short_order, market_idx, order_rec, order_rec.balance, market_idx.order_price ); // Initialize the market transaction market_transaction mtrx; mtrx.bid_index.owner = _current_bid->get_owner(); mtrx.bid_type = short_order; cancel_current_short( mtrx, market_idx.order_price.quote_asset_id ); push_market_transaction( mtrx ); } _pending_state->apply_changes(); }
void execute( asset_id_type quote_id, asset_id_type base_id, const fc::time_point_sec& timestamp ) { try { const uint32_t pending_block_num = _pending_state->get_head_block_num(); _quote_id = quote_id; _base_id = base_id; oasset_record quote_asset = _pending_state->get_asset_record( _quote_id ); oasset_record base_asset = _pending_state->get_asset_record( _base_id ); FC_ASSERT( quote_asset.valid() && base_asset.valid() ); // The order book is sorted from low to high price. So to get the last item (highest bid), // we need to go to the first item in the next market class and then back up one const price next_pair = (base_id+1 == quote_id) ? price( 0, quote_id+1, 0 ) : price( 0, quote_id, base_id+1 ); _bid_itr = _db_impl._bid_db.lower_bound( market_index_key( next_pair ) ); _ask_itr = _db_impl._ask_db.lower_bound( market_index_key( price( 0, quote_id, base_id) ) ); _short_itr = _db_impl._short_db.lower_bound( market_index_key( price( 0, quote_id, base_id) ) ); _collateral_itr = _db_impl._collateral_db.lower_bound( market_index_key( next_pair ) ); if( !_ask_itr.valid() ) { wlog( "ask iter invalid..." ); _ask_itr = _db_impl._ask_db.begin(); } if( _bid_itr.valid() ) --_bid_itr; else _bid_itr = _db_impl._bid_db.last(); if( _collateral_itr.valid() ) --_collateral_itr; else _collateral_itr = _db_impl._collateral_db.last(); asset trading_volume(0, base_id); // Set initial market status { omarket_status market_stat = _pending_state->get_market_status( _quote_id, _base_id ); if( !market_stat ) market_stat = market_status( quote_id, base_id, 0, 0 ); _market_stat = *market_stat; } price min_cover_ask; price opening_price; price closing_price; const oprice median_feed_price = _db_impl.self->get_median_delegate_price( quote_id, base_id ); if( quote_asset->is_market_issued() ) { // If bootstrapping market for the very first time if( _market_stat.center_price.ratio == fc::uint128_t() ) { if( median_feed_price.valid() ) _market_stat.center_price = *median_feed_price; else FC_CAPTURE_AND_THROW( insufficient_feeds, (quote_id) ); } min_cover_ask = _market_stat.minimum_ask(); } int last_orders_filled = -1; bool order_did_execute = false; // prime the pump, to make sure that margin calls (asks) have a bid to check against. get_next_bid(); get_next_ask(); idump( (_current_bid)(_current_ask) ); while( get_next_bid() && get_next_ask() ) { idump( (_current_bid)(_current_ask) ); // Make sure that at least one order was matched every time we enter the loop FC_ASSERT( _orders_filled != last_orders_filled, "We appear caught in an order matching loop" ); last_orders_filled = _orders_filled; const asset bid_quantity_xts = _current_bid->get_quantity(); const asset ask_quantity_xts = _current_ask->get_quantity(); asset current_bid_balance = _current_bid->get_balance(); // Initialize the market transaction market_transaction mtrx; mtrx.bid_owner = _current_bid->get_owner(); mtrx.ask_owner = _current_ask->get_owner(); // Always execute shorts at the center price mtrx.bid_price = (_current_bid->type != short_order) ? _current_bid->get_price() : _market_stat.center_price; mtrx.ask_price = _current_ask->get_price(); mtrx.bid_type = _current_bid->type; mtrx.ask_type = _current_ask->type; if( _current_ask->type == cover_order && _current_bid->type == short_order ) { FC_ASSERT( quote_asset->is_market_issued() ); /** don't allow new shorts to execute unless there is a feed, all other * trades are still valid. (we shouldn't stop the market) */ if( !median_feed_price.valid() ) { _current_bid.reset(); continue; } if( mtrx.ask_price < mtrx.bid_price ) // The call price has not been reached break; if( _current_bid->state.short_price_limit.valid() ) { if( *_current_bid->state.short_price_limit < mtrx.ask_price ) { _current_bid.reset(); continue; // skip shorts that are over the price limit. } mtrx.bid_price = *_current_bid->state.short_price_limit; } mtrx.ask_price = mtrx.bid_price; // Bound collateral ratio price collateral_rate = _current_bid->get_price(); if( collateral_rate > _market_stat.center_price ) collateral_rate = _market_stat.center_price; const asset ask_quantity_usd = _current_ask->get_quote_quantity(); const asset short_quantity_usd = _current_bid->get_balance() / collateral_rate; const asset trade_quantity_usd = std::min( short_quantity_usd, ask_quantity_usd ); mtrx.ask_received = trade_quantity_usd; mtrx.bid_paid = mtrx.ask_received; mtrx.ask_paid = mtrx.ask_received * mtrx.ask_price; mtrx.bid_received = mtrx.ask_paid; mtrx.bid_collateral = mtrx.bid_paid / collateral_rate; // Handle rounding errors if( (*mtrx.bid_collateral - _current_bid->get_balance()).amount < BTS_BLOCKCHAIN_PRECISION ) mtrx.bid_collateral = _current_bid->get_balance(); // If too little collateral at this price if( *mtrx.bid_collateral < mtrx.ask_paid ) { edump( (mtrx) ); _current_bid.reset(); continue; } pay_current_short( mtrx, *quote_asset ); pay_current_cover( mtrx, *quote_asset ); _market_stat.bid_depth -= mtrx.bid_collateral->amount; _market_stat.ask_depth += mtrx.bid_collateral->amount; order_did_execute = true; } else if( _current_ask->type == cover_order && _current_bid->type == bid_order ) { FC_ASSERT( quote_asset->is_market_issued() ); if( mtrx.ask_price < mtrx.bid_price ) // The call price has not been reached break; /** * Don't allow margin calls to be executed too far below * the minimum ask, this could lead to an attack where someone * walks the whole book to steal the collateral. */ if( mtrx.bid_price < _market_stat.minimum_ask() ) { _current_ask.reset(); continue; } mtrx.ask_price = mtrx.bid_price; const asset max_usd_purchase = asset( *_current_ask->collateral, _base_id ) * mtrx.bid_price; asset usd_exchanged = std::min( current_bid_balance, max_usd_purchase ); // Bound quote asset amount exchanged const asset required_usd_purchase = _current_ask->get_balance(); if( required_usd_purchase < usd_exchanged ) usd_exchanged = required_usd_purchase; mtrx.ask_received = usd_exchanged; mtrx.bid_paid = mtrx.ask_received; // Handle rounding errors if( usd_exchanged == max_usd_purchase ) mtrx.ask_paid = asset(*_current_ask->collateral,_base_id); else mtrx.ask_paid = usd_exchanged * mtrx.bid_price; mtrx.bid_received = mtrx.ask_paid; pay_current_bid( mtrx, *quote_asset ); pay_current_cover( mtrx, *quote_asset ); // TODO: Do we need to decrease bid depth as well? _market_stat.ask_depth -= mtrx.ask_paid.amount; order_did_execute = true; } else if( _current_ask->type == ask_order && _current_bid->type == short_order ) { FC_ASSERT( quote_asset->is_market_issued() ); /** don't allow new shorts to execute unless there is a feed, all other * trades are still valid. */ if( !median_feed_price.valid() ) { _current_bid.reset(); continue; } if( mtrx.bid_price < mtrx.ask_price ) // The ask price hasn't been reached break; if( _current_bid->state.short_price_limit.valid() ) { if( *_current_bid->state.short_price_limit < mtrx.ask_price ) { elog( "short price limit < bid price" ); _current_bid.reset(); continue; // skip shorts that are over the price limit. } mtrx.bid_price = *_current_bid->state.short_price_limit; } // Bound collateral ratio price collateral_rate = _current_bid->get_price(); if( collateral_rate > _market_stat.center_price ) collateral_rate = _market_stat.center_price; const asset ask_quantity_usd = _current_ask->get_quote_quantity(); const asset short_quantity_usd = _current_bid->get_balance() / collateral_rate; const asset trade_quantity_usd = std::min( short_quantity_usd, ask_quantity_usd ); mtrx.ask_received = trade_quantity_usd; mtrx.bid_paid = mtrx.ask_received; mtrx.ask_paid = mtrx.ask_received * mtrx.ask_price; mtrx.bid_received = mtrx.ask_paid; mtrx.bid_collateral = mtrx.bid_paid / collateral_rate; // Handle rounding errors if( (*mtrx.bid_collateral - _current_bid->get_balance()).amount < BTS_BLOCKCHAIN_PRECISION ) mtrx.bid_collateral = _current_bid->get_balance(); // If too little collateral at this price if( *mtrx.bid_collateral < mtrx.ask_paid ) { edump( (mtrx) ); _current_bid.reset(); continue; } pay_current_short( mtrx, *quote_asset ); pay_current_ask( mtrx, *quote_asset ); _market_stat.bid_depth -= mtrx.bid_collateral->amount; _market_stat.ask_depth += mtrx.bid_collateral->amount; order_did_execute = true; } else if( _current_ask->type == ask_order && _current_bid->type == bid_order ) { if( mtrx.bid_price < mtrx.ask_price ) // The ask price hasn't been reached break; const asset quantity_xts = std::min( bid_quantity_xts, ask_quantity_xts ); // Everyone gets the price they asked for mtrx.ask_received = quantity_xts * mtrx.ask_price; mtrx.bid_paid = quantity_xts * mtrx.bid_price; mtrx.ask_paid = quantity_xts; mtrx.bid_received = quantity_xts; // Handle rounding errors if( quantity_xts == bid_quantity_xts ) mtrx.bid_paid = current_bid_balance; mtrx.fees_collected = mtrx.bid_paid - mtrx.ask_received; pay_current_bid( mtrx, *quote_asset ); pay_current_ask( mtrx, *base_asset ); // TODO: Do we need to decrease bid depth as well? _market_stat.ask_depth -= mtrx.ask_paid.amount; order_did_execute = true; } push_market_transaction( mtrx ); if( mtrx.ask_received.asset_id == 0 ) trading_volume += mtrx.ask_received; else if( mtrx.bid_received.asset_id == 0 ) trading_volume += mtrx.bid_received; if( opening_price == price() ) opening_price = mtrx.bid_price; closing_price = mtrx.bid_price; if( mtrx.fees_collected.asset_id == base_asset->id ) base_asset->collected_fees += mtrx.fees_collected.amount; else if( mtrx.fees_collected.asset_id == quote_asset->id ) quote_asset->collected_fees += mtrx.fees_collected.amount; } // while( next bid && next ask ) // update any fees collected _pending_state->store_asset_record( *quote_asset ); _pending_state->store_asset_record( *base_asset ); _market_stat.last_error.reset(); // Force at least one center price update every 6 blocks order_did_execute |= (pending_block_num % 6) == 0; if( _current_bid && _current_ask && order_did_execute ) { if( median_feed_price.valid() ) { _market_stat.center_price = *median_feed_price; } else if( _current_bid->type != short_order ) // we cannot use short prices for this { _market_stat.center_price.ratio *= (BTS_BLOCKCHAIN_BLOCKS_PER_HOUR-1); const price max_bid = _market_stat.maximum_bid(); // limit the maximum movement rate of the price. if( _current_bid->get_price() < min_cover_ask ) _market_stat.center_price.ratio += min_cover_ask.ratio; else if( _current_bid->get_price() > max_bid ) _market_stat.center_price.ratio += max_bid.ratio; else _market_stat.center_price.ratio += _current_bid->get_price().ratio; if( _current_ask->get_price() < min_cover_ask ) _market_stat.center_price.ratio += min_cover_ask.ratio; else if( _current_ask->get_price() > max_bid ) _market_stat.center_price.ratio += max_bid.ratio; else _market_stat.center_price.ratio += _current_ask->get_price().ratio; _market_stat.center_price.ratio /= (BTS_BLOCKCHAIN_BLOCKS_PER_HOUR+1); } } // Update market status and market history _pending_state->store_market_status( _market_stat ); update_market_history( trading_volume, opening_price, closing_price, _market_stat, timestamp ); wlog( "done matching orders" ); idump( (_current_bid)(_current_ask) ); _pending_state->apply_changes(); } catch( const fc::exception& e ) { wlog( "error executing market ${quote} / ${base}\n ${e}", ("quote",quote_id)("base",base_id)("e",e.to_detail_string()) ); auto market_state = _prior_state->get_market_status( quote_id, base_id ); if( !market_state ) market_state = market_status( quote_id, base_id, 0, 0 ); market_state->last_error = e; _prior_state->store_market_status( *market_state ); } } // execute(...)