Auto-refresh review list and stats when trades or annotations change.
Clarify that category/style breakdowns need manual entry tagging. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -398,6 +398,43 @@ def count_trades(
|
||||
return int(row["c"] if row else 0)
|
||||
|
||||
|
||||
def trades_fingerprint(
|
||||
conn: sqlite3.Connection,
|
||||
account_id: Optional[int] = None,
|
||||
machine_id: Optional[int] = None,
|
||||
) -> str:
|
||||
"""列表/统计页轮询用:成交条数、最新 id/平仓时间、标注变化任一变即刷新。"""
|
||||
clause, params = _trades_where(account_id, machine_id, hide_synthetic=True)
|
||||
row = conn.execute(
|
||||
f"""
|
||||
SELECT COUNT(*) AS c,
|
||||
COALESCE(MAX(t.id), 0) AS max_id,
|
||||
COALESCE(MAX(t.close_time), 0) AS max_close,
|
||||
COALESCE(MAX(t.open_time), 0) AS max_open,
|
||||
SUM(CASE WHEN IFNULL(t.entry_style,'') != '' THEN 1 ELSE 0 END) AS annotated,
|
||||
SUM(CASE WHEN IFNULL(t.review_issues,'') != '' THEN 1 ELSE 0 END) AS issues,
|
||||
SUM(CASE WHEN t.close_time IS NULL OR t.close_time='' THEN 1 ELSE 0 END) AS open_n
|
||||
FROM trades t
|
||||
{clause}
|
||||
""",
|
||||
tuple(params),
|
||||
).fetchone()
|
||||
if not row:
|
||||
return "0"
|
||||
return "|".join(
|
||||
str(x)
|
||||
for x in (
|
||||
int(row["c"] or 0),
|
||||
int(row["max_id"] or 0),
|
||||
round(float(row["max_close"] or 0), 3),
|
||||
round(float(row["max_open"] or 0), 3),
|
||||
int(row["annotated"] or 0),
|
||||
int(row["issues"] or 0),
|
||||
int(row["open_n"] or 0),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def list_trades(
|
||||
conn: sqlite3.Connection,
|
||||
limit: int = 200,
|
||||
@@ -792,7 +829,7 @@ def trade_stats(
|
||||
"by_style": [
|
||||
_finalize_group(by_style[k])
|
||||
for k in style_order
|
||||
if k in by_style and (by_style[k]["count"] or k in ENTRY_STYLES)
|
||||
if k in by_style and by_style[k]["count"]
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -941,6 +941,20 @@ def register_routes(app: Flask) -> None:
|
||||
total_pages=total_pages,
|
||||
)
|
||||
|
||||
@app.get("/api/records/fingerprint")
|
||||
@login_required
|
||||
def records_fingerprint():
|
||||
"""复盘列表/统计页轮询:成交或标注变化时前端自动刷新。"""
|
||||
u = current_user()
|
||||
machine_raw = (request.args.get("machine_id") or "").strip()
|
||||
account_id = None if u["role"] == "admin" else u.get("account_id")
|
||||
_machines, machine_id = _resolve_machine_filter(u, machine_raw)
|
||||
with database.get_db() as conn:
|
||||
fp = database.trades_fingerprint(
|
||||
conn, account_id=account_id, machine_id=machine_id
|
||||
)
|
||||
return jsonify({"ok": True, "fp": fp, "ts": database.now()})
|
||||
|
||||
@app.get("/records/stats")
|
||||
@login_required
|
||||
def records_stats_page():
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}交易复盘 · {{ app_name }}{% endblock %}
|
||||
{% block content %}
|
||||
<h2 style="margin:0 0 6px">交易记录与复盘</h2>
|
||||
<p class="muted" style="margin:0 0 10px">按机器筛选 · 每页 {{ per_page }} 条 · 时间均为北京时间 · 已隐藏对账/去重影子记录</p>
|
||||
<h2 style="margin:0 0 6px;display:flex;align-items:center;gap:10px;flex-wrap:wrap">
|
||||
交易记录与复盘
|
||||
<span id="records-live-hint" class="tag" style="font-size:12px;font-weight:500">自动刷新中…</span>
|
||||
</h2>
|
||||
<p class="muted" style="margin:0 0 10px">按机器筛选 · 每页 {{ per_page }} 条 · 北京时间 · 成交变化会自动刷新 · 已隐藏对账/去重影子记录</p>
|
||||
|
||||
<div style="display:flex;gap:8px;margin-bottom:14px;flex-wrap:wrap">
|
||||
<a class="btn" href="{{ url_for('records_page', machine_id=selected_machine_id) if selected_machine_id else url_for('records_page') }}">交易列表</a>
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}统计看板 · {{ app_name }}{% endblock %}
|
||||
{% block content %}
|
||||
<h2 style="margin:0 0 6px">统计看板</h2>
|
||||
<h2 style="margin:0 0 6px;display:flex;align-items:center;gap:10px;flex-wrap:wrap">
|
||||
统计看板
|
||||
<span id="records-live-hint" class="tag" style="font-size:12px;font-weight:500">自动刷新中…</span>
|
||||
</h2>
|
||||
<p class="muted" style="margin:0 0 10px">
|
||||
默认按北京时间<strong>今日真实平仓</strong>统计;对账/去重产生的假平仓已排除。
|
||||
成交/标注变化会<strong>自动刷新</strong>本页。
|
||||
{% if selected_period == 'today' and stats.day %}当前:{{ stats.day }}{% endif %}
|
||||
{% if stats.skipped_synthetic %} · 已排除假平仓 {{ stats.skipped_synthetic }} 笔{% endif %}
|
||||
{% if stats.skipped_dup_ticket %} · 已排除重复单号 {{ stats.skipped_dup_ticket }} 笔{% endif %}
|
||||
@@ -82,6 +86,7 @@
|
||||
<div class="grid cols-2" style="margin-top:14px">
|
||||
<div class="card">
|
||||
<h3>按大类(顺势 / 反转 / 其他)</h3>
|
||||
<p class="muted" style="margin:0 0 10px">需在复盘页选「入场形式」后才会分入顺势/反转;未选的汇总在「未标注」。</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -107,6 +112,7 @@
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>按入场形式(细项)</h3>
|
||||
<p class="muted" style="margin:0 0 10px">只显示已标注过的形式;全部为「—」时请先打开复盘标注。</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -125,7 +131,7 @@
|
||||
<td class="muted">{{ g.avg_hold }}</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="5" class="muted">暂无数据</td></tr>
|
||||
<tr><td colspan="5" class="muted">暂无入场形式标注</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -239,3 +245,46 @@
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
(function () {
|
||||
var fpUrl = {{ url_for('records_fingerprint')|tojson }};
|
||||
var machineId = {{ (selected_machine_id or '')|tojson }};
|
||||
var hint = document.getElementById('records-live-hint');
|
||||
var lastFp = null;
|
||||
var q = machineId ? ('?machine_id=' + encodeURIComponent(machineId)) : '';
|
||||
|
||||
function setHint(text, kind) {
|
||||
if (!hint) return;
|
||||
hint.textContent = text;
|
||||
hint.className = 'tag' + (kind ? ' ' + kind : '');
|
||||
}
|
||||
|
||||
function tick() {
|
||||
fetch(fpUrl + q, { credentials: 'same-origin' })
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) {
|
||||
if (!d || !d.ok) return;
|
||||
if (lastFp === null) {
|
||||
lastFp = d.fp;
|
||||
setHint('自动刷新 · 监听中', 'green');
|
||||
return;
|
||||
}
|
||||
if (d.fp !== lastFp) {
|
||||
setHint('有新成交,刷新中…', 'green');
|
||||
location.reload();
|
||||
return;
|
||||
}
|
||||
setHint('自动刷新 · 监听中', 'green');
|
||||
})
|
||||
.catch(function () {
|
||||
setHint('自动刷新暂时不可用', '');
|
||||
});
|
||||
}
|
||||
|
||||
tick();
|
||||
setInterval(tick, 3000);
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user