From 35dd9d4368fa32240262e51e4fca601d42e34c47 Mon Sep 17 00:00:00 2001 From: finlab-support Date: Mon, 20 Jul 2026 03:26:32 +0200 Subject: [PATCH] fix(sinopac): fetch contracts on demand when Stocks lookup misses On shioaji <1.7, login() uses fetch_contract=False so no contracts are downloaded. _get_contract then hit `return stocks[stock_id]` on an empty Contracts.Stocks, raising KeyError('Contract not found: ') for actively traded stocks (e.g. 8421). The fetch_contracts() fallback was unreachable because the empty Stocks object is not None. Now the KeyError (or None) from the Stocks lookup falls through to fetch_contracts() + retry, so <1.7 users can place orders; genuinely delisted symbols still raise as before. shioaji 1.7 path (api.contracts.get) is unchanged. Co-Authored-By: Claude Opus 4.8 --- brokers/sinopac.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/brokers/sinopac.py b/brokers/sinopac.py index b0b69b5..cfd2755 100644 --- a/brokers/sinopac.py +++ b/brokers/sinopac.py @@ -359,7 +359,16 @@ def _get_contract(self, stock_id: str) -> Any: pass stocks = getattr(getattr(self.api, "Contracts", None), "Stocks", None) if stocks is not None: - return stocks[stock_id] + try: + contract = stocks[stock_id] + except KeyError: + contract = None + if contract is not None: + return contract + # Contracts are not populated yet. This happens on shioaji < 1.7 where + # login() used fetch_contract=False and no contracts were downloaded, so + # the lookup above misses even for actively traded stocks. Fetch on + # demand and retry instead of surfacing a bare "Contract not found". self.api.fetch_contracts(contract_download=sj.constant.SecurityType.Stock) return self.api.Contracts.Stocks[stock_id]