stock-ai-agent/backend/app/crypto_agent/regime_engine.py
2026-04-25 14:53:05 +08:00

170 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
市场状态分类引擎
职责:
- 根据量化特征判断当前市场处于哪类 regime
- 输出当前允许的交易行为集合
- 为 LLM 与执行层提供统一约束
"""
from typing import Any, Dict, List
class RegimeEngine:
"""市场状态分类引擎"""
def classify(
self,
*,
range_metrics: Dict[str, Any] | None,
market_location: Dict[str, Any] | None,
trend_direction: str = "neutral",
trend_strength: str = "weak",
derivatives_state: Dict[str, Any] | None = None,
reversal_detection: Dict[str, Any] | None = None,
trend_stage: Dict[str, Any] | None = None,
) -> Dict[str, Any]:
range_metrics = range_metrics or {}
market_location = market_location or {}
derivatives_state = derivatives_state or {}
reversal_detection = reversal_detection or {}
trend_stage = trend_stage or {}
location_tag = str(market_location.get("location_tag") or "unknown")
relative_to_range = str(market_location.get("relative_to_range") or "unknown")
range_regime = str(range_metrics.get("regime") or "unknown")
crowding_regime = str(derivatives_state.get("crowding_regime") or "low")
crowding_bias = str(derivatives_state.get("crowding_bias") or "neutral")
bb_squeeze = bool(range_metrics.get("bb_squeeze"))
no_trade_reasons: List[str] = []
profile: Dict[str, Any] = {
"regime_key": "neutral",
"market_state_label": "中性市",
"tradability": "avoid",
"risk_mode": "defensive",
"allowed_lanes": [],
"preferred_lanes": [],
"allowed_setups": [],
"preferred_entry_types": [],
"crowding_bias": crowding_bias,
"crowding_regime": crowding_regime,
"no_trade_reasons": no_trade_reasons,
"summary": "",
}
if location_tag in {"middle_of_range", "far_from_trade_zone"}:
no_trade_reasons.append(f"位置不佳: {location_tag}")
if range_regime == "ranging":
profile.update(
{
"regime_key": "range",
"market_state_label": "震荡市",
"tradability": "selective",
"risk_mode": "defensive",
"allowed_lanes": ["short_term"],
"preferred_lanes": ["short_term", "medium_term"],
"allowed_setups": ["range_reversal"],
"preferred_entry_types": ["limit", "market"],
}
)
if relative_to_range not in {"near_range_support", "near_range_resistance"}:
no_trade_reasons.append("震荡市但不在区间边界")
elif range_regime == "transitional":
if bb_squeeze and location_tag not in {"far_from_trade_zone", "middle_of_range"}:
profile.update(
{
"regime_key": "breakout_compression",
"market_state_label": "压缩待突破",
"tradability": "selective",
"risk_mode": "defensive",
"allowed_lanes": ["short_term"],
"preferred_lanes": ["short_term", "medium_term"],
"allowed_setups": ["breakout_confirmation", "breakout_pullback"],
"preferred_entry_types": ["market", "limit"],
}
)
else:
profile.update(
{
"regime_key": "transition",
"market_state_label": "过渡市",
"tradability": "avoid",
"risk_mode": "defensive",
"allowed_lanes": [],
"preferred_lanes": [],
"allowed_setups": [],
"preferred_entry_types": [],
}
)
no_trade_reasons.append("趋势与震荡切换期,方向优势不足")
elif range_regime in {"weak_trend", "strong_trend"} and trend_direction in {"uptrend", "downtrend"}:
allow_reversal = bool(reversal_detection.get("is_reversing")) and trend_strength != "strong"
if crowding_regime == "high":
profile.update(
{
"regime_key": "crowded_trend",
"market_state_label": "拥挤趋势",
"tradability": "selective",
"risk_mode": "defensive",
"allowed_lanes": ["medium_term"],
"preferred_lanes": ["medium_term", "short_term"],
"allowed_setups": ["deep_pullback_continuation"],
"preferred_entry_types": ["limit"],
}
)
if location_tag not in {"near_long_zone", "near_short_zone"}:
no_trade_reasons.append("趋势拥挤,且不在深回踩/反抽交易区")
else:
profile.update(
{
"regime_key": "trend",
"market_state_label": "趋势市",
"tradability": "tradable",
"risk_mode": "normal" if range_regime == "strong_trend" else "reduced",
"allowed_lanes": ["medium_term", "short_term"],
"preferred_lanes": ["medium_term", "short_term"],
"allowed_setups": ["trend_continuation_pullback"],
"preferred_entry_types": ["limit", "market"],
}
)
if allow_reversal:
profile["allowed_setups"].append("trend_reversal")
if location_tag in {"far_from_trade_zone", "middle_of_range"}:
profile["tradability"] = "selective"
no_trade_reasons.append("趋势存在,但当前价格没有位置优势")
if str(trend_stage.get("stage") or "") == "late":
profile["tradability"] = "selective"
profile["risk_mode"] = "defensive"
no_trade_reasons.append("趋势晚期,避免追价")
else:
profile.update(
{
"regime_key": "neutral",
"market_state_label": "中性市",
"tradability": "avoid",
"risk_mode": "defensive",
"allowed_lanes": [],
"preferred_lanes": [],
"allowed_setups": [],
"preferred_entry_types": [],
}
)
no_trade_reasons.append("无清晰市场结构")
if no_trade_reasons and profile["tradability"] == "selective":
profile["summary"] = f"{profile['market_state_label']} | 谨慎交易 | {''.join(no_trade_reasons[:2])}"
elif no_trade_reasons and profile["tradability"] == "avoid":
profile["summary"] = f"{profile['market_state_label']} | 观望优先 | {''.join(no_trade_reasons[:2])}"
else:
profile["summary"] = (
f"{profile['market_state_label']} | "
f"允许: {', '.join(profile['allowed_setups']) if profile['allowed_setups'] else '空仓'}"
)
return profile