ZhuLinsen/daily_stock_analysis · error · FutuPortfolioError

Futu 账户查询返回了无效账户 ID

Error message

Futu 账户查询返回了无效账户 ID

What it means

FutuPortfolioError raised in _discover_real_accounts (src/brokers/futu/portfolio.py:197) when the acc_id cell of a get_acc_list row cannot be converted to int — int() raises TypeError (None/missing cell), ValueError (non-numeric string like 'N/A'), or OverflowError (float infinity). This indicates the OpenD/SDK response is malformed, since account IDs are always numeric.

Source

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

        )
        ret, data = context.get_acc_list()
        if ret != api.RET_OK:
            raise FutuPortfolioError(f"查询 Futu 真实账户失败: {data}")
        for row in _iter_rows(data, "Futu 账户查询"):
            if _enum_text(row.get("trd_env")) != "REAL":
                continue
            if _enum_text(row.get("acc_status")) != "ACTIVE":
                continue
            if _enum_text(row.get("acc_role")) not in _SUPPORTED_ACCOUNT_ROLES:
                continue
            raw_acc_id = row.get("acc_id")
            try:
                acc_id = int(raw_acc_id)
                exact_integer = isinstance(raw_acc_id, str) or bool(
                    raw_acc_id == acc_id
                )
            except (TypeError, ValueError, OverflowError) as exc:
                raise FutuPortfolioError(
                    "Futu 账户查询返回了无效账户 ID"
                ) from exc
            if isinstance(raw_acc_id, bool) or not exact_integer or acc_id <= 0:
                raise FutuPortfolioError("Futu 账户查询返回了无效账户 ID")
            if acc_id in seen_ids:
                continue
            returned_firm_name = _enum_text(row.get("security_firm"))
            returned_firm = getattr(
                api.SecurityFirm,
                returned_firm_name,
                security_firm,
            )
            seen_ids.add(acc_id)
            accounts.append(_FutuAccount(acc_id=acc_id, security_firm=returned_firm))
    except FutuPortfolioError:
        raise
    except Exception as exc:  # noqa: BLE001 - translate SDK/network failures
        raise FutuPortfolioError(f"查询 Futu 真实账户失败: {exc}") from exc

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Align versions: use futu-api==10.8.6808 with a compatible OpenD build (check Futu's version matrix).
  2. Restart OpenD to clear degraded session state, then retry the account query.
  3. Log the raw row (row.to_dict()) when this fires to see exactly what OpenD returned; report to Futu if the payload is genuinely malformed.
  4. In tests, always include a valid integer acc_id in stubbed rows.

Example fix

# before (test stub missing acc_id)
pd.DataFrame([{'trd_env': 'REAL', 'acc_status': 'ACTIVE'}])

# after
pd.DataFrame([{'acc_id': 1234567, 'trd_env': 'REAL', 'acc_status': 'ACTIVE', 'acc_role': 'NORMAL'}])
Defensive patterns

Strategy: validation

Validate before calling

def row_has_int_acc_id(row) -> bool:
    raw = row.get('acc_id')
    if raw is None or isinstance(raw, bool):
        return False
    try:
        return int(raw) > 0 and (isinstance(raw, str) or int(raw) == raw)
    except (TypeError, ValueError, OverflowError):
        return False

Type guard

def is_valid_acc_id_cell(raw: object) -> bool:
    if isinstance(raw, bool) or raw is None:
        return False
    try:
        v = int(raw)
    except (TypeError, ValueError, OverflowError):
        return False
    return v > 0 and (isinstance(raw, str) or v == raw)

Prevention

When it happens

Trigger: A DataFrame row where acc_id is None or NaN (masked by filtering), a placeholder string, or an SDK serialization glitch after an OpenD version mismatch with the pinned SDK.

Common situations: Running a newer/older OpenD whose payload schema differs from futu-api==10.8.6808 expectations; corrupted OpenD session state; mocks in tests returning rows without acc_id.

Related errors


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