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
- 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).
- Prefer unsetting FUTU_SECURITY_FIRM (or NONE) to use the SDK's auto-detection unless you specifically need to pin a firm.
- 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
- Enumerate SecurityFirm members for the pinned SDK before setting the env var.
- Default to unset/NONE (auto-detection) unless pinning a firm is required.
- Re-validate firm names after any futu-api version change.
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
- FUTU_OPEND_PORT 不是有效端口: {raw_port!r}
- Futu OpenD 地址无效: {host!r}:{port}
- FUTU_ACC_ID 必须是正整数账户 ID
- Desktop backend origin is invalid
- Desktop window is unavailable
AI-assisted analysis of ZhuLinsen/daily_stock_analysis@5159bd72e8 (2026-08-15).
Data as JSON: /api/errors/4340730b7cd07bb7.
Report an issue: GitHub.