平仓汇总按原始价差计算毛利和净利,红涨绿跌配色
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -37,26 +37,25 @@
|
||||
<div class="value">{{ total_count }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">总盈亏</div>
|
||||
<div class="value" style="{% if total_pnl > 0 %}color:var(--success-fg);{% elif total_pnl < 0 %}color:var(--danger-fg);{% endif %}">
|
||||
{% if total_pnl > 0 %}+{% endif %}{{ "%.0f"|format(total_pnl) }}
|
||||
<div class="label">毛利</div>
|
||||
<div class="value" style="{% if gross_pnl > 0 %}color:var(--danger-fg);{% elif gross_pnl < 0 %}color:var(--success-fg);{% endif %}">
|
||||
{% if gross_pnl > 0 %}+{% endif %}{{ "%.2f"|format(gross_pnl) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">总手续费</div>
|
||||
<div class="value">{{ "%.0f"|format(total_fee) }}</div>
|
||||
<div class="value">{{ "%.2f"|format(total_fee) }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">总胜率</div>
|
||||
<div class="value" style="{% if total_win_rate >= 50 %}color:var(--success-fg);{% else %}color:var(--danger-fg);{% endif %}">
|
||||
<div class="value" style="{% if total_win_rate >= 40 %}color:var(--danger-fg);{% else %}color:var(--success-fg);{% endif %}">
|
||||
{{ total_win_rate }}%
|
||||
</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">净盈亏</div>
|
||||
<div class="value" style="{% if total_pnl - total_fee > 0 %}color:var(--success-fg);{% elif total_pnl - total_fee < 0 %}color:var(--danger-fg);{% endif %}">
|
||||
{% set net = total_pnl - total_fee %}
|
||||
{% if net > 0 %}+{% endif %}{{ "%.0f"|format(net) }}
|
||||
<div class="label">净利润</div>
|
||||
<div class="value" style="{% if net_pnl > 0 %}color:var(--danger-fg);{% elif net_pnl < 0 %}color:var(--success-fg);{% endif %}">
|
||||
{% if net_pnl > 0 %}+{% endif %}{{ "%.2f"|format(net_pnl) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,9 +71,9 @@
|
||||
<div class="tab-panel{% if view != 'options' and view != 'dual' %} active{% endif %}">
|
||||
<div class="sub-stat">
|
||||
<span>笔数 <b>{{ future_stats.count }}</b></span>
|
||||
<span>盈亏 <b style="{% if future_stats.pnl > 0 %}color:var(--success-fg);{% elif future_stats.pnl < 0 %}color:var(--danger-fg);{% endif %}">{% if future_stats.pnl > 0 %}+{% endif %}{{ "%.0f"|format(future_stats.pnl) }}</b></span>
|
||||
<span>手续费 <b>{{ "%.0f"|format(future_stats.total_fee) }}</b></span>
|
||||
<span>胜率 <b style="{% if future_stats.win_rate >= 50 %}color:var(--success-fg);{% else %}color:var(--danger-fg);{% endif %}">{{ future_stats.win_rate }}%</b></span>
|
||||
<span>毛利 <b style="{% if future_stats.gross > 0 %}color:var(--danger-fg);{% elif future_stats.gross < 0 %}color:var(--success-fg);{% endif %}">{% if future_stats.gross > 0 %}+{% endif %}{{ "%.2f"|format(future_stats.gross) }}</b></span>
|
||||
<span>手续费 <b>{{ "%.2f"|format(future_stats.total_fee) }}</b></span>
|
||||
<span>净利 <b style="{% if future_stats.net > 0 %}color:var(--danger-fg);{% elif future_stats.net < 0 %}color:var(--success-fg);{% endif %}">{% if future_stats.net > 0 %}+{% endif %}{{ "%.2f"|format(future_stats.net) }}</b></span>
|
||||
</div>
|
||||
|
||||
{% if future_trades %}
|
||||
@@ -100,7 +99,7 @@
|
||||
<td>
|
||||
{% set p = t.pnl %}
|
||||
{% if p is not none %}
|
||||
<span style="font-weight:700;{% if p > 0 %}color:var(--success-fg);{% elif p < 0 %}color:var(--danger-fg);{% endif %}">
|
||||
<span style="font-weight:700;{% if p > 0 %}color:var(--danger-fg);{% elif p < 0 %}color:var(--success-fg);{% endif %}">
|
||||
{% if p > 0 %}+{% endif %}{{ p }}
|
||||
</span>
|
||||
{% endif %}
|
||||
@@ -118,9 +117,9 @@
|
||||
<div class="tab-panel{% if view == 'options' %} active{% endif %}">
|
||||
<div class="sub-stat">
|
||||
<span>笔数 <b>{{ option_stats.count }}</b></span>
|
||||
<span>盈亏 <b style="{% if option_stats.pnl > 0 %}color:var(--success-fg);{% elif option_stats.pnl < 0 %}color:var(--danger-fg);{% endif %}">{% if option_stats.pnl > 0 %}+{% endif %}{{ "%.0f"|format(option_stats.pnl) }}</b></span>
|
||||
<span>手续费 <b>{{ "%.0f"|format(option_stats.total_fee) }}</b></span>
|
||||
<span>胜率 <b style="{% if option_stats.win_rate >= 50 %}color:var(--success-fg);{% else %}color:var(--danger-fg);{% endif %}">{{ option_stats.win_rate }}%</b></span>
|
||||
<span>毛利 <b style="{% if option_stats.gross > 0 %}color:var(--danger-fg);{% elif option_stats.gross < 0 %}color:var(--success-fg);{% endif %}">{% if option_stats.gross > 0 %}+{% endif %}{{ "%.2f"|format(option_stats.gross) }}</b></span>
|
||||
<span>手续费 <b>{{ "%.2f"|format(option_stats.total_fee) }}</b></span>
|
||||
<span>净利 <b style="{% if option_stats.net > 0 %}color:var(--danger-fg);{% elif option_stats.net < 0 %}color:var(--success-fg);{% endif %}">{% if option_stats.net > 0 %}+{% endif %}{{ "%.2f"|format(option_stats.net) }}</b></span>
|
||||
</div>
|
||||
|
||||
{% if option_trades %}
|
||||
@@ -154,7 +153,7 @@
|
||||
<td>
|
||||
{% set p = t.pnl %}
|
||||
{% if p is not none %}
|
||||
<span style="font-weight:700;{% if p > 0 %}color:var(--success-fg);{% elif p < 0 %}color:var(--danger-fg);{% endif %}">
|
||||
<span style="font-weight:700;{% if p > 0 %}color:var(--danger-fg);{% elif p < 0 %}color:var(--success-fg);{% endif %}">
|
||||
{% if p > 0 %}+{% endif %}{{ p }}
|
||||
</span>
|
||||
{% endif %}
|
||||
@@ -172,9 +171,9 @@
|
||||
<div class="tab-panel{% if view == 'dual' %} active{% endif %}">
|
||||
<div class="sub-stat">
|
||||
<span>笔数 <b>{{ dual_stats.count }}</b></span>
|
||||
<span>盈亏 <b style="{% if dual_stats.pnl > 0 %}color:var(--success-fg);{% elif dual_stats.pnl < 0 %}color:var(--danger-fg);{% endif %}">{% if dual_stats.pnl > 0 %}+{% endif %}{{ "%.0f"|format(dual_stats.pnl) }}</b></span>
|
||||
<span>手续费 <b>{{ "%.0f"|format(dual_stats.total_fee) }}</b></span>
|
||||
<span>胜率 <b style="{% if dual_stats.win_rate >= 50 %}color:var(--success-fg);{% else %}color:var(--danger-fg);{% endif %}">{{ dual_stats.win_rate }}%</b></span>
|
||||
<span>毛利 <b style="{% if dual_stats.gross > 0 %}color:var(--danger-fg);{% elif dual_stats.gross < 0 %}color:var(--success-fg);{% endif %}">{% if dual_stats.gross > 0 %}+{% endif %}{{ "%.2f"|format(dual_stats.gross) }}</b></span>
|
||||
<span>手续费 <b>{{ "%.2f"|format(dual_stats.total_fee) }}</b></span>
|
||||
<span>净利 <b style="{% if dual_stats.net > 0 %}color:var(--danger-fg);{% elif dual_stats.net < 0 %}color:var(--success-fg);{% endif %}">{% if dual_stats.net > 0 %}+{% endif %}{{ "%.2f"|format(dual_stats.net) }}</b></span>
|
||||
</div>
|
||||
|
||||
{% if dual_trades %}
|
||||
@@ -195,7 +194,7 @@
|
||||
<td>
|
||||
{% set p = t.pnl %}
|
||||
{% if p is not none %}
|
||||
<span style="font-weight:700;{% if p > 0 %}color:var(--success-fg);{% elif p < 0 %}color:var(--danger-fg);{% endif %}">
|
||||
<span style="font-weight:700;{% if p > 0 %}color:var(--danger-fg);{% elif p < 0 %}color:var(--success-fg);{% endif %}">
|
||||
{% if p > 0 %}+{% endif %}{{ p }}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user