def instrument_list(mock_load_markets_metadata,
                    loop: asyncio.AbstractEventLoop):
    """Prefill `INSTRUMENTS` cache for tests"""
    global INSTRUMENTS

    # Setup
    logger = LiveLogger(loop=loop,
                        clock=LiveClock(),
                        level_stdout=LogLevel.ERROR)
    client = BetfairTestStubs.betfair_client(loop=loop, logger=logger)
    logger = LiveLogger(loop=loop,
                        clock=LiveClock(),
                        level_stdout=LogLevel.DEBUG)
    instrument_provider = BetfairInstrumentProvider(client=client,
                                                    logger=logger,
                                                    market_filter={})

    # Load instruments
    market_ids = BetfairDataProvider.market_ids()
    catalog = {
        r["marketId"]: r
        for r in BetfairResponses.betting_list_market_catalogue()["result"]
        if r["marketId"] in market_ids
    }
    mock_load_markets_metadata.return_value = catalog
    t = loop.create_task(
        instrument_provider.load_all_async(
            market_filter={"market_id": market_ids}))
    loop.run_until_complete(t)

    # Fill INSTRUMENTS global cache
    INSTRUMENTS.extend(instrument_provider.list_all())
    assert INSTRUMENTS
 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(),
     )
 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=BetfairTestStubs.live_logger(BetfairTestStubs.clock()),
         market_filter=None,
     )
Exemple #4
0
def get_cached_betfair_instrument_provider(
    client: BetfairClient,
    logger: Logger,
    market_filter: tuple,
) -> BetfairInstrumentProvider:
    """
    Cache and return a BetfairInstrumentProvider.

    If a cached provider already exists, then that cached provider will be returned.

    Parameters
    ----------
    client : BinanceHttpClient
        The client for the instrument provider.
    logger : Logger
        The logger for the instrument provider.
    market_filter : tuple
        The market filter to load into the instrument provider.

    Returns
    -------
    BinanceInstrumentProvider

    """
    LoggerAdapter(
        "BetfairFactory",
        logger).warning("Creating new instance of BetfairInstrumentProvider")
    return BetfairInstrumentProvider(client=client,
                                     logger=logger,
                                     market_filter=dict(market_filter))
    def test_byte_reader_parser(self):
        def block_parser(block: bytes, instrument_provider):
            for raw in block.split(b"\\n"):
                ts, line = raw.split(b" - ")
                state = {
                    "ts_init":
                    int(pd.Timestamp(ts.decode(), tz="UTC").to_datetime64())
                }
                line = line.strip().replace(b"b'", b"")
                orjson.loads(line)
                for obj in BetfairTestStubs.parse_betfair(
                        line, instrument_provider=instrument_provider):
                    values = obj.to_dict(obj)
                    values["ts_init"] = state["ts_init"]
                    yield obj.from_dict(values)

        provider = BetfairInstrumentProvider.from_instruments(
            [BetfairTestStubs.betting_instrument()])
        block = BetfairDataProvider.badly_formatted_log()
        reader = ByteReader(
            block_parser=partial(block_parser, instrument_provider=provider),
            instrument_provider=provider,
        )

        data = list(reader.parse(block=block))
        result = [pd.Timestamp(d.ts_init).isoformat() for d in data]
        expected = ["2021-06-29T06:03:14.528000"]
        assert result == expected
 def _loaded_data_into_catalog(self):
     self.instrument_provider = BetfairInstrumentProvider.from_instruments([])
     process_files(
         glob_path=PACKAGE_ROOT + "/data/1.166564490.bz2",
         reader=BetfairTestStubs.betfair_reader(instrument_provider=self.instrument_provider),
         instrument_provider=self.instrument_provider,
         catalog=self.catalog,
     )
 def test_text_reader(self):
     provider = BetfairInstrumentProvider.from_instruments([])
     reader = BetfairTestStubs.betfair_reader(provider)  # type: TextReader
     raw_file = make_raw_files(
         glob_path=f"{TEST_DATA_DIR}/betfair/1.166811431.bz2")[0]
     result = process_raw_file(catalog=self.catalog,
                               raw_file=raw_file,
                               reader=reader)
     assert result == 22692
Exemple #8
0
 def betfair_reader(instrument_provider=None, **kwargs):
     instrument_provider = instrument_provider or BetfairInstrumentProvider.from_instruments(
         [])
     reader = TextReader(
         line_parser=partial(BetfairTestStubs.parse_betfair,
                             instrument_provider=instrument_provider),
         instrument_provider=instrument_provider,
         instrument_provider_update=historical_instrument_provider_loader,
         **kwargs,
     )
     return reader
