{"record":{"id":"98f6219c56a20969","repo":"ZhuLinsen/daily_stock_analysis","slug":"futu-codes","errorCode":null,"errorMessage":"无法确认证券类型的 Futu 持仓: {codes}","messagePattern":"无法确认证券类型的 Futu 持仓: (.+?)","errorType":"exception","errorClass":"FutuPortfolioError","httpStatus":null,"severity":"error","filePath":"src/brokers/futu/portfolio.py","lineNumber":437,"sourceCode":"    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)\n        )\n\n    result: List[str] = []\n    conversion_failures: List[str] = []\n    for futu_code in position_codes:\n        if futu_code not in stock_codes:\n            continue\n        analysis_code = _to_analysis_code(futu_code)\n        if not analysis_code:\n            conversion_failures.append(futu_code)\n            continue\n        if analysis_code not in result:\n            result.append(analysis_code)\n    if conversion_failures:\n        raise FutuPortfolioError(\n            \"无法转换已确认的 Futu 正股代码到当前分析格式: \"\n            + \", \".join(conversion_failures)","sourceCodeStart":419,"sourceCodeEnd":455,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/brokers/futu/portfolio.py#L419-L455","documentation":"After classification, every position code must have been seen in a get_stock_basicinfo response row (classified_codes). Codes that were sent for classification but never appeared in any response row — neither classified as STOCK, nor as a known non-stock type, nor skipped as unknown — cause this hard failure. The loader treats 'asked but got no answer' as unsafe: it cannot decide whether the position is a analyzable stock, so it refuses rather than guessing.","triggerScenarios":"get_stock_basicinfo silently omitting codes from its response (futu does this for codes it cannot resolve), so the code is not in classified_codes. Duplicates across batches, casing mismatches after .strip().upper() normalization, or codes the API drops without error also trigger it. The full offending list is embedded in the message.","commonSituations":"Portfolio containing symbols futu cannot classify (new IPOs, halted symbols, market-specific quirks); response row 'code' formatting differing from the request's normalized form.","solutions":["Read the code list in the message; those symbols were silently dropped by futu's basicinfo API.","Verify each symbol in Futu OpenD / moomoo — often they are halted, pre-IPO, or non-standard symbols.","Close or exclude those positions (or that account via FUTU_ACC_ID) if they are not analyzable stocks.","Report to maintainers if the symbols are ordinary liquid stocks — that indicates a normalization bug between request and response codes."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try:\n    analysis_codes = classify_futu_positions(position_codes)\nexcept FutuPortfolioError as exc:\n    if \"无法确认证券类型\" in str(exc):\n        unclassified = str(exc).split(\":\", 1)[1]\n        logger.warning(\"futu silently dropped: %s\", unclassified)\n    raise","preventionTips":["Treat silently-dropped codes as actionable: check halted/pre-IPO symbols in the account","Exclude unclassifiable accounts via FUTU_ACC_ID when they are not analysis targets","Report reproducible drops on liquid symbols as a normalization bug"],"tags":["futu","broker","data-contract","classification"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}