示例#1
0
    def add_data_client_factory(self, name: str, factory):
        """
        Add the given data client factory to the builder.

        Parameters
        ----------
        name : str
            The name of the client.
        factory : LiveDataClientFactory or LiveExecutionClientFactory
            The factory to add.

        Raises
        ------
        ValueError
            If `name` is not a valid string.
        KeyError
            If `name` has already been added.

        """
        PyCondition.valid_string(name, "name")
        PyCondition.not_none(factory, "factory")
        PyCondition.not_in(name, self._data_factories, "name",
                           "self._data_factories")

        if not issubclass(factory, LiveDataClientFactory):
            self._log.error(f"Factory was not of type `LiveDataClientFactory` "
                            f"was {factory}.")
            return

        self._data_factories[name] = factory
    def test_condition_not_none_when_arg_not_none_does_nothing(self):
        # Arrange
        # Act
        PyCondition.not_none("something", "param")

        # Assert
        self.assertTrue(True)  # ValueError not raised
示例#3
0
    def build_exec_clients(self, config: Dict):
        """
        Build the execution clients with the given configuration.

        Parameters
        ----------
        config : dict[str, object]
            The execution clients configuration.

        """
        PyCondition.not_none(config, "config")

        if not config:
            self._log.warning("No `exec_clients` configuration found.")

        for name, options in config.items():
            pieces = name.partition("-")
            factory = self._exec_factories[pieces[0]]

            client = factory.create(
                loop=self._loop,
                name=name,
                config=options,
                msgbus=self._msgbus,
                cache=self._cache,
                clock=self._clock,
                logger=self._logger,
            )

            self._exec_engine.register_client(client)
示例#4
0
    async def _submit_order(self, command: SubmitOrder) -> None:
        self._log.debug(f"Received submit_order {command}")

        self.generate_order_submitted(
            instrument_id=command.instrument_id,
            strategy_id=command.strategy_id,
            client_order_id=command.order.client_order_id,
            ts_event=self._clock.timestamp_ns(),
        )
        self._log.debug("Generated _generate_order_submitted")

        instrument = self._cache.instrument(command.instrument_id)
        PyCondition.not_none(instrument, "instrument")
        client_order_id = command.order.client_order_id

        place_order = order_submit_to_betfair(command=command, instrument=instrument)
        try:
            result = await self._client.place_orders(**place_order)
        except Exception as exc:
            if isinstance(exc, BetfairAPIError):
                await self.on_api_exception(exc=exc)
            self._log.warning(f"Submit failed: {exc}")
            self.generate_order_rejected(
                strategy_id=command.strategy_id,
                instrument_id=command.instrument_id,
                client_order_id=client_order_id,
                reason="client error",  # type: ignore
                ts_event=self._clock.timestamp_ns(),
            )
            return

        self._log.debug(f"result={result}")
        for report in result["instructionReports"]:
            if result["status"] == "FAILURE":
                reason = f"{result['errorCode']}: {report['errorCode']}"
                self._log.warning(f"Submit failed - {reason}")
                self.generate_order_rejected(
                    strategy_id=command.strategy_id,
                    instrument_id=command.instrument_id,
                    client_order_id=client_order_id,
                    reason=reason,  # type: ignore
                    ts_event=self._clock.timestamp_ns(),
                )
                self._log.debug("Generated _generate_order_rejected")
                return
            else:
                venue_order_id = VenueOrderId(report["betId"])
                self._log.debug(
                    f"Matching venue_order_id: {venue_order_id} to client_order_id: {client_order_id}"
                )
                self.venue_order_id_to_client_order_id[venue_order_id] = client_order_id  # type: ignore
                self.generate_order_accepted(
                    strategy_id=command.strategy_id,
                    instrument_id=command.instrument_id,
                    client_order_id=client_order_id,
                    venue_order_id=venue_order_id,  # type: ignore
                    ts_event=self._clock.timestamp_ns(),
                )
                self._log.debug("Generated _generate_order_accepted")
示例#5
0
    def submit_order(self, command: SubmitOrder) -> None:
        PyCondition.not_none(command, "command")

        contract_details = self._instrument_provider.contract_details[command.instrument_id]
        order: IBOrder = nautilus_order_to_ib_order(order=command.order)
        trade: IBTrade = self._client.placeOrder(contract=contract_details.contract, order=order)
        self._venue_order_id_to_client_order_id[trade.order.orderId] = command.order.client_order_id
        self._client_order_id_to_strategy_id[command.order.client_order_id] = command.strategy_id
        self._ib_insync_orders[command.order.client_order_id] = trade
