Пример #1
0
      /**
       *  This method should not affect market execution or validation and
       *  is for historical purposes only.
       */
      void update_market_history( const asset& trading_volume,
                                  const price& opening_price,
                                  const price& closing_price,
                                  const omarket_status& market_stat,
                                  const fc::time_point_sec& timestamp )
      {
             if( trading_volume.amount > 0 && get_next_bid() && get_next_ask() )
             {
               market_history_key key(_quote_id, _base_id, market_history_key::each_block, _db_impl._head_block_header.timestamp);
               market_history_record new_record(_current_bid->get_price(),
                                                _current_ask->get_price(),
                                                opening_price,
                                                closing_price,
                                                trading_volume.amount);

               FC_ASSERT( market_stat );
               new_record.recent_average_price = market_stat->center_price;

               //LevelDB iterators are dumb and don't support proper past-the-end semantics.
               auto last_key_itr = _db_impl._market_history_db.lower_bound(key);
               if( !last_key_itr.valid() )
                 last_key_itr = _db_impl._market_history_db.last();
               else
                 --last_key_itr;

               key.timestamp = timestamp;

               //Unless the previous record for this market is the same as ours...
               if( (!(last_key_itr.valid()
                   && last_key_itr.key().quote_id == _quote_id
                   && last_key_itr.key().base_id == _base_id
                   && last_key_itr.key().granularity == market_history_key::each_block
                   && last_key_itr.value() == new_record)) )
               {
                 //...add a new entry to the history table.
                 _pending_state->market_history[key] = new_record;
               }

               fc::time_point_sec start_of_this_hour = timestamp - (timestamp.sec_since_epoch() % (60*60));
               market_history_key old_key(_quote_id, _base_id, market_history_key::each_hour, start_of_this_hour);
               if( auto opt = _db_impl._market_history_db.fetch_optional(old_key) )
               {
                 auto old_record = *opt;
                 old_record.volume += new_record.volume;
                 if( new_record.highest_bid > old_record.highest_bid || new_record.lowest_ask < old_record.lowest_ask )
                 {
                   old_record.highest_bid = std::max(new_record.highest_bid, old_record.highest_bid);
                   old_record.lowest_ask = std::min(new_record.lowest_ask, old_record.lowest_ask);
                   old_record.recent_average_price = new_record.recent_average_price;
                   _pending_state->market_history[old_key] = old_record;
                 }
               }
               else
                 _pending_state->market_history[old_key] = new_record;

               fc::time_point_sec start_of_this_day = timestamp - (timestamp.sec_since_epoch() % (60*60*24));
               old_key = market_history_key(_quote_id, _base_id, market_history_key::each_day, start_of_this_day);
               if( auto opt = _db_impl._market_history_db.fetch_optional(old_key) )
               {
                 auto old_record = *opt;
                 old_record.volume += new_record.volume;
                 if( new_record.highest_bid > old_record.highest_bid || new_record.lowest_ask < old_record.lowest_ask )
                 {
                   old_record.highest_bid = std::max(new_record.highest_bid, old_record.highest_bid);
                   old_record.lowest_ask = std::min(new_record.lowest_ask, old_record.lowest_ask);
                   old_record.recent_average_price = new_record.recent_average_price;
                   _pending_state->market_history[old_key] = old_record;
                 }
               }
               else
                 _pending_state->market_history[old_key] = new_record;
             }
      }
                  void execute( asset_id_type quote_id, asset_id_type base_id, const fc::time_point_sec& timestamp )
                  {
                     try {
                         _quote_id = quote_id;
                         _base_id = base_id;
                         auto quote_asset = _pending_state->get_asset_record( _quote_id );

                         // DISABLE MARKET ISSUED ASSETS
                         if( quote_asset->is_market_issued() )
                            return; // don't execute anything.

                         // the order book is soreted 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
                         auto 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( next_pair ) );
                         _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( _short_itr.valid() ) --_short_itr;
                         else _short_itr = _db_impl._short_db.last();

                         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 consumed_bid_depth(0,base_id);
                         asset consumed_ask_depth(0,base_id);


                         asset usd_fees_collected(0,quote_id);
                         asset trading_volume(0, base_id);

                         omarket_status market_stat = _pending_state->get_market_status( _quote_id, _base_id );
                         if( !market_stat.valid() )
                         {
                            if( quote_asset->is_market_issued() ) FC_CAPTURE_AND_THROW( insufficient_depth, (market_stat) );
                            FC_ASSERT( market_stat.valid() );
                         }

                         while( get_next_bid() && get_next_ask() )
                         {
                            idump( (_current_bid)(_current_ask) );
                            price ask_price = _current_ask->get_price();
                            // this works for bids, asks, and shorts.... but in the case of a cover
                            // the current ask can go lower than the call price in order to match
                            // the bid....
                            if( _current_ask->type == cover_order )
                            {
                               ask_price = std::min( _current_bid->get_price(), _current_ask->get_highest_cover_price() );
                            }

                            if( _current_bid->get_price() < ask_price )
                               break;

                            if( quote_asset->is_market_issued() )
                            {
                               if( !market_stat ||
                                   market_stat->ask_depth < BTS_BLOCKCHAIN_MARKET_DEPTH_REQUIREMENT/2 ||
                                   market_stat->bid_depth < BTS_BLOCKCHAIN_MARKET_DEPTH_REQUIREMENT/2
                                 )
                                 FC_CAPTURE_AND_THROW( insufficient_depth, (market_stat) );
                            }

                            auto quantity = std::min( _current_bid->get_quantity(), _current_ask->get_quantity() );

                            auto usd_paid_by_bid     = quantity * _current_bid->get_price();
                            auto usd_received_by_ask = quantity * _current_ask->get_price();
                            auto xts_paid_by_ask     = quantity;
                            auto xts_received_by_bid = quantity;

                            consumed_bid_depth += quantity;
                            consumed_ask_depth += quantity;

                            if( _current_bid->type == short_order )
                            {
                               usd_paid_by_bid = usd_received_by_ask;
                            }

                            if( _current_ask->type == cover_order )
                            {
                                usd_received_by_ask = usd_paid_by_bid;
                            }

                            FC_ASSERT( usd_paid_by_bid.amount >= 0 );
                            FC_ASSERT( xts_paid_by_ask.amount >= 0 );
                            FC_ASSERT( usd_received_by_ask.amount >= 0 );
                            FC_ASSERT( xts_received_by_bid.amount >= 0 );
                            FC_ASSERT( usd_paid_by_bid >= usd_received_by_ask );
                            FC_ASSERT( xts_paid_by_ask >= xts_received_by_bid );

                            // sanity check to keep supply from growing without bound
                            FC_ASSERT( usd_paid_by_bid < asset(quote_asset->maximum_share_supply,quote_id), "", ("usd_paid_by_bid",usd_paid_by_bid)("asset",quote_asset) );

                            usd_fees_collected += usd_paid_by_bid - usd_received_by_ask;
                            idump( (usd_fees_collected)(xts_paid_by_ask)(xts_received_by_bid)(quantity) );

                            market_transaction mtrx;
                            mtrx.bid_owner       = _current_bid->get_owner();
                            mtrx.ask_owner       = _current_ask->get_owner();
                            mtrx.bid_price       = _current_bid->get_price();
                            mtrx.ask_price       = ask_price;
                            mtrx.bid_paid        = usd_paid_by_bid;
                            mtrx.bid_received    = xts_received_by_bid;
                            mtrx.ask_paid        = xts_paid_by_ask;
                            mtrx.ask_received    = usd_received_by_ask;
                            mtrx.bid_type        = _current_bid->type;
                            mtrx.fees_collected  = xts_paid_by_ask - xts_received_by_bid;

                            _market_transactions.push_back(mtrx);
                            trading_volume += mtrx.bid_received;

                            market_stat->ask_depth -= xts_paid_by_ask.amount;
                            if( _current_ask->type == ask_order )
                            {
                               /* rounding errors on price cause this not to go to 0 in some cases */
                               if( quantity == _current_ask->get_quantity() )
                                  _current_ask->state.balance = 0;
                               else
                                  _current_ask->state.balance -= xts_paid_by_ask.amount;

                               FC_ASSERT( _current_ask->state.balance >= 0 );

                               auto ask_balance_address = withdraw_condition( withdraw_with_signature(_current_ask->get_owner()), quote_id ).get_address();
                               auto ask_payout = _pending_state->get_balance_record( ask_balance_address );
                               if( !ask_payout )
                                  ask_payout = balance_record( _current_ask->get_owner(), asset(0,quote_id), 0 );
                               ask_payout->balance += usd_received_by_ask.amount;
                               ask_payout->last_update = _pending_state->now();

                               _pending_state->store_balance_record( *ask_payout );
                               _pending_state->store_ask_record( _current_ask->market_index, _current_ask->state );
                            }
                            else if( _current_ask->type == cover_order )
                            {
                               elog( "MATCHING COVER ORDER recv_usd: ${usd}  paid_collat: ${c}",
                                     ("usd",usd_received_by_ask)("c",xts_paid_by_ask) );
                               wlog( "current ask: ${c}", ("c",_current_ask) );
                               // we are in the margin call range...
                               _current_ask->state.balance  -= usd_received_by_ask.amount;
                               *(_current_ask->collateral)  -= xts_paid_by_ask.amount;

                               FC_ASSERT( _current_ask->state.balance >= 0 );
                               FC_ASSERT( *_current_ask->collateral >= 0 );

                               if( _current_ask->state.balance == 0 ) // no more USD left
                               { // send collateral home to mommy & daddy
                                     wlog( "            collateral balance is now 0!" );
                                     auto ask_balance_address = withdraw_condition(
                                                                       withdraw_with_signature(_current_ask->get_owner()),
                                                                       base_id ).get_address();

                                     auto ask_payout = _pending_state->get_balance_record( ask_balance_address );
                                     if( !ask_payout )
                                        ask_payout = balance_record( _current_ask->get_owner(), asset(0,base_id), 0 );
                                     ask_payout->balance += (*_current_ask->collateral);
                                     ask_payout->last_update = _pending_state->now();

                                     _pending_state->store_balance_record( *ask_payout );
                                     _current_ask->collateral = 0;

                               }
                               wlog( "storing collateral ${c}", ("c",_current_ask) );
                               _pending_state->store_collateral_record( _current_ask->market_index,
                                                                        collateral_record( *_current_ask->collateral,
                                                                                           _current_ask->state.balance ) );
                            }

                            if( _current_bid->type == bid_order )
                            {
                               _current_bid->state.balance -= usd_paid_by_bid.amount;
                               FC_ASSERT( _current_bid->state.balance >= 0 );

                               auto bid_payout = _pending_state->get_balance_record(
                                                         withdraw_condition( withdraw_with_signature(_current_bid->get_owner()), base_id ).get_address() );
                               if( !bid_payout )
                                  bid_payout = balance_record( _current_bid->get_owner(), asset(0,base_id), 0 );
                               bid_payout->balance += xts_received_by_bid.amount;
                               bid_payout->last_update = _pending_state->now();
                               _pending_state->store_balance_record( *bid_payout );
                               _pending_state->store_bid_record( _current_bid->market_index, _current_bid->state );

                            }
                            else if( _current_bid->type == short_order )
                            {
                               market_stat->bid_depth -= xts_received_by_bid.amount;

                               // TODO: what if the amount paid is 0 for bid and ask due to rounding errors,
                               // make sure this doesn't put us in an infinite loop.
                               if( quantity == _current_bid->get_quantity() )
                                  _current_bid->state.balance = 0;
                               else
                                  _current_bid->state.balance -= xts_received_by_bid.amount;
                               FC_ASSERT( _current_bid->state.balance >= 0 );

                               auto collateral = (xts_paid_by_ask + xts_received_by_bid).amount;
                               auto cover_price = usd_received_by_ask / asset( (3*collateral)/4, base_id );

                               market_index_key cover_index( cover_price, _current_ask->get_owner() );
                               auto ocover_record = _pending_state->get_collateral_record( cover_index );

                               if( NOT ocover_record )
                                  ocover_record = collateral_record();

                               ocover_record->collateral_balance += collateral;
                               ocover_record->payoff_balance += usd_received_by_ask.amount;
                               FC_ASSERT( ocover_record->payoff_balance >= 0 );
                               FC_ASSERT( ocover_record->collateral_balance >= 0 );
                               _pending_state->store_collateral_record( cover_index, *ocover_record );

                               _pending_state->store_short_record( _current_bid->market_index, _current_bid->state );
                            }
                         } // while bid && ask

                         if( quote_asset->is_market_issued() )
                         {
                            if( !market_stat ||
                                market_stat->ask_depth < BTS_BLOCKCHAIN_MARKET_DEPTH_REQUIREMENT/2 ||
                                market_stat->bid_depth < BTS_BLOCKCHAIN_MARKET_DEPTH_REQUIREMENT/2
                              )
                              FC_CAPTURE_AND_THROW( insufficient_depth, (market_stat) );
                         }
                         _pending_state->store_market_status( *market_stat );

                         if( trading_volume.amount > 0 && get_next_bid() && get_next_ask() )
                         {
                           market_history_key key(quote_id, base_id, market_history_key::each_block, _db_impl._head_block_header.timestamp);
                           market_history_record new_record(_current_bid->get_price(), _current_ask->get_price(), trading_volume.amount);
                           //LevelDB iterators are dumb and don't support proper past-the-end semantics.
                           auto last_key_itr = _db_impl._market_history_db.lower_bound(key);
                           if( !last_key_itr.valid() )
                             last_key_itr = _db_impl._market_history_db.last();
                           else
                             --last_key_itr;

                           key.timestamp = timestamp;

                           //Unless the previous record for this market is the same as ours...
                           if( (!(last_key_itr.valid()
                               && last_key_itr.key().quote_id == quote_id
                               && last_key_itr.key().base_id == base_id
                               && last_key_itr.key().granularity == market_history_key::each_block
                               && last_key_itr.value() == new_record)) )
                           {
                             //...add a new entry to the history table.
                             _pending_state->market_history[key] = new_record;
                           }

                           fc::time_point_sec start_of_this_hour = timestamp - (timestamp.sec_since_epoch() % (60*60));
                           market_history_key old_key(quote_id, base_id, market_history_key::each_hour, start_of_this_hour);
                           if( auto opt = _db_impl._market_history_db.fetch_optional(old_key) )
                           {
                             auto old_record = *opt;
                             old_record.volume += new_record.volume;
                             if( new_record.highest_bid > old_record.highest_bid || new_record.lowest_ask < old_record.lowest_ask )
                             {
                               old_record.highest_bid = std::max(new_record.highest_bid, old_record.highest_bid);
                               old_record.lowest_ask = std::min(new_record.lowest_ask, old_record.lowest_ask);
                               _pending_state->market_history[old_key] = old_record;
                             }
                           }
                           else
                             _pending_state->market_history[old_key] = new_record;

                           fc::time_point_sec start_of_this_day = timestamp - (timestamp.sec_since_epoch() % (60*60*24));
                           old_key = market_history_key(quote_id, base_id, market_history_key::each_day, start_of_this_day);
                           if( auto opt = _db_impl._market_history_db.fetch_optional(old_key) )
                           {
                             auto old_record = *opt;
                             old_record.volume += new_record.volume;
                             if( new_record.highest_bid > old_record.highest_bid || new_record.lowest_ask < old_record.lowest_ask )
                             {
                               old_record.highest_bid = std::max(new_record.highest_bid, old_record.highest_bid);
                               old_record.lowest_ask = std::min(new_record.lowest_ask, old_record.lowest_ask);
                               _pending_state->market_history[old_key] = old_record;
                             }
                           }
                           else
                             _pending_state->market_history[old_key] = new_record;
                         }

                         auto market_state = _pending_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.reset();
                         _pending_state->store_market_status( *market_state );

                         wlog( "done matching orders" );
                         _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(...)
Пример #3
0
      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(...)