feat: paper trading signal flip - reverse signal closes existing position then opens new
This commit is contained in:
parent
f90df6f3b5
commit
66810701fb
@ -607,6 +607,43 @@ def paper_has_active_position(symbol: str) -> bool:
|
|||||||
return cur.fetchone()[0] > 0
|
return cur.fetchone()[0] > 0
|
||||||
|
|
||||||
|
|
||||||
|
def paper_get_active_direction(symbol: str) -> str | None:
|
||||||
|
"""获取该币种活跃持仓的方向,无持仓返回None"""
|
||||||
|
with get_sync_conn() as conn:
|
||||||
|
with conn.cursor() as cur:
|
||||||
|
cur.execute("SELECT direction FROM paper_trades WHERE symbol=%s AND status IN ('active','tp1_hit') LIMIT 1", (symbol,))
|
||||||
|
row = cur.fetchone()
|
||||||
|
return row[0] if row else None
|
||||||
|
|
||||||
|
|
||||||
|
def paper_close_by_signal(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_hit, atr_at_entry "
|
||||||
|
"FROM paper_trades WHERE symbol=%s AND status IN ('active','tp1_hit')",
|
||||||
|
(symbol,)
|
||||||
|
)
|
||||||
|
positions = cur.fetchall()
|
||||||
|
for pos in positions:
|
||||||
|
pid, direction, entry_price, tp1_hit, atr_entry = pos
|
||||||
|
risk_distance = 2.0 * 0.7 * atr_entry if atr_entry > 0 else 1
|
||||||
|
if direction == "LONG":
|
||||||
|
pnl_r = (current_price - entry_price) / risk_distance if risk_distance > 0 else 0
|
||||||
|
else:
|
||||||
|
pnl_r = (entry_price - current_price) / risk_distance if risk_distance > 0 else 0
|
||||||
|
# 扣手续费
|
||||||
|
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='signal_flip', exit_price=%s, exit_ts=%s, pnl_r=%s WHERE id=%s",
|
||||||
|
(current_price, now_ms, round(pnl_r, 4), pid)
|
||||||
|
)
|
||||||
|
logger.info(f"[{symbol}] 📝 反向信号平仓: {direction} @ {current_price:.2f} pnl={pnl_r:+.2f}R")
|
||||||
|
conn.commit()
|
||||||
|
|
||||||
|
|
||||||
def paper_active_count() -> int:
|
def paper_active_count() -> int:
|
||||||
"""当前所有币种活跃持仓总数"""
|
"""当前所有币种活跃持仓总数"""
|
||||||
with get_sync_conn() as conn:
|
with get_sync_conn() as conn:
|
||||||
@ -651,7 +688,16 @@ def main():
|
|||||||
if result.get("signal"):
|
if result.get("signal"):
|
||||||
logger.info(f"[{sym}] 🚨 信号: {result['signal']} score={result['score']} price={result['price']:.1f}")
|
logger.info(f"[{sym}] 🚨 信号: {result['signal']} score={result['score']} price={result['price']:.1f}")
|
||||||
# 模拟盘开仓(需开关开启)
|
# 模拟盘开仓(需开关开启)
|
||||||
if PAPER_TRADING_ENABLED and not paper_has_active_position(sym):
|
if PAPER_TRADING_ENABLED:
|
||||||
|
existing_dir = paper_get_active_direction(sym)
|
||||||
|
new_dir = result["signal"]
|
||||||
|
|
||||||
|
if existing_dir and existing_dir != new_dir:
|
||||||
|
# 反向信号:先平掉现有仓位
|
||||||
|
paper_close_by_signal(sym, result["price"], now_ms)
|
||||||
|
logger.info(f"[{sym}] 📝 反向信号平仓: {existing_dir} → {new_dir}")
|
||||||
|
|
||||||
|
if not paper_has_active_position(sym):
|
||||||
active_count = paper_active_count()
|
active_count = paper_active_count()
|
||||||
if active_count < PAPER_MAX_POSITIONS:
|
if active_count < PAPER_MAX_POSITIONS:
|
||||||
tier = result.get("tier", "standard")
|
tier = result.get("tier", "standard")
|
||||||
|
|||||||
@ -268,9 +268,10 @@ function TradeHistory() {
|
|||||||
t.status === "tp" ? "bg-emerald-100 text-emerald-700" :
|
t.status === "tp" ? "bg-emerald-100 text-emerald-700" :
|
||||||
t.status === "sl" ? "bg-red-100 text-red-700" :
|
t.status === "sl" ? "bg-red-100 text-red-700" :
|
||||||
t.status === "sl_be" ? "bg-amber-100 text-amber-700" :
|
t.status === "sl_be" ? "bg-amber-100 text-amber-700" :
|
||||||
|
t.status === "signal_flip" ? "bg-purple-100 text-purple-700" :
|
||||||
"bg-slate-100 text-slate-600"
|
"bg-slate-100 text-slate-600"
|
||||||
}`}>
|
}`}>
|
||||||
{t.status === "tp" ? "止盈" : t.status === "sl" ? "止损" : t.status === "sl_be" ? "保本" : t.status === "timeout" ? "超时" : t.status}
|
{t.status === "tp" ? "止盈" : t.status === "sl" ? "止损" : t.status === "sl_be" ? "保本" : t.status === "timeout" ? "超时" : t.status === "signal_flip" ? "翻转" : t.status}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="px-2 py-1.5 text-right font-mono">{t.score}</td>
|
<td className="px-2 py-1.5 text-right font-mono">{t.score}</td>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user