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:
dekun
2026-07-01 12:35:47 +08:00
parent 08d55411aa
commit 9cd81a3ea7
17 changed files with 2214 additions and 227 deletions
+61 -17
View File
@@ -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;