Isolate CTP in worker process and improve strategy roll UX.
Split vn.py into qihuo-ctp worker with IPC client bridge, keep CTP connected during breaks with cached account fallback, speed up strategy page loads, and allow off-session breakout roll submissions. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -109,6 +109,7 @@
|
||||
var breakEl = document.getElementById('roll-break-price');
|
||||
var execHint = document.getElementById('roll-exec-hint');
|
||||
var btnExec = document.getElementById('btn-roll-exec');
|
||||
var btnPreview = document.getElementById('btn-roll-preview');
|
||||
if (!modeEl) return;
|
||||
var mode = modeEl.value || 'market';
|
||||
var isBreak = mode === 'breakout';
|
||||
@@ -120,6 +121,12 @@
|
||||
if (btnExec) {
|
||||
btnExec.textContent = mode === 'market' ? '执行滚仓' : '提交监控';
|
||||
}
|
||||
if (btnPreview) {
|
||||
btnPreview.disabled = !inTradingSession && !isBreak;
|
||||
btnPreview.title = (!inTradingSession && !isBreak)
|
||||
? '休盘期间请切换为突破加仓'
|
||||
: '';
|
||||
}
|
||||
}
|
||||
|
||||
function syncRollRiskHint() {
|
||||
@@ -196,6 +203,7 @@
|
||||
var rollPayload = null;
|
||||
var rollMonitorSel = document.getElementById('roll-monitor-select');
|
||||
var rollModeSel = document.getElementById('roll-add-mode');
|
||||
var inTradingSession = {{ 'true' if trading_session else 'false' }};
|
||||
|
||||
if (rollModeSel) rollModeSel.addEventListener('change', syncRollModeUi);
|
||||
if (rollMonitorSel) rollMonitorSel.addEventListener('change', syncRollRiskHint);
|
||||
@@ -214,6 +222,7 @@
|
||||
}
|
||||
showPreview(rollPrev, formatRoll(d.preview), true, false);
|
||||
btnRollE.hidden = false;
|
||||
syncRollModeUi();
|
||||
}).finally(function () {
|
||||
btnRollP.disabled = false;
|
||||
});
|
||||
@@ -224,6 +233,10 @@
|
||||
var payload = rollPayload || formData(rollForm);
|
||||
var mode = (payload.add_mode || 'market');
|
||||
if (mode === 'market') {
|
||||
if (!inTradingSession) {
|
||||
alert('休盘期间请切换为「突破加仓」后提交监控');
|
||||
return;
|
||||
}
|
||||
if (!confirm('确认执行市价滚仓?')) return;
|
||||
startRollCountdown(btnRollE, payload);
|
||||
return;
|
||||
|
||||
+61
-17
@@ -34,6 +34,8 @@
|
||||
var ctpConnecting = false;
|
||||
var ctpAutoConnectEnabled = true;
|
||||
var positionsRendered = false;
|
||||
var posFastPollTimer = null;
|
||||
var posFastPollCount = 0;
|
||||
var lastPosRowCount = 0;
|
||||
var selectedMaxLots = null;
|
||||
var recommendMaxByProduct = {};
|
||||
@@ -248,6 +250,24 @@
|
||||
});
|
||||
}
|
||||
|
||||
function stopPosFastPoll() {
|
||||
if (posFastPollTimer) {
|
||||
clearInterval(posFastPollTimer);
|
||||
posFastPollTimer = null;
|
||||
}
|
||||
posFastPollCount = 0;
|
||||
}
|
||||
|
||||
function startPosFastPoll() {
|
||||
if (posFastPollTimer) return;
|
||||
posFastPollCount = 0;
|
||||
posFastPollTimer = setInterval(function () {
|
||||
pollPositions();
|
||||
posFastPollCount += 1;
|
||||
if (posFastPollCount >= 90) stopPosFastPoll();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function applyPositionsData(data) {
|
||||
if (!data) return;
|
||||
var cap = document.getElementById('cap-display');
|
||||
@@ -312,6 +332,7 @@
|
||||
if (!connected) {
|
||||
if (connecting) {
|
||||
list.innerHTML = '<div class="empty-hint">CTP 连接中,请稍候…</div>';
|
||||
startPosFastPoll();
|
||||
return;
|
||||
}
|
||||
if (cooldownSec > 0 || (data.ctp_status && data.ctp_status.last_error)) {
|
||||
@@ -325,8 +346,8 @@
|
||||
list.innerHTML = '<div class="empty-hint text-muted">' + offHint + '</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = '<div class="empty-hint">CTP 未连接,正在尝试自动重连…</div>';
|
||||
if (ctpAutoConnectEnabled) tryAutoCtpReconnect();
|
||||
list.innerHTML = '<div class="empty-hint">CTP 未连接,后台自动连接中…</div>';
|
||||
if (ctpAutoConnectEnabled) refreshCtpStatusPassive();
|
||||
return;
|
||||
}
|
||||
var syncing = data.sync_state === 'syncing';
|
||||
@@ -339,6 +360,7 @@
|
||||
syncBadge.textContent = data.sync_label || '持仓同步中…';
|
||||
syncBadge.className = 'sync-badge text-accent';
|
||||
}
|
||||
startPosFastPoll();
|
||||
return;
|
||||
}
|
||||
list.innerHTML = '<div class="empty-hint">暂无持仓。</div>';
|
||||
@@ -347,9 +369,7 @@
|
||||
return;
|
||||
}
|
||||
lastPosRowCount = rows.length;
|
||||
if (!connected && ctpAutoConnectEnabled) {
|
||||
tryAutoCtpReconnect();
|
||||
}
|
||||
stopPosFastPoll();
|
||||
list.innerHTML = rows.map(buildPosCard).join('');
|
||||
syncPositionListScroll(rows.length);
|
||||
bindPendingDismiss(list);
|
||||
@@ -610,8 +630,9 @@
|
||||
}
|
||||
if (st.connecting && Date.now() < deadline) {
|
||||
syncCtpBadgeFromStatus(st);
|
||||
pollPositions();
|
||||
return new Promise(function (resolve) {
|
||||
setTimeout(function () { resolve(tick()); }, 2000);
|
||||
setTimeout(function () { resolve(tick()); }, 800);
|
||||
});
|
||||
}
|
||||
syncCtpBadgeFromStatus(st);
|
||||
@@ -795,18 +816,31 @@
|
||||
});
|
||||
}
|
||||
|
||||
function tryAutoCtpReconnect() {
|
||||
if (!ctpAutoConnectEnabled) return;
|
||||
if (ctpReconnecting || ctpConnectInflight) return;
|
||||
/** 只读 CTP 状态;连接由 qihuo-ctp 后台 worker 负责,前端不发起 connect。 */
|
||||
function refreshCtpStatusPassive() {
|
||||
if (ctpConnected || ctpConnecting) return;
|
||||
var now = Date.now();
|
||||
if (now - lastCtpReconnectAt < 60000) return;
|
||||
if (lastCtpLoginBanAt && now - lastCtpLoginBanAt < 2700000) return;
|
||||
if (lastCtpUnreachableAt && now - lastCtpUnreachableAt < 300000) return;
|
||||
if (now - lastCtpReconnectAt < 8000) return;
|
||||
lastCtpReconnectAt = now;
|
||||
ctpReconnecting = true;
|
||||
requestCtpConnect(false).finally(function () {
|
||||
ctpReconnecting = false;
|
||||
});
|
||||
fetch('/api/ctp/status')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) {
|
||||
var st = d.status || {};
|
||||
syncCtpBadgeFromStatus(st);
|
||||
if (st.connected) {
|
||||
showCtpError('');
|
||||
pollPositions();
|
||||
startPosFastPoll();
|
||||
} else if (st.connecting) {
|
||||
updateCtpBadge(false, true);
|
||||
startPosFastPoll();
|
||||
} else if (st.last_error) {
|
||||
showCtpError(st.last_error);
|
||||
} else if (st.disabled_hint) {
|
||||
showCtpError(st.disabled_hint);
|
||||
}
|
||||
})
|
||||
.catch(function () {});
|
||||
}
|
||||
|
||||
function showOrderMsg(text, ok) {
|
||||
@@ -1911,12 +1945,22 @@
|
||||
} else if (st.last_error) {
|
||||
showCtpError(st.last_error);
|
||||
}
|
||||
if (st.connected) pollPositions();
|
||||
if (st.connected) {
|
||||
pollPositions();
|
||||
startPosFastPoll();
|
||||
} else if (st.connecting) {
|
||||
startPosFastPoll();
|
||||
waitForCtpConnected(90000);
|
||||
} else if (ctpAutoConnectEnabled && !(st.login_cooldown_sec > 0)) {
|
||||
refreshCtpStatusPassive();
|
||||
startPosFastPoll();
|
||||
}
|
||||
})
|
||||
.catch(function () {});
|
||||
}
|
||||
|
||||
function cleanupTradePage() {
|
||||
stopPosFastPoll();
|
||||
if (sessionClockTickTimer) {
|
||||
clearInterval(sessionClockTickTimer);
|
||||
sessionClockTickTimer = null;
|
||||
|
||||
Reference in New Issue
Block a user