refactor: A1 hot-reload strategy configs, A4 remove dead paper_check_positions, A6 signals/history query signal_indicators
This commit is contained in:
parent
cf7756b4e5
commit
8280aaf6ea
@ -226,11 +226,30 @@ async def get_stats_ytd(user: dict = Depends(get_current_user)):
|
||||
|
||||
|
||||
@app.get("/api/signals/history")
|
||||
async def get_signals_history(limit: int = 100, user: dict = Depends(get_current_user)):
|
||||
async def get_signals_history(
|
||||
limit: int = 100,
|
||||
symbol: str = None,
|
||||
strategy: str = None,
|
||||
user: dict = Depends(get_current_user)
|
||||
):
|
||||
try:
|
||||
conditions = []
|
||||
args = []
|
||||
idx = 1
|
||||
if symbol:
|
||||
conditions.append(f"symbol = ${idx}")
|
||||
args.append(symbol.upper())
|
||||
idx += 1
|
||||
if strategy:
|
||||
conditions.append(f"strategy = ${idx}")
|
||||
args.append(strategy)
|
||||
idx += 1
|
||||
where = f"WHERE {' AND '.join(conditions)}" if conditions else ""
|
||||
args.append(limit)
|
||||
rows = await async_fetch(
|
||||
"SELECT id, symbol, rate, annualized, sent_at, message FROM signal_logs ORDER BY sent_at DESC LIMIT $1",
|
||||
limit
|
||||
f"SELECT id, ts, symbol, strategy, score, signal, price, factors "
|
||||
f"FROM signal_indicators {where} ORDER BY ts DESC LIMIT ${idx}",
|
||||
*args
|
||||
)
|
||||
return {"items": rows}
|
||||
except Exception as e:
|
||||
|
||||
@ -785,95 +785,6 @@ def paper_open_trade(
|
||||
)
|
||||
|
||||
|
||||
def paper_check_positions(symbol: str, current_price: float, now_ms: int):
|
||||
"""检查模拟盘持仓的止盈止损"""
|
||||
with get_sync_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"SELECT id, direction, entry_price, tp1_price, tp2_price, sl_price, tp1_hit, entry_ts, atr_at_entry, risk_distance "
|
||||
"FROM paper_trades WHERE symbol=%s AND status IN ('active','tp1_hit') ORDER BY id",
|
||||
(symbol,)
|
||||
)
|
||||
positions = cur.fetchall()
|
||||
|
||||
for pos in positions:
|
||||
pid, direction, entry_price, tp1, tp2, sl, tp1_hit, entry_ts, atr_entry, rd_db = pos
|
||||
closed = False
|
||||
new_status = None
|
||||
pnl_r = 0.0
|
||||
risk_distance = rd_db if rd_db and rd_db > 0 else abs(entry_price - sl)
|
||||
|
||||
# === 实盘模拟:TP/SL视为限价单,以挂单价成交 ===
|
||||
if direction == "LONG":
|
||||
if current_price <= sl:
|
||||
closed = True
|
||||
exit_price = sl
|
||||
if tp1_hit:
|
||||
new_status = "sl_be"
|
||||
tp1_r = (tp1 - entry_price) / risk_distance if risk_distance > 0 else 0
|
||||
pnl_r = 0.5 * tp1_r
|
||||
else:
|
||||
new_status = "sl"
|
||||
pnl_r = (exit_price - entry_price) / risk_distance if risk_distance > 0 else -1.0
|
||||
elif not tp1_hit and current_price >= tp1:
|
||||
# TP1触发,移动止损到成本价
|
||||
new_sl = entry_price * 1.0005
|
||||
cur.execute("UPDATE paper_trades SET tp1_hit=TRUE, sl_price=%s, status='tp1_hit' WHERE id=%s", (new_sl, pid))
|
||||
logger.info(f"[{symbol}] 📝 TP1触发 LONG @ {current_price:.2f}, SL移至成本{new_sl:.2f}")
|
||||
elif tp1_hit and current_price >= tp2:
|
||||
closed = True
|
||||
exit_price = tp2
|
||||
new_status = "tp"
|
||||
tp1_r = (tp1 - entry_price) / risk_distance if risk_distance > 0 else 0
|
||||
tp2_r = (tp2 - entry_price) / risk_distance if risk_distance > 0 else 0
|
||||
pnl_r = 0.5 * tp1_r + 0.5 * tp2_r
|
||||
else: # SHORT
|
||||
if current_price >= sl:
|
||||
closed = True
|
||||
exit_price = sl
|
||||
if tp1_hit:
|
||||
new_status = "sl_be"
|
||||
tp1_r = (entry_price - tp1) / risk_distance if risk_distance > 0 else 0
|
||||
pnl_r = 0.5 * tp1_r
|
||||
else:
|
||||
new_status = "sl"
|
||||
pnl_r = (entry_price - exit_price) / risk_distance if risk_distance > 0 else -1.0
|
||||
elif not tp1_hit and current_price <= tp1:
|
||||
new_sl = entry_price * 0.9995
|
||||
cur.execute("UPDATE paper_trades SET tp1_hit=TRUE, sl_price=%s, status='tp1_hit' WHERE id=%s", (new_sl, pid))
|
||||
logger.info(f"[{symbol}] 📝 TP1触发 SHORT @ {current_price:.2f}, SL移至成本{new_sl:.2f}")
|
||||
elif tp1_hit and current_price <= tp2:
|
||||
closed = True
|
||||
exit_price = tp2
|
||||
new_status = "tp"
|
||||
tp1_r = (entry_price - tp1) / risk_distance if risk_distance > 0 else 0
|
||||
tp2_r = (entry_price - tp2) / risk_distance if risk_distance > 0 else 0
|
||||
pnl_r = 0.5 * tp1_r + 0.5 * tp2_r
|
||||
|
||||
# 时间止损:60分钟(市价平仓)
|
||||
if not closed and (now_ms - entry_ts > 60 * 60 * 1000):
|
||||
closed = True
|
||||
exit_price = current_price
|
||||
new_status = "timeout"
|
||||
if direction == "LONG":
|
||||
move = current_price - entry_price
|
||||
else:
|
||||
move = entry_price - current_price
|
||||
pnl_r = move / risk_distance if risk_distance > 0 else 0
|
||||
if tp1_hit:
|
||||
tp1_r = abs(tp1 - entry_price) / risk_distance if risk_distance > 0 else 0
|
||||
pnl_r = max(pnl_r, 0.5 * tp1_r)
|
||||
|
||||
if closed:
|
||||
# 扣除手续费(开仓+平仓各Taker 0.05%)
|
||||
fee_r = (2 * PAPER_FEE_RATE * entry_price) / risk_distance if risk_distance > 0 else 0
|
||||
pnl_r -= fee_r
|
||||
|
||||
cur.execute(
|
||||
"UPDATE paper_trades SET status=%s, exit_price=%s, exit_ts=%s, pnl_r=%s WHERE id=%s",
|
||||
(new_status, exit_price, now_ms, round(pnl_r, 4), pid)
|
||||
)
|
||||
logger.info(f"[{symbol}] 📝 模拟平仓: {direction} @ {exit_price:.2f} status={new_status} pnl={pnl_r:+.2f}R (fee={fee_r:.3f}R)")
|
||||
|
||||
conn.commit()
|
||||
|
||||
@ -1065,6 +976,9 @@ def main():
|
||||
if cycle % 60 == 0:
|
||||
old_strategies = list(PAPER_ENABLED_STRATEGIES)
|
||||
load_paper_config()
|
||||
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]
|
||||
if list(PAPER_ENABLED_STRATEGIES) != old_strategies:
|
||||
logger.info(f"📋 配置热加载: enabled_strategies={PAPER_ENABLED_STRATEGIES}")
|
||||
for sym, state in states.items():
|
||||
|
||||
Loading…
Reference in New Issue
Block a user