feat(v54): signal_engine reads strategy config from DB, writes strategy_id to paper_trades/signal_indicators

This commit is contained in:
root 2026-03-11 15:46:30 +00:00
parent 06552c2b75
commit d3784aaf79

View File

@ -70,6 +70,96 @@ def load_strategy_configs() -> list[dict]:
)
return configs
def load_strategy_configs_from_db() -> list[dict]:
"""
V5.4: strategies 表读取 running 状态的策略配置
DB 字段映射成现有 JSON 格式保持与 JSON 文件完全兼容
失败时返回空列表调用方应 fallback JSON
内存安全每次读取只返回配置列表无缓存无大对象
"""
try:
with get_sync_conn() as conn:
with conn.cursor() as cur:
cur.execute("""
SELECT
strategy_id::text, display_name, symbol,
cvd_fast_window, cvd_slow_window,
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_spot_perp_enabled, spot_perp_threshold,
sl_atr_multiplier, tp1_ratio, tp2_ratio,
timeout_minutes, flip_threshold, direction
FROM strategies
WHERE status = 'running'
ORDER BY created_at ASC
""")
rows = cur.fetchall()
configs = []
for row in rows:
(sid, display_name, symbol,
cvd_fast, cvd_slow,
w_dir, w_env, w_aux, w_mom,
entry_score,
gate_obi, obi_thr,
gate_whale, whale_thr,
gate_vol, atr_pct_min,
gate_spot, spot_thr,
sl_mult, tp1_r, tp2_r,
timeout_min, flip_thr, direction) = row
# 把 display_name 映射回 legacy strategy name用于兼容评分逻辑
# legacy 策略用固定 UUID 识别
LEGACY_UUID_MAP = {
"00000000-0000-0000-0000-000000000053": "v53",
"00000000-0000-0000-0000-000000000054": "v53_middle",
"00000000-0000-0000-0000-000000000055": "v53_fast",
}
strategy_name = LEGACY_UUID_MAP.get(sid, f"custom_{sid[:8]}")
# 构造与 JSON 文件格式兼容的配置 dict
cfg = {
"name": strategy_name,
"strategy_id": sid, # V5.4 新增:用于写 strategy_id 到 DB
"strategy_name_snapshot": display_name, # V5.4 新增:写入时快照名称
"symbol": symbol,
"direction": direction,
"cvd_fast_window": cvd_fast,
"cvd_slow_window": cvd_slow,
"threshold": entry_score,
"weights": {
"direction": w_dir,
"env": w_env,
"aux": w_aux,
"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},
},
"tp_sl": {
"sl_multiplier": sl_mult,
"tp1_multiplier": tp1_r,
"tp2_multiplier": tp2_r,
},
"timeout_minutes": timeout_min,
"flip_threshold": flip_thr,
}
configs.append(cfg)
logger.info(f"[DB] 已加载 {len(configs)} 个策略配置: {[c['name'] for c in configs]}")
return configs
except Exception as e:
logger.warning(f"[DB] load_strategy_configs_from_db 失败,将 fallback 到 JSON: {e}")
return []
# ─── 模拟盘配置 ───────────────────────────────────────────────────
PAPER_TRADING_ENABLED = False # 总开关(兼容旧逻辑)
PAPER_ENABLED_STRATEGIES = [] # 分策略开关: ["v51_baseline", "v52_8signals"]
@ -1018,7 +1108,8 @@ def fetch_new_trades(symbol: str, last_id: int) -> list:
for r in cur.fetchall()]
def save_indicator(ts: int, symbol: str, result: dict, strategy: str = "v52_8signals"):
def save_indicator(ts: int, symbol: str, result: dict, strategy: str = "v52_8signals",
strategy_id: Optional[str] = None, strategy_name_snapshot: Optional[str] = None):
with get_sync_conn() as conn:
with conn.cursor() as cur:
import json as _json3
@ -1026,13 +1117,13 @@ def save_indicator(ts: int, symbol: str, result: dict, strategy: str = "v52_8sig
cvd_fast_5m = result.get("cvd_fast_5m") # v53_fast 专用5m窗口CVD其他策略为None
cur.execute(
"INSERT INTO signal_indicators "
"(ts,symbol,strategy,cvd_fast,cvd_mid,cvd_day,cvd_fast_slope,atr_5m,atr_percentile,atr_value,vwap_30m,price,p95_qty,p99_qty,score,signal,factors,cvd_fast_5m) "
"VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
"(ts,symbol,strategy,cvd_fast,cvd_mid,cvd_day,cvd_fast_slope,atr_5m,atr_percentile,atr_value,vwap_30m,price,p95_qty,p99_qty,score,signal,factors,cvd_fast_5m,strategy_id,strategy_name_snapshot) "
"VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
(ts, symbol, strategy, result["cvd_fast"], result["cvd_mid"], result["cvd_day"], result["cvd_fast_slope"],
result["atr"], result["atr_pct"], result.get("atr_value", result["atr"]),
result["vwap"], result["price"],
result["p95"], result["p99"], result["score"], result.get("signal"), factors_json,
cvd_fast_5m)
cvd_fast_5m, strategy_id, strategy_name_snapshot)
)
# 有信号时通知live_executor
if result.get("signal"):
@ -1128,6 +1219,8 @@ def paper_open_trade(
factors: dict = None,
strategy: str = "v51_baseline",
tp_sl: Optional[dict] = None,
strategy_id: Optional[str] = None,
strategy_name_snapshot: Optional[str] = None,
):
"""模拟开仓"""
import json as _json3
@ -1158,8 +1251,8 @@ def paper_open_trade(
with get_sync_conn() as conn:
with conn.cursor() as cur:
cur.execute(
"INSERT INTO paper_trades (symbol,direction,score,tier,entry_price,entry_ts,tp1_price,tp2_price,sl_price,atr_at_entry,score_factors,strategy,risk_distance) "
"VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
"INSERT INTO paper_trades (symbol,direction,score,tier,entry_price,entry_ts,tp1_price,tp2_price,sl_price,atr_at_entry,score_factors,strategy,risk_distance,strategy_id,strategy_name_snapshot) "
"VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
(
symbol,
direction,
@ -1174,6 +1267,8 @@ def paper_open_trade(
_json3.dumps(factors) if factors else None,
strategy,
risk_distance,
strategy_id,
strategy_name_snapshot,
),
)
conn.commit()
@ -1370,6 +1465,10 @@ def start_realtime_ws(states: dict):
def main():
init_schema()
# V5.4: 优先从 DB 加载策略配置,失败 fallback 到 JSON
strategy_configs = load_strategy_configs_from_db()
if not strategy_configs:
logger.warning("[DB] 未能从 DB 加载策略配置,使用 JSON fallback")
strategy_configs = load_strategy_configs()
strategy_names = [cfg.get("name", "unknown") for cfg in strategy_configs]
logger.info(f"已加载策略配置: {', '.join(strategy_names)}")
@ -1409,7 +1508,10 @@ def main():
# 每个策略独立存储indicator
for strategy_cfg, strategy_result in strategy_results:
sname = strategy_cfg.get("name", "v51_baseline")
save_indicator(now_ms, sym, strategy_result, strategy=sname)
sid = strategy_cfg.get("strategy_id")
ssnap = strategy_cfg.get("strategy_name_snapshot")
save_indicator(now_ms, sym, strategy_result, strategy=sname,
strategy_id=sid, strategy_name_snapshot=ssnap)
save_feature_event(now_ms, sym, strategy_result, strategy=sname)
# 1m表仍用primary图表用
@ -1463,6 +1565,8 @@ def main():
factors=result.get("factors"),
strategy=strategy_name,
tp_sl=strategy_cfg.get("tp_sl"),
strategy_id=strategy_cfg.get("strategy_id"),
strategy_name_snapshot=strategy_cfg.get("strategy_name_snapshot"),
)
# 模拟盘持仓检查由paper_monitor.py通过WebSocket实时处理这里不再检查
@ -1476,6 +1580,11 @@ def main():
if cycle % 10 == 0:
old_strategies = list(PAPER_ENABLED_STRATEGIES)
load_paper_config()
# V5.4: 热重载优先读 DB失败 fallback 到 JSON
new_configs = load_strategy_configs_from_db()
if new_configs:
strategy_configs = new_configs
else:
strategy_configs = load_strategy_configs() # A1: 热重载权重/阈值/TP/SL
strategy_names = [cfg.get("name", "unknown") for cfg in strategy_configs]
primary_strategy_name = "v52_8signals" if any(cfg.get("name") == "v52_8signals" for cfg in strategy_configs) else strategy_names[0]