ZhuLinsen/daily_stock_analysis · error · ValueError

不支持的 portfolio: {portfolio}

Error message

不支持的 portfolio: {portfolio}

What it means

_resolve_portfolio_stock_codes accepts exactly one portfolio value, 'futu'; anything else non-empty raises ValueError. The CLI argparse choices already block invalid values for command-line users — this guard exists for API/programmatic callers that construct an argparse.Namespace or call the function with arbitrary strings.

Source

Thrown at main.py:544

            settings_from_config,
        )

        result = refresh_remote_stock_index_cache(settings_from_config(config))
        if result.refreshed:
            logger.info("[stock-index] 分析前已刷新股票索引缓存: %s", result.cache_path)
        elif result.error:
            logger.debug("[stock-index] 分析前刷新未完成,继续使用本地索引: %s", result.error)
    except Exception as exc:  # noqa: BLE001 - stock index freshness must not block analysis.
        logger.warning("[stock-index] 分析前刷新股票索引失败,继续执行分析: %s", exc)


def _resolve_portfolio_stock_codes(args: argparse.Namespace) -> Optional[List[str]]:
    """Resolve an optional broker portfolio into the analysis stock list."""
    portfolio = str(getattr(args, "portfolio", "") or "").strip().lower()
    if not portfolio:
        return None
    if portfolio != "futu":  # argparse prevents this for CLI callers; keep API callers safe.
        raise ValueError(f"不支持的 portfolio: {portfolio}")

    from src.brokers.futu.portfolio import load_futu_stock_codes

    stock_codes = [
        canonical_stock_code(code)
        for code in load_futu_stock_codes()
        if (code or "").strip()
    ]
    logger.info("portfolio=futu 已覆盖 stocks/STOCK_LIST,使用 %d 只真实正股", len(stock_codes))
    return stock_codes


def _prime_daily_market_context(
    config: Config,
    pipeline: Any,
    *,
    region: str,
    no_market_review: bool,

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Pass portfolio='futu' (case-insensitive) or leave it empty to use stocks/STOCK_LIST config.
  2. If you are adding a new broker, extend this function and the argparse choices together, plus its loader under src/brokers/.
  3. Validate the portfolio enum at the API boundary before it reaches this function, so callers get a 4xx instead of an unhandled ValueError.

Example fix

# before
args.portfolio = 'ibkr'
_resolve_portfolio_stock_codes(args)  # ValueError: 不支持的 portfolio: ibkr

# after
args.portfolio = 'futu'
_resolve_portfolio_stock_codes(args)
Defensive patterns

Strategy: validation

Validate before calling

SUPPORTED_PORTFOLIOS = {"futu"}
portfolio = (getattr(args, "portfolio", "") or "").strip().lower()
if portfolio and portfolio not in SUPPORTED_PORTFOLIOS:
    raise ValueError(f"portfolio must be one of {sorted(SUPPORTED_PORTFOLIOS)}")

Type guard

def is_supported_portfolio(value: str) -> bool:
    return not value or value.strip().lower() in {"futu"}

Try / catch

try:
    codes = _resolve_portfolio_stock_codes(args)
except ValueError as e:
    # map to 4xx at API boundary, or default to stocks config
    codes = None
    logger.warning("portfolio ignored: %s", e)

Prevention

When it happens

Trigger: Calling main's portfolio resolution path with args.portfolio set to anything other than 'futu' or ''/None (e.g. 'ibkr', 'FUTU' before lower() is fine, but 'futu5', 'tiger', or a typo). API layers that build a Namespace from request payloads without validating the enum.

Common situations: A new broker integration added to an API client but not to this resolver; downstream code passing user input straight into the namespace; case variations are handled (.lower()) but whitespace-only is treated as empty by .strip().

Related errors


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