Fix position flicker, drop futures cooloff, prioritize startup display.

Preserve trading state when CTP memory is empty, bootstrap equity/positions on page load, stabilize risk status from DB monitors, and remove app-layer manual close cooling periods.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-30 23:17:18 +08:00
parent 2386eca324
commit 1b3a7f1bdc
9 changed files with 218 additions and 153 deletions
+4 -4
View File
@@ -422,10 +422,7 @@
{ label: '持仓限制', value: active + ' / ' + (maxPos != null ? maxPos : '—') },
{ 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: manualCnt + ' / ' + (manualLim != null ? manualLim : '—') },
{
label: '综合保证金占比',
valueHtml: riskMarginPctHtml(marginPct, rollMaxPct),
@@ -862,6 +859,9 @@
equityEl.textContent = fmtMoney(data.capital);
}
var rows = positionRows(data);
if (!rows.length && data.sync_state === 'syncing' && lastPosRows.length) {
rows = lastPosRows;
}
var sig = rows.map(function (r) {
var key = r.key || r.position_key || ((r.symbol_code || '') + ':' + (r.direction || ''));
return key + '|' + (isBreakevenLocked(r) ? '1' : '0') + '|' + slText(r) + '|' + tpText(r) + '|' + String(r.lots);
+51 -25
View File
@@ -34,6 +34,7 @@
var ctpConnecting = false;
var ctpAutoConnectEnabled = true;
var positionsRendered = false;
var lastPosRowCount = 0;
var selectedMaxLots = null;
var recommendMaxByProduct = {};
var recommendMaxByCode = {};
@@ -63,6 +64,13 @@
window.TRADE_FIXED_AMOUNT = cfg.fixed_amount;
window.__RECOMMEND_ROWS__ = cfg.recommend_rows || [];
if (cfg.session_clock) applySessionClock(cfg.session_clock);
if (cfg.capital != null) {
var capEl = document.getElementById('cap-display');
if (capEl) capEl.textContent = Number(cfg.capital).toFixed(2);
}
if (cfg.bootstrap_live && (cfg.bootstrap_live.rows || cfg.bootstrap_live.capital != null)) {
window.__BOOTSTRAP_LIVE__ = cfg.bootstrap_live;
}
} catch (e) { /* ignore */ }
}
@@ -149,15 +157,15 @@
function savePosCache(data) {
try {
if (!data) return;
var connected = data.ctp_status && data.ctp_status.connected;
if (!connected) {
sessionStorage.removeItem(POS_CACHE_KEY);
var hasRows = data.rows && data.rows.length;
var hasOrders = data.active_orders && data.active_orders.length;
if (!hasRows && !hasOrders && data.capital == null) {
return;
}
if (!data.rows || !data.rows.length) {
if (!data.active_orders || !data.active_orders.length) {
sessionStorage.removeItem(POS_CACHE_KEY);
return;
if (!hasRows && lastPosRowCount > 0) {
var prev = loadPosCache();
if (prev && prev.rows && prev.rows.length) {
data = Object.assign({}, data, { rows: prev.rows });
}
}
sessionStorage.setItem(POS_CACHE_KEY, JSON.stringify(data));
@@ -321,10 +329,22 @@
if (ctpAutoConnectEnabled) tryAutoCtpReconnect();
return;
}
var syncing = data.sync_state === 'syncing';
var hadPos = lastPosRowCount > 0 || !!list.querySelector('.pos-card');
if (syncing || hadPos) {
if (syncBadge) {
syncBadge.hidden = false;
syncBadge.textContent = data.sync_label || '持仓同步中…';
syncBadge.className = 'sync-badge text-accent';
}
return;
}
list.innerHTML = '<div class="empty-hint">暂无持仓。</div>';
lastPosRowCount = 0;
syncPositionListScroll(0);
return;
}
lastPosRowCount = rows.length;
if (!connected && ctpAutoConnectEnabled) {
tryAutoCtpReconnect();
}
@@ -1926,12 +1946,16 @@
lotsCalc.value = String(window.TRADE_FIXED_LOTS || 1);
if (lotsInput) lotsInput.value = lotsCalc.value;
}
var cached = loadPosCache();
if (cached) {
if (cached.ctp_status) {
cached.ctp_status = Object.assign({}, cached.ctp_status, { connecting: false });
}
if (cached.ctp_status && cached.ctp_status.connected) {
var bootData = window.__BOOTSTRAP_LIVE__;
if (bootData) {
applyPositionsData(bootData);
savePosCache(bootData);
} else {
var cached = loadPosCache();
if (cached && ((cached.rows && cached.rows.length) || cached.capital != null)) {
if (cached.ctp_status) {
cached.ctp_status = Object.assign({}, cached.ctp_status, { connecting: false });
}
applyPositionsData(cached);
}
}
@@ -1939,20 +1963,22 @@
connectPositionStream();
bindSlTpModal();
initCtpOnLoad();
connectRecommendStream();
initRecommendSortControls();
if (window.__RECOMMEND_ROWS__ && window.__RECOMMEND_ROWS__.length) {
recRowsRaw = window.__RECOMMEND_ROWS__.slice();
renderRecommendTable();
}
fetch('/api/recommend/list')
.then(function (r) { return r.json(); })
.then(function (data) { if (data.ok) renderRecommendations(data); })
.catch(function () {});
updateSessionUi();
updateRRDisplay();
scheduleQuote();
scheduleAutoCalc();
setTimeout(function () {
connectRecommendStream();
initRecommendSortControls();
if (window.__RECOMMEND_ROWS__ && window.__RECOMMEND_ROWS__.length) {
recRowsRaw = window.__RECOMMEND_ROWS__.slice();
renderRecommendTable();
}
fetch('/api/recommend/list')
.then(function (r) { return r.json(); })
.then(function (data) { if (data.ok) renderRecommendations(data); })
.catch(function () {});
scheduleQuote();
scheduleAutoCalc();
}, 400);
}
document.addEventListener('visibilitychange', function () {