ZhuLinsen/daily_stock_analysis · error · FutuPortfolioError

查询 Futu 真实账户失败: {exc}

Error message

查询 Futu 真实账户失败: {exc}

What it means

FutuPortfolioError raised at src/brokers/futu/portfolio.py:215 as the catch-all translation around the whole account-discovery block: any exception that is not already a FutuPortfolioError (SDK runtime errors, socket failures, protobuf decode errors, pandas errors) is re-raised with the '查询 Futu 真实账户失败' prefix and the original exception chained via 'from exc'.

Source

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

                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
    finally:
        _safe_close(context)

    if requested_acc_id is not None:
        accounts = [account for account in accounts if account.acc_id == requested_acc_id]
        if not accounts:
            raise FutuPortfolioError(
                "FUTU_ACC_ID 未匹配到可用的真实证券账户;请检查账户 ID、券商和 OpenD 登录状态。"
            )

    if not accounts:
        raise FutuPortfolioError(
            "未找到状态为 ACTIVE 的 Futu REAL 普通或 MASTER 证券账户"
        )
    return accounts


def _load_position_codes(

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Read the chained original exception (raise ... from exc preserves it) — it names the real failure; fix that root cause first.
  2. Verify OpenD stability: is it up, logged in, and does a minimal standalone get_acc_list() with futu-api==10.8.6808 succeed?
  3. If the failure is transient network drop, retry the portfolio fetch after confirming connectivity.
  4. Check that only one context set is used at a time (the code closes contexts via _safe_close; external long-lived contexts can conflict).

Example fix

# before
accounts = portfolio.discover_accounts(api, host, port)

# after — surface root cause and retry once on transient network errors
import socket
try:
    accounts = portfolio.discover_accounts(api, host, port)
except FutuPortfolioError as exc:
    root = exc.__cause__ or exc
    if isinstance(root, (socket.timeout, ConnectionError)):
        accounts = portfolio.discover_accounts(api, host, port)  # one retry
    else:
        raise
Defensive patterns

Strategy: try-catch

Validate before calling

import socket

def opend_healthy(host: str, port: int) -> bool:
    try:
        with socket.create_connection((host, port), timeout=2.0):
            return True
    except OSError:
        return False

Try / catch

from src.brokers.futu.portfolio import FutuPortfolioError
import socket

try:
    accounts = discover(api, host, port)
except FutuPortfolioError as exc:
    root = exc.__cause__
    if isinstance(root, (socket.timeout, ConnectionError)):
        accounts = discover(api, host, port)  # bounded single retry
    else:
        raise

Prevention

When it happens

Trigger: OpenD connection drops mid-query (socket.timeout, ConnectionResetError); SDK internal errors while opening OpenSecTradeContext; protobuf/pandas failures while processing get_acc_list output; any unexpected exception inside the discovery loop.

Common situations: Flaky network to a remote OpenD; OpenD restarting during the query; resource exhaustion (too many open contexts); SDK bugs triggered by unusual payloads.

Related errors


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