示例#6
0
    def default_fx_ccy(symbol: Symbol, leverage: Decimal=Decimal("50")) -> Instrument:
        """
        Return a default FX currency pair instrument from the given symbol.

        Parameters
        ----------
        symbol : Symbol
            The currency pair symbol.
        leverage : Decimal, optional
            The leverage for the instrument.

        Raises
        ------
        ValueError
            If the symbol.code length is not in range [6, 7].

        """
        PyCondition.not_none(symbol, "symbol")
        PyCondition.in_range_int(len(symbol.code), 6, 7, "len(symbol)")

        base_currency = symbol.code[:3]
        quote_currency = symbol.code[-3:]

        # Check tick precision of quote currency
        if quote_currency == 'JPY':
            price_precision = 3
        else:
            price_precision = 5

        return Instrument(
            symbol=symbol,
            asset_class=AssetClass.FX,
            asset_type=AssetType.SPOT,
            base_currency=Currency.from_str(base_currency),
            quote_currency=Currency.from_str(quote_currency),
            settlement_currency=Currency.from_str(quote_currency),
            is_inverse=False,
            price_precision=price_precision,
            size_precision=0,
            tick_size=Decimal(f"{1 / 10 ** price_precision:.{price_precision}f}"),
            multiplier=Decimal("1"),
            leverage=leverage,
            lot_size=Quantity("1000"),
            max_quantity=Quantity("1e7"),
            min_quantity=Quantity("1000"),
            max_price=None,
            min_price=None,
            max_notional=Money(50000000.00, USD),
            min_notional=Money(1000.00, USD),
            margin_init=Decimal("0.03"),
            margin_maint=Decimal("0.03"),
            maker_fee=Decimal("0.00002"),
            taker_fee=Decimal("0.00002"),
            financing={},
            timestamp=UNIX_EPOCH,
        )
示例#7
0
 def cancel_order(self, command: CancelOrder) -> None:
     """
     ib_insync modifies orders by modifying the original order object and calling placeOrder again
     """
     PyCondition.not_none(command, "command")
     # TODO - Can we just reconstruct the IBOrder object from the `command` ?
     trade: IBTrade = self._ib_insync_orders[command.client_order_id]
     order = trade.order
     new_trade: IBTrade = self._client.cancelOrder(order=order)
     self._ib_insync_orders[command.client_order_id] = new_trade
示例#8
0
 async def check_account_currency(self):
     """
     Check account currency against BetfairClient
     """
     self._log.debug("Checking account currency")
     PyCondition.not_none(self.base_currency, "self.base_currency")
     details = await self._client.get_account_details()
     currency_code = details["currencyCode"]
     self._log.debug(f"Account {currency_code=}, {self.base_currency.code=}")
     assert currency_code == self.base_currency.code
     self._log.debug("Base currency matches client details")
示例#9
0
    def handle_trade_tick(self, tick: TradeTick):
        """
        Update the indicator with the given trade tick.

        Parameters
        ----------
        tick : TradeTick
            The update tick to handle.

        """
        PyCondition.not_none(tick, "tick")

        self.update_raw(tick.price.as_double())
示例#10
0
    def handle_bar(self, bar: Bar):
        """
        Update the indicator with the given bar.

        Parameters
        ----------
        bar : Bar
            The update bar to handle.

        """
        PyCondition.not_none(bar, "bar")

        self.update_raw(bar.close.as_double())
示例#11
0
    def handle_quote_tick(self, tick: QuoteTick):
        """
        Update the indicator with the given quote tick.

        Parameters
        ----------
        tick : QuoteTick
            The update tick to handle.

        """
        PyCondition.not_none(tick, "tick")

        self.update_raw(tick.extract_price(self.price_type).as_double())
示例#12
0
    def subscribe_order_book_deltas(
        self,
        instrument_id: InstrumentId,
        book_type: BookType,
        depth: Optional[int] = None,
        kwargs=None,
    ):
        """
        Subscribe to `OrderBook` data for the given instrument ID.

        Parameters
        ----------
        instrument_id : InstrumentId
            The order book instrument to subscribe to.
        book_type : BookType {``L1_TBBO``, ``L2_MBP``, ``L3_MBO``}
            The order book type.
        depth : int, optional, default None
            The maximum depth for the subscription.
        kwargs : dict, optional
            The keyword arguments for exchange specific parameters.

        """
        if kwargs is None:
            kwargs = {}
        PyCondition.not_none(instrument_id, "instrument_id")

        instrument: BettingInstrument = self._instrument_provider.find(
            instrument_id)

        if instrument.market_id in self._subscribed_market_ids:
            self._log.warning(
                f"Already subscribed to market_id: {instrument.market_id} "
                f"[Instrument: {instrument_id.symbol}] <OrderBook> data.", )
            return

        # If this is the first subscription request we're receiving, schedule a
        # subscription after a short delay to allow other strategies to send
        # their subscriptions (every change triggers a full snapshot).
        self._subscribed_market_ids.add(instrument.market_id)
        self._subscribed_instrument_ids.add(instrument.id)
        if self.subscription_status == SubscriptionStatus.UNSUBSCRIBED:
            self._loop.create_task(self.delayed_subscribe(delay=5))
            self.subscription_status = SubscriptionStatus.PENDING_STARTUP
        elif self.subscription_status == SubscriptionStatus.PENDING_STARTUP:
            pass
        elif self.subscription_status == SubscriptionStatus.RUNNING:
            self._loop.create_task(self.delayed_subscribe(delay=0))

        self._log.info(
            f"Added market_id {instrument.market_id} for {instrument_id.symbol} <OrderBook> data."
        )
