feat(v54b): 5门Gate重构 — 与signal_engine执行逻辑完全对应,DB字段改名,前后端统一

This commit is contained in:
root 2026-03-11 16:26:44 +00:00
parent 06f900b89b
commit 2e4c05b2e0
4 changed files with 344 additions and 101 deletions

View File

@ -2202,14 +2202,22 @@ class StrategyCreateRequest(BaseModel):
weight_aux: int = 15 weight_aux: int = 15
weight_momentum: int = 5 weight_momentum: int = 5
entry_score: int = 75 entry_score: int = 75
gate_obi_enabled: bool = True # 门1 波动率
obi_threshold: float = 0.3
gate_whale_enabled: bool = True
whale_cvd_threshold: float = 0.0
gate_vol_enabled: bool = True 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 gate_spot_perp_enabled: bool = False
spot_perp_threshold: float = 0.002 spot_perp_threshold: float = 0.005
# 风控参数
sl_atr_multiplier: float = 1.5 sl_atr_multiplier: float = 1.5
tp1_ratio: float = 0.75 tp1_ratio: float = 0.75
tp2_ratio: float = 1.5 tp2_ratio: float = 1.5
@ -2288,6 +2296,27 @@ class StrategyCreateRequest(BaseModel):
raise ValueError("entry_score must be 60-95") raise ValueError("entry_score must be 60-95")
return v 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") @field_validator("obi_threshold")
@classmethod @classmethod
def validate_obi(cls, v): def validate_obi(cls, v):
@ -2295,20 +2324,6 @@ class StrategyCreateRequest(BaseModel):
raise ValueError("obi_threshold must be 0.1-0.9") raise ValueError("obi_threshold must be 0.1-0.9")
return v 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") @field_validator("spot_perp_threshold")
@classmethod @classmethod
def validate_spot_perp(cls, v): def validate_spot_perp(cls, v):
@ -2370,14 +2385,22 @@ class StrategyUpdateRequest(BaseModel):
weight_aux: Optional[int] = None weight_aux: Optional[int] = None
weight_momentum: Optional[int] = None weight_momentum: Optional[int] = None
entry_score: 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 gate_obi_enabled: Optional[bool] = None
obi_threshold: Optional[float] = None obi_threshold: Optional[float] = None
gate_whale_enabled: Optional[bool] = None # 门5 期现背离
whale_cvd_threshold: Optional[float] = None
gate_vol_enabled: Optional[bool] = None
atr_percentile_min: Optional[int] = None
gate_spot_perp_enabled: Optional[bool] = None gate_spot_perp_enabled: Optional[bool] = None
spot_perp_threshold: Optional[float] = None spot_perp_threshold: Optional[float] = None
# 风控
sl_atr_multiplier: Optional[float] = None sl_atr_multiplier: Optional[float] = None
tp1_ratio: Optional[float] = None tp1_ratio: Optional[float] = None
tp2_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_aux": row["weight_aux"],
"weight_momentum": row["weight_momentum"], "weight_momentum": row["weight_momentum"],
"entry_score": row["entry_score"], "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"], "gate_obi_enabled": row["gate_obi_enabled"],
"obi_threshold": row["obi_threshold"], "obi_threshold": row["obi_threshold"],
"gate_whale_enabled": row["gate_whale_enabled"], # 门5 期现背离
"whale_cvd_threshold": row["whale_cvd_threshold"],
"gate_vol_enabled": row["gate_vol_enabled"],
"atr_percentile_min": row["atr_percentile_min"],
"gate_spot_perp_enabled": row["gate_spot_perp_enabled"], "gate_spot_perp_enabled": row["gate_spot_perp_enabled"],
"spot_perp_threshold": row["spot_perp_threshold"], "spot_perp_threshold": row["spot_perp_threshold"],
"sl_atr_multiplier": row["sl_atr_multiplier"], "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, cvd_fast_window, cvd_slow_window,
weight_direction, weight_env, weight_aux, weight_momentum, weight_direction, weight_env, weight_aux, weight_momentum,
entry_score, 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_obi_enabled, obi_threshold,
gate_whale_enabled, whale_cvd_threshold,
gate_vol_enabled, atr_percentile_min,
gate_spot_perp_enabled, spot_perp_threshold, gate_spot_perp_enabled, spot_perp_threshold,
sl_atr_multiplier, tp1_ratio, tp2_ratio, sl_atr_multiplier, tp1_ratio, tp2_ratio,
timeout_minutes, flip_threshold, timeout_minutes, flip_threshold,
@ -2560,17 +2591,23 @@ async def create_strategy(body: StrategyCreateRequest, user: dict = Depends(get_
$3,$4,$5,$6, $3,$4,$5,$6,
$7,$8,$9,$10, $7,$8,$9,$10,
$11, $11,
$12,$13,$14,$15,$16,$17,$18,$19, $12,$13,
$20,$21,$22,$23,$24, $14,
$25,$25,$26 $15,$16,$17,
$18,$19,
$20,$21,
$22,$23,$24,
$25,$26,
$27,$27,$28
)""", )""",
new_id, body.display_name, new_id, body.display_name,
body.symbol, body.direction, body.cvd_fast_window, body.cvd_slow_window, 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.weight_direction, body.weight_env, body.weight_aux, body.weight_momentum,
body.entry_score, 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_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.gate_spot_perp_enabled, body.spot_perp_threshold,
body.sl_atr_multiplier, body.tp1_ratio, body.tp2_ratio, body.sl_atr_multiplier, body.tp1_ratio, body.tp2_ratio,
body.timeout_minutes, body.flip_threshold, 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, "weight_momentum": lambda v: 0 <= v <= 20,
"entry_score": lambda v: 60 <= v <= 95, "entry_score": lambda v: 60 <= v <= 95,
"obi_threshold": lambda v: 0.1 <= v <= 0.9, "obi_threshold": lambda v: 0.1 <= v <= 0.9,
"whale_cvd_threshold": lambda v: -1.0 <= v <= 1.0, "vol_atr_pct_min": lambda v: 0.0001 <= v <= 0.02,
"atr_percentile_min": lambda v: 5 <= v <= 80, "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, "spot_perp_threshold": lambda v: 0.0005 <= v <= 0.01,
"sl_atr_multiplier": lambda v: 0.5 <= v <= 3.0, "sl_atr_multiplier": lambda v: 0.5 <= v <= 3.0,
"tp1_ratio": lambda v: 0.3 <= v <= 2.0, "tp1_ratio": lambda v: 0.3 <= v <= 2.0,

View File

@ -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_minATR%价格阈值如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_thresholdALT大单USD+ whale_flow_pctBTC 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()

View File

@ -88,8 +88,9 @@ def load_strategy_configs_from_db() -> list[dict]:
weight_direction, weight_env, weight_aux, weight_momentum, weight_direction, weight_env, weight_aux, weight_momentum,
entry_score, entry_score,
gate_obi_enabled, obi_threshold, gate_obi_enabled, obi_threshold,
gate_whale_enabled, whale_cvd_threshold, gate_whale_enabled, whale_usd_threshold, whale_flow_pct,
gate_vol_enabled, atr_percentile_min, gate_vol_enabled, vol_atr_pct_min,
gate_cvd_enabled,
gate_spot_perp_enabled, spot_perp_threshold, gate_spot_perp_enabled, spot_perp_threshold,
sl_atr_multiplier, tp1_ratio, tp2_ratio, sl_atr_multiplier, tp1_ratio, tp2_ratio,
timeout_minutes, flip_threshold, direction 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, w_dir, w_env, w_aux, w_mom,
entry_score, entry_score,
gate_obi, obi_thr, gate_obi, obi_thr,
gate_whale, whale_thr, gate_whale, whale_usd_thr, whale_flow_pct_val,
gate_vol, atr_pct_min, gate_vol, vol_atr_pct,
gate_cvd,
gate_spot, spot_thr, gate_spot, spot_thr,
sl_mult, tp1_r, tp2_r, sl_mult, tp1_r, tp2_r,
timeout_min, flip_thr, direction) = row timeout_min, flip_thr, direction) = row
@ -138,10 +140,16 @@ def load_strategy_configs_from_db() -> list[dict]:
"momentum": w_mom, "momentum": w_mom,
}, },
"gates": { "gates": {
"obi": {"enabled": gate_obi, "threshold": obi_thr}, # 门1 波动率
"whale": {"enabled": gate_whale, "threshold": whale_thr}, "vol": {"enabled": gate_vol, "vol_atr_pct_min": float(vol_atr_pct or 0.002)},
"vol": {"enabled": gate_vol, "atr_percentile_min": atr_pct_min}, # 门2 CVD共振
"spot_perp": {"enabled": gate_spot, "threshold": spot_thr}, "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": { "tp_sl": {
"sl_multiplier": sl_mult, "sl_multiplier": sl_mult,
@ -859,21 +867,44 @@ class SymbolState:
last_signal_ts = self.last_signal_ts.get(strategy_name, 0) last_signal_ts = self.last_signal_ts.get(strategy_name, 0)
in_cooldown = now_ms - last_signal_ts < COOLDOWN_MS in_cooldown = now_ms - last_signal_ts < COOLDOWN_MS
# ── Per-symbol 四门参数 ──────────────────────────────────── # ── 五门参数:优先读 DB configV5.4fallback 到 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, {}) 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)) # 门1 波动率vol_atr_pct_minATR%价格如0.002=价格的0.2%
obi_veto = float(symbol_gates.get("obi_veto_threshold", 0.35)) gate_vol_enabled = db_gates.get("vol", {}).get("enabled", True)
spd_veto = float(symbol_gates.get("spot_perp_divergence_veto", 0.005)) 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_pctBTC
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 gate_block = None
# 门1波动率下限 # 门1波动率下限(可关闭)
atr_pct_price = atr / price if price > 0 else 0 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})" gate_block = f"low_vol({atr_pct_price:.4f}<{min_vol})"
# 门2CVD共振方向门 # 门2CVD共振方向门,可关闭
if cvd_fast > 0 and cvd_mid > 0: if cvd_fast > 0 and cvd_mid > 0:
direction = "LONG" direction = "LONG"
cvd_resonance = 30 cvd_resonance = 30
@ -885,18 +916,20 @@ class SymbolState:
else: else:
direction = "LONG" if cvd_fast > 0 else "SHORT" direction = "LONG" if cvd_fast > 0 else "SHORT"
cvd_resonance = 0 cvd_resonance = 0
no_direction = True if gate_cvd_enabled:
if not gate_block: no_direction = True
gate_block = "no_direction_consensus" if not gate_block:
gate_block = "no_direction_consensus"
else:
no_direction = False # 门2关闭时允许单线CVD方向
# 门3鲸鱼否决BTC用whale_cvd_ratioALT用大单对立 # 门3鲸鱼否决BTC用whale_cvd_ratioALT用大单对立,可关闭
if not gate_block and not no_direction: if gate_whale_enabled and not gate_block and not no_direction:
if self.symbol == "BTCUSDT": if self.symbol == "BTCUSDT":
# BTC巨鲸CVD净方向与信号方向冲突时否决 # 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_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_flow_pct) or \
if (direction == "LONG" and whale_cvd < -whale_threshold_pct) or \ (direction == "SHORT" and whale_cvd > whale_flow_pct):
(direction == "SHORT" and whale_cvd > whale_threshold_pct):
gate_block = f"whale_cvd_veto({whale_cvd:.3f})" gate_block = f"whale_cvd_veto({whale_cvd:.3f})"
else: else:
# ALTrecent_large_trades 里有对立大单则否决 # ALTrecent_large_trades 里有对立大单则否决
@ -913,17 +946,17 @@ class SymbolState:
if whale_adverse and not whale_aligned: if whale_adverse and not whale_aligned:
gate_block = f"whale_adverse(>${whale_usd/1000:.0f}k)" gate_block = f"whale_adverse(>${whale_usd/1000:.0f}k)"
# 门4OBI否决实时WS优先fallback DB # 门4OBI否决实时WS优先fallback DB,可关闭
obi_raw = self.rt_obi if self.rt_obi != 0.0 else to_float(self.market_indicators.get("obi_depth_10")) 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: if direction == "LONG" and obi_raw < -obi_veto:
gate_block = f"obi_veto({obi_raw:.3f}<-{obi_veto})" gate_block = f"obi_veto({obi_raw:.3f}<-{obi_veto})"
elif direction == "SHORT" and obi_raw > obi_veto: elif direction == "SHORT" and obi_raw > obi_veto:
gate_block = f"obi_veto({obi_raw:.3f}>{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")) 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 \ if (direction == "LONG" and spot_perp_div < -spd_veto) or \
(direction == "SHORT" and spot_perp_div > spd_veto): (direction == "SHORT" and spot_perp_div > spd_veto):
gate_block = f"spd_veto({spot_perp_div:.4f})" gate_block = f"spd_veto({spot_perp_div:.4f})"

