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

  1. Read the embedded SDK error string — it distinguishes permission/connect/throttle causes.
  2. Retry after confirming OpenD is still connected and logged in; transient errors are common with refresh_cache=True.
  3. Ensure the security_firm passed to OpenSecTradeContext matches the account (discovery already maps returned firms — check logs if it fell back).
  4. 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

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


AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15). Data as JSON: /api/errors/ebde8f9cc0e96e8a. Report an issue: GitHub.