Beispiel #1
0
    def receiveMessage(self, currentTime, msg):
        super().receiveMessage(currentTime, msg)

        if self.state == 'AWAITING_SPREAD':

            if msg.body['msg'] == 'QUERY_SPREAD':
                if self.mkt_closed:
                    return

                self.placeOrder()
                self.state = 'AWAITING_WAKEUP'

        if msg.body['msg'] == "SLAVE_DELAY_RESPONSE":
            self.slave_delays[msg.body['sender']] = msg.body['delay']
        elif msg.body['msg'] == "ORDER_EXECUTED":
            order = msg.body['order']
            if self.send_signal == 'MASTER_ORDER_EXECUTED':
                for s_id in self.slave_ids:
                    self.sendMessage(recipientID=s_id, msg=Message({"msg": "MASTER_ORDER",
                                                                    "sender": self.id,
                                                                    "symbol": self.symbol,
                                                                    "quantity": order.quantity,
                                                                    "is_buy_order": order.is_buy_order,
                                                                    'limit_price': order.limit_price}),
                                     delay=self.slave_delays[s_id])
        elif msg.body['msg'] == "ORDER_CANCELLED":
            order = msg.body['order']
            for s_id in self.slave_ids:
                self.sendMessage(recipientID=s_id, msg=Message({"msg": "MASTER_ORDER_CANCELLED",
                                                                "sender": self.id,
                                                                "order": order}),
                                 delay=self.slave_delays[s_id])
Beispiel #2
0
    def wakeup(self, currentTime):
        super().wakeup(currentTime)

        if self.first_wake:
            # Log initial holdings.
            self.logEvent('HOLDINGS_UPDATED', self.holdings)
            self.first_wake = False

        if self.mkt_open is None:
            # Ask our exchange when it opens and closes.
            self.sendMessage(
                self.exchangeID,
                Message({
                    "msg": "WHEN_MKT_OPEN",
                    "sender": self.id
                }))
            self.sendMessage(
                self.exchangeID,
                Message({
                    "msg": "WHEN_MKT_CLOSE",
                    "sender": self.id
                }))

        # For the sake of subclasses, TradingAgent now returns a boolean
        # indicating whether the agent is "ready to trade" -- has it received
        # the market open and closed times, and is the market not already closed.
        return (self.mkt_open and self.mkt_close) and not self.mkt_closed
Beispiel #3
0
 def modifyOrder(self, order, new_order):
     book = self.bids if order.is_buy_order else self.asks
     if not book: return
     for i, o in enumerate(book):
         if self.isEqualPrice(order, o[0]):
             for mi, mo in enumerate(book[i]):
                 if order.order_id == mo.order_id:
                     book[i][0] = new_order
                     for idx, orders in enumerate(self.history):
                         if new_order.order_id not in orders: continue
                         self.history[idx][
                             new_order.order_id]['transactions'].append(
                                 (self.owner.currentTime,
                                  new_order.quantity))
                         print("MODIFIED: order {}".format(order))
                         print(
                             "SENT: notifications of order modification to agent {} for order {}"
                             .format(new_order.agent_id,
                                     new_order.order_id))
                         self.owner.sendMessage(
                             order.agent_id,
                             Message({
                                 "msg": "ORDER_MODIFIED",
                                 "new_order": new_order
                             }))
     if order.is_buy_order:
         self.bids = book
     else:
         self.asks = book
     self.updateOrderbookLevelDicts()
Beispiel #4
0
    def wakeup(self, currentTime):
        super().wakeup(currentTime)

        self.state = 'INACTIVE'

        if not self.mkt_open or not self.mkt_close:
            for s_id in self.slave_ids:
                self.sendMessage(recipientID=s_id,
                                 msg=Message({"msg": "SLAVE_DELAY_REQUEST", "sender": self.id}))
            return
        else:
            if not self.trading:
                self.trading = True
                self.oracle.compute_fundamental_value_series(self.symbol, currentTime, sigma_n=0,
                                                             random_state=self.random_state)
                log_print("{} is ready to start trading now.", self.name)

        if self.mkt_closed and (self.symbol in self.daily_close_price):
            return

        if self.mkt_closed and (self.symbol not in self.daily_close_price):
            self.getCurrentSpread(self.symbol)
            self.state = 'AWAITING_SPREAD'
            return

        self.cancelOrders()

        if type(self) == HerdMasterAgent:
            self.getCurrentSpread(self.symbol)
            self.state = 'AWAITING_SPREAD'
        else:
            self.state = 'ACTIVE'
