{"record":{"id":"3af92cfc8f9dafda","repo":"ZhuLinsen/daily_stock_analysis","slug":"futu-exc","errorCode":null,"errorMessage":"查询 Futu 真实账户失败: {exc}","messagePattern":"查询 Futu 真实账户失败: (.+?)","errorType":"exception","errorClass":"FutuPortfolioError","httpStatus":null,"severity":"error","filePath":"src/brokers/futu/portfolio.py","lineNumber":215,"sourceCode":"                raise FutuPortfolioError(\n                    \"Futu 账户查询返回了无效账户 ID\"\n                ) from exc\n            if isinstance(raw_acc_id, bool) or not exact_integer or acc_id <= 0:\n                raise FutuPortfolioError(\"Futu 账户查询返回了无效账户 ID\")\n            if acc_id in seen_ids:\n                continue\n            returned_firm_name = _enum_text(row.get(\"security_firm\"))\n            returned_firm = getattr(\n                api.SecurityFirm,\n                returned_firm_name,\n                security_firm,\n            )\n            seen_ids.add(acc_id)\n            accounts.append(_FutuAccount(acc_id=acc_id, security_firm=returned_firm))\n    except FutuPortfolioError:\n        raise\n    except Exception as exc:  # noqa: BLE001 - translate SDK/network failures\n        raise FutuPortfolioError(f\"查询 Futu 真实账户失败: {exc}\") from exc\n    finally:\n        _safe_close(context)\n\n    if requested_acc_id is not None:\n        accounts = [account for account in accounts if account.acc_id == requested_acc_id]\n        if not accounts:\n            raise FutuPortfolioError(\n                \"FUTU_ACC_ID 未匹配到可用的真实证券账户；请检查账户 ID、券商和 OpenD 登录状态。\"\n            )\n\n    if not accounts:\n        raise FutuPortfolioError(\n            \"未找到状态为 ACTIVE 的 Futu REAL 普通或 MASTER 证券账户\"\n        )\n    return accounts\n\n\ndef _load_position_codes(","sourceCodeStart":197,"sourceCodeEnd":233,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/brokers/futu/portfolio.py#L197-L233","documentation":"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'.","triggerScenarios":"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.","commonSituations":"Flaky network to a remote OpenD; OpenD restarting during the query; resource exhaustion (too many open contexts); SDK bugs triggered by unusual payloads.","solutions":["Read the chained original exception (raise ... from exc preserves it) — it names the real failure; fix that root cause first.","Verify OpenD stability: is it up, logged in, and does a minimal standalone get_acc_list() with futu-api==10.8.6808 succeed?","If the failure is transient network drop, retry the portfolio fetch after confirming connectivity.","Check that only one context set is used at a time (the code closes contexts via _safe_close; external long-lived contexts can conflict)."],"exampleFix":"# before\naccounts = portfolio.discover_accounts(api, host, port)\n\n# after — surface root cause and retry once on transient network errors\nimport socket\ntry:\n    accounts = portfolio.discover_accounts(api, host, port)\nexcept FutuPortfolioError as exc:\n    root = exc.__cause__ or exc\n    if isinstance(root, (socket.timeout, ConnectionError)):\n        accounts = portfolio.discover_accounts(api, host, port)  # one retry\n    else:\n        raise","handlingStrategy":"try-catch","validationCode":"import socket\n\ndef opend_healthy(host: str, port: int) -> bool:\n    try:\n        with socket.create_connection((host, port), timeout=2.0):\n            return True\n    except OSError:\n        return False","typeGuard":null,"tryCatchPattern":"from src.brokers.futu.portfolio import FutuPortfolioError\nimport socket\n\ntry:\n    accounts = discover(api, host, port)\nexcept FutuPortfolioError as exc:\n    root = exc.__cause__\n    if isinstance(root, (socket.timeout, ConnectionError)):\n        accounts = discover(api, host, port)  # bounded single retry\n    else:\n        raise","preventionTips":["Inspect exc.__cause__: the wrapper preserves the real SDK/network exception.","Probe OpenD connectivity before long broker workflows.","Avoid running multiple concurrent OpenSecTradeContext sessions against one OpenD."],"tags":["futu","network","error-translation","opend"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}