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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user