ZhuLinsen/daily_stock_analysis · error · FutuPortfolioError
Futu 非零持仓返回了空证券代码
Error message
Futu 非零持仓返回了空证券代码
What it means
Raised when a Futu position row has a non-zero quantity and the code field IS a string, but it is empty (or whitespace-only before later normalization). The loader deliberately fails instead of skipping, because a non-zero position with no code means real money is held in something the analysis cannot identify — silently dropping it would produce a misleading portfolio report.
Source
Thrown at src/brokers/futu/portfolio.py:292
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)
if skipped_short_count:
logger.info("已跳过 %d 个 Futu SHORT 空头持仓", skipped_short_count)View on GitHub (pinned to 5159bd72e8)
Solutions
- Check the account in Futu OpenD console for suspended/delisted positions and close or exclude them.
- Restart FutuOpenD to refresh the position cache and retry the loader.
- Verify futu-api and OpenD versions match; upgrade both.
- If the empty-code position is not actionable, exclude the account via FUTU_ACC_ID or filter the account selection.
Defensive patterns
Strategy: validation
Validate before calling
code = row.get("code")
if not (isinstance(code, str) and code.strip()):
raise ValueError("position row missing code — check Futu OpenD position cache") Type guard
def has_nonempty_code(row: dict) -> bool:
c = row.get("code")
return isinstance(c, str) and c.strip() != "" Try / catch
try:
codes = load_futu_stock_codes()
except FutuPortfolioError as exc:
if "空证券代码" in str(exc):
logger.warning("Empty code in Futu positions — restart FutuOpenD and retry")
raise Prevention
- Restart FutuOpenD after account changes to refresh caches
- Audit accounts for suspended/delisted positions before automation runs
- Do not feed stub rows into the loader
When it happens
Trigger: position_list returns a row where raw_code is a str but falsy ('' — note .strip()/.upper() happen later, so a whitespace-only string would pass this check and fail at the partition check instead). Happens when the futu SDK returns placeholder rows for suspended/delisted symbols or partial API responses.
Common situations: Positions in symbols being delisted or merged; futu API returning empty code for positions pending settlement; stale OpenD cache after account changes.
Related errors
- Futu 非零持仓返回了无效证券代码
- Futu 非零持仓返回了无效证券代码: {code}
- 无法确认证券类型的 Futu 持仓: {codes}
- 无法转换已确认的 Futu 正股代码到当前分析格式: {codes}
- 不支持的 portfolio: {portfolio}
AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15).
Data as JSON: /api/errors/b6b891e0c2a8ccf3.
Report an issue: GitHub.