View File

@ -20,12 +20,19 @@ export interface StrategyFormData {
weight_aux: number; weight_aux: number;
weight_momentum: number; weight_momentum: number;
entry_score: 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; gate_obi_enabled: boolean;
obi_threshold: number; obi_threshold: number;
gate_whale_enabled: boolean; // 门5 期现背离
whale_cvd_threshold: number;
gate_vol_enabled: boolean;
atr_percentile_min: number;
gate_spot_perp_enabled: boolean; gate_spot_perp_enabled: boolean;
spot_perp_threshold: number; spot_perp_threshold: number;
sl_atr_multiplier: number; sl_atr_multiplier: number;
@ -48,14 +55,16 @@ export const DEFAULT_FORM: StrategyFormData = {
weight_aux: 15, weight_aux: 15,
weight_momentum: 5, weight_momentum: 5,
entry_score: 75, entry_score: 75,
gate_obi_enabled: true,
obi_threshold: 0.3,
gate_whale_enabled: true,
whale_cvd_threshold: 0.0,
gate_vol_enabled: true, 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, gate_spot_perp_enabled: false,
spot_perp_threshold: 0.002, spot_perp_threshold: 0.005,
sl_atr_multiplier: 1.5, sl_atr_multiplier: 1.5,
tp1_ratio: 0.75, tp1_ratio: 0.75,
tp2_ratio: 1.5, tp2_ratio: 1.5,
@ -363,44 +372,52 @@ export default function StrategyForm({ mode, initialData, strategyId, onSuccess,
</div> </div>
</div> </div>
{/* ── 道 Gate ── */} {/* ── 道 Gate ── */}
<div className="bg-white border border-slate-200 rounded-xl p-4"> <div className="bg-white border border-slate-200 rounded-xl p-4">
<h3 className="text-sm font-semibold text-slate-700 mb-3">Gate</h3> <h3 className="text-sm font-semibold text-slate-700 mb-3">Gate</h3>
<div className="space-y-3"> <div className="space-y-3">
<GateRow <GateRow
label="Gate 1订单簿失衡 (OBI)" label="门1波动率门 (ATR%价格)"
hint="要求订单簿方向与信号一致OBI越高越严格" hint="ATR占价格比例低于阈值时不开仓过滤低波动时段"
enabled={form.gate_obi_enabled}
onToggle={() => set("gate_obi_enabled", !form.gate_obi_enabled)}
>
<FieldLabel label="OBI 最低阈值" hint="0.1(宽松) ~ 0.9(严格)默认0.3" />
<NumberInput value={form.obi_threshold} onChange={(v) => set("obi_threshold", v)} min={0.1} max={0.9} step={0.05} />
</GateRow>
<GateRow
label="Gate 2大单 CVD 门"
hint="要求大单累积净买入方向与信号一致"
enabled={form.gate_whale_enabled}
onToggle={() => set("gate_whale_enabled", !form.gate_whale_enabled)}
>
<FieldLabel label="Whale CVD 阈值" hint="-1到1多头信号时大单CVD需>此值默认0" />
<NumberInput value={form.whale_cvd_threshold} onChange={(v) => set("whale_cvd_threshold", v)} min={-1} max={1} step={0.1} />
</GateRow>
<GateRow
label="Gate 3波动率门 (ATR%)"
hint="要求当前ATR百分位高于最低值过滤低波动时段"
enabled={form.gate_vol_enabled} enabled={form.gate_vol_enabled}
onToggle={() => set("gate_vol_enabled", !form.gate_vol_enabled)} onToggle={() => set("gate_vol_enabled", !form.gate_vol_enabled)}
> >
<FieldLabel label="ATR 最低百分位" hint="0-80默认20低于此值不开仓" /> <FieldLabel label="ATR% 最低阈值" hint="如0.002=价格0.2%BTC默认0.002ETH默认0.003" />
<NumberInput value={form.atr_percentile_min} onChange={(v) => set("atr_percentile_min", v)} min={5} max={80} /> <NumberInput value={form.vol_atr_pct_min} onChange={(v) => set("vol_atr_pct_min", v)} min={0.0001} max={0.02} step={0.0005} />
</GateRow> </GateRow>
<GateRow <GateRow
label="Gate 4现货/永续溢价门" label="门2CVD共振方向门"
hint="要求现货与永续价格溢价低于阈值,过滤套利异常时段" hint="要求快慢两条CVD同向双线共振否则视为无方向不开仓"
enabled={form.gate_cvd_enabled}
onToggle={() => set("gate_cvd_enabled", !form.gate_cvd_enabled)}
/>
<GateRow
label="门3鲸鱼否决门"
hint="检测大单方向ALT用USD金额阈值BTC用鲸鱼CVD流量比例"
enabled={form.gate_whale_enabled}
onToggle={() => set("gate_whale_enabled", !form.gate_whale_enabled)}
>
<FieldLabel label="大单USD阈值 (ALT)" hint="超过此USD金额视为鲸鱼单ETH默认5万BTC默认10万" />
<NumberInput value={form.whale_usd_threshold} onChange={(v) => set("whale_usd_threshold", v)} min={1000} max={1000000} step={5000} />
<FieldLabel label="鲸鱼CVD流量阈值 (BTC)" hint="0~1BTC鲸鱼净方向比例超过此值才否决默认0.5" />
<NumberInput value={form.whale_flow_pct} onChange={(v) => set("whale_flow_pct", v)} min={0} max={1} step={0.05} />
</GateRow>
<GateRow
label="门4订单簿失衡门 (OBI)"
hint="要求订单簿方向与信号一致OBI绝对值超过阈值才否决反向信号"
enabled={form.gate_obi_enabled}
onToggle={() => set("gate_obi_enabled", !form.gate_obi_enabled)}
>
<FieldLabel label="OBI 否决阈值" hint="0.1(宽松) ~ 0.9(严格)默认0.35" />
<NumberInput value={form.obi_threshold} onChange={(v) => set("obi_threshold", v)} min={0.1} max={0.9} step={0.05} />
</GateRow>
<GateRow
label="门5期现背离门"
hint="要求现货与永续溢价低于阈值,过滤套利异常时段(默认关闭)"
enabled={form.gate_spot_perp_enabled} enabled={form.gate_spot_perp_enabled}
onToggle={() => set("gate_spot_perp_enabled", !form.gate_spot_perp_enabled)} onToggle={() => set("gate_spot_perp_enabled", !form.gate_spot_perp_enabled)}
> >
<FieldLabel label="溢价率阈值" hint="0.0005~0.01,溢价超过此值不开仓" /> <FieldLabel label="溢价率阈值" hint="0.0005~0.01,溢价超过此值不开仓默认0.005" />
<NumberInput value={form.spot_perp_threshold} onChange={(v) => set("spot_perp_threshold", v)} min={0.0005} max={0.01} step={0.0005} /> <NumberInput value={form.spot_perp_threshold} onChange={(v) => set("spot_perp_threshold", v)} min={0.0005} max={0.01} step={0.0005} />
</GateRow> </GateRow>
</div> </div>