示例#13
0
 def modify_order(self, command: ModifyOrder) -> None:
     """
     ib_insync modifies orders by modifying the original order object and calling placeOrder again
     """
     PyCondition.not_none(command, "command")
     # TODO - Can we just reconstruct the IBOrder object from the `command` ?
     trade: IBTrade = self._ib_insync_orders[command.client_order_id]
     order = trade.order
     if order.totalQuantity != command.quantity:
         order.totalQuantity = command.quantity.as_double()
     if getattr(order, "lmtPrice", None) != command.price:
         order.lmtPrice = command.price.as_double()
     new_trade: IBTrade = self._client.placeOrder(contract=trade.contract, order=order)
     self._ib_insync_orders[command.client_order_id] = new_trade
示例#14
0
 async def _check_order_update(self, update: Dict):
     """
     Ensure we have a client_order_id, instrument and order for this venue order update
     """
     venue_order_id = VenueOrderId(str(update["id"]))
     client_order_id = await self.wait_for_order(
         venue_order_id=venue_order_id, timeout_seconds=10.0)
     if client_order_id is None:
         self._log.warning(f"Can't find client_order_id for {update}")
         return
     PyCondition.type(client_order_id, ClientOrderId, "client_order_id")
     order = self._cache.order(client_order_id)
     PyCondition.not_none(order, "order")
     instrument = self._cache.instrument(order.instrument_id)
     PyCondition.not_none(instrument, "instrument")
示例#15
0
    async def load_async(self,
                         instrument_id: InstrumentId,
                         filters: Optional[Dict] = None):
        """
        Load the instrument for the given ID into the provider asynchronously, optionally
        applying the given filters.

        Parameters
        ----------
        instrument_id: InstrumentId
            The instrument ID to load.
        filters : Dict, optional
            The venue specific instrument loading filters to apply.

        Raises
        ------
        ValueError
            If `instrument_id.venue` is not equal to `self.venue`.

        """
        PyCondition.not_none(instrument_id, "instrument_id")
        PyCondition.equal(instrument_id.venue, self.venue,
                          "instrument_id.venue", "self.venue")

        filters_str = "..." if not filters else f" with filters {filters}..."
        self._log.debug(f"Loading instrument {instrument_id}{filters_str}.")

        symbol = instrument_id.symbol.value

        # Get current commission rates
        # try:
        #     fees: Optional[Dict[str, str]] = None
        # except BinanceClientError:
        #     self._log.error(
        #         "Cannot load instruments: API key authentication failed "
        #         "(this is needed to fetch the applicable account fee tier).",
        #     )
        #     return

        # Get exchange info for all assets
        exchange_info: BinanceFuturesExchangeInfo = await self._market.exchange_info(
            symbol=symbol)
        for symbol_info in exchange_info.symbols:
            self._parse_instrument(
                symbol_info=symbol_info,
                fees=None,
                ts_event=millis_to_nanos(exchange_info.serverTime),
            )
示例#16
0
 def from_dict(values) -> "BSPOrderBookDelta":
     PyCondition.not_none(values, "values")
     action: BookAction = BookActionParser.from_str_py(values["action"])
     order: Order = (Order.from_dict({
         "price": values["order_price"],
         "size": values["order_size"],
         "side": values["order_side"],
         "id": values["order_id"],
     }) if values["action"] != "CLEAR" else None)
     return BSPOrderBookDelta(
         instrument_id=InstrumentId.from_str(values["instrument_id"]),
         book_type=BookTypeParser.from_str_py(values["book_type"]),
         action=action,
         order=order,
         ts_event=values["ts_event"],
         ts_init=values["ts_init"],
     )
示例#17
0
    def __init__(
        self,
        client: BinanceHttpClient,
        account_type: BinanceAccountType = BinanceAccountType.FUTURES_USDT,
    ):
        PyCondition.not_none(client, "client")

        self.client = client
        self.account_type = account_type

        if account_type == BinanceAccountType.FUTURES_USDT:
            self.BASE_ENDPOINT = "/fapi/v1/"
        elif account_type == BinanceAccountType.FUTURES_COIN:
            self.BASE_ENDPOINT = "/dapi/v1/"
        else:  # pragma: no cover (design-time error)
            raise RuntimeError(
                f"invalid Binance account type, was {account_type}")
