平仓汇总按原始价差计算毛利和净利,红涨绿跌配色

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
fish
2026-07-28 15:30:04 +08:00
parent 7ac9de3663
commit 0d01faf464
2 changed files with 110 additions and 45 deletions
+90 -24
View File
@@ -9,31 +9,95 @@ router = APIRouter(prefix="/summary", tags=["summary"])
WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
def _stats(records, pnl_fn=None) -> dict:
"""Compute summary stats for a list of closed trade records."""
if pnl_fn is None:
pnl_fn = lambda r: r.pnl
def _future_stats(records) -> dict:
total = len(records)
total_pnl = sum(pnl_fn(r) or 0 for r in records)
wins = sum(1 for r in records if (pnl_fn(r) or 0) > 0)
open_fee = sum((r.open_fee or 0) for r in records)
close_fee = sum((r.close_fee or 0) for r in records)
total_fee = open_fee + close_fee
# For dual options, also include call/put specific fees
if records and hasattr(records[0], 'call_open_fee'):
open_fee = sum((r.call_open_fee or 0) + (r.put_open_fee or 0) for r in records)
close_fee = sum((r.call_close_fee or 0) + (r.put_close_fee or 0) for r in records)
total_fee = open_fee + close_fee
gross = 0.0
open_fee = 0.0
close_fee = 0.0
wins = 0
for t in records:
mul = t.point_value
if t.direction == "short":
g = (t.open_price - t.close_price) * mul
else:
g = (t.close_price - t.open_price) * mul
gross += g
of = t.open_fee or 0
cf = t.close_fee or 0
open_fee += of
close_fee += cf
if g - of - cf > 0:
wins += 1
return {
"count": total,
"pnl": total_pnl,
"gross": gross,
"net": gross - open_fee - close_fee,
"wins": wins,
"win_rate": round(wins / total * 100, 1) if total > 0 else 0,
"open_fee": open_fee,
"close_fee": close_fee,
"total_fee": total_fee,
"total_fee": open_fee + close_fee,
}
def _option_stats(records) -> dict:
total = len(records)
gross = 0.0
open_fee = 0.0
close_fee = 0.0
wins = 0
for t in records:
mul = t.point_value
if t.direction == "sell":
g = (t.open_price - t.close_price) * mul
else:
g = (t.close_price - t.open_price) * mul
gross += g
of = t.open_fee or 0
cf = t.close_fee or 0
open_fee += of
close_fee += cf
if g - of - cf > 0:
wins += 1
return {
"count": total,
"gross": gross,
"net": gross - open_fee - close_fee,
"wins": wins,
"win_rate": round(wins / total * 100, 1) if total > 0 else 0,
"open_fee": open_fee,
"close_fee": close_fee,
"total_fee": open_fee + close_fee,
}
def _dual_stats(records) -> dict:
total = len(records)
gross = 0.0
open_fee = 0.0
close_fee = 0.0
wins = 0
for t in records:
mul = t.point_value
call_g = (t.call_close_price - t.call_open_price) * mul
put_g = (t.put_close_price - t.put_open_price) * mul
g = call_g + put_g
gross += g
of = (t.call_open_fee or 0) + (t.put_open_fee or 0)
cf = (t.call_close_fee or 0) + (t.put_close_fee or 0)
open_fee += of
close_fee += cf
if g - of - cf > 0:
wins += 1
return {
"count": total,
"gross": gross,
"net": gross - open_fee - close_fee,
"wins": wins,
"win_rate": round(wins / total * 100, 1) if total > 0 else 0,
"open_fee": open_fee,
"close_fee": close_fee,
"total_fee": open_fee + close_fee,
}
@@ -44,28 +108,29 @@ def summary_page(request: Request, db: Session = Depends(get_db)):
db.query(Trade).filter(Trade.status == "closed")
.order_by(Trade.close_date.desc()).all()
)
future_stats = _stats(future_trades)
future_stats = _future_stats(future_trades)
# Options
option_trades = (
db.query(OptionTrade).filter(OptionTrade.status == "closed")
.order_by(OptionTrade.close_date.desc()).all()
)
option_stats = _stats(option_trades)
option_stats = _option_stats(option_trades)
# Dual options
dual_trades = (
db.query(DualOptionTrade).filter(DualOptionTrade.status == "closed")
.order_by(DualOptionTrade.close_date.desc()).all()
)
dual_stats = _stats(dual_trades)
dual_stats = _dual_stats(dual_trades)
# Aggregate
total_count = future_stats["count"] + option_stats["count"] + dual_stats["count"]
total_pnl = future_stats["pnl"] + option_stats["pnl"] + dual_stats["pnl"]
gross_pnl = future_stats["gross"] + option_stats["gross"] + dual_stats["gross"]
net_pnl = future_stats["net"] + option_stats["net"] + dual_stats["net"]
total_fee = future_stats["total_fee"] + option_stats["total_fee"] + dual_stats["total_fee"]
total_wins = future_stats["wins"] + option_stats["wins"] + dual_stats["wins"]
total_win_rate = round(total_wins / total_count * 100, 1) if total_count > 0 else 0
total_fee = future_stats["total_fee"] + option_stats["total_fee"] + dual_stats["total_fee"]
template = request.app.state.templates.get_template("summary.html")
return HTMLResponse(
@@ -73,7 +138,8 @@ def summary_page(request: Request, db: Session = Depends(get_db)):
request=request,
active_nav="summary",
total_count=total_count,
total_pnl=total_pnl,
gross_pnl=gross_pnl,
net_pnl=net_pnl,
total_wins=total_wins,
total_win_rate=total_win_rate,
total_fee=total_fee,