Beispiel #5
0
 def modifyOrder(self, order, new_order):
     # Modifies the quantity of an existing limit order in the order book
     if not self.isSameOrder(order, new_order): return
     book = self.bids if order.is_buy_order else self.asks
     if not book: return
     for i, o in enumerate(book):
         if self.isEqualPrice(order, o[0]):
             for mi, mo in enumerate(book[i]):
                 if order.order_id == mo.order_id:
                     book[i][0] = new_order
                     for idx, orders in enumerate(self.history):
                         if new_order.order_id not in orders: continue
                         self.history[idx][
                             new_order.order_id]['modifications'].append(
                                 (self.owner.currentTime,
                                  new_order.quantity))
                         log_print("MODIFIED: order {}", order)
                         log_print(
                             "SENT: notifications of order modification to agent {} for order {}",
                             new_order.agent_id, new_order.order_id)
                         self.owner.sendMessage(
                             order.agent_id,
                             Message({
                                 "msg": "ORDER_MODIFIED",
                                 "new_order": new_order
                             }))
     if order.is_buy_order:
         self.bids = book
     else:
         self.asks = book
     self.last_update_ts = self.owner.currentTime
  def placeLimitOrder (self, symbol, quantity, is_buy_order, limit_price):
    order = LimitOrder(self.id, self.currentTime, symbol, quantity, is_buy_order, limit_price)

    if quantity > 0:
      # Test if this order can be permitted given our at-risk limits.
      new_holdings = self.holdings.copy()

      q = order.quantity if order.is_buy_order else -order.quantity

      if order.symbol in new_holdings: new_holdings[order.symbol] += q
      else: new_holdings[order.symbol] = q

      # Compute before and after at-risk capital.
      at_risk = self.markToMarket(self.holdings) - self.holdings['CASH']
      new_at_risk = self.markToMarket(new_holdings) - new_holdings['CASH']

      # If at_risk is lower, always allow.  Otherwise, new_at_risk must be below starting cash.
      if (new_at_risk > at_risk) and (new_at_risk > self.startingCash):
        print ("TradingAgent ignored limit order due to at-risk constraints: {}\n{}".format(order, self.fmtHoldings(self.holdings)))
        return

      self.orders[order.order_id] = deepcopy(order)
      self.sendMessage(self.exchangeID, Message({ "msg" : "LIMIT_ORDER", "sender": self.id,
                                                  "order" : order })) 

      # Log this activity.
      self.logEvent('ORDER_SUBMITTED', order)
    else:
      print ("TradingAgent ignored limit order of quantity zero: {}".format(order))
Beispiel #7
0
  def placeLimitOrder (self, symbol, quantity, is_buy_order, limit_price, order_id=None, ignore_risk = True):
    order = LimitOrder(self.id, self.currentTime, symbol, quantity, is_buy_order, limit_price, order_id)

    if quantity > 0:
      # Test if this order can be permitted given our at-risk limits.
      new_holdings = self.holdings.copy()

      q = order.quantity if order.is_buy_order else -order.quantity

      if order.symbol in new_holdings: new_holdings[order.symbol] += q
      else: new_holdings[order.symbol] = q

      # Compute before and after at-risk capital.
      at_risk = self.markToMarket(self.holdings) - self.holdings['CASH']
      new_at_risk = self.markToMarket(new_holdings) - new_holdings['CASH']

      # If at_risk is lower, always allow.  Otherwise, new_at_risk must be below starting cash.
      if not ignore_risk:
        if (new_at_risk > at_risk) and (new_at_risk > self.starting_cash):
          log_print ("TradingAgent ignored limit order due to at-risk constraints: {}\n{}", order, self.fmtHoldings(self.holdings))
          return

      # Copy the intended order for logging, so any changes made to it elsewhere
      # don't retroactively alter our "as placed" log of the order.  Eventually
      # it might be nice to make the whole history of the order into transaction
      # objects inside the order (we're halfway there) so there CAN be just a single
      # object per order, that never alters its original state, and eliminate all these copies.
      self.orders[order.order_id] = deepcopy(order)
      self.sendMessage(self.exchangeID, Message({ "msg" : "LIMIT_ORDER", "sender": self.id,
                                                  "order" : order })) 

      # Log this activity.
      if self.log_orders: self.logEvent('ORDER_SUBMITTED', js.dump(order))
    else:
      log_print ("TradingAgent ignored limit order of quantity zero: {}", order)
  def wakeup (self, currentTime):
    super().wakeup(currentTime)

    if self.first_wake:
      # Log initial holdings.
      #self.logEvent('HOLDINGS_UPDATED', self.fmtHoldings(self.holdings))
      self.logEvent('HOLDINGS_UPDATED', self.holdings)
      self.first_wake = False

    if self.mkt_open is None:
      # Ask our exchange when it opens and closes.
      self.sendMessage(self.exchangeID, Message({ "msg" : "WHEN_MKT_OPEN", "sender": self.id }))
      self.sendMessage(self.exchangeID, Message({ "msg" : "WHEN_MKT_CLOSE", "sender": self.id }))

    # New for MomentumAgent.
    return (self.mkt_open and self.mkt_close) and not self.mkt_closed
