feat(v54): signal_engine reads strategy config from DB, writes strategy_id to paper_trades/signal_indicators
This commit is contained in:
parent
06552c2b75
commit
d3784aaf79
@ -70,6 +70,96 @@ def load_strategy_configs() -> list[dict]:
|
|||||||
)
|
)
|
||||||
return configs
|
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_TRADING_ENABLED = False # 总开关(兼容旧逻辑)
|
||||||
PAPER_ENABLED_STRATEGIES = [] # 分策略开关: ["v51_baseline", "v52_8signals"]
|
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()]
|
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 get_sync_conn() as conn:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
import json as _json3
|
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
|
cvd_fast_5m = result.get("cvd_fast_5m") # v53_fast 专用:5m窗口CVD,其他策略为None
|
||||||
cur.execute(
|
cur.execute(
|
||||||
"INSERT INTO signal_indicators "
|
"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) "
|
"(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)",
|
"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"],
|
(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["atr"], result["atr_pct"], result.get("atr_value", result["atr"]),
|
||||||
result["vwap"], result["price"],
|
result["vwap"], result["price"],
|
||||||
result["p95"], result["p99"], result["score"], result.get("signal"), factors_json,
|
result["p95"], result["p99"], result["score"], result.get("signal"), factors_json,
|
||||||
cvd_fast_5m)
|
cvd_fast_5m, strategy_id, strategy_name_snapshot)
|
||||||
)
|
)
|
||||||
# 有信号时通知live_executor
|
# 有信号时通知live_executor
|
||||||
if result.get("signal"):
|
if result.get("signal"):
|
||||||
@ -1128,6 +1219,8 @@ def paper_open_trade(
|
|||||||
factors: dict = None,
|
factors: dict = None,
|
||||||
strategy: str = "v51_baseline",
|
strategy: str = "v51_baseline",
|
||||||
tp_sl: Optional[dict] = None,
|
tp_sl: Optional[dict] = None,
|
||||||
|
strategy_id: Optional[str] = None,
|
||||||
|
strategy_name_snapshot: Optional[str] = None,
|
||||||
):
|
):
|
||||||
"""模拟开仓"""
|
"""模拟开仓"""
|
||||||
import json as _json3
|
import json as _json3
|
||||||
@ -1158,8 +1251,8 @@ def paper_open_trade(
|
|||||||
with get_sync_conn() as conn:
|
with get_sync_conn() as conn:
|
||||||
with conn.cursor() as cur:
|
with conn.cursor() as cur:
|
||||||
cur.execute(
|
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) "
|
"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)",
|
"VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
|
||||||
(
|
(
|
||||||
symbol,
|
symbol,
|
||||||
direction,
|
direction,
|
||||||
@ -1174,6 +1267,8 @@ def paper_open_trade(
|
|||||||
_json3.dumps(factors) if factors else None,
|
_json3.dumps(factors) if factors else None,
|
||||||
strategy,
|
strategy,
|
||||||
risk_distance,
|
risk_distance,
|
||||||
|
strategy_id,
|
||||||
|
strategy_name_snapshot,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
@ -1370,7 +1465,11 @@ def start_realtime_ws(states: dict):
|
|||||||
|
|
||||||
def main():
|
def main():
|
||||||
init_schema()
|
init_schema()
|
||||||
strategy_configs = load_strategy_configs()
|
# 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]
|
strategy_names = [cfg.get("name", "unknown") for cfg in strategy_configs]
|
||||||
logger.info(f"已加载策略配置: {', '.join(strategy_names)}")
|
logger.info(f"已加载策略配置: {', '.join(strategy_names)}")
|
||||||
primary_strategy_name = "v52_8signals" if any(cfg.get("name") == "v52_8signals" for cfg in strategy_configs) else strategy_names[0]
|
primary_strategy_name = "v52_8signals" if any(cfg.get("name") == "v52_8signals" for cfg in strategy_configs) else strategy_names[0]
|
||||||
@ -1409,7 +1508,10 @@ def main():
|
|||||||
# 每个策略独立存储indicator
|
# 每个策略独立存储indicator
|
||||||
for strategy_cfg, strategy_result in strategy_results:
|
for strategy_cfg, strategy_result in strategy_results:
|
||||||
sname = strategy_cfg.get("name", "v51_baseline")
|
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)
|
save_feature_event(now_ms, sym, strategy_result, strategy=sname)
|
||||||
|
|
||||||
# 1m表仍用primary(图表用)
|
# 1m表仍用primary(图表用)
|
||||||
@ -1463,6 +1565,8 @@ def main():
|
|||||||
factors=result.get("factors"),
|
factors=result.get("factors"),
|
||||||
strategy=strategy_name,
|
strategy=strategy_name,
|
||||||
tp_sl=strategy_cfg.get("tp_sl"),
|
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实时处理,这里不再检查
|
# 模拟盘持仓检查由paper_monitor.py通过WebSocket实时处理,这里不再检查
|
||||||
@ -1476,7 +1580,12 @@ def main():
|
|||||||
if cycle % 10 == 0:
|
if cycle % 10 == 0:
|
||||||
old_strategies = list(PAPER_ENABLED_STRATEGIES)
|
old_strategies = list(PAPER_ENABLED_STRATEGIES)
|
||||||
load_paper_config()
|
load_paper_config()
|
||||||
strategy_configs = load_strategy_configs() # A1: 热重载权重/阈值/TP/SL
|
# 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]
|
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]
|
primary_strategy_name = "v52_8signals" if any(cfg.get("name") == "v52_8signals" for cfg in strategy_configs) else strategy_names[0]
|
||||||
if list(PAPER_ENABLED_STRATEGIES) != old_strategies:
|
if list(PAPER_ENABLED_STRATEGIES) != old_strategies:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user