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")
|
@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:
|
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(
|
rows = await async_fetch(
|
||||||
"SELECT id, symbol, rate, annualized, sent_at, message FROM signal_logs ORDER BY sent_at DESC LIMIT $1",
|
f"SELECT id, ts, symbol, strategy, score, signal, price, factors "
|
||||||
limit
|
f"FROM signal_indicators {where} ORDER BY ts DESC LIMIT ${idx}",
|
||||||
|
*args
|
||||||
)
|
)
|
||||||
return {"items": rows}
|
return {"items": rows}
|
||||||
except Exception as e:
|
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()
|
conn.commit()
|
||||||
|
|
||||||
@ -1065,6 +976,9 @@ def main():
|
|||||||
if cycle % 60 == 0:
|
if cycle % 60 == 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
|
||||||
|
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:
|
if list(PAPER_ENABLED_STRATEGIES) != old_strategies:
|
||||||
logger.info(f"📋 配置热加载: enabled_strategies={PAPER_ENABLED_STRATEGIES}")
|
logger.info(f"📋 配置热加载: enabled_strategies={PAPER_ENABLED_STRATEGIES}")
|
||||||
for sym, state in states.items():
|
for sym, state in states.items():
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user