109 lines
3.4 KiB
Python
109 lines
3.4 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,
|
|
"momentum_breakout_15m_1h": TRIGGER,
|
|
"compression_breakout_1h_4h": TRIGGER,
|
|
"box_retest_4h": TRIGGER,
|
|
"weak_bounce_failure_15m_1h_short": TRIGGER,
|
|
"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
|