Beispiel #9
0
 def cancelDataSubscription(self, symbol):
     self.sendMessage(recipientID=self.exchangeID,
                      msg=Message({
                          "msg": "MARKET_DATA_SUBSCRIPTION_CANCELLATION",
                          "sender": self.id,
                          "symbol": symbol
                      }))
Beispiel #10
0
 def publishOrderBookData(self):
     '''
 The exchange agents sends an order book update to the agents using the subscription API if one of the following
 conditions are met:
 1) agent requests ALL order book updates (freq == 0)
 2) order book update timestamp > last time agent was updated AND the orderbook update time stamp is greater than
 the last agent update time stamp by a period more than that specified in the freq parameter.
 '''
     for agent_id, params in self.subscription_dict.items():
         for symbol, values in params.items():
             levels, freq, last_agent_update = values[0], values[1], values[
                 2]
             orderbook_last_update = self.order_books[symbol].last_update_ts
             if (freq == 0) or \
                ((orderbook_last_update > last_agent_update) and ((orderbook_last_update - last_agent_update).delta >= freq)):
                 self.sendMessage(
                     agent_id,
                     Message({
                         "msg":
                         "MARKET_DATA",
                         "symbol":
                         symbol,
                         "bids":
                         self.order_books[symbol].getInsideBids(levels),
                         "asks":
                         self.order_books[symbol].getInsideAsks(levels),
                         "last_transaction":
                         self.order_books[symbol].last_trade
                     }))
                 self.subscription_dict[agent_id][symbol][
                     2] = orderbook_last_update
Beispiel #11
0
 def getLastTrade(self, symbol):
     self.sendMessage(
         self.exchangeID,
         Message({
             "msg": "QUERY_LAST_TRADE",
             "sender": self.id,
             "symbol": symbol
         }))
Beispiel #12
0
 def getCurrentSpread(self, symbol, depth=1):
     self.sendMessage(
         self.exchangeID,
         Message({
             "msg": "QUERY_SPREAD",
             "sender": self.id,
             "symbol": symbol,
             "depth": depth
         }))
Beispiel #13
0
 def getOrderStream(self, symbol, length=1):
     self.sendMessage(
         self.exchangeID,
         Message({
             "msg": "QUERY_ORDER_STREAM",
             "sender": self.id,
             "symbol": symbol,
             "length": length
         }))
Beispiel #14
0
 def requestDataSubscription(self, symbol, levels, freq):
     self.sendMessage(recipientID=self.exchangeID,
                      msg=Message({
                          "msg": "MARKET_DATA_SUBSCRIPTION_REQUEST",
                          "sender": self.id,
                          "symbol": symbol,
                          "levels": levels,
                          "freq": freq
                      }))
