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])
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
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()
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'
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))
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
def cancelDataSubscription(self, symbol): self.sendMessage(recipientID=self.exchangeID, msg=Message({ "msg": "MARKET_DATA_SUBSCRIPTION_CANCELLATION", "sender": self.id, "symbol": symbol }))
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
def getLastTrade(self, symbol): self.sendMessage( self.exchangeID, Message({ "msg": "QUERY_LAST_TRADE", "sender": self.id, "symbol": symbol }))
def getCurrentSpread(self, symbol, depth=1): self.sendMessage( self.exchangeID, Message({ "msg": "QUERY_SPREAD", "sender": self.id, "symbol": symbol, "depth": depth }))
def getOrderStream(self, symbol, length=1): self.sendMessage( self.exchangeID, Message({ "msg": "QUERY_ORDER_STREAM", "sender": self.id, "symbol": symbol, "length": length }))
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 }))
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
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 }))
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)
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 }))
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))
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'
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))
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)
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])
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
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))
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())
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))
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
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
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()