fix: remove 0.7 ATR multiplier, store risk_distance in DB

- Removed 0.7*ATR multiplier from signal_engine and paper_monitor
- risk_distance now stored per-trade in paper_trades table
- paper_monitor/signal_engine read risk_distance from DB (no hardcoded values)
- V5.1 TP/SL: 2.0/1.5/3.0 -> 1.4/1.05/2.1 (same actual distance)
- V5.2 TP/SL: 3.0/2.0/4.5 -> 2.1/1.4/3.15 (same actual distance)
- Fixed V5.2 pnl_r: all historical values corrected by *2/3
- API unrealized_pnl_r also reads from DB risk_distance
This commit is contained in:
root 2026-03-02 07:00:05 +00:00
parent 02a769f513
commit 72ea0ffd0e
5 changed files with 32 additions and 34 deletions

View File

@ -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:

View File

@ -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_distancefallback用|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

View File

@ -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:

View File

@ -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"]
}

View File

@ -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"]
}