示例#18
0
    def cancel_all_orders(self, command: CancelAllOrders) -> None:
        PyCondition.not_none(command, "command")

        working_orders = self._cache.working_orders(instrument_id=command.instrument_id)

        # TODO(cs): Temporary solution generating individual cancels for all working orders
        for order in working_orders:
            command = CancelOrder(
                trader_id=command.trader_id,
                strategy_id=command.strategy_id,
                instrument_id=command.instrument_id,
                client_order_id=order.client_order_id,
                venue_order_id=order.venue_order_id,
                command_id=self._uuid_factory.generate(),
                ts_init=self._clock.timestamp_ns(),
            )

            self.cancel_order(command)
示例#19
0
    def register_statistic(self, statistic: PortfolioStatistic) -> None:
        """
        Register the given statistic with the analyzer.

        Parameters
        ----------
        statistic : PortfolioStatistic
            The statistic to register.

        Raises
        ------
        KeyError if `statistic` has already been registered.

        """
        PyCondition.not_none(statistic, "statistic")
        PyCondition.not_in(statistic.name, self._statistics, "statistic.name", "_statistics")

        self._statistics[statistic.name] = statistic
示例#20
0
    def load(self, instrument_id: InstrumentId, details: Dict):
        """
        Load the instrument for the given ID and details.

        Parameters
        ----------
        instrument_id : InstrumentId
            The instrument ID.
        details : dict
            The instrument details.

        """
        PyCondition.not_none(instrument_id, "instrument_id")
        PyCondition.not_none(details, "details")
        PyCondition.is_in("asset_type", details, "asset_type", "details")

        if not self._client.client.CONNECTED:
            self.connect()

        contract = ib_insync.contract.Contract(
            symbol=instrument_id.symbol.value,
            exchange=instrument_id.venue.value,
            multiplier=details.get("multiplier"),
            currency=details.get("currency"),
        )

        contract_details: List[ContractDetails] = self._client.reqContractDetails(contract=contract)
        if not contract_details:
            raise ValueError(
                f"No contract details found for the given instrument ID {instrument_id}"
            )
        elif len(contract_details) > 1:
            raise ValueError(
                f"Multiple contract details found for the given instrument ID {instrument_id}"
            )

        instrument: Instrument = self._parse_instrument(
            asset_type=AssetTypeParser.from_str_py(details.get("asset_type")),
            instrument_id=instrument_id,
            details=details,
            contract_details=contract_details[0],
        )

        self.add(instrument)
示例#21
0
    def __init__(
        self,
        client: BinanceHttpClient,
        account_type: BinanceAccountType = BinanceAccountType.FUTURES_USDT,
    ):
        PyCondition.not_none(client, "client")

        self.client = client
        self.account_type = account_type

        if self.account_type == BinanceAccountType.FUTURES_USDT:
            self.BASE_ENDPOINT = "/fapi/v1/"
        elif self.account_type == BinanceAccountType.FUTURES_COIN:
            self.BASE_ENDPOINT = "/dapi/v1/"
        else:  # pragma: no cover (design-time error)
            raise RuntimeError(
                f"invalid Binance Futures account type, was {account_type}")

        self._decoder_exchange_info = msgspec.json.Decoder(
            BinanceFuturesExchangeInfo)
示例#22
0
    def build_exec_clients(self, config: Dict):
        """
        Build the execution clients with the given configuration.

        Parameters
        ----------
        config : dict[str, object]
            The execution clients configuration.

        """
        PyCondition.not_none(config, "config")

        if not config:
            self._log.warning("No `exec_clients` configuration found.")

        for name, client_config in config.items():
            pieces = name.partition("-")
            factory = self._exec_factories[pieces[0]]

            client = factory.create(
                loop=self._loop,
                name=name,
                config=client_config,
                msgbus=self._msgbus,
                cache=self._cache,
                clock=self._clock,
                logger=self._logger,
            )

            self._exec_engine.register_client(client)

            # Default client config
            if client_config.routing.default:
                self._exec_engine.register_default_client(client)

            # Venue routing config
            venues = client_config.routing.venues or []
            for venue in venues:
                if not isinstance(venue, Venue):
                    venue = Venue(venue)
                self._exec_engine.register_venue_routing(client, venue)