Beispiel #15
0
    def cancelOrder(self, order):
        # Attempts to cancel (the remaining, unexecuted portion of) a trade in the order book.
        # By definition, this pretty much has to be a limit order.  If the order cannot be found
        # in the order book (probably because it was already fully executed), presently there is
        # no message back to the agent.  This should possibly change to some kind of failed
        # cancellation message.  (?)  Otherwise, the agent receives ORDER_CANCELLED with the
        # order as the message body, with the cancelled quantity correctly represented as the
        # number of shares that had not already been executed.

        if order.is_buy_order:
            book = self.bids
        else:
            book = self.asks

        # If there are no orders on this side of the book, there is nothing to do.
        if not book: return

        # There are orders on this side.  Find the price level of the order to cancel,
        # then find the exact order and cancel it.
        # Note that o is a LIST of all orders (oldest at index 0) at this same price.
        for i, o in enumerate(book):
            if self.isEqualPrice(order, o[0]):
                # This is the correct price level.
                for ci, co in enumerate(book[i]):
                    if order.order_id == co.order_id:
                        # Cancel this order.
                        cancelled_order = book[i].pop(ci)

                        # Record cancellation of the order if it is still present in the recent history structure.
                        for idx, orders in enumerate(self.history):
                            if cancelled_order.order_id not in orders: continue

                            # Found the cancelled order in history.  Update it with the cancelation.
                            self.history[idx][cancelled_order.order_id][
                                'cancellations'].append(
                                    (self.owner.currentTime,
                                     cancelled_order.quantity))

                        # If the cancelled price now has no orders, remove it completely.
                        if not book[i]:
                            del book[i]

                        print("CANCELLED: order {}".format(order))
                        print(
                            "SENT: notifications of order cancellation to agent {} for order {}"
                            .format(cancelled_order.agent_id,
                                    cancelled_order.order_id))

                        self.owner.sendMessage(
                            order.agent_id,
                            Message({
                                "msg": "ORDER_CANCELLED",
                                "order": cancelled_order
                            }))

                        # We found the order and cancelled it, so stop looking.
                        return
Beispiel #16
0
 def get_transacted_volume(self, symbol, lookback_period='10min'):
     """ Used by any trading agent subclass to query the total transacted volume in a given lookback period """
     self.sendMessage(
         self.exchangeID,
         Message({
             "msg": "QUERY_TRANSACTED_VOLUME",
             "sender": self.id,
             "symbol": symbol,
             "lookback_period": lookback_period
         }))
Beispiel #17
0
  def wakeup (self, currentTime):
    super().wakeup(currentTime)

    if self.mkt_close is None:
      # Ask our exchange when it opens and closes.
      self.sendMessage(self.exchangeID, Message({ "msg" : "WHEN_MKT_CLOSE", "sender": self.id }))
        
    else:
      # Get close price of ETF/nav
      self.getLastTrade(self.symbol)
Beispiel #18
0
  def processSum (self):

    current_sum = sum([ x[0] + x[1] for x in self.numbers.values() ])
    self.total += current_sum

    log_print("Agent {} computed sum: {}", self.id, current_sum)

    for sender in self.numbers.keys():
      self.sendMessage(sender, Message({ "msg" : "SUM_QUERY_RESPONSE", "sender": self.id,
                                         "sum" : current_sum }))
Beispiel #19
0
    def cancelOrder(self, order):
        self.sendMessage(
            self.exchangeID,
            Message({
                "msg": "CANCEL_ORDER",
                "sender": self.id,
                "order": order
            }))

        # Log this activity.
        if self.log_orders: self.logEvent('CANCEL_SUBMITTED', js.dump(order))
Beispiel #20
0
 def placeBasketOrder(self, quantity, is_create_order):
     order = BasketOrder(self.id, self.currentTime, 'ETF', quantity,
                         is_create_order)
     print('BASKET ORDER PLACED: ' + str(order))
     self.sendMessage(
         self.primeID,
         Message({
             "msg": "BASKET_ORDER",
             "sender": self.id,
             "order": order
         }))
     self.state = 'AWAITING_BASKET'
Beispiel #21
0
    def modifyOrder(self, order, newOrder):
        self.sendMessage(
            self.exchangeID,
            Message({
                "msg": "MODIFY_ORDER",
                "sender": self.id,
                "order": order,
                "new_order": newOrder
            }))

        # Log this activity.
        if self.log_orders: self.logEvent('MODIFY_ORDER', js.dump(order))
