ZhuLinsen/daily_stock_analysis · error · FutuPortfolioError

不支持的 FUTU_SECURITY_FIRM: {name}

Error message

不支持的 FUTU_SECURITY_FIRM: {name}

What it means

FutuPortfolioError raised in _configured_security_firm (src/brokers/futu/portfolio.py:161) when FUTU_SECURITY_FIRM is set to a name that has no matching attribute on the SDK's SecurityFirm enum. The value is stripped and upper-cased, then looked up via getattr(api.SecurityFirm, name); unknown names (or unset, defaulting to 'NONE') yield None and raise. 'NONE' is the SDK's official auto-detection mode.

Source

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

    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")
    return account_id


def _configured_security_firm(api: _FutuApi) -> Any:
    """Resolve one firm, defaulting to the SDK's official auto-detection mode."""

    name = (os.getenv("FUTU_SECURITY_FIRM") or "NONE").strip().upper()
    firm = getattr(api.SecurityFirm, name, None)
    if firm is None:
        raise FutuPortfolioError(f"不支持的 FUTU_SECURITY_FIRM: {name}")
    return firm


def _discover_real_accounts(api: _FutuApi, host: str, port: int) -> List[_FutuAccount]:
    """Discover explicitly ACTIVE NORMAL or MASTER REAL accounts."""

    accounts: List[_FutuAccount] = []
    seen_ids = set()
    requested_acc_id = _configured_account_id()
    security_firm = _configured_security_firm(api)
    context = None
    try:
        context = api.OpenSecTradeContext(
            host=host,
            port=port,
            filter_trdmarket=api.TrdMarket.NONE,
            security_firm=security_firm,
        )

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. List the valid members for your pinned SDK: python -c "from futu import SecurityFirm; print([m for m in dir(SecurityFirm) if not m.startswith('_')])" and use one of those exact names (upper-case).
  2. Prefer unsetting FUTU_SECURITY_FIRM (or NONE) to use the SDK's auto-detection unless you specifically need to pin a firm.
  3. Re-check the name after any futu-api version change — enum members shift between releases.

Example fix

# before (.env)
FUTU_SECURITY_FIRM=HK

# after (.env)
FUTU_SECURITY_FIRM=FUTUSECURITIES
# or simply unset for auto-detection:
# FUTU_SECURITY_FIRM=NONE
Defensive patterns

Strategy: validation

Validate before calling

def valid_security_firm(name: str | None) -> bool:
    if not name or not name.strip():
        return True  # defaults to NONE (auto-detect)
    from futu import SecurityFirm
    return hasattr(SecurityFirm, name.strip().upper())

Type guard

def is_security_firm_name(name: str, api) -> bool:
    return getattr(api.SecurityFirm, name.strip().upper(), None) is not None

Prevention

When it happens

Trigger: Setting FUTU_SECURITY_FIRM to a firm name that is not an enum member: 'HK' instead of 'FUTUHK'/'HK', a Chinese firm name, a made-up value like 'AUTO', or a value from a different futu-api version whose enum members differ from 10.8.6808.

Common situations: Copying firm names from newer Futu docs that don't exist in the pinned SDK; guessing abbreviations; typos; leaving a template value in place.

Related errors


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