From 2e4c05b2e0ba450a108e74069937075fa4b2b0db Mon Sep 17 00:00:00 2001 From: root Date: Wed, 11 Mar 2026 16:26:44 +0000 Subject: [PATCH] =?UTF-8?q?feat(v54b):=205=E9=97=A8Gate=E9=87=8D=E6=9E=84?= =?UTF-8?q?=20=E2=80=94=20=E4=B8=8Esignal=5Fengine=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E5=AE=8C=E5=85=A8=E5=AF=B9=E5=BA=94=EF=BC=8C?= =?UTF-8?q?DB=E5=AD=97=E6=AE=B5=E6=94=B9=E5=90=8D=EF=BC=8C=E5=89=8D?= =?UTF-8?q?=E5=90=8E=E7=AB=AF=E7=BB=9F=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/main.py | 112 ++++++++++++------- backend/migrate_v54b_gates.py | 155 +++++++++++++++++++++++++++ backend/signal_engine.py | 89 ++++++++++----- frontend/components/StrategyForm.tsx | 89 ++++++++------- 4 files changed, 344 insertions(+), 101 deletions(-) create mode 100644 backend/migrate_v54b_gates.py diff --git a/backend/main.py b/backend/main.py index 4673289..2e08f91 100644 --- a/backend/main.py +++ b/backend/main.py @@ -2202,14 +2202,22 @@ class StrategyCreateRequest(BaseModel): weight_aux: int = 15 weight_momentum: int = 5 entry_score: int = 75 - gate_obi_enabled: bool = True - obi_threshold: float = 0.3 - gate_whale_enabled: bool = True - whale_cvd_threshold: float = 0.0 + # 门1 波动率 gate_vol_enabled: bool = True - atr_percentile_min: int = 20 + vol_atr_pct_min: float = 0.002 + # 门2 CVD共振 + gate_cvd_enabled: bool = True + # 门3 鲸鱼否决 + gate_whale_enabled: bool = True + whale_usd_threshold: float = 50000.0 + whale_flow_pct: float = 0.5 + # 门4 OBI否决 + gate_obi_enabled: bool = True + obi_threshold: float = 0.35 + # 门5 期现背离 gate_spot_perp_enabled: bool = False - spot_perp_threshold: float = 0.002 + spot_perp_threshold: float = 0.005 + # 风控参数 sl_atr_multiplier: float = 1.5 tp1_ratio: float = 0.75 tp2_ratio: float = 1.5 @@ -2288,6 +2296,27 @@ class StrategyCreateRequest(BaseModel): raise ValueError("entry_score must be 60-95") return v + @field_validator("vol_atr_pct_min") + @classmethod + def validate_vol_atr(cls, v): + if not 0.0001 <= v <= 0.02: + raise ValueError("vol_atr_pct_min must be 0.0001-0.02") + return v + + @field_validator("whale_usd_threshold") + @classmethod + def validate_whale_usd(cls, v): + if not 1000 <= v <= 1000000: + raise ValueError("whale_usd_threshold must be 1000-1000000") + return v + + @field_validator("whale_flow_pct") + @classmethod + def validate_whale_flow(cls, v): + if not 0.0 <= v <= 1.0: + raise ValueError("whale_flow_pct must be 0.0-1.0") + return v + @field_validator("obi_threshold") @classmethod def validate_obi(cls, v): @@ -2295,20 +2324,6 @@ class StrategyCreateRequest(BaseModel): raise ValueError("obi_threshold must be 0.1-0.9") return v - @field_validator("whale_cvd_threshold") - @classmethod - def validate_whale(cls, v): - if not -1.0 <= v <= 1.0: - raise ValueError("whale_cvd_threshold must be -1.0 to 1.0") - return v - - @field_validator("atr_percentile_min") - @classmethod - def validate_atr_pct(cls, v): - if not 5 <= v <= 80: - raise ValueError("atr_percentile_min must be 5-80") - return v - @field_validator("spot_perp_threshold") @classmethod def validate_spot_perp(cls, v): @@ -2370,14 +2385,22 @@ class StrategyUpdateRequest(BaseModel): weight_aux: Optional[int] = None weight_momentum: Optional[int] = None entry_score: Optional[int] = None + # 门1 波动率 + gate_vol_enabled: Optional[bool] = None + vol_atr_pct_min: Optional[float] = None + # 门2 CVD共振 + gate_cvd_enabled: Optional[bool] = None + # 门3 鲸鱼否决 + gate_whale_enabled: Optional[bool] = None + whale_usd_threshold: Optional[float] = None + whale_flow_pct: Optional[float] = None + # 门4 OBI否决 gate_obi_enabled: Optional[bool] = None obi_threshold: Optional[float] = None - gate_whale_enabled: Optional[bool] = None - whale_cvd_threshold: Optional[float] = None - gate_vol_enabled: Optional[bool] = None - atr_percentile_min: Optional[int] = None + # 门5 期现背离 gate_spot_perp_enabled: Optional[bool] = None spot_perp_threshold: Optional[float] = None + # 风控 sl_atr_multiplier: Optional[float] = None tp1_ratio: Optional[float] = None tp2_ratio: Optional[float] = None @@ -2435,12 +2458,19 @@ def _strategy_row_to_detail(row: dict) -> dict: "weight_aux": row["weight_aux"], "weight_momentum": row["weight_momentum"], "entry_score": row["entry_score"], + # 门1 波动率 + "gate_vol_enabled": row["gate_vol_enabled"], + "vol_atr_pct_min": row["vol_atr_pct_min"], + # 门2 CVD共振 + "gate_cvd_enabled": row["gate_cvd_enabled"], + # 门3 鲸鱼否决 + "gate_whale_enabled": row["gate_whale_enabled"], + "whale_usd_threshold": row["whale_usd_threshold"], + "whale_flow_pct": row["whale_flow_pct"], + # 门4 OBI否决 "gate_obi_enabled": row["gate_obi_enabled"], "obi_threshold": row["obi_threshold"], - "gate_whale_enabled": row["gate_whale_enabled"], - "whale_cvd_threshold": row["whale_cvd_threshold"], - "gate_vol_enabled": row["gate_vol_enabled"], - "atr_percentile_min": row["atr_percentile_min"], + # 门5 期现背离 "gate_spot_perp_enabled": row["gate_spot_perp_enabled"], "spot_perp_threshold": row["spot_perp_threshold"], "sl_atr_multiplier": row["sl_atr_multiplier"], @@ -2547,9 +2577,10 @@ async def create_strategy(body: StrategyCreateRequest, user: dict = Depends(get_ cvd_fast_window, cvd_slow_window, weight_direction, weight_env, weight_aux, weight_momentum, entry_score, + gate_vol_enabled, vol_atr_pct_min, + gate_cvd_enabled, + gate_whale_enabled, whale_usd_threshold, whale_flow_pct, gate_obi_enabled, obi_threshold, - gate_whale_enabled, whale_cvd_threshold, - gate_vol_enabled, atr_percentile_min, gate_spot_perp_enabled, spot_perp_threshold, sl_atr_multiplier, tp1_ratio, tp2_ratio, timeout_minutes, flip_threshold, @@ -2560,17 +2591,23 @@ async def create_strategy(body: StrategyCreateRequest, user: dict = Depends(get_ $3,$4,$5,$6, $7,$8,$9,$10, $11, - $12,$13,$14,$15,$16,$17,$18,$19, - $20,$21,$22,$23,$24, - $25,$25,$26 + $12,$13, + $14, + $15,$16,$17, + $18,$19, + $20,$21, + $22,$23,$24, + $25,$26, + $27,$27,$28 )""", new_id, body.display_name, body.symbol, body.direction, body.cvd_fast_window, body.cvd_slow_window, body.weight_direction, body.weight_env, body.weight_aux, body.weight_momentum, body.entry_score, + body.gate_vol_enabled, body.vol_atr_pct_min, + body.gate_cvd_enabled, + body.gate_whale_enabled, body.whale_usd_threshold, body.whale_flow_pct, body.gate_obi_enabled, body.obi_threshold, - body.gate_whale_enabled, body.whale_cvd_threshold, - body.gate_vol_enabled, body.atr_percentile_min, body.gate_spot_perp_enabled, body.spot_perp_threshold, body.sl_atr_multiplier, body.tp1_ratio, body.tp2_ratio, body.timeout_minutes, body.flip_threshold, @@ -2647,8 +2684,9 @@ async def update_strategy(sid: str, body: StrategyUpdateRequest, user: dict = De "weight_momentum": lambda v: 0 <= v <= 20, "entry_score": lambda v: 60 <= v <= 95, "obi_threshold": lambda v: 0.1 <= v <= 0.9, - "whale_cvd_threshold": lambda v: -1.0 <= v <= 1.0, - "atr_percentile_min": lambda v: 5 <= v <= 80, + "vol_atr_pct_min": lambda v: 0.0001 <= v <= 0.02, + "whale_usd_threshold": lambda v: 1000 <= v <= 1000000, + "whale_flow_pct": lambda v: 0.0 <= v <= 1.0, "spot_perp_threshold": lambda v: 0.0005 <= v <= 0.01, "sl_atr_multiplier": lambda v: 0.5 <= v <= 3.0, "tp1_ratio": lambda v: 0.3 <= v <= 2.0, diff --git a/backend/migrate_v54b_gates.py b/backend/migrate_v54b_gates.py new file mode 100644 index 0000000..b7a4ea1 --- /dev/null +++ b/backend/migrate_v54b_gates.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +""" +V5.4b Gate Schema Migration +将 strategies 表的 gate 字段从4门改为5门,与 signal_engine.py 实际执行逻辑完全对应。 + +变更: +1. 重命名 atr_percentile_min → vol_atr_pct_min(ATR%价格阈值,如0.002=0.2%) +2. 重命名 whale_cvd_threshold → whale_flow_pct(鲸鱼CVD流量阈值,BTC专用,0-1) +3. 新增 gate_cvd_enabled BOOLEAN DEFAULT TRUE(门2 CVD共振开关) +4. 新增 whale_usd_threshold FLOAT DEFAULT 50000(门3 大单USD金额阈值) +5. 用 v53.json 里的 per-symbol 默认值回填旧三条策略 + +五门对应: + 门1 波动率:gate_vol_enabled + vol_atr_pct_min + 门2 CVD共振:gate_cvd_enabled(无参数,判断快慢CVD同向) + 门3 鲸鱼否决:gate_whale_enabled + whale_usd_threshold(ALT大单USD)+ whale_flow_pct(BTC CVD流量) + 门4 OBI否决:gate_obi_enabled + obi_threshold + 门5 期现背离:gate_spot_perp_enabled + spot_perp_threshold +""" + +import os, sys +import psycopg2 +from psycopg2.extras import RealDictCursor + +PG_HOST = os.getenv("PG_HOST", "10.106.0.3") +PG_USER = os.getenv("PG_USER", "arb") +PG_PASS = os.getenv("PG_PASS", "arb_engine_2026") +PG_DB = os.getenv("PG_DB", "arb_engine") + +# Per-symbol 默认值(来自 v53.json symbol_gates) +SYMBOL_DEFAULTS = { + "BTCUSDT": {"vol_atr_pct_min": 0.002, "whale_usd_threshold": 100000, "whale_flow_pct": 0.5, "obi_threshold": 0.30, "spot_perp_threshold": 0.003}, + "ETHUSDT": {"vol_atr_pct_min": 0.003, "whale_usd_threshold": 50000, "whale_flow_pct": 0.5, "obi_threshold": 0.35, "spot_perp_threshold": 0.005}, + "SOLUSDT": {"vol_atr_pct_min": 0.004, "whale_usd_threshold": 20000, "whale_flow_pct": 0.5, "obi_threshold": 0.45, "spot_perp_threshold": 0.008}, + "XRPUSDT": {"vol_atr_pct_min": 0.0025,"whale_usd_threshold": 30000, "whale_flow_pct": 0.5, "obi_threshold": 0.40, "spot_perp_threshold": 0.006}, + None: {"vol_atr_pct_min": 0.002, "whale_usd_threshold": 50000, "whale_flow_pct": 0.5, "obi_threshold": 0.35, "spot_perp_threshold": 0.005}, +} + +DRY_RUN = "--dry-run" in sys.argv + +def get_conn(): + return psycopg2.connect( + host=PG_HOST, port=5432, + user=PG_USER, password=PG_PASS, dbname=PG_DB + ) + +def run(): + conn = get_conn() + cur = conn.cursor(cursor_factory=RealDictCursor) + + print("=== V5.4b Gate Schema Migration ===") + print(f"DRY_RUN={DRY_RUN}") + print() + + # Step 1: 检查字段是否已迁移 + cur.execute(""" + SELECT column_name FROM information_schema.columns + WHERE table_name='strategies' AND column_name IN + ('vol_atr_pct_min','whale_flow_pct','gate_cvd_enabled','whale_usd_threshold', + 'atr_percentile_min','whale_cvd_threshold') + """) + existing = {r["column_name"] for r in cur.fetchall()} + print(f"现有相关字段: {existing}") + + sqls = [] + + # Step 2: 改名 atr_percentile_min → vol_atr_pct_min + if "atr_percentile_min" in existing and "vol_atr_pct_min" not in existing: + sqls.append("ALTER TABLE strategies RENAME COLUMN atr_percentile_min TO vol_atr_pct_min") + print("✅ RENAME atr_percentile_min → vol_atr_pct_min") + elif "vol_atr_pct_min" in existing: + print("⏭️ vol_atr_pct_min 已存在,跳过改名") + + # Step 3: 改名 whale_cvd_threshold → whale_flow_pct + if "whale_cvd_threshold" in existing and "whale_flow_pct" not in existing: + sqls.append("ALTER TABLE strategies RENAME COLUMN whale_cvd_threshold TO whale_flow_pct") + print("✅ RENAME whale_cvd_threshold → whale_flow_pct") + elif "whale_flow_pct" in existing: + print("⏭️ whale_flow_pct 已存在,跳过改名") + + # Step 4: 新增 gate_cvd_enabled + if "gate_cvd_enabled" not in existing: + sqls.append("ALTER TABLE strategies ADD COLUMN gate_cvd_enabled BOOLEAN NOT NULL DEFAULT TRUE") + print("✅ ADD gate_cvd_enabled BOOLEAN DEFAULT TRUE") + else: + print("⏭️ gate_cvd_enabled 已存在,跳过") + + # Step 5: 新增 whale_usd_threshold + if "whale_usd_threshold" not in existing: + sqls.append("ALTER TABLE strategies ADD COLUMN whale_usd_threshold FLOAT NOT NULL DEFAULT 50000") + print("✅ ADD whale_usd_threshold FLOAT DEFAULT 50000") + else: + print("⏭️ whale_usd_threshold 已存在,跳过") + + print() + if not sqls: + print("无需迁移,所有字段已是最新状态。") + conn.close() + return + + if DRY_RUN: + print("=== DRY RUN - 以下SQL不会执行 ===") + for sql in sqls: + print(f" {sql};") + conn.close() + return + + # 执行 DDL + for sql in sqls: + print(f"执行: {sql}") + cur.execute(sql) + conn.commit() + print() + + # Step 6: 回填 per-symbol 默认值 + cur.execute("SELECT strategy_id, symbol FROM strategies") + rows = cur.fetchall() + print(f"回填 {len(rows)} 条策略的 per-symbol 默认值...") + for row in rows: + sid = row["strategy_id"] + sym = row["symbol"] + defaults = SYMBOL_DEFAULTS.get(sym, SYMBOL_DEFAULTS[None]) + cur.execute(""" + UPDATE strategies SET + vol_atr_pct_min = %s, + whale_flow_pct = %s, + whale_usd_threshold = %s, + obi_threshold = %s, + spot_perp_threshold = %s + WHERE strategy_id = %s + """, ( + defaults["vol_atr_pct_min"], + defaults["whale_flow_pct"], + defaults["whale_usd_threshold"], + defaults["obi_threshold"], + defaults["spot_perp_threshold"], + sid + )) + print(f" {sid} ({sym}): vol_atr_pct={defaults['vol_atr_pct_min']} whale_usd={defaults['whale_usd_threshold']} obi={defaults['obi_threshold']}") + conn.commit() + + print() + print("=== 迁移完成 ===") + + # 验证 + cur.execute("SELECT strategy_id, display_name, gate_cvd_enabled, gate_vol_enabled, vol_atr_pct_min, gate_whale_enabled, whale_usd_threshold, whale_flow_pct, gate_obi_enabled, obi_threshold, gate_spot_perp_enabled, spot_perp_threshold FROM strategies ORDER BY created_at") + print("\n验证结果:") + print(f"{'display_name':<15} {'cvd':>4} {'vol':>4} {'vol_pct':>8} {'whale':>6} {'whale_usd':>10} {'flow_pct':>9} {'obi':>4} {'obi_thr':>8} {'spd':>4} {'spd_thr':>8}") + for r in cur.fetchall(): + print(f"{r['display_name']:<15} {str(r['gate_cvd_enabled']):>4} {str(r['gate_vol_enabled']):>4} {r['vol_atr_pct_min']:>8.4f} {str(r['gate_whale_enabled']):>6} {r['whale_usd_threshold']:>10.0f} {r['whale_flow_pct']:>9.3f} {str(r['gate_obi_enabled']):>4} {r['obi_threshold']:>8.3f} {str(r['gate_spot_perp_enabled']):>4} {r['spot_perp_threshold']:>8.4f}") + + conn.close() + +if __name__ == "__main__": + run() diff --git a/backend/signal_engine.py b/backend/signal_engine.py index 4c44ac7..cab3ef4 100644 --- a/backend/signal_engine.py +++ b/backend/signal_engine.py @@ -88,8 +88,9 @@ def load_strategy_configs_from_db() -> list[dict]: weight_direction, weight_env, weight_aux, weight_momentum, entry_score, gate_obi_enabled, obi_threshold, - gate_whale_enabled, whale_cvd_threshold, - gate_vol_enabled, atr_percentile_min, + gate_whale_enabled, whale_usd_threshold, whale_flow_pct, + gate_vol_enabled, vol_atr_pct_min, + gate_cvd_enabled, gate_spot_perp_enabled, spot_perp_threshold, sl_atr_multiplier, tp1_ratio, tp2_ratio, timeout_minutes, flip_threshold, direction @@ -106,8 +107,9 @@ def load_strategy_configs_from_db() -> list[dict]: w_dir, w_env, w_aux, w_mom, entry_score, gate_obi, obi_thr, - gate_whale, whale_thr, - gate_vol, atr_pct_min, + gate_whale, whale_usd_thr, whale_flow_pct_val, + gate_vol, vol_atr_pct, + gate_cvd, gate_spot, spot_thr, sl_mult, tp1_r, tp2_r, timeout_min, flip_thr, direction) = row @@ -138,10 +140,16 @@ def load_strategy_configs_from_db() -> list[dict]: "momentum": w_mom, }, "gates": { - "obi": {"enabled": gate_obi, "threshold": obi_thr}, - "whale": {"enabled": gate_whale, "threshold": whale_thr}, - "vol": {"enabled": gate_vol, "atr_percentile_min": atr_pct_min}, - "spot_perp": {"enabled": gate_spot, "threshold": spot_thr}, + # 门1 波动率 + "vol": {"enabled": gate_vol, "vol_atr_pct_min": float(vol_atr_pct or 0.002)}, + # 门2 CVD共振 + "cvd": {"enabled": gate_cvd}, + # 门3 鲸鱼否决 + "whale": {"enabled": gate_whale, "whale_usd_threshold": float(whale_usd_thr or 50000), "whale_flow_pct": float(whale_flow_pct_val or 0.5)}, + # 门4 OBI否决 + "obi": {"enabled": gate_obi, "threshold": float(obi_thr or 0.35)}, + # 门5 期现背离 + "spot_perp": {"enabled": gate_spot, "threshold": float(spot_thr or 0.005)}, }, "tp_sl": { "sl_multiplier": sl_mult, @@ -859,21 +867,44 @@ class SymbolState: last_signal_ts = self.last_signal_ts.get(strategy_name, 0) in_cooldown = now_ms - last_signal_ts < COOLDOWN_MS - # ── Per-symbol 四门参数 ──────────────────────────────────── + # ── 五门参数:优先读 DB config(V5.4),fallback 到 JSON symbol_gates ──── + # DB config 来自 load_strategy_configs_from_db(),已映射到 strategy_cfg["gates"] + db_gates = strategy_cfg.get("gates") or {} symbol_gates = (strategy_cfg.get("symbol_gates") or {}).get(self.symbol, {}) - min_vol = float(symbol_gates.get("min_vol_threshold", 0.002)) - whale_usd = float(symbol_gates.get("whale_threshold_usd", 50000)) - obi_veto = float(symbol_gates.get("obi_veto_threshold", 0.35)) - spd_veto = float(symbol_gates.get("spot_perp_divergence_veto", 0.005)) + + # 门1 波动率:vol_atr_pct_min(ATR%价格,如0.002=价格的0.2%) + gate_vol_enabled = db_gates.get("vol", {}).get("enabled", True) + min_vol = float(db_gates.get("vol", {}).get("vol_atr_pct_min") or + symbol_gates.get("min_vol_threshold", 0.002)) + + # 门2 CVD共振:gate_cvd_enabled(快慢CVD必须同向) + gate_cvd_enabled = db_gates.get("cvd", {}).get("enabled", True) + + # 门3 鲸鱼否决:whale_usd_threshold + whale_flow_pct(BTC) + gate_whale_enabled = db_gates.get("whale", {}).get("enabled", True) + whale_usd = float(db_gates.get("whale", {}).get("whale_usd_threshold") or + symbol_gates.get("whale_threshold_usd", 50000)) + whale_flow_pct = float(db_gates.get("whale", {}).get("whale_flow_pct") or + symbol_gates.get("whale_flow_threshold_pct", 0.5)) / 100 + + # 门4 OBI否决:obi_threshold + gate_obi_enabled = db_gates.get("obi", {}).get("enabled", True) + obi_veto = float(db_gates.get("obi", {}).get("threshold") or + symbol_gates.get("obi_veto_threshold", 0.35)) + + # 门5 期现背离:spot_perp_threshold + gate_spd_enabled = db_gates.get("spot_perp", {}).get("enabled", False) + spd_veto = float(db_gates.get("spot_perp", {}).get("threshold") or + symbol_gates.get("spot_perp_divergence_veto", 0.005)) gate_block = None - # 门1:波动率下限 + # 门1:波动率下限(可关闭) atr_pct_price = atr / price if price > 0 else 0 - if atr_pct_price < min_vol: + if gate_vol_enabled and atr_pct_price < min_vol: gate_block = f"low_vol({atr_pct_price:.4f}<{min_vol})" - # 门2:CVD共振(方向门) + # 门2:CVD共振(方向门,可关闭) if cvd_fast > 0 and cvd_mid > 0: direction = "LONG" cvd_resonance = 30 @@ -885,18 +916,20 @@ class SymbolState: else: direction = "LONG" if cvd_fast > 0 else "SHORT" cvd_resonance = 0 - no_direction = True - if not gate_block: - gate_block = "no_direction_consensus" + if gate_cvd_enabled: + no_direction = True + if not gate_block: + gate_block = "no_direction_consensus" + else: + no_direction = False # 门2关闭时,允许单线CVD方向 - # 门3:鲸鱼否决(BTC用whale_cvd_ratio,ALT用大单对立) - if not gate_block and not no_direction: + # 门3:鲸鱼否决(BTC用whale_cvd_ratio,ALT用大单对立,可关闭) + if gate_whale_enabled and not gate_block and not no_direction: if self.symbol == "BTCUSDT": # BTC:巨鲸CVD净方向与信号方向冲突时否决 whale_cvd = self.whale_cvd_ratio if self._whale_trades else to_float(self.market_indicators.get("tiered_cvd_whale")) or 0.0 - whale_threshold_pct = float(symbol_gates.get("whale_flow_threshold_pct", 0.5)) / 100 - if (direction == "LONG" and whale_cvd < -whale_threshold_pct) or \ - (direction == "SHORT" and whale_cvd > whale_threshold_pct): + if (direction == "LONG" and whale_cvd < -whale_flow_pct) or \ + (direction == "SHORT" and whale_cvd > whale_flow_pct): gate_block = f"whale_cvd_veto({whale_cvd:.3f})" else: # ALT:recent_large_trades 里有对立大单则否决 @@ -913,17 +946,17 @@ class SymbolState: if whale_adverse and not whale_aligned: gate_block = f"whale_adverse(>${whale_usd/1000:.0f}k)" - # 门4:OBI否决(实时WS优先,fallback DB) + # 门4:OBI否决(实时WS优先,fallback DB,可关闭) obi_raw = self.rt_obi if self.rt_obi != 0.0 else to_float(self.market_indicators.get("obi_depth_10")) - if not gate_block and not no_direction and obi_raw is not None: + if gate_obi_enabled and not gate_block and not no_direction and obi_raw is not None: if direction == "LONG" and obi_raw < -obi_veto: gate_block = f"obi_veto({obi_raw:.3f}<-{obi_veto})" elif direction == "SHORT" and obi_raw > obi_veto: gate_block = f"obi_veto({obi_raw:.3f}>{obi_veto})" - # 门5:期现背离否决(实时WS优先,fallback DB) + # 门5:期现背离否决(实时WS优先,fallback DB,可关闭) spot_perp_div = self.rt_spot_perp_div if self.rt_spot_perp_div != 0.0 else to_float(self.market_indicators.get("spot_perp_divergence")) - if not gate_block and not no_direction and spot_perp_div is not None: + if gate_spd_enabled and not gate_block and not no_direction and spot_perp_div is not None: if (direction == "LONG" and spot_perp_div < -spd_veto) or \ (direction == "SHORT" and spot_perp_div > spd_veto): gate_block = f"spd_veto({spot_perp_div:.4f})" diff --git a/frontend/components/StrategyForm.tsx b/frontend/components/StrategyForm.tsx index e55ead0..739d376 100644 --- a/frontend/components/StrategyForm.tsx +++ b/frontend/components/StrategyForm.tsx @@ -20,12 +20,19 @@ export interface StrategyFormData { weight_aux: number; weight_momentum: number; entry_score: number; + // 门1 波动率 + gate_vol_enabled: boolean; + vol_atr_pct_min: number; + // 门2 CVD共振 + gate_cvd_enabled: boolean; + // 门3 鲸鱼否决 + gate_whale_enabled: boolean; + whale_usd_threshold: number; + whale_flow_pct: number; + // 门4 OBI否决 gate_obi_enabled: boolean; obi_threshold: number; - gate_whale_enabled: boolean; - whale_cvd_threshold: number; - gate_vol_enabled: boolean; - atr_percentile_min: number; + // 门5 期现背离 gate_spot_perp_enabled: boolean; spot_perp_threshold: number; sl_atr_multiplier: number; @@ -48,14 +55,16 @@ export const DEFAULT_FORM: StrategyFormData = { weight_aux: 15, weight_momentum: 5, entry_score: 75, - gate_obi_enabled: true, - obi_threshold: 0.3, - gate_whale_enabled: true, - whale_cvd_threshold: 0.0, gate_vol_enabled: true, - atr_percentile_min: 20, + vol_atr_pct_min: 0.002, + gate_cvd_enabled: true, + gate_whale_enabled: true, + whale_usd_threshold: 50000, + whale_flow_pct: 0.5, + gate_obi_enabled: true, + obi_threshold: 0.35, gate_spot_perp_enabled: false, - spot_perp_threshold: 0.002, + spot_perp_threshold: 0.005, sl_atr_multiplier: 1.5, tp1_ratio: 0.75, tp2_ratio: 1.5, @@ -363,44 +372,52 @@ export default function StrategyForm({ mode, initialData, strategyId, onSuccess, - {/* ── 四道 Gate ── */} + {/* ── 五道 Gate ── */}

过滤门控(Gate)

set("gate_obi_enabled", !form.gate_obi_enabled)} - > - - set("obi_threshold", v)} min={0.1} max={0.9} step={0.05} /> - - set("gate_whale_enabled", !form.gate_whale_enabled)} - > - - set("whale_cvd_threshold", v)} min={-1} max={1} step={0.1} /> - - set("gate_vol_enabled", !form.gate_vol_enabled)} > - - set("atr_percentile_min", v)} min={5} max={80} /> + + set("vol_atr_pct_min", v)} min={0.0001} max={0.02} step={0.0005} /> set("gate_cvd_enabled", !form.gate_cvd_enabled)} + /> + set("gate_whale_enabled", !form.gate_whale_enabled)} + > + + set("whale_usd_threshold", v)} min={1000} max={1000000} step={5000} /> + + set("whale_flow_pct", v)} min={0} max={1} step={0.05} /> + + set("gate_obi_enabled", !form.gate_obi_enabled)} + > + + set("obi_threshold", v)} min={0.1} max={0.9} step={0.05} /> + + set("gate_spot_perp_enabled", !form.gate_spot_perp_enabled)} > - + set("spot_perp_threshold", v)} min={0.0005} max={0.01} step={0.0005} />