from fastapi import APIRouter, Depends, Request from fastapi.responses import HTMLResponse from sqlalchemy.orm import Session from app.database import get_db from app.models import Trade, OptionTrade, DualOptionTrade router = APIRouter(prefix="/summary", tags=["summary"]) WEEKDAYS = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"] def _future_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 == "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, "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 _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, } @router.get("/", response_class=HTMLResponse) def summary_page(request: Request, db: Session = Depends(get_db)): # Futures future_trades = ( db.query(Trade).filter(Trade.status == "closed") .order_by(Trade.close_date.desc()).all() ) 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 = _option_stats(option_trades) # Dual options dual_trades = ( db.query(DualOptionTrade).filter(DualOptionTrade.status == "closed") .order_by(DualOptionTrade.close_date.desc()).all() ) dual_stats = _dual_stats(dual_trades) # Aggregate total_count = future_stats["count"] + option_stats["count"] + dual_stats["count"] 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 template = request.app.state.templates.get_template("summary.html") return HTMLResponse( template.render( request=request, active_nav="summary", total_count=total_count, gross_pnl=gross_pnl, net_pnl=net_pnl, total_wins=total_wins, total_win_rate=total_win_rate, total_fee=total_fee, future_stats=future_stats, future_trades=future_trades, option_stats=option_stats, option_trades=option_trades, dual_stats=dual_stats, dual_trades=dual_trades, weekdays=WEEKDAYS, ) )