{"record":{"id":"068fb95f18441a5b","repo":"ZhuLinsen/daily_stock_analysis","slug":"futu-exc-068fb9","errorCode":null,"errorMessage":"查询 Futu 持仓证券类型失败: {exc}","messagePattern":"查询 Futu 持仓证券类型失败: (.+?)","errorType":"exception","errorClass":"FutuPortfolioError","httpStatus":null,"severity":"error","filePath":"src/brokers/futu/portfolio.py","lineNumber":420,"sourceCode":"                )\n                if ret != api.RET_OK:\n                    raise FutuPortfolioError(\n                        f\"查询 Futu 持仓证券类型失败（{prefix}）: {data}\"\n                    )\n                for row in _iter_rows(data, \"Futu 证券类型查询\"):\n                    code = str(row.get(\"code\", \"\") or \"\").strip().upper()\n                    if not code:\n                        continue\n                    stock_type = _enum_text(row.get(\"stock_type\"))\n                    if stock_type in _UNKNOWN_SECURITY_TYPES:\n                        continue\n                    classified_codes.add(code)\n                    if stock_type == \"STOCK\":\n                        stock_codes.add(code)\n    except FutuPortfolioError:\n        raise\n    except Exception as exc:  # noqa: BLE001 - translate SDK/network errors for CLI callers\n        raise FutuPortfolioError(f\"查询 Futu 持仓证券类型失败: {exc}\") from exc\n    finally:\n        _safe_close(context)\n\n    missing_codes = [\n        code\n        for codes in grouped.values()\n        for code in codes\n        if code not in classified_codes\n    ]\n    if unsupported_codes:\n        logger.warning(\n            \"已跳过 %d 个当前分析流程不支持的 Futu 持仓: %s\",\n            len(unsupported_codes),\n            \", \".join(unsupported_codes),\n        )\n    if missing_codes:\n        raise FutuPortfolioError(\n            \"无法确认证券类型的 Futu 持仓: \" + \", \".join(missing_codes)","sourceCodeStart":402,"sourceCodeEnd":438,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/brokers/futu/portfolio.py#L402-L438","documentation":"Catch-all translator for the security-type classification step: any non-FutuPortfolioError exception raised while iterating rows (_iter_rows), parsing stock_type enums, or interacting with the futu context is re-raised as FutuPortfolioError with the original message chained. Context is closed in finally via _safe_close.","triggerScenarios":"Exceptions (not RET_ERROR returns) from get_stock_basicinfo or row iteration: connection drops mid-query, unexpected data shapes raising AttributeError/KeyError inside _iter_rows or _enum_text, SDK internal exceptions.","commonSituations":"Network instability between host and FutuOpenD during classification; futu-api version returning a different row container type; OpenD crash mid-request.","solutions":["Read the chained original exception message to find the real cause and fix that (connection, version, permissions).","Ensure futu-api and FutuOpenD versions are matched and up to date.","Retry after FutuOpenD is confirmed healthy; persistent failures mean a schema/contract bug worth reporting with the traceback."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    result = classify_futu_positions(codes)\nexcept FutuPortfolioError as exc:\n    logger.error(\"classification failed, root cause: %r\", exc.__cause__)\n    raise","preventionTips":["Monitor FutuOpenD health before long jobs","Preserve __cause__ in logs to see the original SDK exception","Retry once after gateway recovery; do not loop blindly"],"tags":["futu","broker","network","wrapper-exception"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}