Dashboard shows open positions only; closed trades stay in review.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -268,6 +268,7 @@ def resolve_web_user_for_activation(conn: sqlite3.Connection, act: dict) -> Opti
|
|||||||
def _trades_where(
|
def _trades_where(
|
||||||
account_id: Optional[int] = None,
|
account_id: Optional[int] = None,
|
||||||
machine_id: Optional[int] = None,
|
machine_id: Optional[int] = None,
|
||||||
|
open_only: bool = False,
|
||||||
) -> tuple[str, list[Any]]:
|
) -> tuple[str, list[Any]]:
|
||||||
where: list[str] = []
|
where: list[str] = []
|
||||||
params: list[Any] = []
|
params: list[Any] = []
|
||||||
@@ -277,6 +278,8 @@ def _trades_where(
|
|||||||
if machine_id is not None:
|
if machine_id is not None:
|
||||||
where.append("t.machine_id=?")
|
where.append("t.machine_id=?")
|
||||||
params.append(machine_id)
|
params.append(machine_id)
|
||||||
|
if open_only:
|
||||||
|
where.append("(t.close_time IS NULL OR t.close_time='')")
|
||||||
clause = (" WHERE " + " AND ".join(where)) if where else ""
|
clause = (" WHERE " + " AND ".join(where)) if where else ""
|
||||||
return clause, params
|
return clause, params
|
||||||
|
|
||||||
@@ -285,8 +288,9 @@ def count_trades(
|
|||||||
conn: sqlite3.Connection,
|
conn: sqlite3.Connection,
|
||||||
account_id: Optional[int] = None,
|
account_id: Optional[int] = None,
|
||||||
machine_id: Optional[int] = None,
|
machine_id: Optional[int] = None,
|
||||||
|
open_only: bool = False,
|
||||||
) -> int:
|
) -> int:
|
||||||
clause, params = _trades_where(account_id, machine_id)
|
clause, params = _trades_where(account_id, machine_id, open_only=open_only)
|
||||||
sql = f"SELECT COUNT(*) AS c FROM trades t{clause}"
|
sql = f"SELECT COUNT(*) AS c FROM trades t{clause}"
|
||||||
row = conn.execute(sql, tuple(params)).fetchone()
|
row = conn.execute(sql, tuple(params)).fetchone()
|
||||||
return int(row["c"] if row else 0)
|
return int(row["c"] if row else 0)
|
||||||
@@ -298,15 +302,21 @@ def list_trades(
|
|||||||
account_id: Optional[int] = None,
|
account_id: Optional[int] = None,
|
||||||
machine_id: Optional[int] = None,
|
machine_id: Optional[int] = None,
|
||||||
offset: int = 0,
|
offset: int = 0,
|
||||||
|
open_only: bool = False,
|
||||||
) -> list[dict]:
|
) -> list[dict]:
|
||||||
clause, params = _trades_where(account_id, machine_id)
|
clause, params = _trades_where(account_id, machine_id, open_only=open_only)
|
||||||
|
order = (
|
||||||
|
"ORDER BY COALESCE(t.open_time, t.created_at) DESC"
|
||||||
|
if open_only
|
||||||
|
else "ORDER BY COALESCE(t.close_time, t.open_time, t.created_at) DESC"
|
||||||
|
)
|
||||||
sql = f"""
|
sql = f"""
|
||||||
SELECT t.*, a.name AS account_name, m.name AS machine_name
|
SELECT t.*, a.name AS account_name, m.name AS machine_name
|
||||||
FROM trades t
|
FROM trades t
|
||||||
LEFT JOIN accounts a ON a.id = t.account_id
|
LEFT JOIN accounts a ON a.id = t.account_id
|
||||||
LEFT JOIN machines m ON m.id = t.machine_id
|
LEFT JOIN machines m ON m.id = t.machine_id
|
||||||
{clause}
|
{clause}
|
||||||
ORDER BY COALESCE(t.close_time, t.open_time, t.created_at) DESC
|
{order}
|
||||||
LIMIT ? OFFSET ?
|
LIMIT ? OFFSET ?
|
||||||
"""
|
"""
|
||||||
params.extend([limit, max(0, int(offset))])
|
params.extend([limit, max(0, int(offset))])
|
||||||
|
|||||||
+6
-5
@@ -280,25 +280,26 @@ def register_routes(app: Flask) -> None:
|
|||||||
t["hold_duration"] = t["hold_duration"] + "(持仓中)"
|
t["hold_duration"] = t["hold_duration"] + "(持仓中)"
|
||||||
|
|
||||||
def _dashboard_payload(u: dict) -> dict:
|
def _dashboard_payload(u: dict) -> dict:
|
||||||
|
# 总览只看当前持仓(未平仓),历史成交在「交易复盘」
|
||||||
with database.get_db() as conn:
|
with database.get_db() as conn:
|
||||||
if u["role"] == "admin":
|
if u["role"] == "admin":
|
||||||
accounts = database.list_accounts(conn)
|
accounts = database.list_accounts(conn)
|
||||||
trades = database.list_trades(conn, 40)
|
trades = database.list_trades(conn, 100, open_only=True)
|
||||||
else:
|
else:
|
||||||
accounts = [
|
accounts = [
|
||||||
a
|
a
|
||||||
for a in database.list_accounts(conn)
|
for a in database.list_accounts(conn)
|
||||||
if a["id"] == u.get("account_id")
|
if a["id"] == u.get("account_id")
|
||||||
]
|
]
|
||||||
trades = database.list_trades(conn, 40, account_id=u.get("account_id"))
|
trades = database.list_trades(
|
||||||
|
conn, 100, account_id=u.get("account_id"), open_only=True
|
||||||
|
)
|
||||||
machines = database.list_machines_for_user(conn, u)
|
machines = database.list_machines_for_user(conn, u)
|
||||||
online_machines = [m for m in machines if m.get("online")]
|
online_machines = [m for m in machines if m.get("online")]
|
||||||
for m in machines:
|
for m in machines:
|
||||||
m["last_seen_fmt"] = _fmt_ts(m.get("last_seen_at"))
|
m["last_seen_fmt"] = _fmt_ts(m.get("last_seen_at"))
|
||||||
for t in trades:
|
for t in trades:
|
||||||
t["time_fmt"] = _fmt_ts(
|
t["time_fmt"] = _fmt_ts(t.get("open_time") or t.get("created_at"))
|
||||||
t.get("close_time") or t.get("open_time") or t.get("created_at")
|
|
||||||
)
|
|
||||||
return {
|
return {
|
||||||
"accounts": accounts,
|
"accounts": accounts,
|
||||||
"machines": machines,
|
"machines": machines,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<div class="muted">在线机器</div>
|
<div class="muted">在线机器</div>
|
||||||
<div class="stat"><span id="stat-online">{{ online_count }}</span><span class="muted" style="font-size:16px"> / {{ machine_count }}</span></div>
|
<div class="stat"><span id="stat-online">{{ online_count }}</span><span class="muted" style="font-size:16px"> / {{ machine_count }}</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card"><div class="muted">实时成交(条)</div><div class="stat" id="stat-trades">{{ trades|length }}</div></div>
|
<div class="card"><div class="muted">当前持仓(笔)</div><div class="stat" id="stat-trades">{{ trades|length }}</div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid cols-2" style="margin-top:14px">
|
<div class="grid cols-2" style="margin-top:14px">
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>快速入口</h3>
|
<h3>快速入口</h3>
|
||||||
<p class="muted">下载通用辅助包并用激活码绑定;成交自动上报到本页实时记录与交易复盘。</p>
|
<p class="muted">下载通用辅助包并用激活码绑定;本页显示当前持仓,历史成交见交易复盘。</p>
|
||||||
<p style="margin-top:12px">
|
<p style="margin-top:12px">
|
||||||
<a class="btn" href="{{ url_for('download_page') }}">下载中心</a>
|
<a class="btn" href="{{ url_for('download_page') }}">下载中心</a>
|
||||||
<a class="btn secondary" href="{{ url_for('records_page') }}">交易复盘</a>
|
<a class="btn secondary" href="{{ url_for('records_page') }}">交易复盘</a>
|
||||||
@@ -44,12 +44,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card" style="margin-top:14px">
|
<div class="card" style="margin-top:14px">
|
||||||
<h3>机器实时交易记录</h3>
|
<h3>当前持仓</h3>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>时间</th><th>机器</th><th>品种</th><th>方向</th><th>手数</th>
|
<th>开仓时间</th><th>机器</th><th>品种</th><th>方向</th><th>手数</th>
|
||||||
<th>盈亏</th><th>账户</th><th>单号</th><th></th>
|
<th>浮盈</th><th>账户</th><th>单号</th><th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="live-trades-body">
|
<tbody id="live-trades-body">
|
||||||
@@ -61,14 +61,14 @@
|
|||||||
<td><span class="tag {% if t.side=='buy' %}green{% else %}red{% endif %}">{{ t.side }}</span></td>
|
<td><span class="tag {% if t.side=='buy' %}green{% else %}red{% endif %}">{{ t.side }}</span></td>
|
||||||
<td>{{ t.volume if t.volume is not none else '—' }}</td>
|
<td>{{ t.volume if t.volume is not none else '—' }}</td>
|
||||||
<td style="color:{% if t.profit is not none and t.profit>0 %}var(--green){% elif t.profit is not none and t.profit<0 %}var(--red){% else %}inherit{% endif %}">
|
<td style="color:{% if t.profit is not none and t.profit>0 %}var(--green){% elif t.profit is not none and t.profit<0 %}var(--red){% else %}inherit{% endif %}">
|
||||||
{% if t.profit is none %}—{% else %}{{ '%.2f'|format(t.profit) }}{% if not t.close_time %}<span class="muted"> 浮</span>{% endif %}{% endif %}
|
{% if t.profit is none %}—{% else %}{{ '%.2f'|format(t.profit) }}{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ t.account_name or '—' }}</td>
|
<td>{{ t.account_name or '—' }}</td>
|
||||||
<td class="muted">{{ t.ticket or '—' }}</td>
|
<td class="muted">{{ t.ticket or '—' }}</td>
|
||||||
<td><a href="{{ url_for('record_detail', tid=t.id) }}">复盘</a></td>
|
<td><a href="{{ url_for('record_detail', tid=t.id) }}">复盘</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr><td colspan="9" class="muted">暂无成交。客户端上报后将实时显示。</td></tr>
|
<tr><td colspan="9" class="muted">当前无持仓。开仓后由客户端上报显示。</td></tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
if (!body) return;
|
if (!body) return;
|
||||||
document.getElementById('stat-trades').textContent = String(list.length);
|
document.getElementById('stat-trades').textContent = String(list.length);
|
||||||
if (!list.length) {
|
if (!list.length) {
|
||||||
body.innerHTML = '<tr><td colspan="9" class="muted">暂无成交。客户端上报后将实时显示。</td></tr>';
|
body.innerHTML = '<tr><td colspan="9" class="muted">当前无持仓。开仓后由客户端上报显示。</td></tr>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
body.innerHTML = list.map(function (t) {
|
body.innerHTML = list.map(function (t) {
|
||||||
@@ -150,9 +150,7 @@
|
|||||||
'<td><span class="tag ' + sideCls + '">' + esc(t.side) + '</span></td>' +
|
'<td><span class="tag ' + sideCls + '">' + esc(t.side) + '</span></td>' +
|
||||||
'<td>' + esc(t.volume != null ? t.volume : '—') + '</td>' +
|
'<td>' + esc(t.volume != null ? t.volume : '—') + '</td>' +
|
||||||
'<td style="color:' + profitColor(t.profit) + '">' +
|
'<td style="color:' + profitColor(t.profit) + '">' +
|
||||||
(t.profit == null || t.profit === ''
|
(t.profit == null || t.profit === '' ? '—' : Number(t.profit).toFixed(2)) +
|
||||||
? '—'
|
|
||||||
: (Number(t.profit).toFixed(2) + (t.close_time ? '' : ' <span class="muted">浮</span>'))) +
|
|
||||||
'</td>' +
|
'</td>' +
|
||||||
'<td>' + esc(t.account_name || '—') + '</td>' +
|
'<td>' + esc(t.account_name || '—') + '</td>' +
|
||||||
'<td class="muted">' + esc(t.ticket || '—') + '</td>' +
|
'<td class="muted">' + esc(t.ticket || '—') + '</td>' +
|
||||||
|
|||||||
Reference in New Issue
Block a user