ZhuLinsen/daily_stock_analysis · error · FutuPortfolioError
Futu 持仓数量无效{suffix}
Error message
Futu 持仓数量无效{suffix} What it means
FutuPortfolioError raised in _load_position_codes (src/brokers/futu/portfolio.py:283) when the qty cell of a position row cannot be converted to float: the value is None/non-numeric (TypeError) or an unparseable string (ValueError). Booleans are explicitly rejected first (bool would otherwise pass float()). The offending code is appended when available.
Source
Thrown at src/brokers/futu/portfolio.py:283
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")
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)View on GitHub (pinned to 5159bd72e8)
Solutions
- Align versions: run futu-api==10.8.6808 against a compatible OpenD build.
- Log the full row (row.to_dict()) when this fires to see the raw qty; if OpenD legitimately returned garbage, restart OpenD and retry.
- In test fixtures, always include a numeric qty per row.
- Retry the query once — transient partial payloads during cache refresh can produce this.
Example fix
# before (test stub)
pd.DataFrame([{'code': 'HK.00700'}]) # qty missing
# after
pd.DataFrame([{'code': 'HK.00700', 'qty': 100, 'position_side': 'LONG'}]) Defensive patterns
Strategy: validation
Validate before calling
def row_qty_is_numeric(row) -> bool:
raw = row.get('qty')
if raw is None or isinstance(raw, bool):
return False
try:
float(raw)
return True
except (TypeError, ValueError):
return False Type guard
def is_numeric_qty(raw: object) -> bool:
if isinstance(raw, bool) or raw is None:
return False
try:
float(raw)
return True
except (TypeError, ValueError):
return False Prevention
- Always provide a numeric qty in position fixtures and mocks.
- Keep futu-api==10.8.6808 paired with a compatible OpenD.
- Log the full offending row when this fires to diagnose payload drift.
When it happens
Trigger: position_list_query returns a row where qty is None/NaN-masked, a string like '--' or 'N/A', or True/False; typically from schema drift between OpenD and the pinned futu-api==10.8.6808, or from test mocks omitting qty.
Common situations: OpenD version mismatch changing the position payload; partially loaded position data during refresh_cache=True; stubbed DataFrames in tests that forget the qty column.
Related errors
- Futu 账户查询返回了无效账户 ID
- {operation}返回了非表格数据
- 查询 Futu 真实持仓失败: {data}
- 选股功能不可用。请检查策略配置、数据依赖和服务日志。
- TickFlow daily K-line response may be truncated by count: sy
AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15).
Data as JSON: /api/errors/5e332e97205b4cfb.
Report an issue: GitHub.