diff --git a/backend/main.py b/backend/main.py index 0ab80d7..2d29371 100644 --- a/backend/main.py +++ b/backend/main.py @@ -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") diff --git a/frontend/app/paper/page.tsx b/frontend/app/paper/page.tsx index 226d618..1bee316 100644 --- a/frontend/app/paper/page.tsx +++ b/frontend/app/paper/page.tsx @@ -136,10 +136,19 @@ function ActivePositions() { 评分{p.score} · {p.tier === "heavy" ? "加仓" : p.tier === "standard" ? "标准" : "轻仓"} - {holdMin}分钟 +
+ = 0 ? "text-emerald-600" : "text-red-500"}`}> + {(p.unrealized_pnl_r ?? 0) >= 0 ? "+" : ""}{(p.unrealized_pnl_r ?? 0).toFixed(2)}R + + = 0 ? "text-emerald-500" : "text-red-400"}`}> + ({(p.unrealized_pnl_usdt ?? 0) >= 0 ? "+" : ""}${(p.unrealized_pnl_usdt ?? 0).toFixed(0)}) + + {holdMin}m +
入场: ${fmtPrice(p.entry_price)} + 现价: ${p.current_price ? fmtPrice(p.current_price) : "-"} TP1: ${fmtPrice(p.tp1_price)}{p.tp1_hit ? " ✅" : ""} TP2: ${fmtPrice(p.tp2_price)} SL: ${fmtPrice(p.sl_price)}