6609b39f7b
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
185 lines
5.7 KiB
Python
185 lines
5.7 KiB
Python
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
|
|
profit_sum = 0.0
|
|
loss_sum = 0.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
|
|
net = g - of - cf
|
|
if net > 0:
|
|
wins += 1
|
|
profit_sum += net
|
|
elif net < 0:
|
|
loss_sum += net
|
|
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,
|
|
"profit_sum": profit_sum,
|
|
"loss_sum": loss_sum,
|
|
}
|
|
|
|
|
|
def _option_stats(records) -> dict:
|
|
total = len(records)
|
|
gross = 0.0
|
|
open_fee = 0.0
|
|
close_fee = 0.0
|
|
wins = 0
|
|
profit_sum = 0.0
|
|
loss_sum = 0.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
|
|
net = g - of - cf
|
|
if net > 0:
|
|
wins += 1
|
|
profit_sum += net
|
|
elif net < 0:
|
|
loss_sum += net
|
|
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,
|
|
"profit_sum": profit_sum,
|
|
"loss_sum": loss_sum,
|
|
}
|
|
|
|
|
|
def _dual_stats(records) -> dict:
|
|
total = len(records)
|
|
gross = 0.0
|
|
open_fee = 0.0
|
|
close_fee = 0.0
|
|
wins = 0
|
|
profit_sum = 0.0
|
|
loss_sum = 0.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
|
|
net = g - of - cf
|
|
if net > 0:
|
|
wins += 1
|
|
profit_sum += net
|
|
elif net < 0:
|
|
loss_sum += net
|
|
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,
|
|
"profit_sum": profit_sum,
|
|
"loss_sum": loss_sum,
|
|
}
|
|
|
|
|
|
@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
|
|
total_profit = future_stats["profit_sum"] + option_stats["profit_sum"] + dual_stats["profit_sum"]
|
|
total_loss = future_stats["loss_sum"] + option_stats["loss_sum"] + dual_stats["loss_sum"]
|
|
profit_ratio = round(total_profit / abs(total_loss), 2) if total_loss != 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,
|
|
total_profit=total_profit,
|
|
total_loss=total_loss,
|
|
profit_ratio=profit_ratio,
|
|
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,
|
|
)
|
|
)
|