Skip to content

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 = {}
  • This is just an example of stop order, please use at your own risk.
  • 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='PAPIUSER01', broker_id='F002000', account_id='9100020', signed=True, username='PAPIUSER01'), 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='PAPIUSER01', broker_id='F002000', account_id='9100020', signed=True, username='PAPIUSER01'), price_type=<StockPriceType.LMT: 'LMT'>, order_type=<FuturesOrderType.ROD: 'ROD'>), 'direction': 'down', 'ts': 1625632027.6016164}