8ebad6e8a2
Calendar shows daily closed trade count and PnL with emotion-day highlighting; day click loads review-first trade list. Use exchange-only entry average and improve vnpy position sync after CTP reconnect.
192 lines
9.5 KiB
HTML
192 lines
9.5 KiB
HTML
{# Copyright (c) 2025-2026 马建军. All rights reserved. 专有软件,详见 LICENSE.zh-CN.txt #}
|
||
{% extends "base.html" %}
|
||
{% block title %}统计分析 - 国内期货 · 交易复盘系统{% endblock %}
|
||
{% block extra_css %}
|
||
<style>
|
||
.stats-summary-card{margin-bottom:1.25rem}
|
||
.stats-toolbar{display:flex;align-items:center;justify-content:flex-start;gap:1rem;margin-bottom:.75rem;flex-wrap:wrap}
|
||
.stat-grid-summary{
|
||
display:flex;flex-wrap:nowrap;align-items:stretch;gap:0;
|
||
margin-bottom:0;overflow-x:auto;-webkit-overflow-scrolling:touch;
|
||
}
|
||
.stat-grid-summary .stat-item{
|
||
flex:1 1 0;min-width:4.5rem;background:transparent;border:none;border-radius:0;
|
||
padding:.35rem .2rem;text-align:center;position:relative;overflow:visible;
|
||
border-right:1px solid var(--table-border);
|
||
}
|
||
.stat-grid-summary .stat-item:last-child{border-right:none}
|
||
.stat-grid-summary .stat-item::before{display:none}
|
||
.stat-grid-summary .stat-item:hover{transform:none;box-shadow:none}
|
||
.stat-grid-summary .stat-item .label{
|
||
font-size:.62rem;line-height:1.25;color:var(--text-muted);white-space:nowrap;
|
||
}
|
||
.stat-grid-summary .stat-item .value{
|
||
font-size:.78rem;font-weight:600;color:var(--text-title);margin-top:.12rem;
|
||
font-variant-numeric:tabular-nums;white-space:nowrap;
|
||
}
|
||
.stats-card-head{display:flex;align-items:flex-end;justify-content:space-between;gap:1rem;flex-wrap:wrap;margin-bottom:1rem}
|
||
.stats-card-head h2{margin-bottom:0}
|
||
.stats-view-field{width:auto;min-width:200px}
|
||
.stats-view-field select{width:100%;min-width:180px}
|
||
|
||
/* 交易日历 */
|
||
.stats-calendar-card{margin-bottom:1.25rem}
|
||
.stats-calendar-head{display:flex;align-items:center;justify-content:space-between;gap:.75rem;flex-wrap:wrap;margin-bottom:.85rem}
|
||
.stats-calendar-head h2{margin:0}
|
||
.stats-calendar-nav{display:flex;align-items:center;gap:.5rem}
|
||
.stats-calendar-nav button{
|
||
border:1px solid var(--card-border);background:var(--card-inner);color:var(--text-title);
|
||
border-radius:8px;padding:.35rem .65rem;cursor:pointer;font:inherit;font-size:.85rem;
|
||
}
|
||
.stats-calendar-nav button:hover{border-color:var(--accent)}
|
||
.stats-calendar-title{font-size:.95rem;font-weight:600;color:var(--text-title);min-width:7rem;text-align:center}
|
||
.stats-calendar-weekdays{
|
||
display:grid;grid-template-columns:repeat(7,1fr);gap:.35rem;margin-bottom:.35rem;
|
||
}
|
||
.stats-calendar-weekdays span{
|
||
text-align:center;font-size:.68rem;color:var(--text-muted);padding:.15rem 0;
|
||
}
|
||
.stats-calendar-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:.35rem}
|
||
.stats-cal-cell{
|
||
min-height:4.6rem;border:1px solid var(--card-border);border-radius:10px;
|
||
background:var(--card-inner);padding:.35rem .4rem;text-align:left;cursor:default;
|
||
transition:border-color .2s,box-shadow .2s;
|
||
}
|
||
.stats-cal-cell.is-empty{background:transparent;border-color:transparent}
|
||
.stats-cal-cell.is-clickable{cursor:pointer}
|
||
.stats-cal-cell.is-clickable:hover{border-color:var(--accent)}
|
||
.stats-cal-cell.is-selected{
|
||
border-color:var(--accent);box-shadow:0 0 0 1px rgba(56,189,248,.25);
|
||
}
|
||
.stats-cal-cell.is-today .stats-cal-day-num{color:var(--accent);font-weight:700}
|
||
.stats-cal-cell.is-emotion{
|
||
border-color:rgba(251,146,60,.65);
|
||
background:linear-gradient(145deg,rgba(251,146,60,.12),var(--card-inner));
|
||
}
|
||
.stats-cal-cell.is-emotion.is-selected{
|
||
border-color:rgba(251,146,60,.9);
|
||
box-shadow:0 0 0 1px rgba(251,146,60,.35);
|
||
}
|
||
.stats-cal-day-num{font-size:.78rem;font-weight:600;color:var(--text-title);line-height:1.2}
|
||
.stats-cal-meta{margin-top:.2rem;font-size:.62rem;line-height:1.35;color:var(--text-muted)}
|
||
.stats-cal-count{font-variant-numeric:tabular-nums}
|
||
.stats-cal-pnl{font-weight:600;font-variant-numeric:tabular-nums;margin-top:.08rem}
|
||
.stats-cal-pnl.is-profit{color:var(--profit)}
|
||
.stats-cal-pnl.is-loss{color:var(--loss)}
|
||
.stats-cal-emotion{
|
||
display:inline-block;margin-top:.12rem;padding:.05rem .28rem;border-radius:4px;
|
||
font-size:.58rem;font-weight:600;color:#fb923c;background:rgba(251,146,60,.15);
|
||
}
|
||
.stats-day-detail{margin-top:1rem;padding-top:.85rem;border-top:1px solid var(--table-border)}
|
||
.stats-day-detail-head{
|
||
display:flex;align-items:center;justify-content:space-between;gap:.75rem;
|
||
flex-wrap:wrap;margin-bottom:.65rem;
|
||
}
|
||
.stats-day-detail-head h3{margin:0;font-size:.95rem}
|
||
.stats-day-summary{font-size:.78rem;color:var(--text-muted)}
|
||
.stats-day-list{display:flex;flex-direction:column;gap:.55rem}
|
||
.stats-day-item{
|
||
border:1px solid var(--card-border);border-radius:12px;background:var(--card-inner);
|
||
padding:.65rem .75rem;
|
||
}
|
||
.stats-day-item.is-emotion{
|
||
border-color:rgba(251,146,60,.55);
|
||
background:linear-gradient(145deg,rgba(251,146,60,.1),var(--card-inner));
|
||
}
|
||
.stats-day-item-head{
|
||
display:flex;align-items:center;justify-content:space-between;gap:.5rem;margin-bottom:.35rem;
|
||
}
|
||
.stats-day-item-symbol{font-weight:600;font-size:.88rem;color:var(--text-title)}
|
||
.stats-day-item-pnl{font-weight:600;font-size:.88rem;font-variant-numeric:tabular-nums}
|
||
.stats-day-item-pnl.is-profit{color:var(--profit)}
|
||
.stats-day-item-pnl.is-loss{color:var(--loss)}
|
||
.stats-day-item-meta{
|
||
display:flex;flex-wrap:wrap;gap:.35rem .55rem;font-size:.72rem;color:var(--text-muted);
|
||
}
|
||
.stats-day-badge{
|
||
display:inline-block;padding:.08rem .35rem;border-radius:4px;font-size:.65rem;font-weight:600;
|
||
}
|
||
.stats-day-badge.review{background:rgba(56,189,248,.15);color:var(--accent)}
|
||
.stats-day-badge.emotion{background:rgba(251,146,60,.18);color:#fb923c}
|
||
.stats-day-item-notes{
|
||
margin-top:.4rem;font-size:.72rem;color:var(--text-muted);line-height:1.45;
|
||
}
|
||
.stats-day-item-shot{margin-top:.45rem}
|
||
.stats-day-item-shot img{
|
||
max-width:100%;max-height:180px;border-radius:8px;border:1px solid var(--card-border);
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
{% block content %}
|
||
|
||
<div class="card stats-summary-card">
|
||
<div class="stats-toolbar">
|
||
<span id="stats-updated" class="hint">正在加载统计…</span>
|
||
</div>
|
||
<div class="stat-grid stat-grid-summary" id="stats-summary">
|
||
<div class="stat-item"><div class="label">总交易次数</div><div class="value" data-k="total_trades">-</div></div>
|
||
<div class="stat-item"><div class="label">胜率</div><div class="value" data-k="win_rate">-</div></div>
|
||
<div class="stat-item"><div class="label">平均盈利</div><div class="value text-profit" data-k="avg_profit">-</div></div>
|
||
<div class="stat-item"><div class="label">平均亏损</div><div class="value text-loss" data-k="avg_loss">-</div></div>
|
||
<div class="stat-item"><div class="label">盈亏比</div><div class="value" data-k="profit_loss_ratio">-</div></div>
|
||
<div class="stat-item"><div class="label">连续亏损次数</div><div class="value" data-k="consecutive_losses">-</div></div>
|
||
<div class="stat-item"><div class="label">最大回撤</div><div class="value" data-k="max_drawdown">-</div></div>
|
||
<div class="stat-item"><div class="label">最大亏损金额</div><div class="value text-loss" data-k="max_loss_amount">-</div></div>
|
||
<div class="stat-item"><div class="label">最大亏损占比</div><div class="value text-loss" data-k="max_loss_pct">-</div></div>
|
||
<div class="stat-item"><div class="label">最大盈利金额</div><div class="value text-profit" data-k="max_profit_amount">-</div></div>
|
||
<div class="stat-item"><div class="label">最大盈利占比</div><div class="value text-profit" data-k="max_profit_pct">-</div></div>
|
||
<div class="stat-item"><div class="label">累计手续费</div><div class="value text-loss" data-k="total_fee">-</div></div>
|
||
<div class="stat-item"><div class="label">情绪单数量</div><div class="value" data-k="emotion_count">-</div></div>
|
||
<div class="stat-item"><div class="label">情绪单占比</div><div class="value" data-k="emotion_ratio">-</div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card stats-calendar-card">
|
||
<div class="stats-calendar-head">
|
||
<h2>交易日历</h2>
|
||
<div class="stats-calendar-nav">
|
||
<button type="button" id="stats-cal-prev" aria-label="上个月">‹</button>
|
||
<span class="stats-calendar-title" id="stats-cal-title">—</span>
|
||
<button type="button" id="stats-cal-next" aria-label="下个月">›</button>
|
||
<button type="button" id="stats-cal-today">本月</button>
|
||
</div>
|
||
</div>
|
||
<div class="stats-calendar-weekdays">
|
||
<span>一</span><span>二</span><span>三</span><span>四</span><span>五</span><span>六</span><span>日</span>
|
||
</div>
|
||
<div class="stats-calendar-grid" id="stats-calendar-grid">
|
||
<div class="text-muted" style="grid-column:1/-1;padding:.5rem 0">加载日历…</div>
|
||
</div>
|
||
<div class="stats-day-detail" id="stats-day-detail" hidden>
|
||
<div class="stats-day-detail-head">
|
||
<h3 id="stats-day-detail-title">当日交易</h3>
|
||
<span class="stats-day-summary" id="stats-day-summary"></span>
|
||
</div>
|
||
<div class="stats-day-list" id="stats-day-list"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="stats-card-head">
|
||
<h2>分项统计</h2>
|
||
<div class="field stats-view-field">
|
||
<label for="stats-view-select">统计维度</label>
|
||
<select id="stats-view-select"></select>
|
||
</div>
|
||
</div>
|
||
<div class="card-scroll">
|
||
<table id="stats-breakdown-table">
|
||
<thead><tr id="stats-breakdown-head"></tr></thead>
|
||
<tbody id="stats-breakdown-body">
|
||
<tr><td colspan="12" class="text-muted">加载中…</td></tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
{% endblock %}
|
||
|
||
{% block extra_js %}
|
||
<script src="{{ url_for('static', filename='js/stats.js') }}"></script>
|
||
{% endblock %}
|