{"record":{"id":"89b3257568efc5b1","repo":"ZhuLinsen/daily_stock_analysis","slug":"futu-opend-host-r-port","errorCode":null,"errorMessage":"Futu OpenD 地址无效: {host!r}:{port}","messagePattern":"Futu OpenD 地址无效: (.+?):(.+?)","errorType":"exception","errorClass":"FutuPortfolioError","httpStatus":null,"severity":"error","filePath":"src/brokers/futu/portfolio.py","lineNumber":125,"sourceCode":"    if context is None:\n        return\n    try:\n        context.close()\n    except Exception:  # pragma: no cover - closing is best effort\n        logger.debug(\"关闭 Futu OpenD 连接失败\", exc_info=True)\n\n\ndef _connection_settings() -> tuple[str, int]:\n    \"\"\"Return the validated IPv4 OpenD host and port from environment settings.\"\"\"\n\n    host = (os.getenv(\"FUTU_OPEND_HOST\") or \"127.0.0.1\").strip()\n    raw_port = (os.getenv(\"FUTU_OPEND_PORT\") or \"11111\").strip()\n    try:\n        port = int(raw_port)\n    except ValueError as exc:\n        raise FutuPortfolioError(f\"FUTU_OPEND_PORT 不是有效端口: {raw_port!r}\") from exc\n    if not host or not 1 <= port <= 65535:\n        raise FutuPortfolioError(f\"Futu OpenD 地址无效: {host!r}:{port}\")\n\n    address_text = host[1:-1] if host.startswith(\"[\") and host.endswith(\"]\") else host\n    try:\n        address = ipaddress.ip_address(address_text)\n    except ValueError:\n        address = None\n    if address is not None and address.version != 4:\n        raise FutuPortfolioError(\n            \"futu-api==10.8.6808 的网络层仅支持 IPv4；\"\n            f\"FUTU_OPEND_HOST 当前为 {host!r}，请改用 IPv4 地址或可解析到 IPv4 的主机名。\"\n        )\n    return host, port\n\n\ndef _configured_account_id() -> Optional[int]:\n    \"\"\"Return the optional configured real account ID.\"\"\"\n\n    value = (os.getenv(\"FUTU_ACC_ID\") or \"\").strip()","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/ZhuLinsen/daily_stock_analysis/blob/5159bd72e8373d215492dff122acc9d389e219c9/src/brokers/futu/portfolio.py#L107-L143","documentation":"FutuPortfolioError raised in _connection_settings (src/brokers/futu/portfolio.py:125) when the OpenD host is empty after stripping, or the parsed port is outside 1..65535. Host defaults to 127.0.0.1 and port to 11111, so this only fires when explicitly configured values are out of range — e.g. port 0, 70000, or FUTU_OPEND_HOST set to only whitespace.","triggerScenarios":"FUTU_OPEND_PORT=0 or >65535 (e.g. accidentally using an OpenD PID or a documentation port like 99999); FUTU_OPEND_HOST=' ' (spaces only) which strips to empty; swapping host and port values in configuration.","commonSituations":"Copy-paste from OpenD docs listing WebSocket ports above 65535; template variables that expand to empty; confusing OpenD's API port with its GUI/protocol ports.","solutions":["Use port 11111 (stock OpenD API port) or your OpenD instance's actual listening port within 1-65535.","Set FUTU_OPEND_HOST to the machine running OpenD (127.0.0.1 for local; an IPv4 LAN address for remote) and ensure it is non-empty.","Unset both variables if the defaults are correct, which eliminates misconfiguration entirely."],"exampleFix":"# before (.env)\nFUTU_OPEND_HOST=\nFUTU_OPEND_PORT=70000\n\n# after (.env)\nFUTU_OPEND_HOST=127.0.0.1\nFUTU_OPEND_PORT=11111","handlingStrategy":"validation","validationCode":"def valid_opend_address(host: str | None, raw_port: str | None) -> bool:\n    h = (host or '127.0.0.1').strip()\n    p = (raw_port or '11111').strip()\n    try:\n        port = int(p)\n    except ValueError:\n        return False\n    return bool(h) and 1 <= port <= 65535","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use the OpenD default pair 127.0.0.1:11111 unless you know otherwise.","Never put host and port in the same variable.","Fail fast on config parse at startup rather than deep inside broker calls."],"tags":["configuration","environment","futu","validation"],"backgroundTag":null,"analyzedSha":"5159bd72e8373d215492dff122acc9d389e219c9","analyzedAt":"2026-08-15T01:59:36.292Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}