示例#23
0
    async def load_async(self, instrument_id: InstrumentId, filters: Optional[Dict] = None):
        """
        Load the instrument for the given ID into the provider asynchronously, optionally
        applying the given filters.

        Parameters
        ----------
        instrument_id: InstrumentId
            The instrument ID to load.
        filters : Dict, optional
            The venue specific instrument loading filters to apply.

        Raises
        ------
        ValueError
            If `instrument_id.venue` is not equal to `self.venue`.

        """
        PyCondition.not_none(instrument_id, "instrument_id")
        PyCondition.equal(instrument_id.venue, self.venue, "instrument_id.venue", "self.venue")

        filters_str = "..." if not filters else f" with filters {filters}..."
        self._log.debug(f"Loading instrument {instrument_id}{filters_str}.")

        try:
            # Get current commission rates
            account_info: Dict[str, Any] = await self._client.get_account_info()
        except FTXClientError:
            self._log.error(
                "Cannot load instruments: API key authentication failed "
                "(this is needed to fetch the applicable account fee tier).",
            )
            return

        data: Dict[str, Any] = await self._client.get_market(instrument_id.symbol.value)

        self._parse_instrument(data, account_info)
示例#24
0
    def __init__(self, client: BinanceHttpClient):
        PyCondition.not_none(client, "client")

        self.client = client
示例#25
0
    def __init__(
        self,
        strategies: List[TradingStrategy],
        config: Dict[str, object],
    ):
        """
        Initialize a new instance of the TradingNode class.

        Parameters
        ----------
        strategies : list[TradingStrategy]
            The list of strategies to run on the trading node.
        config : dict[str, object]
            The configuration for the trading node.

        Raises
        ------
        ValueError
            If strategies is None or empty.
        ValueError
            If config is None or empty.

        """
        PyCondition.not_none(strategies, "strategies")
        PyCondition.not_none(config, "config")
        PyCondition.not_empty(strategies, "strategies")
        PyCondition.not_empty(config, "config")

        # Extract configs
        config_trader = config.get("trader", {})
        config_log = config.get("logging", {})
        config_exec_db = config.get("exec_database", {})
        config_strategy = config.get("strategy", {})
        config_adapters = config.get("adapters", {})

        self._uuid_factory = UUIDFactory()
        self._loop = asyncio.get_event_loop()
        self._executor = concurrent.futures.ThreadPoolExecutor()
        self._loop.set_default_executor(self._executor)
        self._clock = LiveClock(loop=self._loop)

        self.created_time = self._clock.utc_now()
        self._is_running = False

        # Uncomment for debugging
        # self._loop.set_debug(True)

        # Setup identifiers
        self.trader_id = TraderId(
            name=config_trader["name"],
            tag=config_trader["id_tag"],
        )

        # Setup logging
        self._logger = LiveLogger(
            clock=self._clock,
            name=self.trader_id.value,
            level_console=LogLevelParser.from_str_py(config_log.get("log_level_console")),
            level_file=LogLevelParser.from_str_py(config_log.get("log_level_file")),
            level_store=LogLevelParser.from_str_py(config_log.get("log_level_store")),
            run_in_process=config_log.get("run_in_process", True),  # Run logger in a separate process
            log_thread=config_log.get("log_thread_id", False),
            log_to_file=config_log.get("log_to_file", False),
            log_file_path=config_log.get("log_file_path", ""),
        )

        self._log = LoggerAdapter(component_name=self.__class__.__name__, logger=self._logger)
        self._log_header()
        self._log.info("Building...")

        self._setup_loop()  # Requires the logger to be initialized

        self.portfolio = Portfolio(
            clock=self._clock,
            logger=self._logger,
        )

        self._data_engine = LiveDataEngine(
            loop=self._loop,
            portfolio=self.portfolio,
            clock=self._clock,
            logger=self._logger,
            config={"qsize": 10000},
        )

        self.portfolio.register_cache(self._data_engine.cache)
        self.analyzer = PerformanceAnalyzer()

        if config_exec_db["type"] == "redis":
            exec_db = RedisExecutionDatabase(
                trader_id=self.trader_id,
                logger=self._logger,
                command_serializer=MsgPackCommandSerializer(),
                event_serializer=MsgPackEventSerializer(),
                config={
                    "host": config_exec_db["host"],
                    "port": config_exec_db["port"],
                }
            )
        else:
            exec_db = BypassExecutionDatabase(
                trader_id=self.trader_id,
                logger=self._logger,
            )

        self._exec_engine = LiveExecutionEngine(
            loop=self._loop,
            database=exec_db,
            portfolio=self.portfolio,
            clock=self._clock,
            logger=self._logger,
            config={"qsize": 10000},
        )

        self._exec_engine.load_cache()
        self._setup_adapters(config_adapters, self._logger)

        self.trader = Trader(
            trader_id=self.trader_id,
            strategies=strategies,
            portfolio=self.portfolio,
            data_engine=self._data_engine,
            exec_engine=self._exec_engine,
            clock=self._clock,
            logger=self._logger,
        )

        self._check_residuals_delay = config_trader.get("check_residuals_delay", 5.0)
        self._load_strategy_state = config_strategy.get("load_state", True)
        self._save_strategy_state = config_strategy.get("save_state", True)

        if self._load_strategy_state:
            self.trader.load()

        self._log.info("state=INITIALIZED.")
        self.time_to_initialize = self._clock.delta(self.created_time)
        self._log.info(f"Initialized in {self.time_to_initialize.total_seconds():.3f}s.")
