diff --git a/backend/main.py b/backend/main.py index f771e32..06055a0 100644 --- a/backend/main.py +++ b/backend/main.py @@ -753,13 +753,12 @@ async def paper_positions( d["current_price"] = current_price # 浮动盈亏(R) entry = r["entry_price"] - atr = r["atr_at_entry"] or 1 - risk_distance = 2.0 * 0.7 * atr - if risk_distance > 0 and entry > 0: + rd = r.get("risk_distance") or abs(entry - r["sl_price"]) or 1 + if rd > 0 and entry > 0: if r["direction"] == "LONG": - d["unrealized_pnl_r"] = round((current_price - entry) / risk_distance, 2) + d["unrealized_pnl_r"] = round((current_price - entry) / rd, 2) else: - d["unrealized_pnl_r"] = round((entry - current_price) / risk_distance, 2) + d["unrealized_pnl_r"] = round((entry - current_price) / rd, 2) # 浮动盈亏(USDT) — 假设1R = risk_per_trade d["unrealized_pnl_usdt"] = round(d["unrealized_pnl_r"] * 200, 2) # 2% of 10000 else: diff --git a/backend/paper_monitor.py b/backend/paper_monitor.py index 5f7df4b..898d13b 100644 --- a/backend/paper_monitor.py +++ b/backend/paper_monitor.py @@ -49,20 +49,20 @@ def check_and_close(symbol_upper: str, price: float): 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 " + "tp1_hit, entry_ts, atr_at_entry, risk_distance " "FROM paper_trades WHERE symbol=%s AND status IN ('active','tp1_hit')", (symbol_upper,) ) positions = cur.fetchall() for pos in positions: - pid, direction, entry_price, tp1, tp2, sl, tp1_hit, entry_ts, atr_entry = pos + 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 (1R基准距离) - risk_distance = 2.0 * 0.7 * atr_entry if atr_entry > 0 else 1 + # 从DB读risk_distance,fallback用|entry-sl| + risk_distance = rd_db if rd_db and rd_db > 0 else abs(entry_price - sl) # === 实盘模拟:TP/SL视为限价单,以挂单价成交(非市价) === if direction == "LONG": @@ -128,7 +128,6 @@ def check_and_close(symbol_upper: str, price: float): if closed: # 扣手续费 - risk_distance = 2.0 * 0.7 * atr_entry if atr_entry > 0 else 1 fee_r = (2 * FEE_RATE * entry_price) / risk_distance if risk_distance > 0 else 0 pnl_r -= fee_r diff --git a/backend/signal_engine.py b/backend/signal_engine.py index 55009a1..b974381 100644 --- a/backend/signal_engine.py +++ b/backend/signal_engine.py @@ -736,26 +736,26 @@ def paper_open_trade( ): """模拟开仓""" import json as _json3 - risk_atr = 0.7 * atr - if risk_atr <= 0: + if atr <= 0: return sl_multiplier = float((tp_sl or {}).get("sl_multiplier", 2.0)) tp1_multiplier = float((tp_sl or {}).get("tp1_multiplier", 1.5)) tp2_multiplier = float((tp_sl or {}).get("tp2_multiplier", 3.0)) + risk_distance = sl_multiplier * atr # 1R = SL距离 = sl_multiplier × ATR if direction == "LONG": - sl = price - sl_multiplier * risk_atr - tp1 = price + tp1_multiplier * risk_atr - tp2 = price + tp2_multiplier * risk_atr + sl = price - sl_multiplier * atr + tp1 = price + tp1_multiplier * atr + tp2 = price + tp2_multiplier * atr else: - sl = price + sl_multiplier * risk_atr - tp1 = price - tp1_multiplier * risk_atr - tp2 = price - tp2_multiplier * risk_atr + sl = price + sl_multiplier * atr + tp1 = price - tp1_multiplier * atr + tp2 = price - tp2_multiplier * atr with get_sync_conn() as conn: with conn.cursor() as cur: 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) " - "VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", + "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) " + "VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)", ( symbol, direction, @@ -769,6 +769,7 @@ def paper_open_trade( atr, _json3.dumps(factors) if factors else None, strategy, + risk_distance, ), ) conn.commit() @@ -783,18 +784,18 @@ 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 " + "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 = pos + 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 = 2.0 * 0.7 * atr_entry if atr_entry > 0 else 1 + risk_distance = rd_db if rd_db and rd_db > 0 else abs(entry_price - sl) # === 实盘模拟:TP/SL视为限价单,以挂单价成交 === if direction == "LONG": @@ -859,7 +860,6 @@ def paper_check_positions(symbol: str, current_price: float, now_ms: int): if closed: # 扣除手续费(开仓+平仓各Taker 0.05%) - risk_distance = 2.0 * 0.7 * atr_entry if atr_entry > 0 else 1 fee_r = (2 * PAPER_FEE_RATE * entry_price) / risk_distance if risk_distance > 0 else 0 pnl_r -= fee_r @@ -910,20 +910,20 @@ def paper_close_by_signal(symbol: str, current_price: float, now_ms: int, strate with conn.cursor() as cur: if strategy: cur.execute( - "SELECT id, direction, entry_price, tp1_hit, atr_at_entry " + "SELECT id, direction, entry_price, tp1_hit, atr_at_entry, risk_distance " "FROM paper_trades WHERE symbol=%s AND strategy=%s AND status IN ('active','tp1_hit')", (symbol, strategy), ) else: cur.execute( - "SELECT id, direction, entry_price, tp1_hit, atr_at_entry " + "SELECT id, direction, entry_price, tp1_hit, atr_at_entry, risk_distance " "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 + pid, direction, entry_price, tp1_hit, atr_entry, rd_db = pos + risk_distance = rd_db if rd_db and rd_db > 0 else abs(entry_price * 0.01) if direction == "LONG": pnl_r = (current_price - entry_price) / risk_distance if risk_distance > 0 else 0 else: diff --git a/backend/strategies/v51_baseline.json b/backend/strategies/v51_baseline.json index 814d6bf..c632bc4 100644 --- a/backend/strategies/v51_baseline.json +++ b/backend/strategies/v51_baseline.json @@ -11,9 +11,9 @@ }, "accel_bonus": 5, "tp_sl": { - "sl_multiplier": 2.0, - "tp1_multiplier": 1.5, - "tp2_multiplier": 3.0 + "sl_multiplier": 1.4, + "tp1_multiplier": 1.05, + "tp2_multiplier": 2.1 }, "signals": ["cvd", "p99", "accel", "ls_ratio", "oi", "coinbase_premium"] } diff --git a/backend/strategies/v52_8signals.json b/backend/strategies/v52_8signals.json index d405dab..cdfa269 100644 --- a/backend/strategies/v52_8signals.json +++ b/backend/strategies/v52_8signals.json @@ -13,9 +13,9 @@ }, "accel_bonus": 0, "tp_sl": { - "sl_multiplier": 3.0, - "tp1_multiplier": 2.0, - "tp2_multiplier": 4.5 + "sl_multiplier": 2.1, + "tp1_multiplier": 1.4, + "tp2_multiplier": 3.15 }, "signals": ["cvd", "p99", "accel", "ls_ratio", "oi", "coinbase_premium", "funding_rate", "liquidation"] }