ZhuLinsen/daily_stock_analysis · error · ValueError

validation_error

validation_error

Error message

unsupported portfolio alert_type: {alert_type}

What it means

ValueError(f'unsupported portfolio alert_type: {alert_type}') with code validation_error is raised by normalize_portfolio_alert_parameters (src/services/portfolio_alerts.py:89) when alert_type is not in PORTFOLIO_ALERT_TYPES = {'portfolio_stop_loss','portfolio_concentration','portfolio_drawdown','portfolio_price_stale'}. It is the gatekeeping check for P6 portfolio alert parameter normalization.

Source

Thrown at src/services/portfolio_alerts.py:84


@dataclass
class StaticAlertEvaluation:
    """Runtime placeholder for skipped/degraded expansion results."""

    stock_code: str
    alert_type: str
    message: str
    record_status: str = "skipped"
    metadata: Dict[str, Any] = field(default_factory=dict)
    description: str = ""


def normalize_portfolio_alert_parameters(alert_type: str, parameters: Dict[str, Any]) -> Dict[str, Any]:
    """Normalize P6 portfolio alert parameters."""

    if alert_type not in PORTFOLIO_ALERT_TYPES:
        raise ValueError(f"unsupported portfolio alert_type: {alert_type}")
    if not isinstance(parameters, dict):
        raise ValueError("parameters must be an object")

    if alert_type == "portfolio_stop_loss":
        mode = str(parameters.get("mode") or "near").strip().lower()
        if mode not in {"near", "breach"}:
            raise ValueError("portfolio_stop_loss mode must be near or breach")
        return {"mode": mode}

    return {}


def portfolio_effective_target(target: str) -> str:
    target_text = str(target or "all").strip() or "all"
    return "account:all" if target_text == "all" else f"account:{target_text}"


def normalize_batch_target_scope_target(target_scope: str, target: str) -> str:

View on GitHub (pinned to 5159bd72e8)

Solutions

  1. Use one of: portfolio_stop_loss, portfolio_concentration, portfolio_drawdown, portfolio_price_stale
  2. Check the PORTFOLIO_ALERT_TYPES frozenset at src/services/portfolio_alerts.py:20 and align client enums
  3. If a market-level alert was intended, use the market alert API with MARKET_ALERT_TYPES instead

Example fix

# before
normalize_portfolio_alert_parameters("stop_loss", {"mode": "near"})

# after
normalize_portfolio_alert_parameters("portfolio_stop_loss", {"mode": "near"})
Defensive patterns

Strategy: type-guard

Validate before calling

from src.services.portfolio_alerts import PORTFOLIO_ALERT_TYPES

if alert_type not in PORTFOLIO_ALERT_TYPES:
    raise ValueError(f"choose from {sorted(PORTFOLIO_ALERT_TYPES)}")

Type guard

from src.services.portfolio_alerts import PORTFOLIO_ALERT_TYPES

def is_valid_portfolio_alert_type(alert_type: str) -> bool:
    return isinstance(alert_type, str) and alert_type in PORTFOLIO_ALERT_TYPES

Try / catch

try:
    params = normalize_portfolio_alert_parameters(alert_type, parameters)
except ValueError as exc:
    if "unsupported portfolio alert_type" in str(exc):
        return api_error(400, str(exc))

Prevention

When it happens

Trigger: Creating/updating a portfolio alert with any other type string — e.g. 'stop_loss' (missing portfolio_ prefix), 'portfolio_pnl', or a market alert type like 'market_light_status' passed to the portfolio API.

Common situations: Renamed alert types across versions, clients abbreviating the prefixed names, or confusion between the market-alert and portfolio-alert type namespaces.

Related errors


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