Quote-Binding Mode
Shioaji provides quote-binding mode which you can store tick/bidask, 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¶
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:
executed_orders = []
for order in self._stop_orders[code]:
if hasattr(quote, 'ask_price'):
price = 0.5 * float(quote.bid_price[0] + quote.ask_price[0]) #BidAsk mid price
else:
price = float(quote.close) #Tick
if (order['direction'] == 'up' and price >= order['stop_price']) or \
(order['direction'] == 'down' and price <= order['stop_price']):
self.api.place_order(order['contract'], order['order'])
executed_orders.append(order)
print(f"execute stop order: {order}")
# remove executed orders
for order in executed_orders:
self._stop_orders[code].remove(order)
if len(self._stop_orders[code]) == 0:
_ = self._stop_orders.pop(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
curr_price = 0.5*(snap.buy_price + snap.sell_price)
if curr_price > stop_price:
direction = 'down'
else:
direction = 'up'
stop_order = {
'code':contract.code,
'stop_price':stop_price,
'contract':contract,
'order':order,
'direction':direction,
'ts':time.time()
}
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 = {}
- We use mid price of snapshots as our benchmark price to differentiate the direction of stop order, so you may encounter some problems when you submit
presubmitted orders
.
Basically, stop 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 with stop price = 8888
.
Set up a stop order
contract = api.Contracts.Futures.TXF['TXF202107']
order = api.Order(
action='Buy',
price=8900,
quantity=1,
price_type='LMT',
order_type='ROD',
octype=sj.constant.FuturesOCType.Auto,
account=api.futopt_account
)
soe = StopOrderExcecutor(api)
soe.add_stop_order(contract=contract, stop_price=8888, order=order)
Out
add stop order: {
'code': 'TXFG1',
'stop_price': 8888,
'contract': Future(code='TXFG1', symbol='TXF202107', name='臺股期貨', category='TXF', delivery_month='202107', underlying_kind='I', unit=1, limit_up=19662.0, limit_down=16088.0, reference=17875.0, update_date='2021/07/07'),
'order': Order(action=<Action.Sell: 'Sell'>, price=8900, quantity=1, account=FutureAccount(person_id='YOUR_PERSON_ID', broker_id='F002000', account_id='9100020', signed=True, username='YOUR_PERSON_ID'), price_type=<StockPriceType.LMT: 'LMT'>, order_type=<FuturesOrderType.ROD: 'ROD'>),
'direction': 'down',
'ts': 1625631227.7142398
}
- Stop-Market Order:
price_type = 'MKT'
Finally, we bind StopOrderExcecutor
to quote callback function. Note that stop order will never be executed if we don't pass quote to StopOrderExcecutor
.
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': 'TXFG1', 'stop_price': 17845, 'contract': Future(code='TXFG1', symbol='TXF202107', name='臺股期貨', category='TXF', delivery_month='202107', underlying_kind='I', unit=1, limit_up=19662.0, limit_down=16088.0, reference=17875.0, update_date='2021/07/07'), 'order': Order(action=<Action.Buy: 'Buy'>, price=8900, quantity=1, account=FutureAccount(person_id='YOUR_PERSON_ID', broker_id='F002000', account_id='9100020', signed=True, username='YOUR_PERSON_ID'), price_type=<StockPriceType.LMT: 'LMT'>, order_type=<FuturesOrderType.ROD: 'ROD'>), 'direction': 'down', 'ts': 1625632027.6016164}