Beispiel #22
0
    def placeMarketOrder(self,
                         symbol,
                         quantity,
                         is_buy_order,
                         order_id=None,
                         ignore_risk=True,
                         tag=None):
        """
      Used by any Trading Agent subclass to place a market order. The market order is created as multiple limit orders
      crossing the spread walking the book until all the quantities are matched.
      :param symbol (str):        name of the stock traded
      :param quantity (int):      order quantity
      :param is_buy_order (bool): True if Buy else False
      :param order_id:            Order ID for market replay
      :param ignore_risk (bool):  Determines whether cash or risk limits should be enforced or ignored for the order
      :return:
    """
        order = MarketOrder(self.id, self.currentTime, symbol, quantity,
                            is_buy_order, order_id)
        if quantity > 0:
            # compute new holdings
            new_holdings = self.holdings.copy()
            q = order.quantity if order.is_buy_order else -order.quantity
            if order.symbol in new_holdings: new_holdings[order.symbol] += q
            else: new_holdings[order.symbol] = q

            if not ignore_risk:
                # Compute before and after at-risk capital.
                at_risk = self.markToMarket(
                    self.holdings) - self.holdings['CASH']
                new_at_risk = self.markToMarket(
                    new_holdings) - new_holdings['CASH']

                if (new_at_risk > at_risk) and (new_at_risk >
                                                self.starting_cash):
                    log_print(
                        "TradingAgent ignored market order due to at-risk constraints: {}\n{}",
                        order, self.fmtHoldings(self.holdings))
                    return
            self.orders[order.order_id] = deepcopy(order)
            self.sendMessage(
                self.exchangeID,
                Message({
                    "msg": "MARKET_ORDER",
                    "sender": self.id,
                    "order": order
                }))
            if self.log_orders:
                self.logEvent('ORDER_SUBMITTED', order.to_dict())
        else:
            log_print("TradingAgent ignored market order of quantity zero: {}",
                      order)
Beispiel #23
0
    def placeOrder(self):
        delta = pd.Timedelta(self.future_window, unit='ns')
        if self.currentTime+delta < self.mkt_close:
            self.setWakeup(self.currentTime + delta)

        r_f = self.oracle.observeFuturePrice(self.symbol, self.currentTime + delta, sigma_n=self.sigma_n,
                                             random_state=self.random_state)
        bid, bid_vol, ask, ask_vol = self.getKnownBidAsk(self.symbol)
        if bid and ask:
            spread = abs(ask - bid)

            if np.random.rand() < self.percent_aggr:
                adjust_int = 0
            else:
                adjust_int = np.random.randint(0, self.depth_spread*spread)

            if ask < r_f:
                buy = True
                p = ask - adjust_int
                size = self.getHoldings(self.symbol)*(-1) if self.getHoldings(self.symbol) < 0 else self.size
                if p >= r_f:
                    return
            elif bid > r_f:
                buy = False
                p = bid + adjust_int
                size = self.getHoldings(self.symbol) if self.getHoldings(self.symbol) > 0 else self.size
                if p <= r_f:
                    return
            else:
                return
        else:
            return

        if self.currentTime+delta < self.mkt_close:
            order_type = np.random.choice(['limit', 'market']) if self.strategy == 'mixed' else self.strategy

            if order_type == 'limit':
                self.placeLimitOrder(self.symbol, size, buy, p)
            else:
                p = 0
                self.placeMarketOrder(self.symbol, size, buy)
            if self.send_signal == 'MASTER_ORDER_PLACED':
                for s_id in self.slave_ids:
                    self.sendMessage(recipientID=s_id, msg=Message({"msg": "MASTER_ORDER",
                                                                    "sender": self.id,
                                                                    "symbol": self.symbol,
                                                                    "quantity": size,
                                                                    "is_buy_order": buy,
                                                                    'limit_price': p}),
                                     delay=self.slave_delays[s_id])
