Source code for mlthon.api.istrategy

"""ML Tech, Inc. Strategy API docstrings

**Note**: The trading environment is an :obj:`.IStratEnv` class instance, which provides key functionalities for order entry,
market data update, etc.

A trading strategy runnable in our framework is actually a class implementing the IStrategy interface below.
It's nothing more than an API, providing multiple callback functions, which would be invoke by the trading environment
when the corresponding information comes into the system.

Inside these callback, researchers can use the functionalities of the trading environment to send orders, get
information, etc.(Check the documentation of the trading environment :obj:`.istrat_env` for more details).
Check the docstrings of callback functions/methods below to see their usage and arguments.

See :obj:`.demo_strat` for the example of a trading strategy.


Q&A
---

**Are the callbacks async?**

   **Yes**. Callbacks are async. We don't need to wait for the response once a request is sent to the trading
   environment. The response is delivered through different callback by the trading environment (Eg: Response for
   NewOrder is delivered through NewOrderAck or NewOrderReject callback).

**Could we perform a blocking operation for the callbacks?**

   **No**. Blocking operations in any of the callbacks will hinder the trading environment to deliver the response of any
   previous requests. So blocking operations should not be performed in any of the callbacks.
   
**What's the typical ordering of initialization-related callbacks?** 

(__init__(),  => on_framework_connected(), => on_start(), => on_instruments(), => on_strat_params_update(), => on_gateway_status(), => on_feed_handler_status())

In a live trading environment, here's a typical process:

   | a) the constructor (__init__()) of the strategy would be called at first in order to run it.
   | b) python side will be connected to trading system and then on_framework_connected(env) is invoked. A REQUIRED
    practice in on_framework_connected(env) is logging into the trading system:

    .. code-block::

       def  on_framework_connected(env: IStratEnv):
            ...
            env.login(<strategy_name>)
            ...

   | c) if researchers login into the trading system, the trading system side will be able to confirm that it's good to
    send msg back and forward. Then the following things would happen:

        | c.1) a "start" execution command would be sent back from the trading system (just like a hook command,
            but triggered by the system itself automatically). Then on_start() method would be called.
        | c.2) all instrument information would be sent from trading system at once, on_instruments() method would be
            invoked.
        | c.3) all strat params would be sent from trading system, on_strat_params_update() would be invoked.
   | d) on_gateway_status() and on_feed_handler_status() won't be invoked when gtw and feed handler are still ready to
    trade (gtw and feed handler are ready even before the connection between python side and trading system is
    established). These two methods would be invoked only when gtw and fee handler change from ready to other states (or
    later on, change from other states back to ready). So these two methods are not initialized-related callbacks indeed

"""

import abc
import typing

from mlthon.api.istrat_env import IStratEnv
from mlthon.basics.defs import GtwRejectCode, CancelCode, GtwStatus, FeedHandlerStatus, Exchange, Side, OrderType, \
    TIF, ExecInstructions
from mlthon.basics.defs import OrderStatus
from mlthon.basics.price import Price
from mlthon.basics.qty import Qty
from mlthon.instruments.instrument import Instrument
from mlthon.order.order import Order


