"""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