綁訂報價模式
Shioaji 提供綁訂報價模式,可以用來將報價儲存於訊息佇列,將報價推送至Redis Stream,或者實現觸價委託單。我們提供以下範例,讓您可以更了解綁訂報價模式如何運作。
範例¶
綁訂報價至訊息佇列¶
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)
]
}
)
將報價推送至Redis Stream¶
在開始之前,請先安裝redis。
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
},
]
觸價委託單¶
觸價委託單,在市場價格觸及委託單上所設定之價位時,委託單立刻轉為限價單或市價單。
以下僅為範例,請小心使用並自行承擔風險
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()
- 使用snapshots的中價作為參考價格,以區分觸價委託單的方向。
基本上,委託單會在您的電腦上待命,只有在商品價格觸擊所設定價格時,觸價委託單才會被送出,以下範例顯示如何提交限價觸價委託單(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'
最後,我們將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': '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
}