Add key-level auto trade, AI analysis, and trading UX improvements.

Key monitors use 5m close triggers with WeChat alerts and box/convergence auto orders; add pending-order worker, structured WeChat notify, AI settings/messages, session clock, CTP margin sizing, and dual-layer position limits.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-28 10:36:56 +08:00
parent 0109b59f27
commit 840e88daad
33 changed files with 2514 additions and 143 deletions
+80
View File
@@ -61,6 +61,7 @@
window.TRADE_FIXED_LOTS = cfg.fixed_lots;
window.TRADE_FIXED_AMOUNT = cfg.fixed_amount;
window.__RECOMMEND_ROWS__ = cfg.recommend_rows || [];
if (cfg.session_clock) applySessionClock(cfg.session_clock);
} catch (e) { /* ignore */ }
}
@@ -119,6 +120,12 @@
var sym = selectedSymbol();
var maxLots = maxLotsForSymbol(sym);
var lots = effectiveLots();
if (lastSizingInfo && lastSizingInfo.capped_by === 'margin' && lastSizingInfo.lots_by_risk > lots) {
warn.hidden = false;
warn.textContent = '以损定仓 ' + lastSizingInfo.lots_by_risk + ' 手,保证金上限 ' +
(lastSizingInfo.max_margin_pct || '') + '% 收紧为 ' + lots + ' 手';
return;
}
if (maxLots > 0 && lots > maxLots) {
warn.hidden = false;
warn.textContent = '已超过最大手数 ' + maxLots + ' 手,请调整手数';
@@ -205,6 +212,7 @@
ctpConnected = !!connected;
ctpConnecting = !!connecting;
isTradingSession = !!data.trading_session;
if (data.session_clock) applySessionClock(data.session_clock);
syncCtpBadgeFromStatus(data.ctp_status || { connected: connected, connecting: connecting });
if (data.ctp_status && typeof data.ctp_status.auto_connect_enabled === 'boolean') {
ctpAutoConnectEnabled = data.ctp_status.auto_connect_enabled;
@@ -380,6 +388,71 @@
}
var lastPreviewMetrics = null;
var lastSizingInfo = null;
var sessionClockBase = null;
var sessionClockTickTimer = null;
function fmtCountdown(secs) {
secs = Math.max(0, parseInt(secs, 10) || 0);
var h = Math.floor(secs / 3600);
var m = Math.floor((secs % 3600) / 60);
var s = secs % 60;
if (h > 0) return h + '小时' + (m < 10 ? '0' : '') + m + '分' + (s < 10 ? '0' : '') + s + '秒';
if (m > 0) return m + '分' + (s < 10 ? '0' : '') + s + '秒';
return s + '秒';
}
function fmtClockNow(d) {
var mo = d.getMonth() + 1;
var da = d.getDate();
var pad = function (n) { return n < 10 ? '0' + n : String(n); };
return pad(mo) + '-' + pad(da) + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());
}
function tickSessionClock() {
var base = sessionClockBase;
if (!base || !base.clock) return;
var c = base.clock;
var elapsed = Math.floor((Date.now() - base.at) / 1000);
var nowEl = document.getElementById('clock-now');
if (nowEl && c.now) {
var t = new Date(String(c.now).replace(/-/g, '/'));
if (!isNaN(t.getTime())) {
t.setSeconds(t.getSeconds() + elapsed);
nowEl.textContent = fmtClockNow(t);
}
}
var statusEl = document.getElementById('clock-status');
if (statusEl) {
statusEl.textContent = c.status_label || (c.in_session ? '交易时间段' : '非交易时间段');
statusEl.className = c.in_session ? 'text-profit' : 'text-muted';
}
var detail = document.getElementById('clock-detail');
if (!detail) return;
var html = '';
if (!c.in_session && c.secs_to_open != null) {
html = ' · 下次' + (c.next_open_label || '开盘') + ' ' + (c.next_open_at || '') +
' · 距开盘 <strong>' + fmtCountdown(c.secs_to_open - elapsed) + '</strong>';
} else if (c.in_session) {
if (c.secs_to_break != null) {
html += ' · 距' + (c.break_label || '休盘') + ' <strong>' +
fmtCountdown(c.secs_to_break - elapsed) + '</strong>';
}
if (c.secs_to_close != null) {
html += ' · 距' + (c.close_label || '收盘') + ' <strong>' +
fmtCountdown(c.secs_to_close - elapsed) + '</strong>';
}
}
detail.innerHTML = html;
}
function applySessionClock(clock) {
if (!clock) return;
sessionClockBase = { clock: clock, at: Date.now() };
tickSessionClock();
if (sessionClockTickTimer) clearInterval(sessionClockTickTimer);
sessionClockTickTimer = setInterval(tickSessionClock, 1000);
}
function setPriceType(type) {
priceType = type === 'market' ? 'market' : 'limit';
@@ -633,6 +706,7 @@
lotsCalc.placeholder = data.error || '无法计算';
}
lastPreviewMetrics = null;
lastSizingInfo = null;
updateRRDisplay();
checkLotsLimit();
return;
@@ -641,12 +715,14 @@
if (lotsInput) lotsInput.value = String(data.lots || '');
lotsCalc.placeholder = isAmountMode() ? '填写止损后自动计算' : '—';
lastPreviewMetrics = data.metrics || null;
lastSizingInfo = data.sizing_info || null;
updateRRDisplay();
checkLotsLimit();
scheduleQuote();
}).catch(function () {
if (isAmountMode()) lotsCalc.placeholder = '计算失败';
lastPreviewMetrics = null;
lastSizingInfo = null;
updateRRDisplay();
});
}
@@ -1631,6 +1707,10 @@
}
function cleanupTradePage() {
if (sessionClockTickTimer) {
clearInterval(sessionClockTickTimer);
sessionClockTickTimer = null;
}
if (positionSource) {
positionSource.close();
positionSource = null;