ZhuLinsen/daily_stock_analysis · error · FutuPortfolioError
查询 Futu 真实持仓失败: {data}
Error message
查询 Futu 真实持仓失败: {data} What it means
FutuPortfolioError raised in _load_position_codes (src/brokers/futu/portfolio.py:261) when position_list_query(trd_env=REAL, acc_id=..., refresh_cache=True) for a selected account returns ret != RET_OK. The SDK's error string is embedded. refresh_cache=True forces a fresh OpenD fetch, so stale-cache masking is deliberately avoided.
Source
Thrown at src/brokers/futu/portfolio.py:261
skipped_short_count = 0
skipped_unknown_side_count = 0
for account in accounts:
context = None
try:
context = api.OpenSecTradeContext(
host=host,
port=port,
filter_trdmarket=api.TrdMarket.NONE,
security_firm=account.security_firm,
)
ret, data = context.position_list_query(
trd_env=api.TrdEnv.REAL,
acc_id=account.acc_id,
refresh_cache=True,
)
if ret != api.RET_OK:
raise FutuPortfolioError(f"查询 Futu 真实持仓失败: {data}")
for row in _iter_rows(data, "Futu 持仓查询"):
position_side = _enum_text(row.get("position_side"))
if position_side == "SHORT":
skipped_short_count += 1
continue
if position_side != "LONG":
skipped_unknown_side_count += 1
continue
raw_code = row.get("code")
code = (
raw_code.strip().upper()
if isinstance(raw_code, str)
else ""
)
raw_quantity = row.get("qty")
try:
if isinstance(raw_quantity, bool):
raise TypeError("boolean quantity")View on GitHub (pinned to 5159bd72e8)
Solutions
- Read the embedded SDK error string — it distinguishes permission/connect/throttle causes.
- Retry after confirming OpenD is still connected and logged in; transient errors are common with refresh_cache=True.
- Ensure the security_firm passed to OpenSecTradeContext matches the account (discovery already maps returned firms — check logs if it fell back).
- Slow down loops over multiple accounts or add small backoff to avoid OpenD rate limits.
Example fix
# before
ret, data = ctx.position_list_query(trd_env=TrdEnv.REAL,
acc_id=acc_id, refresh_cache=True)
# after — honor (ret, data) and retry once on failure
ret, data = ctx.position_list_query(trd_env=TrdEnv.REAL,
acc_id=acc_id, refresh_cache=True)
if ret != RET_OK:
time.sleep(1)
ret, data = ctx.position_list_query(trd_env=TrdEnv.REAL,
acc_id=acc_id, refresh_cache=True)
if ret != RET_OK:
raise RuntimeError(f"position_list_query failed: {data}") Defensive patterns
Strategy: retry
Validate before calling
def can_query_positions(ctx, acc_id) -> bool:
ret, _ = ctx.position_list_query(trd_env=ctx.get_trd_env(), acc_id=acc_id, refresh_cache=False)
return ret == 0 Try / catch
from src.brokers.futu.portfolio import FutuPortfolioError
import time
for attempt in range(2):
try:
codes = load_position_codes(api, host, port, accounts)
break
except FutuPortfolioError as exc:
if '查询 Futu 真实持仓失败' in str(exc) and attempt == 0:
time.sleep(1)
continue
raise Prevention
- Treat embedded SDK error strings as the diagnosis source (permission vs connection vs throttle).
- Add backoff when iterating position queries across many accounts (refresh_cache=True is expensive).
- Keep OpenD sessions stable during batch runs; reconnect before retrying after drops.
When it happens
Trigger: The account loses trading permission for the queried market; OpenD session interrupted between account discovery and position query; the security_firm used to open the context doesn't match the account; market closed with data unavailable; rate limiting from rapid refresh_cache=True calls.
Common situations: Remote OpenD over unstable links; iterating many accounts rapidly and tripping OpenD throttles; firm mismatch after discovery picked a returned_firm that fails for positions; OpenD reconnect mid-run.
Related errors
- 查询 Futu 真实账户失败: {data}
- 查询 Futu 真实账户失败: {exc}
- futu-api==10.8.6808 的网络层仅支持 IPv4;FUTU_OPEND_HOST 当前为 {host!r
- 未找到状态为 ACTIVE 的 Futu REAL 普通或 MASTER 证券账户
- Futu 持仓数量无效{suffix}
AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15).
Data as JSON: /api/errors/ebde8f9cc0e96e8a.
Report an issue: GitHub.