[docs]class IStrategy(metaclass=abc.ABCMeta): """ The interface of a strategy runnable on the trading environment. """ @classmethod def __subclasshook__(cls, subclass): return (hasattr(subclass, 'on_instruments') and callable(subclass.on_instruments) and hasattr(subclass, 'on_strat_params_update') and callable(subclass.on_strat_params_update) and hasattr(subclass, 'on_account_info') and callable(subclass.on_account_info) and hasattr(subclass, 'on_add_level') and callable(subclass.on_add_level) and hasattr(subclass, 'on_add_price_level') and callable(subclass.on_add_price_level) and hasattr(subclass, 'on_cancel_all_ack') and callable(subclass.on_cancel_all_ack) and hasattr(subclass, 'on_cancel_all_reject') and callable(subclass.on_cancel_all_reject) and hasattr(subclass, 'on_cancel_order_ack') and callable(subclass.on_cancel_order_ack) and hasattr(subclass, 'on_cancel_order_reject') and callable(subclass.on_cancel_order_reject) and hasattr(subclass, 'on_delete_level') and callable(subclass.on_delete_level) and hasattr(subclass, 'on_delete_price_level') and callable(subclass.on_delete_price_level) and hasattr(subclass, 'on_feed_handler_status') and callable(subclass.on_feed_handler_status) and hasattr(subclass, 'on_framework_connected') and callable(subclass.on_framework_connected) and hasattr(subclass, 'on_framework_disconnected') and callable(subclass.on_framework_disconnected) and hasattr(subclass, 'on_funding_info') and callable(subclass.on_funding_info) and hasattr(subclass, 'on_gateway_status') and callable(subclass.on_gateway_status) and hasattr(subclass, 'on_modify_level') and callable(subclass.on_modify_level) and hasattr(subclass, 'on_modify_order_ack') and callable(subclass.on_modify_order_ack) and hasattr(subclass, 'on_modify_order_reject') and callable(subclass.on_modify_order_reject) and hasattr(subclass, 'on_modify_price_level') and callable(subclass.on_modify_price_level) and hasattr(subclass, 'on_new_order_ack') and callable(subclass.on_new_order_ack) and hasattr(subclass, 'on_new_order_reject') and callable(subclass.on_new_order_reject) and hasattr(subclass, 'on_order_execution') and callable(subclass.on_order_execution) and hasattr(subclass, 'on_order_cancelled') and callable(subclass.on_order_cancelled) and hasattr(subclass, 'on_order_status') and callable(subclass.on_order_status) and hasattr(subclass, 'on_orders_info') and callable(subclass.on_orders_info) and hasattr(subclass, 'on_position_info') and callable(subclass.on_position_info) and hasattr(subclass, 'on_public_trade') and callable(subclass.on_public_trade) and hasattr(subclass, 'on_rate_limit_info') and callable(subclass.on_rate_limit_info) and hasattr(subclass, 'on_request_reject') and callable(subclass.on_request_reject) and hasattr(subclass, 'on_start') and callable(subclass.on_start) and hasattr(subclass, 'on_stop') and callable(subclass.on_stop) and hasattr(subclass, 'on_unsupported_op') and callable(subclass.on_unsupported_op) or hasattr(subclass, 'on_wallet_info') and callable(subclass.on_wallet_info) or NotImplemented)
[docs] @abc.abstractmethod def on_framework_connected(self, env: IStratEnv) -> None: """In this callback method, researchers should define the behavior of the strategy (the api) when it's connected to the trading framework. When the trading framework (gateway, feed handler, etc.) is connected, the environment will invoke this method. Note that login via trading environment is REQUIRED in this method so that the strategy could be registered into the system and start sending msg back and forward. An example is provided below. Parameters ---------- env: :obj:`.IStratEnv` this callback may provide trading env as a parameter Returns ------- None Example ------- # A simple implementation for this method (need to initiate the internal states in the constructor, in order to keep them) : .. code-block:: class ExampleStrat(IStrategy): def __init__(self, cfg): # or no cfg ... self._env_ = None # keep the internal state for trading env object ...... def on_framework_connected(env: IStratEnv): self._env_ = env ... self._env_.login("example strat") ... ...... """ raise NotImplementedError
[docs] @abc.abstractmethod def on_framework_disconnected(self) -> None: """In this callback method, researchers should define the behavior of the strategy (the api) when losing connection to the framework itself. When losing connections, the environment will invoke this method. Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_start(self, params: str) -> None: """ This method is called by the environment when running 'start' command hook to start the strategy manually. During and after this call, the strat may request market data and/or gateway connections (if no manual start, do these in :obj:`.on_framework_connected`). Parameters ---------- params: str params for 'start' command book. If manually starting the strat with some parameter setups via command hook, the parameters entered for the command hook would be passed here, as one string Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_stop(self, params: str) -> None: """ This method is called by the environment when the strategy should be stopped (running a 'stop' command hook manually). the strategy (the api) may take usual actions as long after this call, but it is expected to exit its trading logic. Returns ------- None """ raise NotImplementedError
[docs] def on_instruments(self, instruments: typing.Iterable[Instrument]): """ This method is invoked when all instruments used by this strategy have been loaded. It would include all the information necessary for trading purposes, e.g. min quantity for an instrument, etc. It is the burden of the strategy to save the information it requires in order to trade. An example implementation of this method is provided below. Parameters ---------- instruments: typing.Iterable[:obj:`.Instrument`] an iterable (set or list) of Instrument objects, containing the necessary info for product/instruments. Check Instrument class for more details. Note that this method is only called once, providing all the instruments needed by this strategy. Returns ------- None Example ------- # A simple implementation for this method (need to initiate the internal states in the constructor, in order to keep them) : .. code-block:: class ExampleStrat(IStrategy): def __init__(self, cfg): # or no cfg ... self._instruments_by_name_ = {} # if using instrument id to find instrument, researchers can also keep self._instruments_by_id_ ...... def on_instrument(self, instruments: typing.Iterable[Instrument]): self._instruments_by_name_ = {instrument.get_instrument_name(): instrument for instrument in instruments} # self._instruments_by_id_ = {instrument.get_instrument_id(): instrument for instrument in instruments} ...... """ raise NotImplementedError
[docs] def on_strat_params_update(self, strat_params: dict): """ This method is invoked when strategy parameters used by this strategy have been loaded. It would include all the parameters necessary for trading purposes, e.g. any custom strategy parameters required for strategy. It is up to strategy to save these parameters and used them as required. An example implementation of this method is provided below. Parameters ---------- strat_params: typing.Dict Dictionary of strategy parameters expected by strategy. Note that this method is also called when strategy parameters are updated. Returns ------- None Example ------- # A simple implementation for this method (need to initiate the internal states in the constructor, in order to keep them) : .. code-block:: class ExampleStrat(IStrategy): def __init__(self, cfg): # or no cfg self._strat_params = None self._prev_bid_price = 0 ... ...... def on_strat_params_update(self, strat_params: typing.Dict): self._strat_params = strat_params ... ...... def on_best_bid_level_update(self, exchange: Exchange, instrument_id: int, price: Price, qty: Qty, recv_ts: int): | ._prev_bid_price = price ... ...... """ raise NotImplementedError
# ----------------------------------------------------------------------------------- # ------------------------------ Order Entry Callbacks ------------------------------ # -----------------------------------------------------------------------------------
[docs] @abc.abstractmethod def on_new_order_ack(self, exchange: Exchange, client_id: str, exchange_id: str, instrument_id: int, side: Side, price: Price, qty: Qty, leaves_qty: Qty, order_type: OrderType, tif: TIF, exec_instr: ExecInstructions) -> None: """ When sending a new order and get acknowledgement from the exchange, exchange will send a new order ack message back to the trading environment. Then the environment will invoke this method with all the necessary information of a new order ack message. So in this callback method, researchers should define the behavior of the strategy (the api) when the new order requested is acknowledged. Parameters ---------- exchange: :obj:`.Exchange` exchange of this order, e.g. Exchange.Bybit. client_id: str client ID is an identification in the internal system. exchange_id: str ID in external exchange. instrument_id: int product of this order, represented via its ID, e.g. 30000000000 represents BTC-USD inverse perpetual in Bybit. side: :obj:`.Side` side of this order, Side.Buy or Side.Sell. price: :obj:`.Price` price of this order. qty: :obj:`.Qty` quantity of this order. leaves_qty: :obj:`.Qty` leave quantity (number of shares still open for execution) in this order. order_type: :obj:`.OrderType` type of this order, OrderType.Unset or OrderType.Limit (limit order) tif: :obj:`.TIF` time in force, TIF.Unset, TIF.GTC (Good to Cancel), TIF.IOC (Execute immediately, even partially, the rest is cancelled) exec_instr: :obj:`.ExecInstructions` execution instruction, ExecInstructions.Unset or ExecInstructions.PostOnly Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_modify_order_ack(self, exchange: Exchange, client_id: str, new_price: Price, new_qty: Qty, leaves_qty: Qty) -> None: """When sending a modify order and get acknowledgement from the exchange, exchange will send a modify order ack message back to the trading environment. Then the environment will invoke this method with all the necessary information of a modify order ack message. So in this callback method, researchers should define the behavior of the strategy (the api) when the modify order requested is acknowledged. Parameters ---------- exchange: :obj:`.Exchange` exchange of this order, e.g. Exchange.Bybit. client_id: str client ID is an identification in the internal system. new_price: :obj:`.Price` new price of this modified order. new_qty: :obj:`.Qty` new quantity of this modified order. leaves_qty: :obj:`.Qty` remaining quantity of this order. Note that new_price and new_qty are optional, but at least one of them must be present. Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_cancel_order_ack(self, exchange: Exchange, client_id: str) -> None: """When sending a cancel order and get acknowledgement from the exchange, exchange will send a cancel order ack message back to the trading environment. Then the environment will invoke this method with all the necessary information of a cancel order ack message. So in this callback method, researchers should define the behavior of the strategy (the api) when the cancel order requested is acknowledged. Parameters ---------- exchange: :obj:`.Exchange` exchange of this cancel order. client_id: str client ID (generated and kept in the internal system) for this cancel order. This DOES NOT correspond the the actual cancellation of the corresponding order, which is signaled by the on_order_cancelled callback. Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_cancel_all_ack(self, exchange: Exchange, instrument_id: int) -> None: """When sending a cancel-all request and get acknowledgement from the exchange, exchange will send a cancel all ack message back to the trading environment. Then the environment will invoke this method with all the necessary information of a cancel order ack message. So in this callback method, researchers should define the behavior of the strategy (the api) when the cancel-all requested is acknowledged. Parameters ---------- exchange: :obj:`.Exchange` exchange of the cancel-all request. instrument_id: int instrument (product) of this cancel-all request, represented by ID. This DOES NOT correspond the the actual cancellation of the corresponding order(s), which is signaled by the on_order_cancelled callback. Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_new_order_reject(self, exchange: Exchange, client_id: str, reject_code: GtwRejectCode, reject_reason: str) -> None: """In this callback method, researchers should define the behavior of the strategy (the api) when the new order requested is rejected. Trading environment would invoke it with all the necessary information when the request is rejected by the exchange. Parameters ---------- exchange: :obj:`.Exchange` exchange of this order. client_id: str internal client ID. reject_code: :obj:`.GtwRejectCode` reject code from Gateway. Code represents the reason of rejection, e.g. 1000 = GtwNotReady (gateway is not ready for requests). reject_reason: str attached reason for the rejection. Note that 'reject_reason' may be empty. Strategy developers should familiarize themselves with the meaning of GtwRejectCode, as it may help them react differently to different types of rejection. Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_modify_order_reject(self, exchange: Exchange, client_id: str, reject_code: GtwRejectCode, reject_reason: str) -> None: """In this callback method, researchers should define the behavior of the strategy (the api) when the modify order requested is rejected. Trading environment would invoke it with all the necessary information when the request is rejected by the exchange. Parameters ---------- exchange: :obj:`.Exchange` exchange of this order. client_id: str internal client ID. reject_code: :obj:`.GtwRejectCode` reject code from Gateway. Code represents the reason of rejection, e.g. 1000 = GtwNotReady (gateway is not ready for requests). reject_reason: str attached reason for the rejection. Note that 'reject_reason' may be empty. Strategy developers should familiarize themselves with the meaning of GtwRejectCode, as it may help them react differently to different types of rejection. Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_cancel_order_reject(self, exchange: Exchange, nullable_client_id: typing.Optional[str], reject_code: GtwRejectCode, reject_reason: str) -> None: """In this callback method, researchers should define the behavior of the strategy (the api) when the cancel order requested is rejected. Trading environment would invoke it with all the necessary information when the request is rejected by the exchange. Parameters ---------- exchange: :obj:`.Exchange` exchange of this order. nullable_client_id: str internal client ID. client_id is optional here. If it's None, the corresponding request would be cancel-all. reject_code: :obj:`.GtwRejectCode` reject code from Gateway. Code represents the reason of rejection, e.g. 1000 = GtwNotReady (gateway is not ready for requests). reject_reason: str attached reason for the rejection. Note that 'reject_reason' may be empty. Strategy developers should familiarize themselves with the meaning of GtwRejectCode, as it may help them react differently to different types of rejection. Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_cancel_all_reject(self, exchange: Exchange, instrument_id: int, reject_code: GtwRejectCode, reject_reason: str) -> None: """In this callback method, researchers should define the behavior of the strategy (the api) when the cancel-all requested is rejected. Trading environment would invoke it with all the necessary information when the request is rejected by the exchange. Parameters ---------- exchange: :obj:`.Exchange` exchange of this request. instrument_id: int instrument/product of this request, represented as ID reject_code: :obj:`.GtwRejectCode` reject code from Gateway. Code represents the reason of rejection, e.g. 1000 = GtwNotReady (gateway is not ready for requests). reject_reason: str attached reason for the rejection. Note that 'reject_reason' may be empty. Strategy developers should familiarize themselves with the meaning of GtwRejectCode, as it may help them react differently to different types of rejection. Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_order_execution(self, exchange: Exchange, client_id: str, side: Side, price: Price, fill_qty: Qty, leaves_qty: Qty, exec_ts: int, recv_ts: int) -> None: """In this callback method, researchers should define the behavior of the strategy (the api) when the order is executed (filled). Trading environment would invoke it on the execution (or fill) of an order, with all the necessary information. Parameters ---------- exchange: :obj:`.Exchange` exchange of this order. client_id: str internal client ID. side: :obj:`.Side` side of this order. price: :obj:`.Price` price level of this order. fill_qty: :obj:`.Qty` filled quantity of this order. leaves_qty: :obj:`.Qty` leave quantity (number of shares still open for execution) of this order. exec_ts: int execution timestamp. recv_ts: int record reception timestamp. Trading environment will pass current timestamp to it if unspecified. Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_order_cancelled(self, exchange: Exchange, client_id: str, unsolicited: bool, engine_ts: int, recv_ts: int, cancel_code: CancelCode, cancel_reason: str) -> None: """In this callback method, researchers should define the behavior of the strategy (the api) when the order is cancelled. Trading environment would invoke it, with all the necessary information, on the cancellation of an order, regardless of whether it was requested by the user or not. Parameters ---------- exchange: :obj:`.Exchange` exchange of this order. client_id: str internal client ID. unsolicited: bool True means that the cancel was not requested by the user. engine_ts: int timestamp at which the cancellation occurred. 0 if unknown. recv_ts: int record reception timestamp. Trading environment will pass current timestamp to it if unspecified. cancel_code: :obj:`.CancelCode` cancel code, 'Unset', 'CanceledByUser' or 'SelfTradePrevention' (cancellation not requested by user) cancel_reason: str reason for cancellation Returns ------- None """ raise NotImplementedError
# ----------------------------------------------------------------------------------- # ------------------------------ Account Info Callbacks ----------------------------- # -----------------------------------------------------------------------------------
[docs] @abc.abstractmethod def on_order_status(self, exchange: Exchange, instrument_id: int, client_ord_id: str, exch_ord_id: str, side: Side, price: Price, leaves_qty: Qty, status: OrderStatus, order_type: OrderType, tif: TIF, exec_instr: ExecInstructions) -> None: """In this callback method, define behaviors of the strategy when receiving the order information for a single order as requested by a call to fetch_order_status(). Parameters ---------- exchange: :obj:`.Exchange` exchange of this order. client_ord_id: str client ID is an identification in our internal system. exch_ord_id: str ID in external exchange. instrument_id: int instrument ID of this single order info. side: :obj:`.Side` side of this order, Side.Buy or Side.Sell. price: :obj:`.Price` price of this order. leaves_qty: :obj:`.Qty` leave quantity (number of shares still open for execution) in this order. status: :obj:`.OrderStatus` status of the order, e.g. filled order_type: :obj:`.OrderType` type of this order, OrderType.Unset or OrderType.Limit (limit order) tif: :obj:`.TIF` time in force, TIF.Unset, TIF.GTC (Good to Cancel), TIF.IOC (Execute immediately, even partially, the rest is cancelled) exec_instr: :obj:`.ExecInstructions` execution instruction, ExecInstructions.Unset or ExecInstructions.PostOnly Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_orders_info(self, exchange: Exchange, orders: typing.List[Order]) -> None: """In this callback method, researchers should define the behavior of the strategy when receiving orders info. The trading environment would invoke this method with exchange and orders containing a list of open orders matching the filtering criteria passed when making the request. Parameters ---------- exchange: :obj:`.Exchange` exchange of this orders info orders: typing.List[:obj:`.Order`] a list of open orders matching the filtering criteria passed when making the request Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_wallet_info(self, exchange: Exchange, coin: str, total_balance: Qty, available_balance: Qty) -> None: """In this callback method, researchers should define the behavior of the strategy when receiving wallet info. Trading environment would invoke this method with the balance of the wallet for the listed coin. Note that for non spot products, this is what the strategy (the api) may use to derive positional changes. Parameters ---------- exchange: :obj:`.Exchange` exchange this info comes from. coin: str coin of this wallet info. total_balance: Qty total balance of the listed coin in this wallet. available_balance: Qty available balance of the listed coin in this wallet. Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_position_info(self, exchange: Exchange, instrument_id: int, position: Qty) -> None: """In this callback method, researchers should define the behavior of the strategy when receiving position info. For derivative products, this callback method would be invoked by the trading environment with the current position. For non derivative products, cf. :obj:`.on_wallet_info`. Parameters ---------- exchange: :obj:`.Exchange` exchange this info comes from. instrument_id: int instrument ID of this position info. position: :obj:`.Qty` current position for this instrument. Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_funding_info(self, exchange: Exchange, instrument_id: int, funding_rate: float, next_funding_ts: int) -> None: """In this callback method, researchers should define the behavior of the strategy when receiving funding info. Some exchanges reward users for being long or short certain derivative products. This method will be invoked by the trading environment, with information about the rate of such rewards. Parameters ---------- exchange: :obj:`.Exchange` exchange this info comes from. instrument_id: int instrument ID of this funding info. funding_rate: float funding rate of this funding info. next_funding_ts: int timestamp of next funding event. Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_account_info(self, exchange: Exchange, user_id: str) -> None: """In this callback method, researchers should define the behavior of the strategy when receiving account info. For now, this message (and thus, this method) is not very useful. Parameters ---------- exchange: :obj:`.Exchange` exchange the account info comes from. user_id: str account information - user ID of this account. Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_request_reject(self, exchange: Exchange, reject_code: GtwRejectCode, detail: str, rejected_rqst_type: str) -> None: """In this callback method, researchers should define the behavior of the strategy when the request is rejected. This method would be invoked by the trading environment when a request sent by the strategy got rejection in gateway. Parameters ---------- exchange: :obj:`.Exchange` exchange rejecting the request. reject_code: :obj:`.GtwRejectCode` reject code from Gateway. Code represents the reason of rejection, e.g. 1000 = GtwNotReady (gateway is not ready for requests). detail: str attached details for the rejection. rejected_rqst_type: str type of the rejected request. Returns ------- None """ raise NotImplementedError
# ----------------------------------------------------------------------------------- # ------------------------------- Market Data Callbacks ----------------------------- # -----------------------------------------------------------------------------------
[docs] @abc.abstractmethod def on_public_trade(self, exchange: Exchange, instrument_id: int, side: Side, price: Price, qty: Qty, exec_ts: int, recv_ts: int) -> None: """This method invoked when a public trade is observed in the market data. So in this callback method, researchers should define the behavior of the strategy when receiving a public trade market data record. Parameters ---------- exchange: :obj:`.Exchange` exchange with market data update (public trade). instrument_id: int instrument ID with market data update. side: :obj:`.Side` taker side of this public trade. price: :obj:`.Price` price of this public trade. qty: :obj:`.Qty` quantity of this trade. exec_ts: int trade execution timestamp. recv_ts: int timestamp when receiving this record. Trading environment will pass current timestamp to it if unspecified. Returns ------- None Example ------- For example, when the feed handler receive a public trade from exchange Bybit, for instrument ID 30000000000 (BTC-USD inverse perpetual in Bybit), with sell taker side, price 9297 and qty 50, executed at 1594097430153 in exchange and received to us at 1594097430427, trading environment will invoke this method with exchange Exchange.Bybit, instrument ID 30000000000, side Side.Sell, price Price('9297.0'), qty Qty('50.0'), exec_ts 1594097430153 and recv_ts 1594097430427. """ raise NotImplementedError
[docs] @abc.abstractmethod def on_add_price_level(self, exchange: Exchange, instrument_id: int, side: Side, price: Price, qty: Qty, recv_ts: int) -> None: """In this callback method, researchers should define the behavior of the strategy when receiving a add-price-level market data. This method would be invoked by the trading environment, with information of this market data record. Parameters ---------- exchange: :obj:`.Exchange` exchange with market data update (add price level). instrument_id: int instrument ID with market data update. side: :obj:`.Side` side of the add-price-level update, Side.Sell or Side.Buy. price: :obj:`.Price` price for this level. qty: :obj:`.Qty` quantity for this level. recv_ts: int timestamp when receiving this record. Trading environment will pass current timestamp to it if unspecified. Returns ------- None Example ------- For example, when the feed handler receive an add-price-level buy order update from exchange Bybit, for instrument ID 30000000000 (BTC-USD Inverse Perpetual in Bybit) BTC-USD, with price 9297 and qty 13952, the trading environment will invoke this callback method with exchange Exchange.Bybit, instrument ID 30000000000, side Side.Buy, price Price('9226.50000000') and qty Qty( '13952.00000000'). """ raise NotImplementedError
[docs] @abc.abstractmethod def on_modify_price_level(self, exchange: Exchange, instrument_id: int, side: Side, price: Price, new_qty: Qty, recv_ts: int) -> None: """In this callback method, researchers should define the behavior of the strategy when receiving a modify-price-level market data. This method would be invoked by the trading environment, with information of this market data record. Parameters ---------- exchange: :obj:`.Exchange` exchange with market data update (modify price level). instrument_id: int instrumen id with market data update. side: :obj:`.Side` side of the modify-price-level update. price: :obj:`.Price` price level ID of the update. new_qty: :obj:`.Qty` new quantity for this level. recv_ts: int timestamp when receiving this record. Trading environment will pass it as current timestamp if unspecified. Returns ------- None Example ------- For example, when the feed handler receive a modify-price-level buy order update from exchange Bybit, for instrument ID 30000000000 (BTC-USD inverse perpetual in Bybit), with price 9226.5 and new qty 64628, the trading environment will invoke this callback method with exchange Exchange.Bybit, instrument ID 30000000000, side Side.Buy, price Price('9226.50000000'). """ raise NotImplementedError
[docs] @abc.abstractmethod def on_delete_price_level(self, exchange: Exchange, instrument_id: int, side: Side, price: Price, recv_ts: int) -> None: """In this callback method, researchers should define the behavior of the strategy when receiving a delete-price-level market data update. This method would be invoked by the trading environment, with information of this market data record. Parameters ---------- exchange: :obj:`.Exchange` exchange with market data update (delete price level). instrument_id: int instrument ID with market data update. side: :obj:`.Side` side of the delete-price-level update. price: :obj:`.Price` price level of this update recv_ts: int timestamp when receiving this record. Trading environment will pass current timestamp to it if unspecified. Returns ------- None Example ------- For example, when the feed handler receive a delete-price-level buy order update from exchange Bybit, for instrument ID 30000000000 (BTC-USD inverse perpetual in Bybit), with price 9226.5, the trading environment will invoke this callback method with exchange Exchange.Bybit, instrument ID 30000000000, side Side.Buy and price Price('9226.50000000'). """ raise NotImplementedError
[docs] @abc.abstractmethod def on_add_level(self, exchange: Exchange, instrument_id: int, side: Side, level_id: int, price: Price, qty: Qty, recv_ts: int) -> None: """In this callback method, researchers should define the behavior of the strategy when receiving a add-level market data. This method would be invoked by the trading environment, with information of this market data record. Parameters ---------- exchange: :obj:`.Exchange` exchange with market data update (add level). instrument_id: int instrument ID with market data update. side: :obj:`.Side` side of the add-level update. level_id: int level ID of the update. price: :obj:`.Price` price for this level. qty: :obj:`.Qty` quantity for this level. recv_ts: int timestamp when receiving this record. Trading environment will pass current timestamp to it if unspecified. Returns ------- None Example ------- For example, when the feed handler receive an add-level buy order update from exchange Bybit, for instrument ID 30000000000 (BTC-USD inverse perpetual in Bybit), with level ID 92265000, price 9226.5 and qty 13952, the trading environment will invoke this callback method with exchange Exchange.Bybit, instrument ID 30000000000, side Side.Buy, level_id 92265000, price Price('9226.50000000') and qty Qty('13952.00000000'). """ raise NotImplementedError
[docs] @abc.abstractmethod def on_modify_level(self, exchange: Exchange, instrument_id: int, side: Side, level_id: int, new_qty: Qty, recv_ts: int) -> None: """In this callback method, researchers should define the behavior of the strategy when receiving a modify-level market data. This method would be invoked by the trading environment, with information of this market data record. Parameters ---------- exchange: :obj:`.Exchange` exchange with market data update (modify level). instrument_id: int instrument ID with market data update. side: :obj:`.Side` side of the modify-level update. level_id: int level ID of the update. new_qty: :obj:`.Qty` new quantity for this level. recv_ts: int timestamp when receiving this record. Trading environment will pass current timestamp to it if unspecified. Returns ------- None Example ------- For example, when the feed handler receive a modify-level buy order update from exchange Bybit, for instrument ID 30000000000 (BTC-USD inverse perpetual in Bybit), with level ID 92360000 and qty 64628, the trading environment will invoke this callback method with exchange Exchange.Bybit, instrument ID 30000000000, side Side.Buy, level_id 92360000 and new_qty Qty('64628.00000000'). """ raise NotImplementedError
[docs] @abc.abstractmethod def on_delete_level(self, exchange: Exchange, instrument_id: int, side: Side, level_id: int, recv_ts: int) -> None: """In this callback method, researchers should define the behavior of the strategy when receiving a delete-level market data. This method would be invoked by the trading environment, with information of this market data record. Parameters ---------- exchange: :obj:`.Exchange` exchange with market data update (delete level). instrument_id: int instrument ID with market data update. side: :obj:`.Side` side of the delete-level update. level_id: int level ID of the update. recv_ts: int timestamp when receiving this record. Trading environment will pass current timestamp to it if unspecified. Returns ------- None Example ------- For example, when the feed handler receive a delete-level buy order update from exchange Bybit, for instrument ID 30000000000 (BTC-USD inverse perpetual in Bybit), with level ID 92375000, the trading environment will invoke this callback method with exchange Exchange.Bybit, instrument ID 30000000000, side Side.Buy and level_id 92375000. """ raise NotImplementedError
[docs] @abc.abstractmethod def on_best_bid_level_update(self, exchange: Exchange, instrument_id: int, price: Price, qty: Qty, recv_ts: int) -> None: """In this callback method, researchers should define the behavior of the strategy when best bid level is updated. This method would be invoked by the trading environment, with information of the new best bid level. Parameters ---------- exchange: :obj:`.Exchange` the exchange where one of the product has best bid level update. instrument_id: int the product with best bid level update. price: :obj:`.Price` price level for the new best bid. qty: :obj:`.Qty` quantity for the new best bid. recv_ts: int timestamp when receiving this update. Trading environment will pass current timestamp to it if unspecified. Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_best_ask_level_update(self, exchange: Exchange, instrument_id: int, price: Price, qty: Qty, recv_ts: int) -> None: """In this callback method, researchers should define the behavior of the strategy when best ask level is updated. This method would be invoked by the trading environment, with information of the new best ask level. Parameters ---------- exchange: :obj:`.Exchange` the exchange where one of the product has best ask level update. instrument_id: int the product with best ask level update. price: :obj:`.Price` price level for the new best ask. qty: :obj:`.Qty` quantity for the new best ask. recv_ts: int timestamp when receiving this update. Trading environment will pass current timestamp to it if unspecified. Returns ------- None """ raise NotImplementedError
# ----------------------------------------------------------------------------------- # --------------------------- Gtw and Feed Status Callbacks ------------------------- # -----------------------------------------------------------------------------------
[docs] @abc.abstractmethod def on_gateway_status(self, exchange: Exchange, status: GtwStatus, detail: str) -> None: """ This callback method should define the behaviors of the strategy (the api) when receiving Gateway status transition. Possible status values are 'ReadyToTrade', 'Connecting' and 'Maintenance': ReadyToTrade - connected to trading system and ready to trade (get/send orders etc..) Connecting - The gateway is connecting and trying to get ready Maintenance - The gateway of some exchanges would report this status when it's in maintenance The api should only try and send order on an gateway if it is in ReadyToTrade status. Not doing so will result in a reject. Parameters ---------- exchange: :obj:`.Exchange` exchange with gateway status transition. In the example above, it's Exchange.Blade. status: :obj:`.GtwStatus` new gateway status. In the example above, it's GtwStatus.ReadyToTrade. detail: str attached details for this record. Returns ------- None Example ------- For example, when the gateway for Bybit is connected and ready to trade, the trading environment would invoke this callback method, with exchange Exchange.Bybit, status ReadyToTrade, and detail 'null'. Then in this callback method, researchers can set up some internal states to tell the strategy (the api) that it's able to send orders now. """ raise NotImplementedError
[docs] @abc.abstractmethod def on_feed_handler_status(self, exchange: Exchange, status: FeedHandlerStatus, detail: str) -> None: """ This callback method should define the behaviors of the strategy (the api) when receiving Feed Handler status transition. Possible status values are: 'SnapshotStart', 'SnapshotEnd', 'Disconnected': | SnapshotStart - the feed is connected and the following events are a snapshot of the feed | SnapshotEnd - all the events between SnaphotStart and SnapshotEnd constitute the initial snapshot of the feed. subsequent events are incremental updates (AKA delta updates) | Disconnected - The feed got disconnected, and is attempting to reconnect, once connected a new snapshot will be observed The api (this callback method) should clear the orders books when receiving a Connecting status. Between SnapshotStart and End, the api should build the order book. After receiving SnapshotEnd, the api should continue building its order book, knowing that the updates it receives are now live incremental updates. Parameters ---------- exchange: :obj:`.Exchange` exchange with Feed Handler status transition. status: :obj:`.FeedHandlerStatus` new Feed Handler status, 'SnapshotStart', 'SnapshotEnd' or 'Disconnected'. detail: str attached details for the status transition. Returns ------- None """ raise NotImplementedError
[docs] @abc.abstractmethod def on_rate_limit_info(self, exchange: Exchange, rate_limit: int, rate_remaining: int, reset_ts: int) -> None: """ Exchanges restrict the rate at which users may send requests. This callback would be invoked by the trading environment to informs the api of its rate usage. The gtw arbitrarily invokes this callback when 50% has been used, 90% and 100% has been used. When in the danger zone (90%+) the api (this callback) should take actions to reduce its exposure so as to not end up being locked out. Repetitive violation of these limits may result in a ban. Parameters ---------- exchange: :obj:`.Exchange` exchange reaching this rate limit. rate_limit: int overall rate limit of this exchange. In the example above, it's 100, i.e. 100% is the limit of Bybit. rate_remaining: int rate limit remaining. In the example above, it's 10, i.e. have 10% rate left. reset_ts: int timestamp when the usage of the rate limit is expected to be reset to zero. Returns ------- None Example ------- For example, when reaching a 90% rate limit for requests in Bybit exchange, and the timestamp of rate limit reset is 1594159619, the trading environment would invoke this method, with exchange Exchange.Bybit, rate_limit 100, rate_remaining 10 and reset_ts 1594159619. Since that it has already been in the danger zone, researchers should take actions, e.g. reduce the rate of the requests, etc., in this callback method. """ raise NotImplementedError
[docs] @abc.abstractmethod def on_unsupported_op(self, exchange: Exchange, unsupported_msg_type: str) -> None: """ Some operations may only be supported by certain exchanges. If an exchange does not support a request sent by the api, this callback method is invoked by the trading environment. Parameters ---------- exchange: :obj:`.Exchange` exchange not supporting this operation. unsupported_msg_type: str a string indicating the message type of this unsupported operation. Returns ------- None Example ------- For example, when sending a Modify Order request but not supported by Blockchain.com exchange, the trading environment would invoke this method, with exchange Exchange.Blockchain and unsupported_msg_type 'ModifyOrder'. """ raise NotImplementedError