Beispiel #24
0
  def wakeup (self, currentTime):
    # Allow the base Agent to do whatever it needs to.
    super().wakeup(currentTime)

    # This agent only needs one wakeup call at simulation start.  At this time,
    # each client agent will send a number to each agent in its peer list.
    # Each number will be sampled independently.  That is, client agent 1 will
    # send n2 to agent 2, n3 to agent 3, and so forth.

    # Once a client agent has received these initial random numbers from all
    # agents in the peer list, it will make its first request from the sum
    # service.  Afterwards, it will simply request new sums when answers are
    # delivered to previous queries.

    # At the first wakeup, initiate peer exchange.
    if not self.peer_exchange_complete:
      n = [self.random_state.randint(low = 0, high = 100) for i in range(len(self.peer_list))]
      log_print ("agent {} peer list: {}", self.id, self.peer_list)
      log_print ("agent {} numbers to exchange: {}", self.id, n)

      for idx, peer in enumerate(self.peer_list):
        self.sendMessage(peer, Message({ "msg" : "PEER_EXCHANGE", "sender": self.id, "n" : n[idx] }))

    else:
      # For subsequent (self-induced) wakeups, place a sum query.
      n1, n2 = [self.random_state.randint(low = 0, high = 100) for i in range(2)]

      log_print ("agent {} transmitting numbers {} and {} with peer sum {}", self.id, n1, n2, self.peer_sum)

      # Add the sum of the peer exchange values to both numbers.
      n1 += self.peer_sum
      n2 += self.peer_sum

      self.sendMessage(self.serviceAgentID, Message({ "msg" : "SUM_QUERY", "sender": self.id,
                                                      "n1" : n1, "n2" : n2 })) 

    return
Beispiel #25
0
    def cancelOrder(self, order):
        """Used by any Trading Agent subclass to cancel any order.  The order must currently
    appear in the agent's open orders list."""
        self.sendMessage(
            self.exchangeID,
            Message({
                "msg": "CANCEL_ORDER",
                "sender": self.id,
                "order": order
            }))

        # Log this activity.
        if self.log_orders:
            self.logEvent('CANCEL_SUBMITTED',
                          js.dump(order, strip_privates=True))
Beispiel #26
0
    def modifyOrder(self, order, newOrder):
        """ Used by any Trading Agent subclass to modify any existing limit order.  The order must currently
        appear in the agent's open orders list.  Some additional tests might be useful here
        to ensure the old and new orders are the same in some way."""
        self.sendMessage(
            self.exchangeID,
            Message({
                "msg": "MODIFY_ORDER",
                "sender": self.id,
                "order": order,
                "new_order": newOrder
            }))

        # Log this activity.
        if self.log_orders: self.logEvent('MODIFY_ORDER', order.to_dict())
Beispiel #27
0
 def cancelOrder(self, order):
     """Used by any Trading Agent subclass to cancel any order.  The order must currently
 appear in the agent's open orders list."""
     if isinstance(order, LimitOrder):
         self.sendMessage(
             self.exchangeID,
             Message({
                 "msg": "CANCEL_ORDER",
                 "sender": self.id,
                 "order": order
             }))
         # Log this activity.
         if self.log_orders:
             self.logEvent('CANCEL_SUBMITTED', order.to_dict())
     else:
         log_print("order {} of type, {} cannot be cancelled", order,
                   type(order))
Beispiel #28
0
  def combineWeights (self):

    log_print ("total: {}", self.total)

    # Don't respond after the final iteration.
    if (self.current_iteration < self.no_of_iterations):

      # Take the mean weights across the clients.
      self.total = np.array(self.total)
      totals = np.mean(self.total, axis=0)

      # Send the combined weights back to each client who participated.
      for sender in self.received.keys():
        log_print ("Sending {} to {}", totals, sender)
        self.sendMessage(sender, Message({ "msg" : "SHARED_WEIGHTS", "sender": self.id, "weights" : deepcopy(totals) }))

      # This is the end of one round of the protocol.
      self.current_iteration += 1
Beispiel #29
0
    def wakeup(self, currentTime):
        # Allow the base Agent to do whatever it needs to.
        super().wakeup(currentTime)

        # This agent only needs one wakeup call at simulation start.  Afterwards,
        # it will simply request new sums when answers are delivered to previous
        # queries.

        # At that wakeup, it places its first sum query.
        n1, n2 = [self.random_state.randint(low=0, high=100) for i in range(2)]

        self.sendMessage(
            self.serviceAgentID,
            Message({
                "msg": "SUM_QUERY",
                "sender": self.id,
                "n1": n1,
                "n2": n2
            }))

        return
