Example #1
0
def build_market_snapshot_messages(
        raw, instrument_provider: BetfairInstrumentProvider
) -> List[OrderBookSnapshot]:
    updates = []
    for market in raw.get("mc", []):
        # Market status events
        # market_definition = market.get("marketDefinition", {})
        # TODO - Need to handle instrument status = CLOSED here

        # Orderbook snapshots
        if market.get("img") is True:
            market_id = market["id"]
            for (selection_id, handicap), selections in itertools.groupby(
                    market.get("rc", []), lambda x: (x["id"], x.get("hc"))):
                for selection in list(selections):
                    kw = dict(
                        market_id=market_id,
                        selection_id=str(selection_id),
                        handicap=str(handicap or "0.0"),
                    )
                    instrument = instrument_provider.get_betting_instrument(
                        **kw)
                    # Check we only have one of [best bets / depth bets / all bets]
                    bid_keys = [k for k in B_BID_KINDS if k in selection
                                ] or ["atb"]
                    ask_keys = [k for k in B_ASK_KINDS if k in selection
                                ] or ["atl"]
                    assert len(bid_keys) <= 1
                    assert len(ask_keys) <= 1
                    # TODO Clean this crap up
                    if bid_keys[0] == "atb":
                        bids = selection.get("atb", [])
                    else:
                        bids = [(p, v)
                                for _, p, v in selection.get(bid_keys[0], [])]
                    if ask_keys[0] == "atl":
                        asks = selection.get("atl", [])
                    else:
                        asks = [(p, v)
                                for _, p, v in selection.get(ask_keys[0], [])]
                    snapshot = OrderBookSnapshot(
                        level=OrderBookLevel.L2,
                        instrument_id=instrument.id,
                        bids=[(price_to_probability(p, OrderSide.BUY), v)
                              for p, v in asks],
                        asks=[(price_to_probability(p, OrderSide.SELL), v)
                              for p, v in bids],
                        timestamp_ns=millis_to_nanos(raw["pt"]),
                    )
                    updates.append(snapshot)
    return updates
Example #2
0
def build_market_update_messages(  # noqa TODO: cyclomatic complexity 14
    raw, instrument_provider: BetfairInstrumentProvider
) -> List[Union[OrderBookOperation, TradeTick]]:
    updates = []
    for market in raw.get("mc", []):
        market_id = market["id"]
        for runner in market.get("rc", []):
            kw = dict(
                market_id=market_id,
                selection_id=str(runner["id"]),
                handicap=str(runner.get("hc") or "0.0"),
            )
            instrument = instrument_provider.get_betting_instrument(**kw)
            if not instrument:
                continue
            operations = []
            for side in B_SIDE_KINDS:
                for upd in runner.get(side, []):
                    # TODO - Fix this crap
                    if len(upd) == 3:
                        _, price, volume = upd
                    else:
                        price, volume = upd
                    operations.append(
                        OrderBookOperation(
                            op_type=OrderBookOperationType.DELETE
                            if volume == 0 else OrderBookOperationType.UPDATE,
                            order=Order(
                                price=price_to_probability(
                                    price, side=B2N_MARKET_STREAM_SIDE[side]),
                                volume=volume,
                                side=B2N_MARKET_STREAM_SIDE[side],
                            ),
                            timestamp_ns=millis_to_nanos(raw["pt"]),
                        ))
            ob_update = OrderBookOperations(
                level=OrderBookLevel.L2,
                instrument_id=instrument.id,
                ops=operations,
                timestamp_ns=millis_to_nanos(raw["pt"]),
            )
            updates.append(ob_update)

            for price, volume in runner.get("trd", []):
                # Betfair doesn't publish trade ids, so we make our own
                # TODO - should we use clk here?
                trade_id = hash_json(data=(
                    raw["pt"],
                    market_id,
                    str(runner["id"]),
                    str(runner.get("hc", "0.0")),
                    price,
                    volume,
                ))
                trade_tick = TradeTick(
                    instrument_id=instrument.id,
                    price=Price(price_to_probability(price)),
                    size=Quantity(volume, precision=4),
                    side=OrderSide.BUY,
                    match_id=TradeMatchId(trade_id),
                    timestamp_ns=millis_to_nanos(raw["pt"]),
                )
                updates.append(trade_tick)

        if market.get("marketDefinition", {}).get("status") == "CLOSED":
            for runner in market["marketDefinition"]["runners"]:
                kw = dict(
                    market_id=market_id,
                    selection_id=str(runner["id"]),
                    handicap=str(runner.get("hc") or "0.0"),
                )
                instrument = instrument_provider.get_betting_instrument(**kw)
                assert instrument
                # TODO - handle market closed
                # on_market_status()

                if runner["status"] == "LOSER":
                    # TODO - handle closing valuation = 0
                    pass
                elif runner["status"] == "WINNER":
                    # TODO handle closing valuation = 1
                    pass
        if (market.get("marketDefinition", {}).get("inPlay")
                and not market.get("marketDefinition",
                                   {}).get("status") == "CLOSED"):
            for selection in market["marketDefinition"]["runners"]:
                kw = dict(
                    market_id=market_id,
                    selection_id=str(selection["id"]),
                    handicap=str(
                        selection.get("hc", selection.get("handicap"))
                        or "0.0"),
                )
                instrument = instrument_provider.get_betting_instrument(**kw)
                assert instrument
                # TODO - handle instrument status IN_PLAY
    return updates