示例#26
0
    def __init__(
        self,
        strategies: List[TradingStrategy],
        config: Dict[str, object],
    ):
        """
        Initialize a new instance of the TradingNode class.

        Parameters
        ----------
        strategies : list[TradingStrategy]
            The list of strategies to run on the trading node.
        config : dict[str, object]
            The configuration for the trading node.

        Raises
        ------
        ValueError
            If strategies is None or empty.
        ValueError
            If config is None or empty.

        """
        PyCondition.not_none(strategies, "strategies")
        PyCondition.not_none(config, "config")
        PyCondition.not_empty(strategies, "strategies")
        PyCondition.not_empty(config, "config")

        self._config = config

        # Extract configs
        config_trader = config.get("trader", {})
        config_system = config.get("system", {})
        config_log = config.get("logging", {})
        config_exec_db = config.get("exec_database", {})
        config_risk = config.get("risk", {})
        config_strategy = config.get("strategy", {})

        # System config
        self._connection_timeout = config_system.get("connection_timeout", 5.0)
        self._disconnection_timeout = config_system.get(
            "disconnection_timeout", 5.0)
        self._check_residuals_delay = config_system.get(
            "check_residuals_delay", 5.0)
        self._load_strategy_state = config_strategy.get("load_state", True)
        self._save_strategy_state = config_strategy.get("save_state", True)

        # Setup loop
        self._loop = asyncio.get_event_loop()
        self._executor = concurrent.futures.ThreadPoolExecutor()
        self._loop.set_default_executor(self._executor)
        self._loop.set_debug(config_system.get("loop_debug", False))

        # Components
        self._clock = LiveClock(loop=self._loop)
        self._uuid_factory = UUIDFactory()
        self.system_id = self._uuid_factory.generate()
        self.created_time = self._clock.utc_now()
        self._is_running = False

        # Setup identifiers
        self.trader_id = TraderId(
            name=config_trader["name"],
            tag=config_trader["id_tag"],
        )

        # Setup logging
        level_stdout = LogLevelParser.from_str_py(
            config_log.get("level_stdout"))

        self._logger = LiveLogger(
            loop=self._loop,
            clock=self._clock,
            trader_id=self.trader_id,
            system_id=self.system_id,
            level_stdout=level_stdout,
        )

        self._log = LoggerAdapter(
            component=self.__class__.__name__,
            logger=self._logger,
        )

        self._log_header()
        self._log.info("Building...")

        if platform.system() != "Windows":
            # Requires the logger to be initialized
            # Windows does not support signal handling
            # https://stackoverflow.com/questions/45987985/asyncio-loops-add-signal-handler-in-windows
            self._setup_loop()

        # Build platform
        # ----------------------------------------------------------------------
        self.portfolio = Portfolio(
            clock=self._clock,
            logger=self._logger,
        )

        self._data_engine = LiveDataEngine(
            loop=self._loop,
            portfolio=self.portfolio,
            clock=self._clock,
            logger=self._logger,
            config={"qsize": 10000},
        )

        self.portfolio.register_cache(self._data_engine.cache)
        self.analyzer = PerformanceAnalyzer()

        if config_exec_db["type"] == "redis":
            exec_db = RedisExecutionDatabase(
                trader_id=self.trader_id,
                logger=self._logger,
                command_serializer=MsgPackCommandSerializer(),
                event_serializer=MsgPackEventSerializer(),
                config={
                    "host": config_exec_db["host"],
                    "port": config_exec_db["port"],
                },
            )
        else:
            exec_db = BypassExecutionDatabase(
                trader_id=self.trader_id,
                logger=self._logger,
            )

        self._exec_engine = LiveExecutionEngine(
            loop=self._loop,
            database=exec_db,
            portfolio=self.portfolio,
            clock=self._clock,
            logger=self._logger,
            config={"qsize": 10000},
        )

        self._risk_engine = LiveRiskEngine(
            loop=self._loop,
            exec_engine=self._exec_engine,
            portfolio=self.portfolio,
            clock=self._clock,
            logger=self._logger,
            config=config_risk,
        )

        self._exec_engine.load_cache()
        self._exec_engine.register_risk_engine(self._risk_engine)

        self.trader = Trader(
            trader_id=self.trader_id,
            strategies=strategies,
            portfolio=self.portfolio,
            data_engine=self._data_engine,
            exec_engine=self._exec_engine,
            risk_engine=self._risk_engine,
            clock=self._clock,
            logger=self._logger,
        )

        if self._load_strategy_state:
            self.trader.load()

        self._builder = TradingNodeBuilder(
            data_engine=self._data_engine,
            exec_engine=self._exec_engine,
            risk_engine=self._risk_engine,
            clock=self._clock,
            logger=self._logger,
            log=self._log,
        )

        self._log.info("state=INITIALIZED.")
        self.time_to_initialize = self._clock.delta(self.created_time)
        self._log.info(
            f"Initialized in {self.time_to_initialize.total_seconds():.3f}s.")

        self._is_built = False
