alphax/app/core/factor_roles.py
2026-05-31 19:00:46 +08:00

105 lines
3.2 KiB
Python

"""Explicit factor roles for multi-strategy attribution.
Factors are evidence. They only become strategy behavior after a strategy
contract assigns them a role in a complete trading playbook.
"""
from __future__ import annotations
from collections.abc import Iterable
PREREQUISITE = "prerequisite"
TRIGGER = "trigger"
CONFIRMATION = "confirmation"
ENTRY = "entry"
RISK = "risk"
ATTRIBUTION = "attribution"
UNKNOWN = "unknown"
VALID_FACTOR_ROLES = {
PREREQUISITE,
TRIGGER,
CONFIRMATION,
ENTRY,
RISK,
ATTRIBUTION,
UNKNOWN,
}
DEFAULT_FACTOR_ROLES: dict[str, str] = {
"box_breakout_pullback_4h": TRIGGER,
"box_breakout_pullback_1h": TRIGGER,
"breakdown_retest_1h_short": TRIGGER,
"retest_reject_15m_short": ENTRY,
"market_risk_off_short": CONFIRMATION,
"vp_fly_1h_current": TRIGGER,
"volume_consecutive_1h": CONFIRMATION,
"volume_divergence_1h": RISK,
"short_tf_15m_ignition": TRIGGER,
"short_tf_5m_ignition": PREREQUISITE,
"short_tf_resonance": CONFIRMATION,
"static_accum_4h": CONFIRMATION,
"higher_lows_4h": CONFIRMATION,
"compression_surge_4h": CONFIRMATION,
"ignition_1h_current": TRIGGER,
"ignition_4h_current": CONFIRMATION,
"ignition_d1_current": CONFIRMATION,
"ignition_stale": ATTRIBUTION,
"dynamic_k_1h_bull": CONFIRMATION,
"dynamic_k_d1_bull": CONFIRMATION,
"breakout_pullback_d1": CONFIRMATION,
"breakout_pullback_w1": CONFIRMATION,
"breakout_15m_current": ENTRY,
"pullback_15m_confirm": ENTRY,
"strong_resonance_bypass": CONFIRMATION,
"entry_quality_gate": RISK,
"top_trader_long": CONFIRMATION,
"sector_rotation": CONFIRMATION,
"sentiment_resonance": CONFIRMATION,
"dex_volume_spike": CONFIRMATION,
"liquidity_add": CONFIRMATION,
"liquidity_remove_risk": RISK,
"exchange_outflow": CONFIRMATION,
"exchange_inflow_risk": RISK,
"whale_accumulation": CONFIRMATION,
"holder_concentration_risk": RISK,
"smart_money_buying": CONFIRMATION,
"funding_extreme": RISK,
"trend_exhaustion": RISK,
"false_breakout": RISK,
"high_position_reject": RISK,
"risk_reward_bad": RISK,
"cex_top_gainer_24h": PREREQUISITE,
"unknown": UNKNOWN,
}
def factor_role(factor_code: str | None, overrides: dict[str, str] | None = None) -> str:
code = str(factor_code or "").strip() or "unknown"
mapping = {**DEFAULT_FACTOR_ROLES, **(overrides or {})}
role = str(mapping.get(code) or UNKNOWN).strip()
return role if role in VALID_FACTOR_ROLES else UNKNOWN
def factor_roles_for_codes(codes: Iterable[str] | None, overrides: dict[str, str] | None = None) -> dict[str, str]:
roles = {}
for code in codes or []:
text = str(code or "").strip()
if text and text not in roles:
roles[text] = factor_role(text, overrides=overrides)
return roles
def validate_factor_roles(roles: dict[str, str] | None) -> dict[str, str]:
"""Normalize role payloads without promoting unknown factors to triggers."""
normalized = {}
for code, role in (roles or {}).items():
key = str(code or "").strip()
value = str(role or "").strip()
if not key:
continue
normalized[key] = value if value in VALID_FACTOR_ROLES else UNKNOWN
return normalized