async def test_duplicate_execution_id(self): # Arrange await self._setup_account() for update in BetfairStreaming.ocm_DUPLICATE_EXECUTION(): self._setup_exec_client_and_cache(update) # # Load submitted orders # for client_order_id in (ClientOrderId('0'), ClientOrderId('1')): # order = BetfairTestStubs.make_order( # price=Price.from_str("0.5"), quantity=Quantity.from_int(10), client_order_id=client_order_id # ) # command = BetfairTestStubs.submit_order_command(order=order) # self.client.submit_order(command) # await asyncio.sleep(0) # Act for update in BetfairStreaming.ocm_DUPLICATE_EXECUTION(): self._setup_exec_client_and_cache(update=update) await self.client._handle_order_stream_update(update=update) await asyncio.sleep(0) # Assert _, fill1, cancel, fill2, fill3 = self.messages # First order example, partial fill followed by remainder canceled assert isinstance(fill1, OrderFilled) assert isinstance(cancel, OrderCanceled) # Second order example, partial fill followed by remainder filled assert (isinstance(fill2, OrderFilled) and fill2.execution_id.value == "4721ad7594e7a4a4dffb1bacb0cb45ccdec0747a") assert (isinstance(fill3, OrderFilled) and fill3.execution_id.value == "8b3e65be779968a3fdf2d72731c848c5153e88cd")
def test_orderbook_updates(self): order_books = {} for raw_update in BetfairStreaming.market_updates(): for update in on_market_update( update=raw_update, instrument_provider=self.client.instrument_provider(), ): if len(order_books) > 1 and update.instrument_id != list(order_books)[1]: continue print(update) if isinstance(update, OrderBookSnapshot): order_books[update.instrument_id] = L2OrderBook( instrument_id=update.instrument_id, price_precision=4, size_precision=4, ) order_books[update.instrument_id].apply_snapshot(update) elif isinstance(update, OrderBookDeltas): order_books[update.instrument_id].apply_deltas(update) elif isinstance(update, TradeTick): pass else: raise KeyError book = order_books[list(order_books)[0]] expected = """bids price asks -------- ------- --------- 0.8621 [932.64] 0.8547 [1275.83] 0.8475 [151.96] [147.79] 0.8403 [156.74] 0.8333 [11.19] 0.8197""" result = book.pprint() assert result == expected
async def test_betfair_order_cancelled_no_timestamp(self): update = BetfairStreaming.ocm_error_fill() self._setup_exec_client_and_cache(update) for upd in update["oc"][0]["orc"][0]["uo"]: self.client._handle_stream_execution_complete_order_update( update=upd) await asyncio.sleep(1)
async def test_market_sub_image_market_def(self): update = BetfairStreaming.mcm_SUB_IMAGE() self.client._on_market_update(update) result = [type(event).__name__ for event in self.messages] expected = ["InstrumentStatusUpdate"] * 7 + ["OrderBookSnapshot"] * 7 assert result == expected # Check prices are probabilities result = set( float(order[0]) for ob_snap in self.messages if isinstance(ob_snap, OrderBookSnapshot) for order in ob_snap.bids + ob_snap.asks ) expected = set( [ 0.0010204, 0.0076923, 0.0217391, 0.0238095, 0.1724138, 0.2173913, 0.3676471, 0.3937008, 0.4587156, 0.5555556, ] ) assert result == expected
def test_market_update_live_image(self): self.client._on_market_update(BetfairStreaming.mcm_live_IMAGE()) result = [type(event).__name__ for event in self.messages] expected = ( ["OrderBookSnapshot"] + ["TradeTick"] * 13 + ["OrderBookSnapshot"] + ["TradeTick"] * 17 ) assert result == expected
async def test_order_multiple_fills(self): # Arrange self.exec_engine.start() client_order_id = ClientOrderId("1") venue_order_id = VenueOrderId("246938411724") submitted = BetfairTestStubs.make_submitted_order( client_order_id=client_order_id, quantity=Quantity.from_int(20)) self.cache.add_order(submitted, position_id=BetfairTestStubs.position_id()) self.client.venue_order_id_to_client_order_id[ venue_order_id] = client_order_id # Act for update in BetfairStreaming.ocm_multiple_fills(): await self.client._handle_order_stream_update(update) await asyncio.sleep(0.1) # Assert result = [fill.last_qty for fill in self.messages] expected = [ Quantity.from_str("16.1900"), Quantity.from_str("0.77"), Quantity.from_str("0.77"), ] assert result == expected
async def test_order_stream_new_full_image(self): update = BetfairStreaming.ocm_NEW_FULL_IMAGE() await self._setup_account() self._setup_exec_client_and_cache(update=update) await self.client._handle_order_stream_update(update=update) await asyncio.sleep(0) assert len(self.messages) == 4
def test_orderbook_repr(self): self.client._on_market_update(BetfairStreaming.mcm_live_IMAGE()) ob_snap = self.messages[14] ob = L2OrderBook(InstrumentId(Symbol("1"), BETFAIR_VENUE), 5, 5) ob.apply_snapshot(ob_snap) print(ob.pprint()) assert ob.best_ask_price() == 0.5882353 assert ob.best_bid_price() == 0.5847953
async def test_order_filled_avp_update(self): # Arrange update = BetfairStreaming.ocm_filled_different_price() self._setup_exec_client_and_cache(update) await self._setup_account() # Act update = BetfairStreaming.generate_order_update( price="1.50", size=20, side="B", status="E", avp="1.50", sm=10 ) await self.client._handle_order_stream_update(update=update) await asyncio.sleep(0) update = BetfairStreaming.generate_order_update( price="1.30", size=20, side="B", status="E", avp="1.50", sm=10 ) await self.client._handle_order_stream_update(update=update) await asyncio.sleep(0)
def test_stream_latency(self): logs = [] self.logger.register_sink(logs.append) self.client.start() self.client._on_market_update(BetfairStreaming.mcm_latency()) warning, degrading, degraded = logs[2:] assert warning["level"] == "WRN" assert warning["msg"] == "Stream unhealthy, waiting for recover" assert degraded["msg"] == "DEGRADED."
def test_market_sub_image_no_market_def(self): self.client._on_market_update( BetfairStreaming.mcm_SUB_IMAGE_no_market_def()) result = Counter([type(event).__name__ for event in self.messages]) expected = Counter({ "InstrumentStatusUpdate": 270, "OrderBookSnapshot": 270, "InstrumentClosePrice": 22, }) assert result == expected
def test_stream_con_true(self): logs = [] self.logger.register_sink(logs.append) self.client._on_market_update(BetfairStreaming.mcm_con_true()) (warning, ) = logs assert warning["level"] == "WRN" assert ( warning["msg"] == "Conflated stream - consuming data too slow (data received is delayed)" )
async def test_various_betfair_order_fill_scenarios(self, price, size, side, status, updates): # Arrange update = BetfairStreaming.ocm_filled_different_price() self._setup_exec_client_and_cache(update) await self._setup_account() # Act for raw in updates: update = BetfairStreaming.generate_order_update( price=price, size=size, side=side, status=status, **raw ) await self.client._handle_order_stream_update(update=update) await asyncio.sleep(0) # Assert assert len(self.messages) == 1 + len(updates) for msg, raw in zip(self.messages[1:], updates): assert isinstance(msg, OrderFilled) assert msg.last_qty == raw["sm"]
def test_market_update(self): self.client._on_market_update(BetfairStreaming.mcm_UPDATE()) result = [type(event).__name__ for event in self.messages] expected = ["OrderBookDeltas"] * 1 assert result == expected result = [d.action for d in self.messages[0].deltas] expected = [BookAction.UPDATE, BookAction.DELETE] assert result == expected # Ensure order prices are coming through as probability update_op = self.messages[0].deltas[0] assert update_op.order.price == 0.212766
async def test_order_stream_update(self): # Arrange update = BetfairStreaming.ocm_UPDATE() await self._setup_account() self._setup_exec_client_and_cache(update=update) # Act await self.client._handle_order_stream_update(update=update) await asyncio.sleep(0) # Assert assert len(self.messages) == 2
async def test_order_stream_filled(self): # Arrange update = BetfairStreaming.ocm_FILLED() self._setup_exec_client_and_cache(update) await self._setup_account() # Act await self.client._handle_order_stream_update(update=update) await asyncio.sleep(0) # Assert assert len(self.messages) == 2 assert isinstance(self.messages[1], OrderFilled) assert self.messages[1].last_px == Price.from_str("0.9090909")
async def test_order_stream_filled_multiple_prices(self): # Arrange await self._setup_account() update1 = BetfairStreaming.generate_order_update( price="1.50", size=20, side="B", status="E", sm=10, avp="1.60", ) self._setup_exec_client_and_cache(update1) await self.client._handle_order_stream_update(update=update1) await asyncio.sleep(0) order = self.cache.order(client_order_id=ClientOrderId("0")) event = self.messages[-1] order.apply(event) # Act update2 = BetfairStreaming.generate_order_update( price="1.50", size=20, side="B", status="EC", sm=20, avp="1.55", ) self._setup_exec_client_and_cache(update2) await self.client._handle_order_stream_update(update=update2) await asyncio.sleep(0) # Assert assert len(self.messages) == 3 assert isinstance(self.messages[1], OrderFilled) assert isinstance(self.messages[2], OrderFilled) assert self.messages[1].last_px == price_to_probability("1.60") assert self.messages[2].last_px == price_to_probability("1.50")
async def test_order_stream_mixed(self): # Arrange update = BetfairStreaming.ocm_MIXED() self._setup_exec_client_and_cache(update) await self._setup_account() # Act await self.client._handle_order_stream_update(update=update) await asyncio.sleep(0) # Assert _, fill1, fill2, cancel = self.messages assert isinstance(fill1, OrderFilled) and fill1.venue_order_id.value == "229430281341" assert isinstance(fill2, OrderFilled) and fill2.venue_order_id.value == "229430281339" assert isinstance(cancel, OrderCanceled) and cancel.venue_order_id.value == "229430281339"
def test_market_update_runner_removed(self): update = BetfairStreaming.market_definition_runner_removed() # Setup market_def = update["mc"][0]["marketDefinition"] market_def["marketId"] = update["mc"][0]["id"] instruments = make_instruments( market_definition=update["mc"][0]["marketDefinition"], currency="GBP") self.provider.add_bulk(instruments) results = [] for data in on_market_update(instrument_provider=self.provider, update=update): results.append(data) result = [r.status for r in results[:8]] expected = [InstrumentStatus.PRE_OPEN] * 7 + [InstrumentStatus.CLOSED] assert result == expected
def test_market_bsp(self): # Setup update = BetfairStreaming.mcm_BSP() provider = self.client.instrument_provider() for mc in update[0]["mc"]: market_def = {**mc["marketDefinition"], "marketId": mc["id"]} instruments = make_instruments(market_definition=market_def, currency="GBP") provider.add_bulk(instruments) for update in update: self.client._on_market_update(update) result = Counter([type(event).__name__ for event in self.messages]) expected = { "TradeTick": 95, "BSPOrderBookDelta": 30, "InstrumentStatusUpdate": 9, "OrderBookSnapshot": 8, "OrderBookDeltas": 2, } assert result == expected
def test_market_resub_delta(self): self.client._on_market_update(BetfairStreaming.mcm_RESUB_DELTA()) result = [type(event).__name__ for event in self.messages] expected = ["InstrumentStatusUpdate"] * 12 + ["OrderBookDeltas"] * 269 assert result == expected
def test_market_update_md(self): self.client._on_market_update(BetfairStreaming.mcm_UPDATE_md()) result = [type(event).__name__ for event in self.messages] expected = ["InstrumentStatusUpdate"] * 2 assert result == expected
def test_market_heartbeat(self): self.client._on_market_update(BetfairStreaming.mcm_HEARTBEAT())
def test_market_update_live_update(self): self.client._on_market_update(BetfairStreaming.mcm_live_UPDATE()) result = [type(event).__name__ for event in self.messages] expected = ["TradeTick", "OrderBookDeltas"] assert result == expected
def test_betfair_ticker(self): self.client._on_market_update(BetfairStreaming.mcm_UPDATE_tv()) ticker: BetfairTicker = self.messages[1] assert ticker.last_traded_price == Price.from_str("0.3174603") assert ticker.traded_volume == Quantity.from_str("364.45")