def test_cancel_order(self): book = OrderBook() limit_buy_order = Order(ID_GENERATOR, 1, 100, "LIMIT", 100) book.add_order(limit_buy_order) self.assertEqual(limit_buy_order.order_state, 'ACTIVE') book.cancel_order(limit_buy_order.id) self.assertEqual(limit_buy_order.order_state, 'CANCELLED')
def test_lazy_cleanup(self): book = OrderBook() limit_buy_order = Order(ID_GENERATOR, 1, 100, "LIMIT", 100) limit_sell_order = Order(ID_GENERATOR, 2, 105, "LIMIT", -100) book.add_order(limit_buy_order) book.add_order(limit_sell_order) book.cancel_order(limit_buy_order.id) book.cancel_order(limit_sell_order.id) self.assertEqual(limit_buy_order.order_state, 'CANCELLED') self.assertEqual(limit_sell_order.order_state, 'CANCELLED') self.assertEqual(len(book.bids), 1) self.assertEqual(len(book.asks), 1) book._OrderBook__lazy_remove_completed_orders() self.assertEqual(len(book.bids), 0) self.assertEqual(len(book.asks), 0)
class test_order_book(unittest.TestCase): def setUp(self): self.completed_trades = [] self.completed_orders = [] self.orders = [] self.order_book = OrderBook("KEQ") for i in range(1, 10): quote = test_quotes[str(i)] if "price" in quote: quote["price"] = Decimal(quote["price"]) order = json.loads(self.order_book.process_order(quote)) self.orders.append(order) for trades in self.order_book.completed_trades: self.completed_trades.append(trades) for orders in self.order_book.completed_orders: self.completed_orders.append(orders) def test_get_min_ask(self): self.assertEqual(self.order_book.get_min_ask(), 99.5) def test_get_max_bid(self): self.assertEqual(self.order_book.get_max_bid(), 99) def test_process_order(self): self.assertEqual(len(self.order_book.bids.keys()), 1) self.assertEqual(len(self.order_book.asks.keys()), 2) self.assertEqual(len(self.completed_trades), 6) self.assertEqual(len(self.order_book.ongoing_orders), 3) self.assertEqual(len(self.completed_orders), 6) def test_modify_order(self): quote = test_quotes[str(10)] if "price" in quote: quote["price"] = Decimal(quote["price"]) new_order = json.loads( self.order_book.modify_order(self.orders[8]["order_id"], quote)) self.assertEqual(new_order["price"], '75') def test_cancel_order(self): quote = test_quotes[str(10)] if "price" in quote: quote["price"] = Decimal(quote["price"]) new_order = json.loads(self.order_book.process_order(quote)) self.order_book.cancel_order(new_order["order_id"]) self.assertEqual(len(self.order_book.bids.keys()), 1)
class Engine: def __init__(self): self.orderbook = OrderBook() self.orderbook.on_event = self.on_event self.trades = [] self._next_order_id = 0 self.all_orders = {} def next_order_id(self): n = self._next_order_id self._next_order_id += 1 return n def limit_order(self, side, price, amount): o = Order(self.next_order_id(), side, price, amount) self.all_orders[o.id] = o self.orderbook.limit_order(o) self.validate() return o def cancel_order(self, orderid): o = self.orderbook.cancel_order(self.all_orders[orderid].price, orderid) self.validate() return o def on_event(self, name, evt): if name == 'trade': self.trades.append(evt) def validate(self): book = self.orderbook assert not book.bids or not book.asks or book.bids.max() < book.asks.min(), \ 'bids asks shouldn\'t cross' assert all(price == self.all_orders[makerid].price for _, makerid, _, price in self.trades), \ 'trade price equals to maker price' assert all(better_price(price, self.all_orders[takerid].price, self.all_orders[takerid].side) for takerid, _, _, price in self.trades), \ 'trade price equal or better than taker price' assert all(order.price == price for price, lvl in book.levels.items() for order in lvl.orders), \ 'level price is correct' assert all(sum(o.size for o in lvl.orders) == lvl.volume for price, lvl in book.levels.items()), \ 'level volume is correct'
class MarketSim(object): def __init__(self, order_arrival_lambda=2, initial_price=100., price_sigma=.2, quantity_mu=2, quantity_sigma=1, periods=None, traders=50, tick_size=.0001, purge_after_vol=100, open_time=dt.datetime(2017, 1, 1, 8, 0, 0), close_time=dt.datetime(2017, 1, 1, 15, 0, 0)): self.p0 = initial_price self.p = initial_price self.tick_size = tick_size self.purge_after_vol = purge_after_vol self.order_arrival_lambda = order_arrival_lambda self.price_sigma = price_sigma self.quantity_mu = quantity_mu self.quantity_sigma = quantity_sigma if periods: self.periods = periods else: self.periods = (close_time-open_time).total_seconds() if type(traders) == int: traders = range(traders) self.traders = traders self.orderbook = OrderBook() self.total_orders_submitted = 0 self.sides = np.array(['ask', 'bid']) self.sign = np.array([1., -1.]) self.time = 0 self.orders_by_vol = deque(maxlen=None) def simulate_batch(self, size=10000, verbose=False): # number of orders that arrive inter_arrival_times = np.random.exponential(self.order_arrival_lambda, size=size) timestamps = inter_arrival_times.cumsum() + self.time self.process_orders(timestamps, verbose) self.purge_stale_orders() self.time = timestamps[-1] def simulate_periods(self, periods=None, verbose=False): if not periods: periods = self.periods while self.time < periods: self.simulate_batch(size=min(10000, periods/10), verbose=verbose) def process_orders(self, timestamps, verbose=False): # if new orders arrived--add them to the book n_orders = len(timestamps) # draw side (buy/sell) distribution is binomial, p=.5 order_side = np.random.choice([0, 1], size=n_orders, replace=True) # randomly assign traders to each slot order_trader = np.random.choice(self.traders, size=n_orders, replace=True) order_price_shocks = np.random.lognormal(0, self.price_sigma, size=n_orders) order_quantity = np.ceil(np.random.lognormal(self.quantity_mu, self.quantity_sigma, size=n_orders)) for i in xrange(n_orders): if timestamps[i] > self.periods: break side = self.sides[order_side][i] order_price = self.p * order_price_shocks[i] + self.sign[order_side][i] * self.tick_size * self.p quote = { "type": "limit", "side": side, "timestamp": timestamps[i], "trade_id": order_trader[i], "order_id": self.total_orders_submitted, "price": order_price, 'quantity': int(order_quantity[i]) } self.orderbook.process_order(quote, from_data=True, verbose=verbose) self.log_order_type(side) self.total_orders_submitted += 1 self.sync_book_price() def purge_stale_orders(self): if len(self.orders_by_vol) > 0 & len(self.orderbook.tape) >= 100: current_vol = len(self.orderbook.tape) while current_vol-self.purge_after_vol >= self.orders_by_vol[0][0]: _, side, order_id = self.orders_by_vol.popleft() self.orderbook.cancel_order(side=side, order_id=order_id) if len(self.orders_by_vol) == 0: break def sync_book_price(self): if len(self.orderbook.tape) > 0: self.p = float(self.orderbook.tape[-1]['price']) def plot_prices(self, time_scale=1.,plot_params={}): interval_trades = self.get_transactions(time_scale=time_scale) interval_trades.set_index('time')['price'].apply(lambda x: float(x)).plot(**plot_params) def get_tape_dataframe(self): return pd.DataFrame(list(self.orderbook.tape)) def show_stats(self): n_trades = len(self.orderbook.tape) mean_quantity = self.get_tape_dataframe()['quantity'].apply(lambda x: float(x)).mean() print("trades per second: %3.3f" % (n_trades / self.time)) print("quantity per 600: %3.3f" % (mean_quantity * 600)) def log_order_type(self, side): order_id = self.total_orders_submitted current_vol = len(self.orderbook.tape) self.orders_by_vol.append((current_vol, str(side), int(order_id))) @staticmethod def get_side_id(side, party1, party2): if party1[1] == side: return party1[0] else: return party2[0] def get_transactions(self, time_scale=1): tape = self.get_tape_dataframe() tape['price'] = tape['price'].apply(float) tape['quantity'] = tape['quantity'].apply(int) tape['time'] = tape['timestamp'].apply(lambda x: np.floor(x/time_scale)) tape['quantity'] = (tape.groupby('time')['quantity'].transform('sum')) agg = tape.groupby('time').last().reset_index() agg['ask_id'] = agg.apply(lambda row: MarketSim.get_side_id('ask', row['party1'], row['party2']), axis=1) agg['bid_id'] = agg.apply(lambda row: MarketSim.get_side_id('bid', row['party1'], row['party2']), axis=1) return agg[['time', 'ask_id', 'bid_id', 'quantity', 'price']] def export_transactions(self, filename): self.get_transactions().to_csv(filename, index=False)
printme ("\n") print_orderbook(order_book, old_orderbook) stats=myalgo.stats() printme ("total volume=", stats[0]) printme ("my volume=", stats[1]) printme ("participation=", stats[3]) if myalgo != None: (algo_orders, mode) = myalgo.process_order(line, order) printme('') printme("RUNNING ALGO WITH MODE=", mode) old_orderbook = copy.deepcopy(order_book) for line in algo_orders: printme(pprint.pformat(line)) if line['type'] == 'cancel': order_book.cancel_order(line['side'], line['order_id']) elif line['type'] == 'cancel_all': if line['side'] == "bid": q = order_book.bids elif line['size'] == 'ask': q = order_book.asks else: sys.exit('not given bid or ask') for order in q.price_tree.get(Decimal(line['price']), []): if order.trade_id == line['trade_id']: order_book.cancel_order(line['side'], order.order_id) elif line['type'] == 'modify': order_book.modify_order(line['order_id'], { 'side': line['side'],
print_orderbook(order_book, old_orderbook) stats=myalgo.stats() printme ("total volume=", stats[0]) printme ("my volume=", stats[1]) printme ("participation=", stats[3]) if myalgo != None: (algo_orders, mode) = myalgo.process_order(line, trade, order) printme('') printme("RUNNING ALGO WITH MODE=", mode) old_orderbook = copy.deepcopy(order_book) for line in algo_orders: printme(pprint.pformat(line)) if line['type'] == 'cancel': order_book.cancel_order(line['side'], line['order_id']) elif line['type'] == 'modify': order_book.modify_order(line['order_id'], { 'side': line['side'], 'price': line['price'], 'quantity': line['quantity']}) else: (trade, order) = order_book.process_order(line, False, False) myalgo.process_trade(trade, mode) if len(algo_orders) > 0: printme("\n") printme("After algo") print_orderbook(order_book, old_orderbook) stats=myalgo.stats()