170 lines
7.4 KiB
Python
170 lines
7.4 KiB
Python
"""
|
||
市场状态分类引擎
|
||
|
||
职责:
|
||
- 根据量化特征判断当前市场处于哪类 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
|