Quote-Binding Mode
Shioaji provides quote-binding mode which you can store tick/bidask in queue, push them to redis, or submit a stop order inside quote callback function. We show examples to make you more understand how to use quote-binding mode.
Examples¶
Bind quote to message queue¶
In: pythonic way by using decorator
from collections import defaultdict, deque
from shioaji import TickFOPv1, Exchange
# set context
msg_queue = defaultdict(deque)
api.set_context(msg_queue)
# In order to use context, set bind=True
@api.on_tick_fop_v1(bind=True)
def quote_callback(self, exchange:Exchange, tick:TickFOPv1):
# append quote to message queue
self[tick.code].append(tick)
# subscribe
api.quote.subscribe(
api.Contracts.Futures.TXF['TXF202107'],
quote_type = sj.constant.QuoteType.Tick,
version = sj.constant.QuoteVersion.v1
)
In: traditional way
def quote_callback(self, exchange:Exchange, tick:TickFOPv1):
# append tick to context
self[tick.code].append(tick)
# In order to use context, set bind=True
api.quote.set_on_tick_fop_v1_callback(quote_callback, bind=True)
Out
# after subscribe and wait for a few seconds ...
# print(msg_queue)
defaultdict(collections.deque,
{
'TXFG1': [
Tick(code='TXFG1', datetime=datetime.datetime(2021, 7, 5, 10, 0, 21, 220000), open=Decimal('17755'), underlying_price=Decimal('17851.88'), bid_side_total_vol=34824, ask_side_total_vol=36212, avg_price=Decimal('17837.053112'), close=Decimal('17833'), high=Decimal('17900'), low=Decimal('17742'), amount=Decimal('17833'), total_amount=Decimal('981323314'), volume=1, total_volume=55016, tick_type=1, chg_type=2, price_chg=Decimal('184'), pct_chg=Decimal('1.042552'), simtrade=0),
Tick(code='TXFG1', datetime=datetime.datetime(2021, 7, 5, 10, 0, 21, 781000), open=Decimal('17755'), underlying_price=Decimal('17851.88'), bid_side_total_vol=34825, ask_side_total_vol=36213, avg_price=Decimal('17837.053056'), close=Decimal('17834'), high=Decimal('17900'), low=Decimal('17742'), amount=Decimal('17834'), total_amount=Decimal('981341148'), volume=1, total_volume=55017, tick_type=1, chg_type=2, price_chg=Decimal('185'), pct_chg=Decimal('1.048218'), simtrade=0)
]
}
)
Push quote to redis stream¶
Before start, please install redis first. Below example shows how to push quote massages to redis stream.
In
import redis
import json
from shioaji import TickFOPv1, Exchange
# redis setting
r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)
# set up context
api.set_context(r)
# In order to use context, set bind=True
@api.on_tick_fop_v1(bind=True)
def quote_callback(self, exchange:Exchange, tick:TickFOPv1):
# push them to redis stream
channel = 'Q:' + tick.code # ='Q:TXFG1' in this example
self.xadd(channel, {'tick':json.dumps(tick.to_dict(raw=True))})
Out
# after subscribe and wait for a few seconds ...
# r.xread({'Q:TXFG1':'0-0'})
[
['Q:TXFG1',
[
('1625454940107-0',
{'tick':
'{"code": "TXFG1", "datetime": "2021-07-05T11:15:49.066000", "open": "17755", "underlying_price": "17904.03", "bid_side_total_vol": 49698, "ask_side_total_vol": 51490, "avg_price": "17851.312322", "close": "17889", "high": "17918", "low": "17742", "amount": "268335", "total_amount": "1399310819", "volume": 15, "total_volume": 78387, "tick_type": 2, "chg_type": 2, "price_chg": "240", "pct_chg": "1.35985", "simtrade": 0}'
}
),
('1625454941854-0',
{'tick':
'{"code": "TXFG1", "datetime": "2021-07-05T11:15:50.815000", "open": "17755", "underlying_price": "17902.58", "bid_side_total_vol": 49702, "ask_side_total_vol": 51478, "avg_price": "17851.313258", "close": "17888", "high": "17918", "low": "17742", "amount": "35776", "total_amount": "1399346595", "volume": 2, "total_volume": 78389, "tick_type": 2, "chg_type": 2, "price_chg": "239", "pct_chg": "1.354184", "simtrade": 0}'
}
)
]
]
]
# parse redis stream
# [json.loads(x[-1]['tick']) for x in r.xread({'Q:TXFG1':'0-0'})[0][-1]]
[
{
'code': 'TXFG1',
'datetime': '2021-07-05T11:15:49.066000',
'open': '17755',
'underlying_price': '17904.03',
'bid_side_total_vol': 49698,
'ask_side_total_vol': 51490,
'avg_price': '17851.312322',
'close': '17889',
'high': '17918',
'low': '17742',
'amount': '268335',
'total_amount': '1399310819',
'volume': 15,
'total_volume': 78387,
'tick_type': 2,
'chg_type': 2,
'price_chg': '240',
'pct_chg': '1.35985',
'simtrade': 0
},
{
'code': 'TXFG1',
'datetime': '2021-07-05T11:15:50.815000',
'open': '17755',
'underlying_price': '17902.58',
'bid_side_total_vol': 49702,
'ask_side_total_vol': 51478,
'avg_price': '17851.313258',
'close': '17888',
'high': '17918',
'low': '17742',
'amount': '35776',
'total_amount': '1399346595',
'volume': 2,
'total_volume': 78389,
'tick_type': 2,
'chg_type': 2,
'price_chg': '239',
'pct_chg': '1.354184',
'simtrade': 0
},
]
Stop Order Implementation¶
A stop order is an order to buy or sell a security when its price moves past a particular point, ensuring a higher probability of achieving a predetermined entry or exit price, limiting the investor's loss, or locking in a profit. Once the price crosses the predefined entry/exit point, the stop order becomes a market order.
We provide an example of stop order below. Please use at your own risk.
Example: stop order
import time
from typing import Union
import shioaji as sj
class StopOrderExcecutor:
def __init__(self, api: sj.Shioaji) -> None:
self.api = api
self._stop_orders = {}
def on_quote(
self, quote: Union[sj.BidAskFOPv1, sj.BidAskSTKv1, sj.TickFOPv1, sj.TickSTKv1]
) -> None:
code = quote.code
if code in self._stop_orders:
for stop_order in self._stop_orders[code]:
if stop_order['executed']:
continue
if hasattr(quote, "ask_price"):
price = 0.5 * float(
quote.bid_price[0] + quote.ask_price[0]
) # mid price
else:
price = float(quote.close) # Tick
is_execute = False
if stop_order["stop_price"] >= stop_order["ref_price"]:
if price >= stop_order["stop_price"]:
is_execute = True
elif stop_order["stop_price"] < stop_order["ref_price"]:
if price <= stop_order["stop_price"]:
is_execute = True
if is_execute:
self.api.place_order(stop_order["contract"], stop_order["pending_order"])
stop_order['executed'] = True
stop_order['ts_executed'] = time.time()
print(f"execute stop order: {stop_order}")
else:
self._stop_orders[code]
def add_stop_order(
self,
contract: sj.contracts.Contract,
stop_price: float,
order: sj.order.Order,
) -> None:
code = contract.code
snap = self.api.snapshots([contract])[0]
# use mid price as current price to avoid illiquidity
ref_price = 0.5 * (snap.buy_price + snap.sell_price)
stop_order = {
"code": contract.code,
"stop_price": stop_price,
"ref_price": ref_price,
"contract": contract,
"pending_order": order,
"ts_create": time.time(),
"executed": False,
"ts_executed": 0.0
}
if code not in self._stop_orders:
self._stop_orders[code] = []
self._stop_orders[code].append(stop_order)
print(f"add stop order: {stop_order}")
def get_stop_orders(self) -> dict:
return self._stop_orders
def cancel_stop_order_by_code(self, code: str) -> None:
if code in self._stop_orders:
_ = self._stop_orders.pop(code)
def cancel_stop_order(self, stop_order: dict) -> None:
code = stop_order["code"]
if code in self._stop_orders:
self._stop_orders[code].remove(stop_order)
if len(self._stop_orders[code]) == 0:
self._stop_orders.pop(code)
def cancel_all_stop_orders(self) -> None:
self._stop_orders.clear()
- We use mid price of snapshots as our reference price to differentiate the direction of stop order.
Basically, order will be pending at your computer. The order won't be submitted to exchange until close/mid price hit the stop price. Below example shows how to submit a stop-limit order.
Set up a stop order
# shioaji order
contract = api.Contracts.Futures.TXF['TXF202301']
order = api.Order(
action='Buy',
price=14800,
quantity=1,
price_type='LMT',
order_type='ROD',
octype=sj.constant.FuturesOCType.Auto,
account=api.futopt_account
)
# Stop Order Excecutor
soe = StopOrderExcecutor(api)
soe.add_stop_order(contract=contract, stop_price=14805, order=order)
Out
add stop order: {
'code': 'TXFA3',
'stop_price': 14805,
'ref_price': 14790,
'contract': Future(
code='TXFA3',
symbol='TXF202301',
name='臺股期貨01',
category='TXF',
delivery_month='202301',
delivery_date='2023/01/30',
underlying_kind='I',
unit=1,
limit_up=16241.0,
limit_down=13289.0,
reference=14765.0,
update_date='2023/01/10'
),
'pending_order': Order(
action=<Action.Buy: 'Buy'>,
price=14800,
quantity=1,
account=FutureAccount(person_id='A123456789', broker_id='F002000', account_id='1234567', signed=True, username='PAIUSER'),
price_type=<StockPriceType.LMT: 'LMT'>,
order_type=<OrderType.ROD: 'ROD'>
),
'ts_create': 1673329115.1056178,
'executed': False,
'ts_executed': 0.0
}
- Stop-Market Order:
price_type = 'MKT'
Finally, we bind StopOrderExcecutor
to quote callback function. Note that you have to subscribe quote, so that stop order will be executed.
Set up context and callback function
from shioaji import TickFOPv1, Exchange
# set up context
api.set_context(soe)
# In order to use context, set bind=True
@api.on_tick_fop_v1(bind=True)
def quote_callback(self, exchange:Exchange, tick:TickFOPv1):
# pass tick object to Stop Order Excecutor
self.on_quote(tick)
# subscribe
api.quote.subscribe(
contract,
quote_type = sj.constant.QuoteType.Tick,
version = sj.constant.QuoteVersion.v1
)
Out: Once close/mid price hit stop price
execute stop order: {
'code': 'TXFA3',
'stop_price': 14805,
'ref_price': 14790,
'contract': Future(
code='TXFA3',
symbol='TXF202301',
name='臺股期貨01',
category='TXF',
delivery_month='202301',
delivery_date='2023/01/30',
underlying_kind='I',
unit=1,
limit_up=16241.0,
limit_down=13289.0,
reference=14765.0,
update_date='2023/01/10'
),
'pending_order': Order(
action=<Action.Buy: 'Buy'>,
price=14800,
quantity=1,
account=FutureAccount(person_id='A123456789', broker_id='F002000', account_id='1234567', signed=True, username='PAIUSER'),
price_type=<StockPriceType.LMT: 'LMT'>,
order_type=<OrderType.ROD: 'ROD'>
),
'ts_create': 1673329115.1056178,
'executed': True,
'ts_executed': 1673329161.3224185
}