ZhuLinsen/daily_stock_analysis · error · FutuPortfolioError

Futu OpenD 地址无效: {host!r}:{port}

Error message

Futu OpenD 地址无效: {host!r}:{port}

What it means

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.

Source

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

    if context is None:
        return
    try:
        context.close()
    except Exception:  # pragma: no cover - closing is best effort
        logger.debug("关闭 Futu OpenD 连接失败", exc_info=True)


def _connection_settings() -> tuple[str, int]:
    """Return the validated IPv4 OpenD host and port from environment settings."""

    host = (os.getenv("FUTU_OPEND_HOST") or "127.0.0.1").strip()
    raw_port = (os.getenv("FUTU_OPEND_PORT") or "11111").strip()
    try:
        port = int(raw_port)
    except ValueError as exc:
        raise FutuPortfolioError(f"FUTU_OPEND_PORT 不是有效端口: {raw_port!r}") from exc
    if not host or not 1 <= port <= 65535:
        raise FutuPortfolioError(f"Futu OpenD 地址无效: {host!r}:{port}")

    address_text = host[1:-1] if host.startswith("[") and host.endswith("]") else host
    try:
        address = ipaddress.ip_address(address_text)
    except ValueError:
        address = None
    if address is not None and address.version != 4:
        raise FutuPortfolioError(
            "futu-api==10.8.6808 的网络层仅支持 IPv4;"
            f"FUTU_OPEND_HOST 当前为 {host!r},请改用 IPv4 地址或可解析到 IPv4 的主机名。"
        )
    return host, port


def _configured_account_id() -> Optional[int]:
    """Return the optional configured real account ID."""

    value = (os.getenv("FUTU_ACC_ID") or "").strip()

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Use port 11111 (stock OpenD API port) or your OpenD instance's actual listening port within 1-65535.
  2. 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.
  3. Unset both variables if the defaults are correct, which eliminates misconfiguration entirely.

Example fix

# before (.env)
FUTU_OPEND_HOST=
FUTU_OPEND_PORT=70000

# after (.env)
FUTU_OPEND_HOST=127.0.0.1
FUTU_OPEND_PORT=11111
Defensive patterns

Strategy: validation

Validate before calling

def valid_opend_address(host: str | None, raw_port: str | None) -> bool:
    h = (host or '127.0.0.1').strip()
    p = (raw_port or '11111').strip()
    try:
        port = int(p)
    except ValueError:
        return False
    return bool(h) and 1 <= port <= 65535

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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