示例#27
0
    async def _cancel_all_orders(self, command: CancelAllOrders) -> None:
        # TODO(cs): I've had to duplicate the logic as couldn't refactor and tease
        #  apart the cancel rejects and execution report. This will possibly fail
        #  badly if there are any API errors...
        self._log.debug(f"Received cancel all orders: {command}")

        instrument = self._cache.instrument(command.instrument_id)
        PyCondition.not_none(instrument, "instrument")

        # Format
        cancel_orders = order_cancel_all_to_betfair(instrument=instrument)  # type: ignore
        self._log.debug(f"cancel_orders {cancel_orders}")

        # Send to client
        try:
            result = await self._client.cancel_orders(**cancel_orders)
        except Exception as exc:
            if isinstance(exc, BetfairAPIError):
                await self.on_api_exception(exc=exc)
            self._log.error(f"Cancel failed: {exc}")
            # TODO(cs): Will probably just need to recover the client order ID
            #  and order ID from the execution report?
            # self.generate_order_cancel_rejected(
            #     strategy_id=command.strategy_id,
            #     instrument_id=command.instrument_id,
            #     client_order_id=command.client_order_id,
            #     venue_order_id=command.venue_order_id,
            #     reason="client error",
            #     ts_event=self._clock.timestamp_ns(),
            # )
            return
        self._log.debug(f"result={result}")

        # Parse response
        for report in result["instructionReports"]:
            venue_order_id = VenueOrderId(report["instruction"]["betId"])
            if report["status"] == "FAILURE":
                reason = f"{result.get('errorCode', 'Error')}: {report['errorCode']}"
                self._log.error(f"cancel failed - {reason}")
                # TODO(cs): Will probably just need to recover the client order ID
                #  and order ID from the execution report?
                # self.generate_order_cancel_rejected(
                #     strategy_id=command.strategy_id,
                #     instrument_id=command.instrument_id,
                #     client_order_id=command.client_order_id,
                #     venue_order_id=venue_order_id,
                #     reason=reason,
                #     ts_event=self._clock.timestamp_ns(),
                # )
                # return

            self._log.debug(
                f"Matching venue_order_id: {venue_order_id} to client_order_id: {command.client_order_id}"
            )
            self.venue_order_id_to_client_order_id[venue_order_id] = command.client_order_id  # type: ignore
            self.generate_order_canceled(
                strategy_id=command.strategy_id,
                instrument_id=command.instrument_id,
                client_order_id=command.client_order_id,
                venue_order_id=venue_order_id,  # type: ignore
                ts_event=self._clock.timestamp_ns(),
            )
            self._log.debug("Sent order cancel")
示例#28
0
    async def _cancel_order(self, command: CancelOrder) -> None:
        self._log.debug(f"Received cancel order: {command}")
        self.generate_order_pending_cancel(
            strategy_id=command.strategy_id,
            instrument_id=command.instrument_id,
            client_order_id=command.client_order_id,
            venue_order_id=command.venue_order_id,
            ts_event=self._clock.timestamp_ns(),
        )

        instrument = self._cache.instrument(command.instrument_id)
        PyCondition.not_none(instrument, "instrument")

        # Format
        cancel_order = order_cancel_to_betfair(command=command, instrument=instrument)  # type: ignore
        self._log.debug(f"cancel_order {cancel_order}")

        # Send to client
        try:
            result = await self._client.cancel_orders(**cancel_order)
        except Exception as exc:
            if isinstance(exc, BetfairAPIError):
                await self.on_api_exception(exc=exc)
            self._log.warning(f"Cancel failed: {exc}")
            self.generate_order_cancel_rejected(
                strategy_id=command.strategy_id,
                instrument_id=command.instrument_id,
                client_order_id=command.client_order_id,
                venue_order_id=command.venue_order_id,
                reason="client error",
                ts_event=self._clock.timestamp_ns(),
            )
            return
        self._log.debug(f"result={result}")

        # Parse response
        for report in result["instructionReports"]:
            venue_order_id = VenueOrderId(report["instruction"]["betId"])
            if report["status"] == "FAILURE":
                reason = f"{result.get('errorCode', 'Error')}: {report['errorCode']}"
                self._log.warning(f"cancel failed - {reason}")
                self.generate_order_cancel_rejected(
                    strategy_id=command.strategy_id,
                    instrument_id=command.instrument_id,
                    client_order_id=command.client_order_id,
                    venue_order_id=venue_order_id,
                    reason=reason,
                    ts_event=self._clock.timestamp_ns(),
                )
                return

            self._log.debug(
                f"Matching venue_order_id: {venue_order_id} to client_order_id: {command.client_order_id}"
            )
            self.venue_order_id_to_client_order_id[venue_order_id] = command.client_order_id  # type: ignore
            self.generate_order_canceled(
                strategy_id=command.strategy_id,
                instrument_id=command.instrument_id,
                client_order_id=command.client_order_id,
                venue_order_id=venue_order_id,  # type: ignore
                ts_event=self._clock.timestamp_ns(),
            )
            self._log.debug("Sent order cancel")