Beispiel #30
0
    def handleLimitOrder(self, order):
        # Matches a limit order or adds it to the order book.  Handles partial matches piecewise,
        # consuming all possible shares at the best price before moving on, without regard to
        # order size "fit" or minimizing number of transactions.  Sends one notification per
        # match.
        if order.symbol != self.symbol:
            print("{} order discarded.  Does not match OrderBook symbol: {}".
                  format(order.symbol, self.symbol))
            return

        if (order.quantity <= 0) or (int(order.quantity) != order.quantity):
            print(
                "{} order discarded.  Quantity ({}) must be a positive integer."
                .format(order.symbol, order.quantity))
            return

        # Add the order under index 0 of history: orders since the most recent trade.
        self.history[0][order.order_id] = {
            'entry_time': self.owner.currentTime,
            'quantity': order.quantity,
            'is_buy_order': order.is_buy_order,
            'limit_price': order.limit_price,
            'transactions': [],
            'cancellations': []
        }

        matching = True

        self.prettyPrint()

        executed = []

        while matching:
            matched_order = deepcopy(self.executeOrder(order))

            if matched_order:
                # Decrement quantity on new order and notify traders of execution.
                filled_order = deepcopy(order)
                filled_order.quantity = matched_order.quantity
                filled_order.fill_price = matched_order.fill_price

                order.quantity -= filled_order.quantity

                print("MATCHED: new order {} vs old order {}".format(
                    filled_order, matched_order))
                print(
                    "SENT: notifications of order execution to agents {} and {} for orders {} and {}"
                    .format(filled_order.agent_id, matched_order.agent_id,
                            filled_order.order_id, matched_order.order_id))

                self.owner.sendMessage(
                    order.agent_id,
                    Message({
                        "msg": "ORDER_EXECUTED",
                        "order": filled_order
                    }))
                self.owner.sendMessage(
                    matched_order.agent_id,
                    Message({
                        "msg": "ORDER_EXECUTED",
                        "order": matched_order
                    }))

                # Accumulate the volume and average share price of the currently executing inbound trade.
                executed.append(
                    (filled_order.quantity, filled_order.fill_price))

                if order.quantity <= 0:
                    matching = False

            else:
                # No matching order was found, so the new order enters the order book.  Notify the agent.
                self.enterOrder(deepcopy(order))

                print("ACCEPTED: new order {}".format(order))
                print(
                    "SENT: notifications of order acceptance to agent {} for order {}"
                    .format(order.agent_id, order.order_id))

                self.owner.sendMessage(
                    order.agent_id,
                    Message({
                        "msg": "ORDER_ACCEPTED",
                        "order": order
                    }))

                matching = False

        if not matching:
            # Now that we are done executing or accepting this order, log the new best bid and ask.
            if self.bids:
                self.owner.logEvent(
                    'BEST_BID',
                    "{},{},{}".format(self.symbol, self.bids[0][0].limit_price,
                                      sum([o.quantity for o in self.bids[0]])))

            if self.asks:
                self.owner.logEvent(
                    'BEST_ASK',
                    "{},{},{}".format(self.symbol, self.asks[0][0].limit_price,
                                      sum([o.quantity for o in self.asks[0]])))

            # Also log the last trade (total share quantity, average share price).
            if executed:
                trade_qty = 0
                trade_price = 0
                for q, p in executed:
                    print("Executed: {} @ {}".format(q, p))
                    trade_qty += q
                    trade_price += (p * q)

                avg_price = int(round(trade_price / trade_qty))
                print("Avg: {} @ ${:0.4f}".format(trade_qty, avg_price))
                self.owner.logEvent('LAST_TRADE',
                                    "{},${:0.4f}".format(trade_qty, avg_price))

                self.last_trade = avg_price

            # Finally, log the full depth of the order book.
            row = {'QuoteTime': self.owner.currentTime}
            for quote in self.quotes_seen:
                row[quote] = 0
            for quote, volume in self.getInsideBids():
                row[quote] = -volume
                self.quotes_seen.add(quote)
            for quote, volume in self.getInsideAsks():
                if quote in row:
                    if row[quote] != 0:
                        print(
                            "WARNING: THIS IS A REAL PROBLEM: an order book contains bids and asks at the same quote price!",
                            override=True)
                row[quote] = volume
                self.quotes_seen.add(quote)
            self.book_log.append(row)

        self.prettyPrint()