fix: V5.2 scoring uses strategy weights, total capped at 100

Backend:
- Each layer score scaled by strategy config weights
- direction: 0~40, crowding: 0~18, FR: 0~5, environment: 0~12
- confirmation: 0~15, liquidation: 0~5, auxiliary: 0~5
- total_score clamped to 0~100
- factors include max field for frontend

Frontend:
- V5.2 signals page reads max from factors
This commit is contained in:
root 2026-03-02 00:34:22 +00:00
parent 5849bf6522
commit 05673c0850
3 changed files with 62 additions and 22 deletions

View File

@ -556,24 +556,49 @@ class SymbolState:
else:
aux_score = 0
total_score = direction_score + accel_bonus + crowding_score + fr_score + environment_score + confirmation_score + liq_score + aux_score
# 按策略配置的权重缩放各层分数
weights = strategy_cfg.get("weights", {})
w_direction = weights.get("direction", 45)
w_crowding = weights.get("crowding", 20)
w_fr = weights.get("funding_rate", 0)
w_environment = weights.get("environment", 15)
w_confirmation = weights.get("confirmation", 15)
w_liq = weights.get("liquidation", 0)
w_auxiliary = weights.get("auxiliary", 5)
# 原始评分范围: direction 0~45, crowding 0~20, environment 0~15, confirmation 0~15, auxiliary 0~5
# FR 0~5 (or -5~5), Liq 0~5
# 缩放到配置权重
scaled_direction = round(direction_score / 45 * w_direction) + accel_bonus
scaled_crowding = round(crowding_score / 20 * w_crowding)
scaled_fr = fr_score if w_fr > 0 else 0 # FR already 0~5 range, matches w_fr=5
scaled_environment = round(environment_score / 15 * w_environment)
scaled_confirmation = round(confirmation_score / 15 * w_confirmation)
scaled_liq = liq_score if w_liq > 0 else 0 # Liq already 0~5 range, matches w_liq=5
scaled_auxiliary = round(aux_score / 5 * w_auxiliary)
total_score = scaled_direction + scaled_crowding + scaled_fr + scaled_environment + scaled_confirmation + scaled_liq + scaled_auxiliary
total_score = max(0, min(total_score, 100)) # clamp 0~100
result["score"] = total_score
result["direction"] = direction
result["factors"] = {
"direction": {
"score": direction_score,
"score": scaled_direction,
"max": w_direction,
"cvd_fast": 15 if ((direction == "LONG" and cvd_fast > 0) or (direction == "SHORT" and cvd_fast < 0)) else 0,
"cvd_mid": 15 if ((direction == "LONG" and cvd_mid > 0) or (direction == "SHORT" and cvd_mid < 0)) else 0,
"p99_flow": 15 if has_aligned_p99 else (10 if not has_adverse_p99 else 0),
"accel_bonus": accel_bonus,
},
"crowding": {"score": crowding_score, "long_short_ratio": ls_score, "top_trader_position": top_trader_score},
"environment": {"score": environment_score, "open_interest_hist": oi_change},
"confirmation": {"score": confirmation_score},
"auxiliary": {"score": aux_score, "coinbase_premium": coinbase_premium},
"funding_rate": {"score": fr_score, "value": funding_rate},
"crowding": {"score": scaled_crowding, "max": w_crowding, "long_short_ratio": ls_score, "top_trader_position": top_trader_score},
"environment": {"score": scaled_environment, "max": w_environment, "open_interest_hist": oi_change},
"confirmation": {"score": scaled_confirmation, "max": w_confirmation},
"auxiliary": {"score": scaled_auxiliary, "max": w_auxiliary, "coinbase_premium": coinbase_premium},
"funding_rate": {"score": scaled_fr, "max": w_fr, "value": funding_rate},
"liquidation": {
"score": liq_score,
"score": scaled_liq,
"max": w_liq,
"long_usd": liq_data.get("long_usd", 0) if liq_data else 0,
"short_usd": liq_data.get("short_usd", 0) if liq_data else 0,
},

View File

@ -39,13 +39,13 @@ interface LatestIndicator {
signal: string | null;
tier?: "light" | "standard" | "heavy" | null;
factors?: {
direction?: { score?: number };
crowding?: { score?: number };
environment?: { score?: number };
confirmation?: { score?: number };
auxiliary?: { score?: number };
funding_rate?: { score?: number; value?: number };
liquidation?: { score?: number; long_usd?: number; short_usd?: number };
direction?: { score?: number; max?: number };
crowding?: { score?: number; max?: number };
environment?: { score?: number; max?: number };
confirmation?: { score?: number; max?: number };
auxiliary?: { score?: number; max?: number };
funding_rate?: { score?: number; max?: number; value?: number };
liquidation?: { score?: number; max?: number; long_usd?: number; short_usd?: number };
} | null;
}
@ -325,13 +325,13 @@ function IndicatorCards({ symbol }: { symbol: Symbol }) {
</div>
</div>
<div className="mt-2 space-y-1">
<LayerScore label="方向" score={data.factors?.direction?.score ?? Math.min(Math.round(data.score * 0.40), 40)} max={40} colorClass="bg-blue-600" />
<LayerScore label="拥挤" score={data.factors?.crowding?.score ?? Math.min(Math.round(data.score * 0.20), 20)} max={20} colorClass="bg-violet-600" />
<LayerScore label="FR" score={data.factors?.funding_rate?.score ?? 0} max={5} colorClass="bg-cyan-600" />
<LayerScore label="环境" score={data.factors?.environment?.score ?? Math.min(Math.round(data.score * 0.15), 15)} max={15} colorClass="bg-emerald-600" />
<LayerScore label="确认" score={data.factors?.confirmation?.score ?? Math.min(Math.round(data.score * 0.15), 15)} max={15} colorClass="bg-amber-500" />
<LayerScore label="清算" score={data.factors?.liquidation?.score ?? 0} max={5} colorClass="bg-orange-500" />
<LayerScore label="辅助" score={data.factors?.auxiliary?.score ?? Math.min(Math.round(data.score * 0.05), 5)} max={5} colorClass="bg-slate-500" />
<LayerScore label="方向" score={data.factors?.direction?.score ?? 0} max={data.factors?.direction?.max ?? 40} colorClass="bg-blue-600" />
<LayerScore label="拥挤" score={data.factors?.crowding?.score ?? 0} max={data.factors?.crowding?.max ?? 18} colorClass="bg-violet-600" />
<LayerScore label="FR" score={data.factors?.funding_rate?.score ?? 0} max={data.factors?.funding_rate?.max ?? 5} colorClass="bg-cyan-600" />
<LayerScore label="环境" score={data.factors?.environment?.score ?? 0} max={data.factors?.environment?.max ?? 12} colorClass="bg-emerald-600" />
<LayerScore label="确认" score={data.factors?.confirmation?.score ?? 0} max={data.factors?.confirmation?.max ?? 15} colorClass="bg-amber-500" />
<LayerScore label="清算" score={data.factors?.liquidation?.score ?? 0} max={data.factors?.liquidation?.max ?? 5} colorClass="bg-orange-500" />
<LayerScore label="辅助" score={data.factors?.auxiliary?.score ?? 0} max={data.factors?.auxiliary?.max ?? 5} colorClass="bg-slate-500" />
</div>
</div>
</div>

View File

@ -109,3 +109,18 @@
2026-03-02 00:22:37,395 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v52_8signals]: LONG score=77 price=1.4
2026-03-02 00:23:08,756 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v51_baseline]: LONG score=85 price=1943.5
2026-03-02 00:23:08,756 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v52_8signals]: LONG score=90 price=1943.5
2026-03-02 00:25:13,809 [INFO] signal-engine: 已加载策略配置: v51_baseline, v52_8signals
2026-03-02 00:25:17,084 [INFO] signal-engine: [BTCUSDT] 冷启动完成: 加载467,836条历史数据 (窗口=4h)
2026-03-02 00:25:20,192 [INFO] signal-engine: [ETHUSDT] 冷启动完成: 加载457,043条历史数据 (窗口=4h)
2026-03-02 00:25:20,634 [INFO] signal-engine: [XRPUSDT] 冷启动完成: 加载65,387条历史数据 (窗口=4h)
2026-03-02 00:25:21,081 [INFO] signal-engine: [SOLUSDT] 冷启动完成: 加载67,363条历史数据 (窗口=4h)
2026-03-02 00:25:21,081 [INFO] signal-engine: === Signal Engine (PG) 启动完成 ===
2026-03-02 00:25:21,333 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=90 price=65913.4
2026-03-02 00:25:21,334 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=90 price=65913.4
2026-03-02 00:25:21,612 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v51_baseline]: LONG score=90 price=1943.7
2026-03-02 00:25:21,612 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v52_8signals]: LONG score=90 price=1943.7
2026-03-02 00:25:21,693 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: LONG score=87 price=1.4
2026-03-02 00:25:21,693 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v52_8signals]: LONG score=82 price=1.4
2026-03-02 00:25:21,780 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=92 price=83.8
2026-03-02 00:25:21,780 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=87 price=83.8
2026-03-02 00:25:53,073 [INFO] signal-engine: 冷启动保护期结束,模拟盘开仓已启用