feat: paper positions show real-time price + unrealized PnL (R + USDT)
This commit is contained in:
parent
47004ece8c
commit
f90df6f3b5
@ -562,13 +562,46 @@ async def paper_summary(user: dict = Depends(get_current_user)):
|
||||
|
||||
@app.get("/api/paper/positions")
|
||||
async def paper_positions(user: dict = Depends(get_current_user)):
|
||||
"""当前活跃持仓"""
|
||||
"""当前活跃持仓(含实时价格和浮动盈亏)"""
|
||||
rows = await async_fetch(
|
||||
"SELECT id, symbol, direction, score, tier, entry_price, entry_ts, "
|
||||
"tp1_price, tp2_price, sl_price, tp1_hit, status, atr_at_entry "
|
||||
"FROM paper_trades WHERE status IN ('active','tp1_hit') ORDER BY entry_ts DESC"
|
||||
)
|
||||
return {"data": rows}
|
||||
# 获取实时价格
|
||||
prices = {}
|
||||
for r in rows:
|
||||
sym = r["symbol"]
|
||||
if sym not in prices:
|
||||
try:
|
||||
latest = await async_fetchrow(
|
||||
"SELECT price FROM signal_indicators WHERE symbol=$1 ORDER BY ts DESC LIMIT 1", sym
|
||||
)
|
||||
prices[sym] = latest["price"] if latest else 0
|
||||
except Exception:
|
||||
prices[sym] = 0
|
||||
|
||||
result = []
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
current_price = prices.get(r["symbol"], 0)
|
||||
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:
|
||||
if r["direction"] == "LONG":
|
||||
d["unrealized_pnl_r"] = round((current_price - entry) / risk_distance, 2)
|
||||
else:
|
||||
d["unrealized_pnl_r"] = round((entry - current_price) / risk_distance, 2)
|
||||
# 浮动盈亏(USDT) — 假设1R = risk_per_trade
|
||||
d["unrealized_pnl_usdt"] = round(d["unrealized_pnl_r"] * 200, 2) # 2% of 10000
|
||||
else:
|
||||
d["unrealized_pnl_r"] = 0
|
||||
d["unrealized_pnl_usdt"] = 0
|
||||
result.append(d)
|
||||
return {"data": result}
|
||||
|
||||
|
||||
@app.get("/api/paper/trades")
|
||||
|
||||
@ -136,10 +136,19 @@ function ActivePositions() {
|
||||
</span>
|
||||
<span className="text-[10px] text-slate-400">评分{p.score} · {p.tier === "heavy" ? "加仓" : p.tier === "standard" ? "标准" : "轻仓"}</span>
|
||||
</div>
|
||||
<span className="text-[10px] text-slate-400">{holdMin}分钟</span>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={`font-mono text-sm font-bold ${(p.unrealized_pnl_r ?? 0) >= 0 ? "text-emerald-600" : "text-red-500"}`}>
|
||||
{(p.unrealized_pnl_r ?? 0) >= 0 ? "+" : ""}{(p.unrealized_pnl_r ?? 0).toFixed(2)}R
|
||||
</span>
|
||||
<span className={`font-mono text-[10px] ${(p.unrealized_pnl_usdt ?? 0) >= 0 ? "text-emerald-500" : "text-red-400"}`}>
|
||||
({(p.unrealized_pnl_usdt ?? 0) >= 0 ? "+" : ""}${(p.unrealized_pnl_usdt ?? 0).toFixed(0)})
|
||||
</span>
|
||||
<span className="text-[10px] text-slate-400">{holdMin}m</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-3 mt-1 text-[10px] font-mono text-slate-600">
|
||||
<span>入场: ${fmtPrice(p.entry_price)}</span>
|
||||
<span className="text-blue-600">现价: ${p.current_price ? fmtPrice(p.current_price) : "-"}</span>
|
||||
<span className="text-emerald-600">TP1: ${fmtPrice(p.tp1_price)}{p.tp1_hit ? " ✅" : ""}</span>
|
||||
<span className="text-emerald-600">TP2: ${fmtPrice(p.tp2_price)}</span>
|
||||
<span className="text-red-500">SL: ${fmtPrice(p.sl_price)}</span>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user