Exemple #9
0
def make_betfair_reader(instrument_provider=None, line_preprocessor=None) -> TextReader:
    from nautilus_trader.adapters.betfair.providers import BetfairInstrumentProvider

    instrument_provider = instrument_provider or BetfairInstrumentProvider.from_instruments([])

    return TextReader(
        # Use the standard `on_market_update` betfair parser that the adapter uses
        line_preprocessor=line_preprocessor,
        line_parser=partial(line_parser, instrument_provider=instrument_provider),
        instrument_provider_update=historical_instrument_provider_loader,
        instrument_provider=instrument_provider,
    )
Exemple #10
0
    def betfair_feed_parsed(market_id="1.166564490", folder="data"):
        instrument_provider = BetfairInstrumentProvider.from_instruments([])
        reader = BetfairTestStubs.betfair_reader(
            instrument_provider=instrument_provider)
        files = make_raw_files(
            glob_path=f"{PACKAGE_ROOT}/{folder}/{market_id}*")

        data = []
        for rf in files:
            for block in rf.iter():
                data.extend(reader.parse(block=block))

        return data
Exemple #11
0
def data_loader():
    instrument_provider = BetfairInstrumentProvider.from_instruments([])
    parser = TextParser(
        parser=lambda x, state: on_market_update(
            instrument_provider=instrument_provider, update=orjson.loads(x)),
        instrument_provider_update=historical_instrument_provider_loader,
    )
    return DataLoader(
        path=TEST_DATA_DIR,
        parser=parser,
        glob_pattern="1.166564490*",
        instrument_provider=instrument_provider,
    )
Exemple #12
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
Exemple #13
0
 def instrument_provider(betfair_client) -> BetfairInstrumentProvider:
     mock.patch(
         "betfairlightweight.endpoints.navigation.Navigation.list_navigation",
         return_value=BetfairTestStubs.navigation(),
     )
     mock.patch(
         "betfairlightweight.endpoints.betting.Betting.resp_market_catalogue",
         return_value=BetfairTestStubs.market_catalogue(),
     )
     return BetfairInstrumentProvider(
         client=betfair_client,
         logger=BetfairTestStubs.live_logger(BetfairTestStubs.clock()),
         market_filter={"event_type_name": "Tennis"},
         load_all=False,
     )
Exemple #14
0
 def _loaded_data_into_catalog(self):
     self.instrument_provider = BetfairInstrumentProvider.from_instruments(
         [])
     result = process_files(
         glob_path=PACKAGE_ROOT + "/data/1.166564490*.bz2",
         reader=BetfairTestStubs.betfair_reader(
             instrument_provider=self.instrument_provider),
         instrument_provider=self.instrument_provider,
         catalog=self.catalog,
     )
     assert result
     data = (self.catalog.instruments(as_nautilus=True) +
             self.catalog.instrument_status_updates(as_nautilus=True) +
             self.catalog.trade_ticks(as_nautilus=True) +
             self.catalog.order_book_deltas(as_nautilus=True) +
             self.catalog.tickers(as_nautilus=True))
     return data
Exemple #15
0
    def __init__(
        self,
        loop: asyncio.AbstractEventLoop,
        client: BetfairClient,
        base_currency: Currency,
        msgbus: MessageBus,
        cache: Cache,
        clock: LiveClock,
        logger: Logger,
        market_filter: Dict,
        instrument_provider: BetfairInstrumentProvider,
    ):
        super().__init__(
            loop=loop,
            client_id=ClientId(BETFAIR_VENUE.value),
            venue=BETFAIR_VENUE,
            oms_type=OMSType.NETTING,
            account_type=AccountType.BETTING,
            base_currency=base_currency,
            instrument_provider=instrument_provider
            or BetfairInstrumentProvider(
                client=client, logger=logger, filters=market_filter),
            msgbus=msgbus,
            cache=cache,
            clock=clock,
            logger=logger,
        )

        self._client: BetfairClient = client
        self.stream = BetfairOrderStreamClient(
            client=self._client,
            logger=logger,
            message_handler=self.handle_order_stream_update,
        )

        self.venue_order_id_to_client_order_id: Dict[VenueOrderId,
                                                     ClientOrderId] = {}
        self.pending_update_order_client_ids: Set[Tuple[ClientOrderId,
                                                        VenueOrderId]] = set()
        self.published_executions: Dict[ClientOrderId,
                                        TradeId] = defaultdict(list)

        self._set_account_id(AccountId(BETFAIR_VENUE.value,
                                       "001"))  # TODO(cs): Temporary
        AccountFactory.register_calculated_account(BETFAIR_VENUE.value)
