0

I'm trying to use shioaji API to create a Taiwan stock market trading application.

However, I found a strange behavior during the developement.

Here's my code:

import tkinter as tk
import os
import shioaji as sj
from shioaji import BidAskFOPv1, Exchange
from dotenv import load_dotenv

class TouchOrderBuy:
    def __init__(self, api, contract):
        print(f"{contract.symbol} TouchOrder init ...")
        self.api = api
        self.contract = contract
        self.is_triggered = False
        self.api.quote.subscribe(
            contract=self.contract,
            quote_type=sj.constant.QuoteType.BidAsk,
            version=sj.constant.QuoteVersion.v1
        )
        print(f"self.quote_bidask_callback address: {hex(id(self.quote_bidask_callback))}")
        self.api.quote.set_on_bidask_fop_v1_callback(self.quote_bidask_callback)
        
    def quote_bidask_callback(self, exchange: Exchange, bidask: BidAskFOPv1):
        print(f"{bidask.code} quote_bidask_callback")
        print(f"self.is_triggered: {self.is_triggered}")
        print(f"self.is_triggered address: {hex(id(self.is_triggered))}")
        if bidask.code == 'TXO17500C2':
            print(f"set self.is_triggered as True")
            self.is_triggered = True

class TradingApp:
    def __init__(self):
        self.main_window = tk.Tk()
        self.main_window.wm_title('test subscription')

        self.initialize()
        self.crate_gui()
        self.main_window.mainloop()

    def initialize(self):
        self.api = sj.Shioaji(simulation=False)
        login_info = self.api.login(
            person_id=os.getenv('SHIOAJI_USERID'),
            passwd=os.getenv("SHIOAJI_PASSWORD"),
            contracts_timeout=10000,
            contracts_cb=print,
            fetch_contract=True
        )

        print('Login Done')
        self.api.set_default_account(self.api.list_accounts()[2])
        resp = self.api.activate_ca(
            ca_path="Sinopac.pfx",
            ca_passwd=os.getenv('CA_PASSWORD'),
            person_id=os.getenv('SHIOAJI_USERID'),
        )

    def crate_gui(self):
        sub_put_button = tk.Button(self.main_window, text='subs put', command=self.subscribe_put)
        sub_put_button.pack()
        sub_call_button = tk.Button(self.main_window, text='subs call', command=self.subscribe_call)
        sub_call_button.pack()

    def subscribe_call(self):
        t_call = TouchOrderBuy(self.api, self.api.Contracts.Options.TXO.TXO202203017500C)

    def subscribe_put(self):
        t_put = TouchOrderBuy(self.api, self.api.Contracts.Options.TXO.TXO202203017500P)

if __name__ == '__main__':
    load_dotenv()
    app = TradingApp()

As you can see from the above code, I can create 2 TouchOrderBuy objects by first clicking subs put button, then clicking subs call button.

enter image description here

I found that when TouchOrderBuy's self.is_triggered of t_call becomes True, TouchOrderBuy's self.is_triggered of t_put also becomes True.

Why does this happen?

Here's a snippet of the output log:

enter image description here

According to this article, the contents of an instance variable are completely independent from one object instance to the other.

I tried to create a minimum reproducible code without using shioaji, but without success. I feel sorry about that.

Another question I have is why do t_put's and t_call's is_triggered variables refer to the same memory address.

I guess it's because that python has something similar to integer cache for boolean variables.

I also created another version of code to test if self.is_triggered is shared between t_put and t_call:

import tkinter as tk
import os
import shioaji as sj
from shioaji import BidAskFOPv1, Exchange
from dotenv import load_dotenv

class TouchOrderBuy:
    def __init__(self, api, contract):
        print(f"{contract.symbol} TouchOrder init ...")
        self.api = api
        self.contract = contract
        self.is_triggered = False
    
    def get_is_triggered(self):
        print(f"is_triggered address: {hex(id(self.is_triggered))}")
        return self.is_triggered

    def set_is_triggered(self, value):
        self.is_triggered = value

if __name__ == '__main__':
    load_dotenv()
    api = sj.Shioaji(simulation=False)
    login_info = api.login(
        person_id=os.getenv('SHIOAJI_USERID'),
        passwd=os.getenv("SHIOAJI_PASSWORD"),
        contracts_timeout=10000,
        contracts_cb=print,
        fetch_contract=True
    )

    print('Login Done')
    api.set_default_account(api.list_accounts()[2])
    resp = api.activate_ca(
        ca_path="Sinopac.pfx",
        ca_passwd=os.getenv('CA_PASSWORD'),
        person_id=os.getenv('SHIOAJI_USERID'),
    )
    t_put = TouchOrderBuy(api, api.Contracts.Options.TXO.TXO202203017500P)
    t_call = TouchOrderBuy(api, api.Contracts.Options.TXO.TXO202203017500C)
    print(f"put is_triggered: {t_put.get_is_triggered()}")
    print(f"call is_triggered: {t_call.get_is_triggered()}")
    t_call.set_is_triggered(True)
    print(f"put is_triggered: {t_put.get_is_triggered()}")
    print(f"call is_triggered: {t_call.get_is_triggered()}")

In this version, when t_call's self.is_triggered is chagned, t_put's self.is_triggered does not change.

Here's its output:

TXO202203017500P TouchOrder init ...
TXO202203017500C TouchOrder init ...
is_triggered address: 0x7ffb2a9fa970
put is_triggered: False
is_triggered address: 0x7ffb2a9fa970
call is_triggered: False
is_triggered address: 0x7ffb2a9fa970
put is_triggered: False
is_triggered address: 0x7ffb2a9fa950
call is_triggered: True

Why does this version of code not have a weird behavior?

Brian
  • 10,096
  • 18
  • 73
  • 131
  • `True` and `False` are language guaranteed singletons, so not like the integer cache, which is just an implementation detail, that should never be relied on. – juanpa.arrivillaga Feb 25 '22 at 09:46

0 Answers0