arbitrage-engine/backend/fix_historical_pnl.py

99 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
fix_historical_pnl.py — 修正历史paper_trades中虚高的pnl_r
问题TP/SL_BE场景用了硬编码倍数(1.5R/2.25R)实际应为0.75R/1.125R
修复:用(exit_price - entry_price) / risk_distance 重算
"""
import os, sys
sys.path.insert(0, os.path.dirname(__file__))
from db import get_sync_conn
FEE_RATE = 0.0005 # Taker 0.05%
def fix():
with get_sync_conn() as conn:
cur = conn.cursor()
# 读取所有已平仓记录
cur.execute("""
SELECT id, direction, entry_price, exit_price, tp1_price, tp2_price,
sl_price, tp1_hit, status, pnl_r, atr_at_entry
FROM paper_trades
WHERE status NOT IN ('active', 'tp1_hit')
ORDER BY id
""")
rows = cur.fetchall()
fixed = 0
for row in rows:
pid, direction, entry, exit_p, tp1, tp2, sl, tp1_hit, status, old_pnl, atr_entry = row
if exit_p is None or entry is None or atr_entry is None or atr_entry <= 0:
continue
risk_distance = 2.0 * 0.7 * atr_entry
if risk_distance <= 0:
continue
# 重算pnl_r
if status == "tp":
# 半仓TP1 + 半仓TP2
if direction == "LONG":
tp1_r = (tp1 - entry) / risk_distance
tp2_r = (tp2 - entry) / risk_distance
else:
tp1_r = (entry - tp1) / risk_distance
tp2_r = (entry - tp2) / risk_distance
new_pnl = 0.5 * tp1_r + 0.5 * tp2_r
elif status == "sl_be":
# TP1半仓锁定 + SL_BE半仓归零
if direction == "LONG":
tp1_r = (tp1 - entry) / risk_distance
else:
tp1_r = (entry - tp1) / risk_distance
new_pnl = 0.5 * tp1_r
elif status == "sl":
# 全仓止损
if direction == "LONG":
new_pnl = (exit_p - entry) / risk_distance
else:
new_pnl = (entry - exit_p) / risk_distance
elif status == "timeout":
if direction == "LONG":
move = exit_p - entry
else:
move = entry - exit_p
new_pnl = move / risk_distance
if tp1_hit:
tp1_r = abs(tp1 - entry) / risk_distance
new_pnl = max(new_pnl, 0.5 * tp1_r)
elif status == "signal_flip":
if direction == "LONG":
new_pnl = (exit_p - entry) / risk_distance
else:
new_pnl = (entry - exit_p) / risk_distance
else:
continue
# 扣手续费
fee_r = (2 * FEE_RATE * entry) / risk_distance if risk_distance > 0 else 0
new_pnl -= fee_r
new_pnl = round(new_pnl, 4)
if abs(new_pnl - old_pnl) > 0.001:
print(f" #{pid} {status:10s} {direction:5s}: {old_pnl:+.4f}R → {new_pnl:+.4f}R (Δ{new_pnl-old_pnl:+.4f})")
cur.execute("UPDATE paper_trades SET pnl_r = %s WHERE id = %s", (new_pnl, pid))
fixed += 1
conn.commit()
# 汇总
cur.execute("SELECT COUNT(*), SUM(pnl_r) FROM paper_trades WHERE status NOT IN ('active','tp1_hit')")
total, total_pnl = cur.fetchone()
print(f"\n修正了 {fixed}/{len(rows)} 笔交易")
print(f"修正后总计: {total}笔, 总pnl={total_pnl:+.2f}R")
if __name__ == "__main__":
fix()