138 lines
5.1 KiB
Python
138 lines
5.1 KiB
Python
"""Market regime classification for strategy risk controls."""
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
def _safe_float(value, default: float = 0.0) -> float:
|
|
try:
|
|
if value is None or value == "":
|
|
return default
|
|
return float(value)
|
|
except Exception:
|
|
return default
|
|
|
|
|
|
def _benchmark_change(overview: dict, symbol: str) -> float:
|
|
benchmarks = overview.get("benchmarks") if isinstance(overview, dict) else {}
|
|
item = benchmarks.get(symbol) if isinstance(benchmarks, dict) else {}
|
|
return _safe_float((item or {}).get("change_24h"))
|
|
|
|
|
|
def classify_market_regime(overview: dict | None) -> dict:
|
|
"""Return a plain-language market regime from the latest market snapshot.
|
|
|
|
The first version intentionally uses broad market facts that already exist
|
|
in `market_overview`: BTC/ETH 24h change, alt breadth, hot/crash counts and
|
|
funding heat. It is meant to be a guardrail, not a prediction model.
|
|
"""
|
|
data = overview if isinstance(overview, dict) else {}
|
|
if not data or data.get("snapshot_missing") or _safe_float(data.get("sample_count")) <= 0:
|
|
return {
|
|
"regime": "unknown",
|
|
"label": "数据不足",
|
|
"confidence": 0.2,
|
|
"risk_level": "medium",
|
|
"position_multiplier": 0.75,
|
|
"reasons": ["市场快照不足,保守运行但不直接停止交易"],
|
|
"metrics": {},
|
|
}
|
|
|
|
btc_change = _benchmark_change(data, "BTC/USDT")
|
|
eth_change = _benchmark_change(data, "ETH/USDT")
|
|
adv_dec = _safe_float(data.get("advance_decline_ratio"))
|
|
avg_change = _safe_float(data.get("avg_change_24h"))
|
|
hot_count = int(_safe_float(data.get("hot_count_5pct")))
|
|
crash_count = int(_safe_float(data.get("crash_count_5pct")))
|
|
funding = data.get("funding") if isinstance(data.get("funding"), dict) else {}
|
|
extreme_positive = int(_safe_float(funding.get("extreme_positive_count")))
|
|
avg_funding = _safe_float(funding.get("avg_funding_rate"))
|
|
metrics = {
|
|
"btc_change_24h": btc_change,
|
|
"eth_change_24h": eth_change,
|
|
"advance_decline_ratio": adv_dec,
|
|
"avg_change_24h": avg_change,
|
|
"hot_count_5pct": hot_count,
|
|
"crash_count_5pct": crash_count,
|
|
"extreme_positive_funding_count": extreme_positive,
|
|
"avg_funding_rate": avg_funding,
|
|
}
|
|
|
|
if btc_change <= -3 or eth_change <= -4 or adv_dec < 0.55 or crash_count >= 30:
|
|
return {
|
|
"regime": "risk_off",
|
|
"label": "风险释放期",
|
|
"confidence": 0.88,
|
|
"risk_level": "critical",
|
|
"position_multiplier": 0.25,
|
|
"reasons": ["主流币或山寨广度明显走弱,新开山寨仓位必须显著降仓并提高质量门槛"],
|
|
"metrics": metrics,
|
|
}
|
|
|
|
if crash_count >= 18 or adv_dec < 0.75 or (btc_change <= -1.5 and avg_change < 0):
|
|
return {
|
|
"regime": "risk_off",
|
|
"label": "偏风险释放",
|
|
"confidence": 0.75,
|
|
"risk_level": "high",
|
|
"position_multiplier": 0.35,
|
|
"reasons": ["市场下跌覆盖面较大,只允许特别高质量的机会"],
|
|
"metrics": metrics,
|
|
}
|
|
|
|
if hot_count >= 35 and extreme_positive >= 20 and avg_funding >= 0.00045:
|
|
return {
|
|
"regime": "meme_frenzy",
|
|
"label": "情绪过热期",
|
|
"confidence": 0.72,
|
|
"risk_level": "high",
|
|
"position_multiplier": 0.5,
|
|
"reasons": ["强势币和正 funding 同时过热,追高回撤风险升高"],
|
|
"metrics": metrics,
|
|
}
|
|
|
|
if avg_change >= 0.8 and adv_dec >= 1.2 and hot_count >= 15 and btc_change > -1:
|
|
return {
|
|
"regime": "altcoin_rotation",
|
|
"label": "山寨轮动期",
|
|
"confidence": 0.78,
|
|
"risk_level": "medium",
|
|
"position_multiplier": 1.0,
|
|
"reasons": ["上涨覆盖面和强势币数量支持继续寻找精选机会"],
|
|
"metrics": metrics,
|
|
}
|
|
|
|
if btc_change >= 1.2 and eth_change >= 0.8 and adv_dec >= 0.85:
|
|
return {
|
|
"regime": "btc_main_uptrend",
|
|
"label": "主流带动期",
|
|
"confidence": 0.7,
|
|
"risk_level": "medium",
|
|
"position_multiplier": 0.8,
|
|
"reasons": ["BTC/ETH 提供方向支撑,但山寨仍要精选"],
|
|
"metrics": metrics,
|
|
}
|
|
|
|
if abs(avg_change) <= 0.6 and 0.75 <= adv_dec <= 1.25:
|
|
return {
|
|
"regime": "sideways_chop",
|
|
"label": "横盘震荡期",
|
|
"confidence": 0.65,
|
|
"risk_level": "medium",
|
|
"position_multiplier": 0.65,
|
|
"reasons": ["市场没有单边方向,追突破容易来回挨打,偏向等回踩"],
|
|
"metrics": metrics,
|
|
}
|
|
|
|
return {
|
|
"regime": "unknown",
|
|
"label": "结构性行情",
|
|
"confidence": 0.45,
|
|
"risk_level": "medium",
|
|
"position_multiplier": 0.75,
|
|
"reasons": ["市场环境不够清晰,保持精选和轻仓"],
|
|
"metrics": metrics,
|
|
}
|
|
|
|
|
|
__all__ = ["classify_market_regime"]
|