ZhuLinsen/daily_stock_analysis · error · FutuPortfolioError

FUTU_ACC_ID 未匹配到可用的真实证券账户;请检查账户 ID、券商和 OpenD 登录状态。

Error message

FUTU_ACC_ID 未匹配到可用的真实证券账户;请检查账户 ID、券商和 OpenD 登录状态。

What it means

FutuPortfolioError raised in _discover_real_accounts (src/brokers/futu/portfolio.py:222) when FUTU_ACC_ID is configured but, after filtering discovered ACTIVE REAL accounts, none matches the configured ID. Discovery succeeded — the account list simply does not contain that ID (wrong ID, different broker firm, or the account is not in ACTIVE REAL state in OpenD).

Source

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

            returned_firm_name = _enum_text(row.get("security_firm"))
            returned_firm = getattr(
                api.SecurityFirm,
                returned_firm_name,
                security_firm,
            )
            seen_ids.add(acc_id)
            accounts.append(_FutuAccount(acc_id=acc_id, security_firm=returned_firm))
    except FutuPortfolioError:
        raise
    except Exception as exc:  # noqa: BLE001 - translate SDK/network failures
        raise FutuPortfolioError(f"查询 Futu 真实账户失败: {exc}") from exc
    finally:
        _safe_close(context)

    if requested_acc_id is not None:
        accounts = [account for account in accounts if account.acc_id == requested_acc_id]
        if not accounts:
            raise FutuPortfolioError(
                "FUTU_ACC_ID 未匹配到可用的真实证券账户;请检查账户 ID、券商和 OpenD 登录状态。"
            )

    if not accounts:
        raise FutuPortfolioError(
            "未找到状态为 ACTIVE 的 Futu REAL 普通或 MASTER 证券账户"
        )
    return accounts


def _load_position_codes(
    api: _FutuApi,
    host: str,
    port: int,
    accounts: Iterable[_FutuAccount],
) -> List[str]:
    """Load deduplicated non-zero LONG position codes from selected accounts."""

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Unset FUTU_ACC_ID temporarily and let discovery log the available ACTIVE REAL accounts; copy the exact acc_id from that list.
  2. Confirm OpenD is logged into the broker that owns the account and the account status is ACTIVE in OpenD's UI.
  3. If you intended paper trading, note this source only supports REAL environments — SIMULATE accounts are filtered by design.
  4. Fix typos: the ID must be the plain positive integer acc_id.

Example fix

# before (.env)
FUTU_ACC_ID=3865276   # app display number

# after — discover first, then pin
# 1) comment out FUTU_ACC_ID, run once, read logged acc_id values
# 2) FUTU_ACC_ID=<exact acc_id from get_acc_list>
Defensive patterns

Strategy: validation

Validate before calling

def acc_id_exists(acc_id: int, discovered) -> bool:
    return any(a.acc_id == acc_id for a in discovered)

Try / catch

from src.brokers.futu.portfolio import FutuPortfolioError
try:
    accounts = discover(api, host, port, requested_acc_id=acc_id)
except FutuPortfolioError as exc:
    if '未匹配到可用的真实证券账户' in str(exc):
        all_accounts = discover(api, host, port)  # unset filter, inspect ids
        log.error('configured %s; available: %s', acc_id, [a.acc_id for a in all_accounts])
    raise

Prevention

When it happens

Trigger: FUTU_ACC_ID points to a SIMULATE account (filtered out because trd_env != REAL); the ID belongs to a different security firm than the one OpenD logged into; typo in the ID; the account exists but its acc_status is not ACTIVE; paper-trading IDs used against a real-account filter.

Common situations: Users copying the account number from Futu moomoo app (display number) instead of the OpenD acc_id; accounts under a second broker not logged into OpenD; account frozen/pending so status != ACTIVE.

Related errors


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