Enhance dashboard with exchange labels, split SL/TP columns, and daily risk limits.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-29 22:04:44 +08:00
parent b460c6c4e5
commit d8c6428eb5
7 changed files with 303 additions and 54 deletions
+38 -5
View File
@@ -66,7 +66,41 @@
}
.dashboard-risk-grid {
display: flex;
flex-wrap: wrap;
align-items: stretch;
gap: 0;
margin-bottom: 0;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.dashboard-risk-item {
flex: 1 1 0;
min-width: 5.8rem;
padding: 0.45rem 0.55rem;
text-align: center;
border-right: 1px solid var(--table-border);
}
.dashboard-risk-item:last-child {
border-right: none;
}
.dashboard-risk-label {
font-size: 0.62rem;
line-height: 1.3;
color: var(--text-muted);
white-space: nowrap;
}
.dashboard-risk-value {
font-size: 0.8rem;
font-weight: 600;
color: var(--text-title);
margin-top: 0.18rem;
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
.dashboard-risk-grid .stat-item {
@@ -84,13 +118,12 @@
.dash-symbol-title {
font-weight: 600;
line-height: 1.35;
line-height: 1.45;
}
.dash-symbol-sub {
font-size: 0.72rem;
line-height: 1.35;
margin-top: 0.12rem;
.dash-symbol-ex {
font-weight: 400;
font-size: 0.78rem;
}
.dash-main-badge {
+54 -20
View File
@@ -59,30 +59,42 @@
.replace(/"/g, '&quot;');
}
function slTpText(row) {
var parts = [];
if (row.stop_loss != null) parts.push('SL ' + fmtNum(row.stop_loss));
if (row.take_profit != null) parts.push('TP ' + fmtNum(row.take_profit));
if (row.trailing_be) parts.push('移动保本');
return parts.length ? parts.join(' · ') : '—';
function slText(row) {
if (row.trailing_be && row.stop_loss == null) return '移动保本';
if (row.stop_loss != null) return fmtNum(row.stop_loss);
return '—';
}
function tpText(row) {
if (row.trailing_be) return '移动保本';
if (row.take_profit != null) return fmtNum(row.take_profit);
return '—';
}
function symbolCellHtml(row) {
var name = row.symbol_name || row.symbol || '';
var code = row.symbol_code || '';
if (!code && row.symbol && String(row.symbol).toLowerCase() !== String(name).toLowerCase()) {
code = row.symbol;
}
var exchange = row.symbol_exchange || '';
var mainBadge = row.symbol_is_main
? ' <span class="badge planned dash-main-badge">主力</span>' : '';
var titleInner = escHtml(name) + mainBadge;
var titleInner = escHtml(name);
if (exchange) {
titleInner += ' <span class="dash-symbol-ex text-muted">' + escHtml(exchange) + '</span>';
}
titleInner += mainBadge;
if (code && String(name).toLowerCase() !== String(code).toLowerCase()) {
titleInner += ' <span class="text-accent">' + escHtml(code) + '</span>';
} else if (!name && code) {
titleInner = '<span class="text-accent">' + escHtml(code) + '</span>';
titleInner = (exchange
? '<span class="dash-symbol-ex text-muted">' + escHtml(exchange) + '</span> '
: '') + '<span class="text-accent">' + escHtml(code) + '</span>';
}
var sub = row.symbol_exchange || code || '';
return (
'<div class="dash-symbol-cell">' +
'<div class="dash-symbol-title">' + titleInner + '</div>' +
(sub ? '<div class="dash-symbol-sub text-muted">' + escHtml(sub) + '</div>' : '') +
'</div>'
);
}
@@ -132,6 +144,24 @@
var maxPos = lim.max_active_positions != null ? lim.max_active_positions : st.max_active_positions;
var manualCnt = risk.manual_close_count_today != null ? risk.manual_close_count_today : 0;
var manualLim = lim.manual_close_daily_limit;
var dailyOpens = risk.daily_open_count != null
? risk.daily_open_count
: (st.daily_open_count != null ? st.daily_open_count : '—');
var dailyPosLim = lim.daily_position_limit != null
? lim.daily_position_limit
: st.daily_position_limit;
var dailyRiskUsed = risk.daily_risk_used_pct != null
? risk.daily_risk_used_pct
: st.daily_risk_used_pct;
var dailyRiskLim = lim.daily_trading_risk_pct_limit != null
? lim.daily_trading_risk_pct_limit
: st.daily_trading_risk_pct_limit;
var dailyRiskText = dailyRiskUsed != null ? fmtNum(dailyRiskUsed) + '%' : '—';
if (dailyRiskLim != null && dailyRiskUsed != null) {
dailyRiskText += ' / ' + fmtNum(dailyRiskLim) + '%';
} else if (dailyRiskLim != null) {
dailyRiskText += ' / ' + fmtNum(dailyRiskLim) + '%';
}
var marginPct = risk.margin_pct_used;
var maxMarginPct = lim.max_margin_pct;
var marginPctText = marginPct != null ? fmtNum(marginPct) + '%' : '—';
@@ -157,23 +187,24 @@
var items = [
{ label: '风控开关', value: enabled ? '开启' : '关闭' },
{ label: '持仓限制', value: active + ' / ' + (maxPos != null ? maxPos : '—') },
{ label: '日手动平仓', value: manualCnt + ' / ' + (manualLim != null ? manualLim : '—') },
{ label: '日持仓限制', value: dailyOpens + ' / ' + (dailyPosLim != null ? dailyPosLim : '—') },
{ label: '日交易风险', value: dailyRiskText },
{ label: '手动平仓(冷静期触发)', value: manualCnt + ' / ' + (manualLim != null ? manualLim : '—') },
{ label: '冷静期(默认)', value: fmtHours(lim.cooling_hours_manual) },
{ label: '复盘后冷静', value: fmtHours(lim.cooling_hours_manual_journal) },
{ label: '冷静剩余', value: fmtRemainSec(st.freeze_remaining_sec) },
{ label: '保证金占比', value: marginPctText },
{ label: '保证金上限', value: maxMarginPct != null ? fmtNum(maxMarginPct) + '%' : '—' },
{ label: '滚仓保证金上限', value: lim.roll_max_margin_pct != null ? fmtNum(lim.roll_max_margin_pct) + '%' : '—' },
{ label: '综合保证金上限', value: lim.roll_max_margin_pct != null ? fmtNum(lim.roll_max_margin_pct) + '%' : '—' },
{ label: '计仓模式', value: sizingDetail },
{ label: '单笔风险', value: lim.risk_percent != null ? fmtNum(lim.risk_percent) + '%' : '—' },
{ label: '交易日切', value: lim.trading_day_reset_hour != null ? lim.trading_day_reset_hour + ':00' : '—' }
];
riskGridEl.innerHTML = items.map(function (it) {
return (
'<div class="stat-item">' +
'<div class="label">' + escHtml(it.label) + '</div>' +
'<div class="value">' + escHtml(it.value) + '</div>' +
'<div class="dashboard-risk-item">' +
'<div class="dashboard-risk-label">' + escHtml(it.label) + '</div>' +
'<div class="dashboard-risk-value">' + escHtml(it.value) + '</div>' +
'</div>'
);
}).join('');
@@ -320,7 +351,7 @@
function renderPositions(rows) {
if (!posBody) return;
if (!rows.length) {
posBody.innerHTML = '<tr><td colspan="8" class="text-muted">暂无持仓</td></tr>';
posBody.innerHTML = '<tr><td colspan="9" class="text-muted">暂无持仓</td></tr>';
posRowCache = {};
return;
}
@@ -337,7 +368,8 @@
'<td class="dash-p-mark">' + (r.current_price != null ? fmtNum(r.current_price) : '—') + '</td>' +
'<td class="dash-p-pnl ' + pnlClass(r.float_pnl) + '">' + fmtPnl(r.float_pnl) + '</td>' +
'<td class="dash-p-margin">' + (r.margin != null ? fmtMoney(r.margin) : '—') + '</td>' +
'<td class="dash-p-sltp">' + escHtml(slTpText(r)) + '</td>' +
'<td class="dash-p-sl">' + escHtml(slText(r)) + '</td>' +
'<td class="dash-p-tp">' + escHtml(tpText(r)) + '</td>' +
'</tr>'
);
}).join('');
@@ -396,7 +428,8 @@
var markEl = row.querySelector('.dash-p-mark');
var pnlEl = row.querySelector('.dash-p-pnl');
var marginEl = row.querySelector('.dash-p-margin');
var sltpEl = row.querySelector('.dash-p-sltp');
var slEl = row.querySelector('.dash-p-sl');
var tpEl = row.querySelector('.dash-p-tp');
if (lotsEl) lotsEl.textContent = String(r.lots);
if (entryEl) entryEl.textContent = fmtNum(r.entry_price);
if (markEl) markEl.textContent = r.current_price != null ? fmtNum(r.current_price) : '—';
@@ -405,7 +438,8 @@
pnlEl.className = 'dash-p-pnl ' + pnlClass(r.float_pnl);
}
if (marginEl) marginEl.textContent = r.margin != null ? fmtMoney(r.margin) : '—';
if (sltpEl) sltpEl.textContent = slTpText(r);
if (slEl) slEl.textContent = slText(r);
if (tpEl) tpEl.textContent = tpText(r);
});
}
}