1
This commit is contained in:
parent
1b8c1310bb
commit
c781dfef08
@ -1256,6 +1256,98 @@ def get_paper_trading_summary(days: int = 30) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def get_paper_trading_performance(days: int = 30) -> dict:
|
||||
days = max(1, min(_safe_int(days, 30), 365))
|
||||
cfg = paper_trading_config()
|
||||
initial_equity = default_account_equity_usdt(cfg)
|
||||
today = datetime.now().date()
|
||||
start_date = today - timedelta(days=days - 1)
|
||||
conn = get_conn()
|
||||
try:
|
||||
closed_rows = conn.execute(
|
||||
"""
|
||||
SELECT closed_at, realized_pnl_usdt
|
||||
FROM paper_trades
|
||||
WHERE status='closed' AND closed_at IS NOT NULL
|
||||
ORDER BY closed_at ASC, id ASC
|
||||
"""
|
||||
).fetchall()
|
||||
open_unrealized = conn.execute(
|
||||
"""
|
||||
SELECT COALESCE(SUM(notional_usdt * pnl_pct / 100.0), 0)
|
||||
FROM paper_trades
|
||||
WHERE status='open'
|
||||
"""
|
||||
).fetchone()[0]
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
daily_realized = {}
|
||||
realized_before = 0.0
|
||||
for row in closed_rows:
|
||||
closed_at = _parse_time(row["closed_at"])
|
||||
pnl = _safe_float(row["realized_pnl_usdt"])
|
||||
if not closed_at:
|
||||
continue
|
||||
closed_date = closed_at.date()
|
||||
if closed_date < start_date:
|
||||
realized_before += pnl
|
||||
continue
|
||||
if closed_date > today:
|
||||
continue
|
||||
key = closed_date.isoformat()
|
||||
daily_realized[key] = daily_realized.get(key, 0.0) + pnl
|
||||
|
||||
points = []
|
||||
cumulative_realized = realized_before
|
||||
prev_equity = initial_equity + realized_before
|
||||
peak_equity = max(initial_equity, prev_equity)
|
||||
max_drawdown_pct = 0.0
|
||||
max_drawdown_usdt = 0.0
|
||||
open_unrealized = _safe_float(open_unrealized)
|
||||
|
||||
for idx in range(days):
|
||||
day = start_date + timedelta(days=idx)
|
||||
key = day.isoformat()
|
||||
realized = round(daily_realized.get(key, 0.0), 8)
|
||||
cumulative_realized += realized
|
||||
unrealized = open_unrealized if day == today else 0.0
|
||||
equity = round(initial_equity + cumulative_realized + unrealized, 8)
|
||||
daily_pnl = round(equity - prev_equity, 8)
|
||||
daily_return_pct = round(daily_pnl / prev_equity * 100, 6) if prev_equity > 0 else 0.0
|
||||
peak_equity = max(peak_equity, equity)
|
||||
drawdown_usdt = round(max(0.0, peak_equity - equity), 8)
|
||||
drawdown_pct = round(drawdown_usdt / peak_equity * 100, 6) if peak_equity > 0 else 0.0
|
||||
max_drawdown_pct = max(max_drawdown_pct, drawdown_pct)
|
||||
max_drawdown_usdt = max(max_drawdown_usdt, drawdown_usdt)
|
||||
points.append(
|
||||
{
|
||||
"date": key,
|
||||
"equity_usdt": equity,
|
||||
"daily_pnl_usdt": daily_pnl,
|
||||
"daily_return_pct": daily_return_pct,
|
||||
"realized_pnl_usdt": round(cumulative_realized, 8),
|
||||
"unrealized_pnl_usdt": round(unrealized, 8),
|
||||
"return_pct": _account_return_pct(equity - initial_equity, initial_equity, cfg),
|
||||
"drawdown_usdt": drawdown_usdt,
|
||||
"drawdown_pct": drawdown_pct,
|
||||
}
|
||||
)
|
||||
prev_equity = equity
|
||||
|
||||
total_pnl = round((points[-1]["equity_usdt"] - initial_equity) if points else 0.0, 8)
|
||||
return {
|
||||
"days": days,
|
||||
"initial_equity_usdt": initial_equity,
|
||||
"current_equity_usdt": points[-1]["equity_usdt"] if points else initial_equity,
|
||||
"total_pnl_usdt": total_pnl,
|
||||
"total_return_pct": _account_return_pct(total_pnl, initial_equity, cfg),
|
||||
"max_drawdown_usdt": round(max_drawdown_usdt, 8),
|
||||
"max_drawdown_pct": round(max_drawdown_pct, 6),
|
||||
"points": points,
|
||||
}
|
||||
|
||||
|
||||
def list_paper_trades(limit: int = 50, offset: int = 0, status: str = "") -> dict:
|
||||
limit = max(1, min(_safe_int(limit, 50), 200))
|
||||
offset = max(0, _safe_int(offset, 0))
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
from fastapi import APIRouter, Cookie
|
||||
|
||||
from app.db.paper_trading import (
|
||||
get_paper_trading_performance,
|
||||
get_paper_trading_summary,
|
||||
list_paper_orders,
|
||||
list_paper_trade_events,
|
||||
@ -19,6 +20,12 @@ async def api_paper_trading_summary(days: int = 30, altcoin_session: str = Cooki
|
||||
return get_paper_trading_summary(days=days)
|
||||
|
||||
|
||||
@router.get("/api/paper-trading/performance")
|
||||
async def api_paper_trading_performance(days: int = 30, altcoin_session: str = Cookie(default="")):
|
||||
require_admin(altcoin_session)
|
||||
return get_paper_trading_performance(days=days)
|
||||
|
||||
|
||||
@router.get("/api/paper-trading/trades")
|
||||
async def api_paper_trading_trades(
|
||||
limit: int = 50,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,9 +1,11 @@
|
||||
import json
|
||||
from datetime import datetime
|
||||
|
||||
import pytest
|
||||
|
||||
from app.db import altcoin_db
|
||||
from app.db.paper_trading import (
|
||||
get_paper_trading_performance,
|
||||
get_paper_trading_summary,
|
||||
list_paper_orders,
|
||||
list_paper_trade_events,
|
||||
@ -136,6 +138,26 @@ def test_paper_margin_is_derived_from_notional_and_leverage(monkeypatch):
|
||||
assert summary["margin_usdt"] == pytest.approx(1000.0)
|
||||
|
||||
|
||||
def test_paper_performance_returns_daily_equity_curve(monkeypatch, buy_now_rec):
|
||||
monkeypatch.setenv("ALPHAX_PAPER_TRADE_NOTIONAL_USDT", "100")
|
||||
monkeypatch.setenv("ALPHAX_PAPER_TRADE_FEE_RATE", "0")
|
||||
monkeypatch.setenv("ALPHAX_PAPER_TRADE_SLIPPAGE_PCT", "0")
|
||||
now = datetime.now().replace(microsecond=0).isoformat()
|
||||
|
||||
sync_recommendation(buy_now_rec, 100, event_time=now)
|
||||
sync_recommendation(buy_now_rec, 106, event_time=now)
|
||||
|
||||
curve = get_paper_trading_performance(days=7)
|
||||
|
||||
assert curve["points"]
|
||||
assert curve["current_equity_usdt"] == pytest.approx(20006.0)
|
||||
assert curve["total_pnl_usdt"] == pytest.approx(6.0)
|
||||
assert curve["total_return_pct"] == pytest.approx(0.03)
|
||||
assert curve["max_drawdown_pct"] >= 0
|
||||
assert curve["points"][-1]["daily_pnl_usdt"] == pytest.approx(6.0)
|
||||
assert curve["points"][-1]["equity_usdt"] == pytest.approx(20006.0)
|
||||
|
||||
|
||||
def test_observation_does_not_open_paper_trade(monkeypatch):
|
||||
monkeypatch.setenv("ALPHAX_PAPER_TRADING_ENABLED", "1")
|
||||
altcoin_db.init_db()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user