diff --git a/backend/signal_engine.py b/backend/signal_engine.py index a5724fb..e31986f 100644 --- a/backend/signal_engine.py +++ b/backend/signal_engine.py @@ -256,6 +256,31 @@ class ATRCalculator: return (rank / len(sorted_hist)) * 100 +# ─── FR历史最大值缓存(每小时更新)─────────────────────────────── +_max_fr_cache: dict = {} # {symbol: max_abs_fr} +_max_fr_updated: float = 0 + +def get_max_fr(symbol: str) -> float: + """获取该币种历史最大|FR|,每小时刷新一次""" + global _max_fr_cache, _max_fr_updated + now = time.time() + if now - _max_fr_updated > 3600 or symbol not in _max_fr_cache: + try: + with get_sync_conn() as conn: + with conn.cursor() as cur: + cur.execute( + "SELECT symbol, MAX(ABS((value->>'fundingRate')::float)) as max_fr " + "FROM market_indicators WHERE indicator_type='funding_rate' " + "GROUP BY symbol" + ) + for row in cur.fetchall(): + _max_fr_cache[row[0]] = row[1] if row[1] else 0.0001 + _max_fr_updated = now + except Exception as e: + logger.warning(f"get_max_fr error: {e}") + return _max_fr_cache.get(symbol, 0.0001) # 默认0.01%防除零 + + class SymbolState: def __init__(self, symbol: str): self.symbol = symbol @@ -491,21 +516,19 @@ class SymbolState: # Funding Rate scoring (拥挤层加分) # Read from market_indicators table funding_rate = to_float(self.market_indicators.get("funding_rate")) - fr_score = 0 - if "funding_rate" in enabled_signals and funding_rate is not None: + fr_score = 0.0 + if "funding_rate" in enabled_signals and funding_rate is not None and funding_rate != 0: + max_fr = get_max_fr(self.symbol) fr_abs = abs(funding_rate) - if fr_abs >= 0.0001: # extreme ±0.01% — FR明显偏移 - # 做多但FR高(多头拥挤付费)= 危险 - if (direction == "LONG" and funding_rate > 0) or (direction == "SHORT" and funding_rate < 0): - fr_score = -5 - else: - fr_score = 5 # 反向操作,加分 - elif fr_abs >= 0.00003: # moderate ±0.003% — 轻度偏移 - # 做多且FR为负(空头付钱给多头)= 顺势 - if (direction == "LONG" and funding_rate < 0) or (direction == "SHORT" and funding_rate > 0): - fr_score = 5 - else: - fr_score = 0 + # 线性映射: raw = (|当前FR| / 历史最大FR) × 5, clamp到5 + raw_score = min(fr_abs / max_fr * 5.0, 5.0) + # 方向判断: 逆向操作加分,顺向操作减分 + # 做多+FR负(空头付钱)=逆向=好 → 正分 + # 做多+FR正(多头付钱)=顺向=坏 → 负分 + if (direction == "LONG" and funding_rate < 0) or (direction == "SHORT" and funding_rate > 0): + fr_score = round(raw_score, 2) # 逆向操作,加分 + else: + fr_score = round(-raw_score, 2) # 顺向操作,减分 # 4) 确认层(15分) confirmation_score = 15 if ( @@ -578,7 +601,7 @@ class SymbolState: 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 + total_score = max(0, min(round(total_score, 1), 100)) # clamp 0~100, 保留1位小数 result["score"] = total_score result["direction"] = direction @@ -595,7 +618,7 @@ class SymbolState: "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}, + "funding_rate": {"score": round(scaled_fr, 2), "max": w_fr, "value": funding_rate, "max_fr_hist": get_max_fr(self.symbol)}, "liquidation": { "score": scaled_liq, "max": w_liq, diff --git a/signal-engine.log b/signal-engine.log index 772430d..a440b97 100644 --- a/signal-engine.log +++ b/signal-engine.log @@ -139,3 +139,22 @@ 2026-03-02 00:34:32,236 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=84 price=83.8 2026-03-02 00:35:03,725 [INFO] signal-engine: 冷启动保护期结束,模拟盘开仓已启用 2026-03-02 00:38:28,367 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v52_8signals]: LONG score=75 price=1.4 +2026-03-02 00:44:45,649 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=90 price=66018.3 +2026-03-02 00:44:45,649 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=83 price=66018.3 +2026-03-02 00:44:45,939 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v51_baseline]: LONG score=90 price=1947.1 +2026-03-02 00:44:45,940 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v52_8signals]: LONG score=83 price=1947.1 +2026-03-02 00:44:46,036 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: LONG score=82 price=1.4 +2026-03-02 00:44:46,128 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=87 price=84.0 +2026-03-02 00:44:46,129 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=80 price=84.0 +2026-03-02 00:48:42,855 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v52_8signals]: LONG score=75 price=1.4 +2026-03-02 00:50:01,898 [INFO] signal-engine: [BTCUSDT] 状态: CVD_fast=587.6 CVD_mid=5583.3 ATR=230.66 (69%) VWAP=66328.7 +2026-03-02 00:50:01,898 [INFO] signal-engine: [ETHUSDT] 状态: CVD_fast=13374.7 CVD_mid=74135.1 ATR=7.58 (69%) VWAP=1953.2 +2026-03-02 00:50:01,899 [INFO] signal-engine: [XRPUSDT] 状态: CVD_fast=686323.2 CVD_mid=4294786.9 ATR=0.00 (66%) VWAP=1.4 +2026-03-02 00:50:01,899 [INFO] signal-engine: [SOLUSDT] 状态: CVD_fast=159663.5 CVD_mid=524572.1 ATR=0.37 (38%) VWAP=84.4 +2026-03-02 00:55:01,241 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v51_baseline]: LONG score=85 price=66391.1 +2026-03-02 00:55:01,241 [INFO] signal-engine: [BTCUSDT] 🚨 信号[v52_8signals]: LONG score=83 price=66391.1 +2026-03-02 00:55:01,565 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v51_baseline]: LONG score=90 price=1954.9 +2026-03-02 00:55:01,565 [INFO] signal-engine: [ETHUSDT] 🚨 信号[v52_8signals]: LONG score=83 price=1954.9 +2026-03-02 00:55:01,681 [INFO] signal-engine: [XRPUSDT] 🚨 信号[v51_baseline]: LONG score=77 price=1.4 +2026-03-02 00:55:01,797 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v51_baseline]: LONG score=87 price=84.5 +2026-03-02 00:55:01,797 [INFO] signal-engine: [SOLUSDT] 🚨 信号[v52_8signals]: LONG score=75 price=84.5