ZhuLinsen/daily_stock_analysis · error · FutuPortfolioError

Futu 非零持仓返回了无效证券代码

Error message

Futu 非零持仓返回了无效证券代码

What it means

Raised by FutuOpenD portfolio ingestion when a position row has a non-zero parsed quantity but the underlying code field is not a Python string (e.g. None, int, or a futu SDK enum object). The broker adapter treats a non-string code as a contract violation of the position list API (context.position_list) and refuses to silently coerce it, because guessing could analyze the wrong security. It is wrapped into FutuPortfolioError so CLI callers see one typed failure instead of a raw SDK TypeError.

Source

Thrown at src/brokers/futu/portfolio.py:290

                    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")
                    quantity = float(raw_quantity)
                except (TypeError, ValueError) as exc:
                    suffix = f": {code}" if code else ""
                    raise FutuPortfolioError(f"Futu 持仓数量无效{suffix}") from exc
                if not math.isfinite(quantity):
                    suffix = f": {code}" if code else ""
                    raise FutuPortfolioError(f"Futu 持仓数量无效{suffix}")
                if quantity == 0:
                    continue
                if not isinstance(raw_code, str):
                    raise FutuPortfolioError("Futu 非零持仓返回了无效证券代码")
                if not code:
                    raise FutuPortfolioError("Futu 非零持仓返回了空证券代码")
                market, separator, symbol = code.partition(".")
                if not separator or not market or not symbol:
                    raise FutuPortfolioError(
                        f"Futu 非零持仓返回了无效证券代码: {code}"
                    )
                if code in seen_codes:
                    continue
                seen_codes.add(code)
                codes.append(code)
        except FutuPortfolioError:
            raise
        except Exception as exc:  # noqa: BLE001 - translate SDK/network errors for CLI callers
            raise FutuPortfolioError(f"查询 Futu 真实持仓失败: {exc}") from exc
        finally:
            _safe_close(context)

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Upgrade/pin futu-api to a version matching your FutuOpenD gateway so position_list rows always carry string 'code' fields.
  2. Reproduce with a small script calling OpenQuoteContext.position_list(account_id) and inspect the row type for 'code' to confirm the schema drift.
  3. If testing, fix the fixture to use real dict rows with string codes instead of objects/None.
  4. If the account genuinely returns non-string codes, sanitize at the boundary before calling this loader and report the schema change upstream.

Example fix

// before (test fixture / stub row)
row = {"qty": 100, "code": None}

// after
row = {"qty": 100, "code": "HK.00700"}
Defensive patterns

Strategy: validation

Validate before calling

def is_valid_futu_position_row(row) -> bool:
    code = row.get("code")
    return isinstance(code, str) and bool(code)

Type guard

def is_futu_position_row(row: object) -> bool:
    return isinstance(row, dict) and isinstance(row.get("code"), str) and isinstance(row.get("qty"), (int, float)) and not isinstance(row.get("qty"), bool)

Try / catch

try:
    codes = load_futu_stock_codes()
except FutuPortfolioError as exc:
    logger.error("Futu portfolio load failed: %s", exc)
    raise SystemExit(2) from exc

Prevention

When it happens

Trigger: Calling the Futu portfolio loader (load_futu_stock_codes / the REAL account position scan in src/brokers/futu/portfolio.py) when futu API returns a position_list row whose 'code' key is missing (row.get defaults to None) or holds a non-string after qty parsing succeeded and quantity != 0. Typical with futu SDK versions that change the row schema or return qty for positions whose code field is dropped.

Common situations: FutuOpenD/otg gateway version mismatch with the installed futu-api package; mock/stub position fixtures used in tests that omit the code field; a futu account containing derivative or unsupported position types whose rows serialize code differently.

Related errors


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