feat(v54b): 5门Gate重构 — 与signal_engine执行逻辑完全对应,DB字段改名,前后端统一
This commit is contained in:
parent
06f900b89b
commit
2e4c05b2e0
112
backend/main.py
112
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,
|
||||
|
||||
155
backend/migrate_v54b_gates.py
Normal file
155
backend/migrate_v54b_gates.py
Normal 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_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()
|
||||
@ -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
|
||||
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})"
|
||||
|
||||
@ -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,
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ── 四道 Gate ── */}
|
||||
{/* ── 五道 Gate ── */}
|
||||
<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>
|
||||
<div className="space-y-3">
|
||||
<GateRow
|
||||
label="Gate 1:订单簿失衡 (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.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百分位高于最低值,过滤低波动时段"
|
||||
label="门1:波动率门 (ATR%价格)"
|
||||
hint="ATR占价格比例低于阈值时不开仓,过滤低波动时段"
|
||||
enabled={form.gate_vol_enabled}
|
||||
onToggle={() => set("gate_vol_enabled", !form.gate_vol_enabled)}
|
||||
>
|
||||
<FieldLabel label="ATR 最低百分位" hint="0-80,默认20,低于此值不开仓" />
|
||||
<NumberInput value={form.atr_percentile_min} onChange={(v) => set("atr_percentile_min", v)} min={5} max={80} />
|
||||
<FieldLabel label="ATR% 最低阈值" hint="如0.002=价格0.2%,BTC默认0.002,ETH默认0.003" />
|
||||
<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
|
||||
label="Gate 4:现货/永续溢价门"
|
||||
hint="要求现货与永续价格溢价低于阈值,过滤套利异常时段"
|
||||
label="门2:CVD共振方向门"
|
||||
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~1,BTC鲸鱼净方向比例超过此值才否决,默认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}
|
||||
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} />
|
||||
</GateRow>
|
||||
</div>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user