HKUDS/Vibe-Trading · error · FutuConfigError

profile must be 'paper', 'live-readonly' or 'live'

Error message

profile must be 'paper', 'live-readonly' or 'live'

What it means

Raised by FutuConfig.from_mapping when the 'profile' key is not one of 'paper', 'live-readonly', or 'live' (after strip/lower; empty defaults to 'paper').

Source

Thrown at agent/src/trading/connectors/futu/sdk.py:110

    readonly: bool = True

    @classmethod
    def from_mapping(cls, data: Mapping[str, Any] | None = None) -> "FutuConfig":
        """Build a config from a JSON-like mapping, normalizing the profile.

        Args:
            data: Mapping with any subset of config fields.

        Returns:
            A normalized :class:`FutuConfig`.

        Raises:
            FutuConfigError: If the profile is not a recognized value.
        """
        payload = dict(data or {})
        profile = str(payload.get("profile") or "paper").strip().lower()
        if profile not in PROFILE_ENVIRONMENTS:
            raise FutuConfigError("profile must be 'paper', 'live-readonly' or 'live'")
        return cls(
            host=str(payload.get("host") or DEFAULT_HOST).strip(),
            port=int(payload.get("port") or DEFAULT_PORT),
            profile=profile,
            security_firm=str(payload.get("security_firm") or "FUTUSECURITIES").strip().upper(),
            filter_trdmarket=str(payload.get("filter_trdmarket") or "HK").strip().upper(),
            acc_id=int(payload.get("acc_id") or 0),
            timeout=float(payload.get("timeout") or 15.0),
            readonly=bool(payload.get("readonly", True)),
        )

    def with_overrides(
        self,
        *,
        host: str | None = None,
        port: int | None = None,
        profile: str | None = None,
        security_firm: str | None = None,

View on GitHub (pinned to 80ffdda44c)

Solutions

  1. Set profile to exactly 'paper', 'live-readonly', or 'live'
  2. Check for trailing spaces/case issues — values are normalized, so fix genuine typos

Example fix

// before
{"profile": "prod"}
// after
{"profile": "live"}
Defensive patterns

Strategy: validation

Validate before calling

PROFILES = {'paper', 'live-readonly', 'live'}
assert cfg_dict.get('profile', 'paper') in PROFILES

Type guard

def is_valid_futu_profile(p: str | None) -> bool:
    return (p or 'paper').strip().lower() in {'paper','live-readonly','live'}

Prevention

When it happens

Trigger: from_mapping({'profile': 'demo'}) or {'profile': 'LIVE_READ'}; config file with a mistyped profile name.

Common situations: Typos in futu config JSON ('real', 'prod', 'simulation'), stale profile names from older library versions, copy-paste from other brokers' configs.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of HKUDS/Vibe-Trading@80ffdda44c (2026-08-28). Data as JSON: /api/errors/a96155b66412cb76. Report an issue: GitHub.