class TestBetfairInstrumentProvider:
    def setup(self):
        # Fixture Setup
        self.loop = asyncio.get_event_loop()
        self.clock = LiveClock()
        self.logger = LiveLogger(loop=self.loop, clock=self.clock)
        self.client = BetfairTestStubs.betfair_client(loop=self.loop,
                                                      logger=self.logger)
        self.provider = BetfairInstrumentProvider(
            client=self.client,
            logger=TestComponentStubs.logger(),
        )

    @pytest.mark.asyncio
    async def test_load_markets(self):
        markets = await load_markets(self.client, market_filter={})
        assert len(markets) == 13227

        markets = await load_markets(
            self.client, market_filter={"event_type_name": "Basketball"})
        assert len(markets) == 302

        markets = await load_markets(
            self.client, market_filter={"event_type_name": "Tennis"})
        assert len(markets) == 1958

        markets = await load_markets(
            self.client, market_filter={"market_id": "1.177125728"})
        assert len(markets) == 1

    @pytest.mark.asyncio
    async def test_load_markets_metadata(self):
        markets = await load_markets(
            self.client, market_filter={"event_type_name": "Basketball"})
        market_metadata = await load_markets_metadata(client=self.client,
                                                      markets=markets)
        assert isinstance(market_metadata, dict)
        assert len(market_metadata) == 169

    @pytest.mark.asyncio
    async def test_make_instruments(self):
        # Arrange
        list_market_catalogue_data = {
            m["marketId"]: m
            for m in BetfairResponses.betting_list_market_catalogue()["result"]
            if m["eventType"]["name"] == "Basketball"
        }

        # Act
        instruments = [
            instrument for metadata in list_market_catalogue_data.values()
            for instrument in make_instruments(metadata, currency="GBP")
        ]

        # Assert
        assert len(instruments) == 30412

    @pytest.mark.asyncio
    async def test_load_all(self):
        await self.provider.load_all_async({"event_type_name": "Tennis"})
        assert len(self.provider.list_all()) == 4711

    @pytest.mark.asyncio
    async def test_list_all(self):
        await self.provider.load_all_async(
            market_filter={"event_type_name": "Basketball"})
        instruments = self.provider.list_all()
        assert len(instruments) == 23908

    @pytest.mark.asyncio
    async def test_search_instruments(self):
        await self.provider.load_all_async(
            market_filter={"event_type_name": "Basketball"})
        instruments = self.provider.search_instruments(
            instrument_filter={"market_type": "MATCH_ODDS"})
        assert len(instruments) == 104

    @pytest.mark.asyncio
    async def test_get_betting_instrument(self):
        await self.provider.load_all_async(
            market_filter={"market_id": ["1.180678317"]})
        kw = dict(
            market_id="1.180678317",
            selection_id="11313157",
            handicap="0.0",
        )
        instrument = self.provider.get_betting_instrument(**kw)
        assert instrument.market_id == "1.180678317"

        # Test throwing warning
        kw["handicap"] = "-1000"
        instrument = self.provider.get_betting_instrument(**kw)
        assert instrument is None

        # Test already in self._subscribed_instruments
        instrument = self.provider.get_betting_instrument(**kw)
        assert instrument is None

    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