Files
qihuo/templates/stats.html
T
dekun 8ebad6e8a2 Add stats trading calendar and fix CTP position avg/sync.
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.
2026-06-30 11:59:25 +08:00

192 lines
9.5 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{# 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 %}