示例#29
0
    def cancel_order(self, command: CancelOrder) -> None:
        PyCondition.not_none(command, "command")

        self.create_task(self._cancel_order(command))
示例#30
0
    async def _modify_order(self, command: ModifyOrder) -> None:
        self._log.debug(f"Received modify_order {command}")
        client_order_id: ClientOrderId = command.client_order_id
        instrument = self._cache.instrument(command.instrument_id)
        PyCondition.not_none(instrument, "instrument")
        existing_order = self._cache.order(client_order_id)  # type: Order

        self.generate_order_pending_update(
            strategy_id=command.strategy_id,
            instrument_id=command.instrument_id,
            client_order_id=command.client_order_id,
            venue_order_id=command.venue_order_id,
            ts_event=self._clock.timestamp_ns(),
        )

        if existing_order is None:
            self._log.warning(
                f"Attempting to update order that does not exist in the cache: {command}"
            )
            self.generate_order_modify_rejected(
                strategy_id=command.strategy_id,
                instrument_id=command.instrument_id,
                client_order_id=client_order_id,
                venue_order_id=command.venue_order_id,
                reason="ORDER NOT IN CACHE",
                ts_event=self._clock.timestamp_ns(),
            )
            return
        if existing_order.venue_order_id is None:
            self._log.warning(f"Order found does not have `id` set: {existing_order}")
            PyCondition.not_none(command.strategy_id, "command.strategy_id")
            PyCondition.not_none(command.instrument_id, "command.instrument_id")
            PyCondition.not_none(client_order_id, "client_order_id")
            self.generate_order_modify_rejected(
                strategy_id=command.strategy_id,
                instrument_id=command.instrument_id,
                client_order_id=client_order_id,
                venue_order_id=VenueOrderId("-1"),
                reason="ORDER MISSING VENUE_ORDER_ID",
                ts_event=self._clock.timestamp_ns(),
            )
            return

        # Send order to client
        kw = order_update_to_betfair(
            command=command,
            venue_order_id=existing_order.venue_order_id,
            side=existing_order.side,
            instrument=instrument,
        )
        self.pending_update_order_client_ids.add(
            (command.client_order_id, existing_order.venue_order_id)
        )
        try:
            result = await self._client.replace_orders(**kw)
        except Exception as exc:
            if isinstance(exc, BetfairAPIError):
                await self.on_api_exception(exc=exc)
            self._log.warning(f"Modify failed: {exc}")
            self.generate_order_modify_rejected(
                strategy_id=command.strategy_id,
                instrument_id=command.instrument_id,
                client_order_id=command.client_order_id,
                venue_order_id=existing_order.venue_order_id,
                reason="client error",
                ts_event=self._clock.timestamp_ns(),
            )
            return

        self._log.debug(f"result={result}")

        for report in result["instructionReports"]:
            if report["status"] == "FAILURE":
                reason = f"{result['errorCode']}: {report['errorCode']}"
                self._log.warning(f"replace failed - {reason}")
                self.generate_order_rejected(
                    strategy_id=command.strategy_id,
                    instrument_id=command.instrument_id,
                    client_order_id=command.client_order_id,
                    reason=reason,
                    ts_event=self._clock.timestamp_ns(),
                )
                return

            # Check the venue_order_id that has been deleted currently exists on our order
            deleted_bet_id = report["cancelInstructionReport"]["instruction"]["betId"]
            self._log.debug(f"{existing_order}, {deleted_bet_id}")
            assert existing_order.venue_order_id == VenueOrderId(deleted_bet_id)

            update_instruction = report["placeInstructionReport"]
            venue_order_id = VenueOrderId(update_instruction["betId"])
            self.venue_order_id_to_client_order_id[venue_order_id] = client_order_id
            self.generate_order_updated(
                strategy_id=command.strategy_id,
                instrument_id=command.instrument_id,
                client_order_id=client_order_id,
                venue_order_id=VenueOrderId(update_instruction["betId"]),
                quantity=Quantity(
                    update_instruction["instruction"]["limitOrder"]["size"], precision=4
                ),
                price=price_to_probability(
                    str(update_instruction["instruction"]["limitOrder"]["price"])
                ),
                trigger=None,  # Not applicable for Betfair
                ts_event=self._clock.timestamp_ns(),
                venue_order_id_modified=True,
            )