ZhuLinsen/daily_stock_analysis · error · FutuPortfolioError

futu-api==10.8.6808 的网络层仅支持 IPv4;FUTU_OPEND_HOST 当前为 {host!r

Error message

futu-api==10.8.6808 的网络层仅支持 IPv4;FUTU_OPEND_HOST 当前为 {host!r},请改用 IPv4 地址或可解析到 IPv4 的主机名。

What it means

FutuPortfolioError raised in _connection_settings (src/brokers/futu/portfolio.py:133) when FUTU_OPEND_HOST resolves (via ipaddress.ip_address) to an IPv6 literal. The pinned futu-api==10.8.6808 network layer only supports IPv4, so the guard rejects IPv6 addresses up front (including bracketed [::1] form, which is unbracketed before parsing) with a message telling you to use an IPv4 address or an IPv4-resolvable hostname.

Source

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

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()
    if not value:
        return None
    try:
        account_id = int(value)
    except ValueError as exc:
        raise FutuPortfolioError("FUTU_ACC_ID 必须是正整数账户 ID") from exc
    if account_id <= 0:
        raise FutuPortfolioError("FUTU_ACC_ID 必须是正整数账户 ID")

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Set FUTU_OPEND_HOST to an IPv4 literal: 127.0.0.1 for local, or the server's IPv4 address for remote.
  2. If using a hostname, make sure it resolves to an A record (IPv4) — check with 'dig <host> A' or 'ping <host>' — since the SDK will connect over IPv4 only.
  3. If OpenD is only listening on IPv6, reconfigure OpenD to bind an IPv4 address (its own config), or front it with an IPv4 proxy/port-forward (e.g. socat) and point FUTU_OPEND_HOST at that.
  4. Ensure any ssh tunnel/port-forward you use terminates on an IPv4 bind address.

Example fix

# before (.env)
FUTU_OPEND_HOST=::1

# after (.env)
FUTU_OPEND_HOST=127.0.0.1
Defensive patterns

Strategy: validation

Validate before calling

import ipaddress

def opend_host_is_ipv4(host: str) -> bool:
    text = host[1:-1] if host.startswith('[') and host.endswith(']') else host
    try:
        return ipaddress.ip_address(text).version == 4
    except ValueError:
        return True  # hostname: resolution checked at connect time

Prevention

When it happens

Trigger: Setting FUTU_OPEND_HOST=::1 or [::1] to reach a local OpenD; entering an IPv6 LAN address like fe80::1 when OpenD listens on IPv6; systems where localhost preferentially resolves to ::1 — note hostnames are NOT rejected here, only literals, so 'localhost' passes but must resolve to IPv4 at connect time.

Common situations: Modern hosts where 127.0.0.1 is replaced with ::1 out of habit; IPv6-only Docker networks; remote OpenD reached over a tunnel that yields IPv6 addresses.

Related errors


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