def test_all_or_none_maker(self): self.ob = OrderBook(_INSTRUMENT) _seed(self.ob, _INSTRUMENT, Order.Flags.ALL_OR_NONE) assert self.ob.topOfBook() == { Side.BUY: [5.0, 1.0], Side.SELL: [5.5, 1.0] } data = Order(volume=0.5, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.LIMIT, instrument=_INSTRUMENT) print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [4.5, 1.0], Side.SELL: [5.0, 0.5] } data = Order(volume=1.5, price=4.0, side=Order.Sides.SELL, order_type=Order.Types.LIMIT, instrument=_INSTRUMENT) print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [3.5, 1.0], Side.SELL: [4.0, 0.5] }
def test_order_book_run(self): ob = OrderBook(_INSTRUMENT) _seed(ob, _INSTRUMENT) assert ob.topOfBook()[Side.BUY] == [5.0, 1.0] assert ob.topOfBook()[Side.SELL] == [5.5, 1.0] data = Order(volume=5.0, price=4.5, side=Order.Sides.SELL, instrument=_INSTRUMENT) ob.add(data) print(ob) assert ob.topOfBook() == {Side.BUY: [4.0, 1.0], Side.SELL: [4.5, 3.0]} print(ob.levels(3)) assert ob.levels(3) == {Side.BUY: [[4.0, 1.0], [3.5, 1.0], [3.0, 1.0]], Side.SELL: [[4.5, 3.0], [5.5, 1.0], [6.0, 1.0]]} data = Order(volume=4.0, price=5.5, side=Order.Sides.BUY, instrument=_INSTRUMENT) ob.add(data) print(ob) assert ob.topOfBook() == {Side.BUY: [4.0, 1.0], Side.SELL: [6.0, 1.0]} print(ob.levels(3)) assert ob.levels(3) == {Side.BUY: [[4.0, 1.0], [3.5, 1.0], [3.0, 1.0]], Side.SELL: [[6.0, 1.0], [6.5, 1.0], [7.0, 1.0]]}
def test_stop_limit(self): ob = OrderBook(_INSTRUMENT) _seed(ob, _INSTRUMENT) assert ob.topOfBook()[Side.BUY] == [5.0, 1.0] assert ob.topOfBook()[Side.SELL] == [5.5, 1.0] print(ob) assert ob.topOfBook() == {Side.BUY: [5.0, 1.0], Side.SELL: [5.5, 1.0]} data = Order( volume=0.0, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.STOP, stop_target=Order( volume=1.0, price=4.0, side=Order.Sides.SELL, order_type=Order.Types.MARKET, instrument=_INSTRUMENT, ), instrument=_INSTRUMENT, ) print(ob) ob.add(data) data = Order( volume=0.0, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.STOP, stop_target=Order( volume=0.5, price=5.0, side=Order.Sides.SELL, instrument=_INSTRUMENT, order_type=Order.Types.LIMIT, ), instrument=_INSTRUMENT, ) print(ob) ob.add(data) print(ob.topOfBook()) assert ob.topOfBook() == {Side.BUY: [5.0, 1.0], Side.SELL: [5.5, 1.0]} data = Order( volume=0.5, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.LIMIT, instrument=_INSTRUMENT, ) print(ob) ob.add(data) print(ob.topOfBook()) assert ob.topOfBook() == {Side.BUY: [4.5, 0.5], Side.SELL: [5.0, 0.5]}
def test_immediate_or_cancel_taker_limit(self): data = Order(id=1, timestamp=datetime.now(), volume=2.0, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.LIMIT, flag=Order.Flags.IMMEDIATE_OR_CANCEL, instrument=_INSTRUMENT) print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [4.5, 1.0], Side.SELL: [5.5, 1.0] } data = Order(id=1, timestamp=datetime.now(), volume=2.0, price=4.0, side=Order.Sides.SELL, order_type=Order.Types.LIMIT, flag=Order.Flags.IMMEDIATE_OR_CANCEL, instrument=_INSTRUMENT) print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [3.5, 1.0], Side.SELL: [5.5, 1.0] }
async def connect(self) -> None: with open(self._filename) as csvfile: self._reader = csv.DictReader(csvfile, delimiter=",") for row in self._reader: order = Order( volume=float(row["volume"]), price=float(row["close"]), side=Side.BUY, exchange=self.exchange(), instrument=Instrument( row["symbol"].split("-")[0], InstrumentType(row["symbol"].split("-")[1].upper()), ), filled=float(row["volume"]), ) if "time" in row: order.timestamp = datetime.fromtimestamp(float( row["time"])) elif "date" in row: order.timestamp = datetime.fromisoformat(row["date"]) elif "datetime" in row: order.timestamp = datetime.fromisoformat(row["datetime"]) self._data.append( Trade( volume=float(row["volume"]), price=float(row["close"]), maker_orders=[], taker_order=order, ))
def test_fill_or_kill_taker_limit(self): data = Order(id=1, timestamp=datetime.now(), volume=2.0, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.LIMIT, flag=Order.Flags.FILL_OR_KILL, instrument=_INSTRUMENT) print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [5.0, 1.0], Side.SELL: [5.5, 1.0] } data = Order(id=1, timestamp=datetime.now(), volume=2.0, price=4.5, side=Order.Sides.SELL, order_type=Order.Types.LIMIT, flag=Order.Flags.FILL_OR_KILL, instrument=_INSTRUMENT) print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [4.0, 1.0], Side.SELL: [5.5, 1.0] }
def test_all_or_none_market(self): data = Order(id=1, timestamp=datetime.now(), volume=1.5, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.MARKET, flag=Order.Flags.ALL_OR_NONE, instrument=_INSTRUMENT) print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [5.0, 1.0], Side.SELL: [5.5, 1.0] } data = Order(id=1, timestamp=datetime.now(), volume=0.5, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.MARKET, flag=Order.Flags.ALL_OR_NONE, instrument=_INSTRUMENT) print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [5.0, 0.5], Side.SELL: [5.5, 1.0] }
def clone(self): """clone an order book. useful when you want to do experiments on an orderbook without destroying it""" obl = OrderBookLite(self.instrument, self.exchange_name, self.callback) # create one order per price level for level in self._sell_levels: pl: _PriceLevel = self._sells[level] self.add( Order( pl.volume, pl.price, Side.SELL, self.instrument, self.exchange, order_type=OrderType.LIMIT, )) # create one order per price level for level in self._buy_levels: pl: _PriceLevel = self._buys[level] self.add( Order( pl.volume, pl.price, Side.BUY, self.instrument, self.exchange, order_type=OrderType.LIMIT, )) # return newly constructed order book return obl
def _seed(ob, instrument, flag=OrderFlag.NONE): x = .5 while x < 10.0: side = Side.BUY if x <= 5 else Side.SELL order = Order(volume=1.0, price=x, side=side, instrument=instrument, exchange=ExchangeType(""), order_type=OrderType.LIMIT, flag=flag) order.id = "1" ob.add(order) x += .5
def test_order_book_market_order(self): ob = OrderBook(_INSTRUMENT) _seed(ob, _INSTRUMENT) assert ob.topOfBook()[Side.BUY] == [5.0, 1.0] assert ob.topOfBook()[Side.SELL] == [5.5, 1.0] data = Order( volume=100.0, price=0.0, side=Order.Sides.SELL, order_type=Order.Types.MARKET, instrument=_INSTRUMENT, ) ob.add(data) print(ob) print(ob.topOfBook()) assert ob.topOfBook() == {Side.BUY: [0, 0], Side.SELL: [5.5, 1.0]} print(ob.levels(3)) assert ob.levels(3) == { Side.BUY: [[0, 0], [0, 0], [0, 0]], Side.SELL: [[5.5, 1.0], [6.0, 1.0], [6.5, 1.0]], }
async def tick( self) -> AsyncGenerator[Any, Event]: # type: ignore[override] now = self._start for i in range(1000): if self._client_order: self._client_order.filled = self._client_order.volume t = Trade( self._client_order.volume, i, taker_order=self._client_order, maker_orders=[], ) t.taker_order.timestamp = now self._client_order = None yield Event(type=EventType.TRADE, target=t) continue o = Order( 1, i, Side.BUY, self._instrument, self.exchange(), timestamp=now, filled=1, ) t = Trade(1, i, o, []) yield Event(type=EventType.TRADE, target=t) now += timedelta(minutes=30)
async def newOrder(self, order: Order): if self._trading_type == TradingType.LIVE: raise NotImplementedError("Live OE not available for CSV") order.id = str(self._order_id) self._order_id += 1 self._queued_orders.append(order) return order
async def tick(self): now = self._start for i in range(1000): if self._client_order: self._client_order.filled = self._client_order.volume t = Trade(self._client_order.volume, i, [], self._client_order) t.taker_order.timestamp = now self._client_order = None yield Event(type=EventType.TRADE, target=t) continue o = Order(1, i, Side.BUY, self._instrument, self.exchange()) o.filled = 1 o.timestamp = now t = Trade(1, i, [], o) yield Event(type=EventType.TRADE, target=t) now += timedelta(minutes=30)
async def tick(self): '''return data from exchange''' while True: # clear order events while self._order_event_queue.qsize() > 0: order_data = self._order_event_queue.get() status = order_data['status'] order = self._orders[order_data['orderId']] if status in ('ApiPending', 'PendingSubmit', 'PendingCancel', 'PreSubmitted', 'ApiCancelled', 'Inactive'): # ignore continue elif status in ('Submitted',): e = Event(type=EventType.RECEIVED, target=order) yield e elif status in ('Cancelled',): e = Event(type=EventType.CANCELED, target=order) yield e elif status in ('Filled',): # this is the filled from orderStatus, but we # want to use the one from execDetails # From the IB Docs: # "There are not guaranteed to be orderStatus # callbacks for every change in order status" # It is recommended to use execDetails # ignore pass elif status in ('Execution',): # set filled order.filled = order_data['filled'] # create trade object t = Trade(volume=order_data['filled'], price=order_data['avgFillPrice'], maker_orders=[], taker_order=order) # set my order t.my_order = order e = Event(type=EventType.TRADE, target=t) yield e # clear market data events while self._market_data_queue.qsize() > 0: market_data = self._market_data_queue.get() instrument = market_data['instrument'] price = market_data['price'] o = Order(volume=1, price=price, side=Side.BUY, instrument=instrument, exchange=self.exchange()) t = Trade(volume=1, price=price, taker_order=o, maker_orders=[]) yield Event(type=EventType.TRADE, target=t) await asyncio.sleep(0)
async def tick(self): '''return data from exchange''' while True: # clear order events while self._order_event_queue.qsize() > 0: order_data = self._order_event_queue.get() status = order_data['status'] order = self._orders[order_data['orderId']] if status in ('ApiPending', 'PendingSubmit', 'PendingCancel', 'PreSubmitted', 'ApiCancelled', 'Inactive'): # ignore continue elif status in ('Submitted', ): # TODO more granular order events api? # ignore pass elif status in ('Cancelled', ): e = Event(type=EventType.CANCELED, target=order) yield e elif status in ('Filled', ): # set filled order.filled = order_data['filled'] # create trade object t = Trade(volume=order_data['filled'], price=order_data['avgFillPrice'], maker_orders=[], taker_order=order) # set my order t.my_order = order e = Event(type=EventType.TRADE, target=t) yield e # clear market data events while self._market_data_queue.qsize() > 0: market_data = self._market_data_queue.get() instrument = market_data['instrument'] price = market_data['price'] o = Order(volume=1, price=price, side=Side.BUY, instrument=instrument, exchange=self.exchange()) t = Trade(volume=1, price=price, taker_order=o, maker_orders=[]) yield Event(type=EventType.TRADE, target=t) await asyncio.sleep(0)
def test_stop_order_validation(self): if _in_cpp(): return with pytest.raises(AssertionError): Order(volume=0.0, price=5.0, side=Order.Sides.SELL, exchange=ExchangeType(''), order_type=Order.Types.STOP, stop_target=Order( volume=0.5, price=5.0, side=Order.Sides.SELL, exchange=ExchangeType(''), order_type=Order.Types.STOP, instrument=_INSTRUMENT, ), instrument=_INSTRUMENT)
def test_maker_orders_validation(self): if not os.environ.get('AAT_USE_CPP'): with pytest.raises(Exception): o = Order(volume=0.0, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.LIMIT, exchange=ExchangeType(''), instrument=_INSTRUMENT) Trade(maker_orders=deque(), taker_order=o)
def test_stop_market(self): ob = OrderBook(_INSTRUMENT) _seed(ob, _INSTRUMENT) assert ob.topOfBook()[Side.BUY] == [5.0, 1.0] assert ob.topOfBook()[Side.SELL] == [5.5, 1.0] print(ob) assert ob.topOfBook() == {Side.BUY: [5.0, 1.0], Side.SELL: [5.5, 1.0]} data = Order(id=1, timestamp=datetime.now(), volume=0.0, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.STOP, stop_target=Order(id=1, timestamp=datetime.now(), volume=0.5, price=4.5, side=Order.Sides.SELL, instrument=_INSTRUMENT), instrument=_INSTRUMENT) print(ob) ob.add(data) print(ob.topOfBook()) assert ob.topOfBook() == {Side.BUY: [5.0, 1.0], Side.SELL: [5.5, 1.0]} data = Order(id=1, timestamp=datetime.now(), volume=0.5, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.LIMIT, instrument=_INSTRUMENT) print(ob) ob.add(data) print(ob.topOfBook()) assert ob.topOfBook() == {Side.BUY: [4.5, 1.0], Side.SELL: [5.5, 1.0]}
def fromPriceLevels( self, instrument: Instrument, exchange_name: str = "", callback: Optional[Callable] = None, price_levels: Optional[Mapping[Side, List[PriceLevelRO]]] = None, ) -> "OrderBookLite": """construct order book from price levels""" price_levels = price_levels or {} obl = OrderBookLite(instrument, exchange_name, callback) # create one order per price level for level in price_levels[Side.SELL]: level = level if isinstance(level, PriceLevelRO) else PriceLevelRO( level[0], level[1], 1) # type: ignore self.add( Order( level.volume, level.price, Side.SELL, obl.instrument, obl.exchange, order_type=OrderType.LIMIT, )) # create one order per price level for level in price_levels[Side.BUY]: level = level if isinstance(level, PriceLevelRO) else PriceLevelRO( level[0], level[1], 1) # type: ignore self.add( Order( level.volume, level.price, Side.BUY, obl.instrument, obl.exchange, order_type=OrderType.LIMIT, )) # return newly constructed order book return obl
def test_price_level_iter(self): pl = _PriceLevel(5, _Collector()) orders = [ Order(10 + i, 5, Side.BUY, Instrument('TEST'), ExchangeType(""), 0.0, OrderType.LIMIT, OrderFlag.NONE, None) for i in range(2) ] for o in orders: # This causes a segfault pl.add(o) for o, op in zip(orders, pl): assert o == op
async def newOrder(self, order: Order): '''submit a new order to the exchange. should set the given order's `id` field to exchange-assigned id For MarketData-only, can just return None ''' if self._trading_type == TradingType.LIVE: raise NotImplementedError("Live OE not available for IEX") order.id = self._order_id self._order_id += 1 self._queued_orders.append(order) return order
def test_fill_or_kill_market(self): data = Order(volume=2.0, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.MARKET, flag=Order.Flags.FILL_OR_KILL, instrument=_INSTRUMENT) data.id = "1" print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [5.0, 1.0], Side.SELL: [5.5, 1.0] } data = Order(volume=2.0, price=4.5, side=Order.Sides.SELL, order_type=Order.Types.MARKET, flag=Order.Flags.FILL_OR_KILL, instrument=_INSTRUMENT) data.id = "1" print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [4.0, 1.0], Side.SELL: [5.5, 1.0] }
async def connect(self): with open(self._filename) as csvfile: self._reader = csv.DictReader(csvfile, delimiter=',') for row in self._reader: order = Order(volume=float(row['volume']), price=float(row['close']), side=Side.BUY, exchange=self.exchange(), instrument=Instrument( row['symbol'].split('-')[0], InstrumentType(row['symbol'].split('-')[1].upper()) ) ) order.filled = float(row['volume']) if 'time' in row: order.timestamp = datetime.fromtimestamp(float(row['time'])) elif 'date' in row: order.timestamp = datetime.fromisoformat(row['date']) elif 'datetime' in row: order.timestamp = datetime.fromisoformat(row['datetime']) self._data.append(Trade(volume=float(row['volume']), price=float(row['close']), maker_orders=[], taker_order=order))
def test_all_or_none_taker_limit(self): data = Order(volume=1.5, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.LIMIT, flag=Order.Flags.ALL_OR_NONE, instrument=_INSTRUMENT) data.id = "1" print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [5.0, 1.0], Side.SELL: [5.5, 1.0] } data = Order(volume=0.5, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.LIMIT, flag=Order.Flags.ALL_OR_NONE, instrument=_INSTRUMENT) data.id = "1" print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [5.0, 0.5], Side.SELL: [5.5, 1.0] }
def test_immediate_or_cancel_market(self): data = Order(volume=2.0, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.MARKET, flag=Order.Flags.IMMEDIATE_OR_CANCEL, instrument=_INSTRUMENT) data.id = "1" print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [4.5, 1.0], Side.SELL: [5.5, 1.0] } data = Order(volume=2.0, price=4.0, side=Order.Sides.SELL, order_type=Order.Types.MARKET, flag=Order.Flags.IMMEDIATE_OR_CANCEL, instrument=_INSTRUMENT) data.id = "1" print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [3.5, 1.0], Side.SELL: [5.5, 1.0] }
def test_immediate_or_cancel_maker(self): self.ob = OrderBook(_INSTRUMENT) _seed(self.ob, _INSTRUMENT, Order.Flags.IMMEDIATE_OR_CANCEL) assert self.ob.topOfBook() == { Side.BUY: [5.0, 1.0], Side.SELL: [5.5, 1.0] } data = Order(volume=0.5, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.LIMIT, instrument=_INSTRUMENT) data.id = "1" print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [4.5, 1.0], Side.SELL: [5.5, 1.0] }
def test_fill_or_kill_maker(self): self.ob = OrderBook(_INSTRUMENT) _seed(self.ob, _INSTRUMENT, Order.Flags.FILL_OR_KILL) assert self.ob.topOfBook() == { Side.BUY: [5.0, 1.0], Side.SELL: [5.5, 1.0] } data = Order(id=1, timestamp=datetime.now(), volume=0.5, price=5.0, side=Order.Sides.SELL, order_type=Order.Types.LIMIT, instrument=_INSTRUMENT) print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [4.5, 1.0], Side.SELL: [5.0, 0.5] } data = Order(id=1, timestamp=datetime.now(), volume=1.5, price=4.0, side=Order.Sides.SELL, order_type=Order.Types.LIMIT, instrument=_INSTRUMENT) print(self.ob) self.ob.add(data) print(self.ob.topOfBook()) assert self.ob.topOfBook() == { Side.BUY: [3.5, 1.0], Side.SELL: [4.0, 0.5] }
def test_stop_order_validation(self): if _in_cpp(): return with pytest.raises(pydantic.ValidationError): Order(id=1, timestamp=datetime.now(), volume=0.0, price=5.0, side=Order.Sides.SELL, exchange=ExchangeType(''), order_type=Order.Types.STOP, stop_target=Order( id=1, timestamp=datetime.now(), volume=0.5, price=5.0, side=Order.Sides.SELL, exchange=ExchangeType(''), order_type=Order.Types.STOP, instrument=_INSTRUMENT, ), instrument=_INSTRUMENT)
async def tick(self): '''return data from exchange''' dfs = [] for i in self._subscriptions: df = self._client.chartDF(i.name, timeframe='6m') df = df[['close', 'volume']] df.columns = ['close:{}'.format(i.name), 'volume:{}'.format(i.name)] dfs.append(df) data = pd.concat(dfs, axis=1) data.sort_index(inplace=True) data = data.groupby(data.index).last() data.drop_duplicates(inplace=True) data.fillna(method='ffill', inplace=True) for index in data.index: for i in self._subscriptions: volume = data.loc[index]['volume:{}'.format(i.name)] price = data.loc[index]['close:{}'.format(i.name)] o = Order(volume=volume, price=price, side=Side.BUY, instrument=i, exchange=self.exchange()) o.timestamp = index.to_pydatetime() t = Trade(volume=volume, price=price, taker_order=o, maker_orders=[]) yield Event(type=EventType.TRADE, target=t) await asyncio.sleep(0) while self._queued_orders: order = self._queued_orders.popleft() order.timestamp = index t = Trade(volume=order.volume, price=order.price, taker_order=order, maker_orders=[]) t.my_order = order yield Event(type=EventType.TRADE, target=t) await asyncio.sleep(0)
def _seed(ob, instrument, flag=OrderFlag.NONE): x = .5 while x < 10.0: side = Side.BUY if x <= 5 else Side.SELL ob.add(Order(id=1, timestamp=datetime.now(), volume=1.0, price=x, side=side, instrument=instrument, exchange=ExchangeType(""), filled=0.0, order_type=OrderType.LIMIT, flag=flag)) x += .5