Exemple #16
0
def test_data_loader_json_betting_parser():
    instrument_provider = BetfairInstrumentProvider.from_instruments([])

    parser = TextParser(
        parser=lambda x, state: on_market_update(
            instrument_provider=instrument_provider, update=orjson.loads(x)),
        instrument_provider_update=historical_instrument_provider_loader,
    )
    loader = DataLoader(
        path=TEST_DATA_DIR,
        parser=parser,
        glob_pattern="**.bz2",
        instrument_provider=instrument_provider,
    )
    assert len(loader.path) == 3

    data = [x for y in loader.run() for x in y]
    assert len(data) == 30829
    def test_load_text_betfair(self):
        # Arrange
        instrument_provider = BetfairInstrumentProvider.from_instruments([])

        # Act
        files = process_files(
            glob_path=f"{TEST_DATA_DIR}/**.bz2",
            reader=BetfairTestStubs.betfair_reader(instrument_provider=instrument_provider),
            catalog=self.catalog,
            instrument_provider=instrument_provider,
        )

        # Assert
        assert files == {
            TEST_DATA_DIR + "/1.166564490.bz2": 2908,
            TEST_DATA_DIR + "/betfair/1.180305278.bz2": 17085,
            TEST_DATA_DIR + "/betfair/1.166811431.bz2": 22692,
        }
    def test_load_dask_distributed_client(self):
        # Arrange
        from distributed import Client

        instrument_provider = BetfairInstrumentProvider.from_instruments([])

        with Client(processes=False, threads_per_worker=1) as c:
            tasks = process_files(
                glob_path=f"{TEST_DATA_DIR}/1.166564490*",
                reader=make_betfair_reader(instrument_provider),
                catalog=self.catalog,
                instrument_provider=instrument_provider,
            )

            # Act
            results = c.gather(c.compute(tasks))

        # Assert
        expected = {TEST_DATA + "/1.166564490.bz2": 2908}
        assert results == expected
Exemple #19
0
    def __init__(
        self,
        loop: asyncio.AbstractEventLoop,
        client: BetfairClient,
        account_id: AccountId,
        base_currency: Currency,
        msgbus: MessageBus,
        cache: Cache,
        clock: LiveClock,
        logger: Logger,
        market_filter: Dict,
        instrument_provider: BetfairInstrumentProvider,
    ):
        super().__init__(
            loop=loop,
            client_id=ClientId(BETFAIR_VENUE.value),
            instrument_provider=instrument_provider
            or BetfairInstrumentProvider(client=client, logger=logger, market_filter=market_filter),
            venue_type=VenueType.EXCHANGE,
            account_id=account_id,
            account_type=AccountType.BETTING,
            base_currency=base_currency,
            msgbus=msgbus,
            cache=cache,
            clock=clock,
            logger=logger,
            config={"name": "BetfairExecClient"},
        )

        self._client: BetfairClient = client
        self.stream = BetfairOrderStreamClient(
            client=self._client,
            logger=logger,
            message_handler=self.handle_order_stream_update,
        )

        self.venue_order_id_to_client_order_id: Dict[VenueOrderId, ClientOrderId] = {}
        self.pending_update_order_client_ids: Set[Tuple[ClientOrderId, VenueOrderId]] = set()
        self.published_executions: Dict[ClientOrderId, ExecutionId] = defaultdict(list)

        AccountFactory.register_calculated_account(account_id.issuer)
Exemple #20
0
    def __init__(
        self,
        loop: asyncio.AbstractEventLoop,
        client: BetfairClient,
        msgbus: MessageBus,
        cache: Cache,
        clock: LiveClock,
        logger: Logger,
        market_filter: Dict,
        instrument_provider: Optional[BetfairInstrumentProvider] = None,
        strict_handling: bool = False,
    ):
        super().__init__(
            loop=loop,
            client_id=ClientId(BETFAIR_VENUE.value),
            venue=BETFAIR_VENUE,
            instrument_provider=instrument_provider
            or BetfairInstrumentProvider(
                client=client, logger=logger, filters=market_filter),
            msgbus=msgbus,
            cache=cache,
            clock=clock,
            logger=logger,
        )

        self._client = client
        self._stream = BetfairMarketStreamClient(
            client=self._client,
            logger=logger,
            message_handler=self.on_market_update,
        )

        self.subscription_status = SubscriptionStatus.UNSUBSCRIBED

        # Subscriptions
        self._subscribed_instrument_ids: Set[InstrumentId] = set()
        self._strict_handling = strict_handling
        self._subscribed_market_ids: Set[InstrumentId] = set()
Exemple #21
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
Exemple #22
0
 def instrument_provider(betfair_client) -> BetfairInstrumentProvider:
     return BetfairInstrumentProvider(
         client=betfair_client,
         logger=BetfairTestStubs.live_logger(BetfairTestStubs.clock()),
         # market_filter={"event_type_name": "Tennis"},
     )
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