feat: 计仓改为固定手数/固定金额,推荐过滤与CTP保证金,下单与持仓UI优化
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+84
-36
@@ -1,5 +1,6 @@
|
||||
(function () {
|
||||
var sizingMode = window.TRADE_SIZING_MODE || 'risk';
|
||||
var sizingMode = window.TRADE_SIZING_MODE || 'fixed';
|
||||
if (sizingMode === 'risk') sizingMode = 'amount';
|
||||
var list = document.getElementById('position-live-list');
|
||||
var recommendList = document.getElementById('recommend-list');
|
||||
var symInput = document.getElementById('trade-symbol');
|
||||
@@ -48,16 +49,17 @@
|
||||
return (symInput && symInput.value || '').trim();
|
||||
}
|
||||
|
||||
function isRiskMode() {
|
||||
return sizingMode === 'risk';
|
||||
function isFixedMode() {
|
||||
return sizingMode === 'fixed';
|
||||
}
|
||||
|
||||
function isAmountMode() {
|
||||
return sizingMode === 'amount';
|
||||
}
|
||||
|
||||
function effectiveLots() {
|
||||
if (isRiskMode()) {
|
||||
var v = parseInt(lotsCalc && lotsCalc.value, 10);
|
||||
return v > 0 ? v : 0;
|
||||
}
|
||||
return parseInt(lotsInput && lotsInput.value, 10) || 1;
|
||||
var v = parseInt(lotsCalc && lotsCalc.value, 10);
|
||||
return v > 0 ? v : 0;
|
||||
}
|
||||
|
||||
function updateRecommendMaxMaps(data) {
|
||||
@@ -238,9 +240,20 @@
|
||||
var entry = entryPrice();
|
||||
var sl = slInput && slInput.value ? parseFloat(slInput.value) : 0;
|
||||
var tp = tpInput && tpInput.value ? parseFloat(tpInput.value) : 0;
|
||||
var lots = effectiveLots();
|
||||
var parts = [];
|
||||
var rr = calcRR(dir, entry, sl, tp);
|
||||
if (rr) {
|
||||
el.textContent = '盈亏比 ' + rr + ':1';
|
||||
if (rr) parts.push('盈亏比 ' + rr + ':1');
|
||||
if (sl > 0 && entry > 0 && lots > 0 && lastPreviewMetrics) {
|
||||
if (lastPreviewMetrics.risk_amount != null) {
|
||||
parts.push('止损金额 ' + fmtNum(lastPreviewMetrics.risk_amount) + ' 元');
|
||||
}
|
||||
if (lastPreviewMetrics.reward_amount != null && tp > 0) {
|
||||
parts.push('止盈金额 ' + fmtNum(lastPreviewMetrics.reward_amount) + ' 元');
|
||||
}
|
||||
}
|
||||
if (parts.length) {
|
||||
el.textContent = parts.join(' · ');
|
||||
el.hidden = false;
|
||||
} else {
|
||||
el.textContent = '';
|
||||
@@ -248,6 +261,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
var lastPreviewMetrics = null;
|
||||
|
||||
function setPriceType(type) {
|
||||
priceType = type === 'market' ? 'market' : 'limit';
|
||||
@@ -353,7 +367,7 @@
|
||||
|
||||
function refreshQuote() {
|
||||
var sym = selectedSymbol();
|
||||
var lots = isRiskMode() ? (effectiveLots() || 1) : (lotsInput ? lotsInput.value : '1');
|
||||
var lots = effectiveLots() || (isFixedMode() ? (window.TRADE_FIXED_LOTS || 1) : 1);
|
||||
if (!sym) return;
|
||||
fetch('/api/trade/quote?symbol=' + encodeURIComponent(sym) + '&lots=' + encodeURIComponent(lots))
|
||||
.then(function (r) { return r.json(); })
|
||||
@@ -381,23 +395,39 @@
|
||||
}
|
||||
|
||||
function scheduleAutoCalc() {
|
||||
if (!isRiskMode()) return;
|
||||
clearTimeout(calcTimer);
|
||||
calcTimer = setTimeout(autoCalcLots, 450);
|
||||
}
|
||||
|
||||
function autoCalcLots() {
|
||||
if (!isRiskMode() || !lotsCalc) return;
|
||||
if (!lotsCalc) return;
|
||||
var sym = selectedSymbol();
|
||||
var entry = entryPrice() || parseFloat(priceInput && priceInput.value) || 0;
|
||||
var sl = parseFloat(slInput && slInput.value) || 0;
|
||||
if (!sym || !entry || !sl) {
|
||||
lotsCalc.value = '';
|
||||
lotsCalc.placeholder = '填写止损后自动计算';
|
||||
checkLotsLimit();
|
||||
var tp = parseFloat(tpInput && tpInput.value) || 0;
|
||||
if (isFixedMode()) {
|
||||
var fixedLots = parseInt(window.TRADE_FIXED_LOTS, 10) || 1;
|
||||
lotsCalc.value = String(fixedLots);
|
||||
if (lotsInput) lotsInput.value = String(fixedLots);
|
||||
if (!sym || !entry) {
|
||||
lastPreviewMetrics = null;
|
||||
updateRRDisplay();
|
||||
checkLotsLimit();
|
||||
return;
|
||||
}
|
||||
} else if (isAmountMode()) {
|
||||
if (!sym || !entry || !sl) {
|
||||
lotsCalc.value = '';
|
||||
lotsCalc.placeholder = '填写止损后自动计算';
|
||||
lastPreviewMetrics = null;
|
||||
updateRRDisplay();
|
||||
checkLotsLimit();
|
||||
return;
|
||||
}
|
||||
lotsCalc.placeholder = '计算中…';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
lotsCalc.placeholder = '计算中…';
|
||||
fetch('/api/trade/preview', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
@@ -407,20 +437,30 @@
|
||||
entry: entry,
|
||||
price: entry,
|
||||
stop_loss: sl,
|
||||
take_profit: parseFloat(tpInput && tpInput.value) || 0
|
||||
take_profit: tp
|
||||
})
|
||||
}).then(function (r) { return r.json(); }).then(function (data) {
|
||||
if (!data.ok) {
|
||||
lotsCalc.value = '';
|
||||
lotsCalc.placeholder = data.error || '无法计算';
|
||||
if (isAmountMode()) {
|
||||
lotsCalc.value = '';
|
||||
lotsCalc.placeholder = data.error || '无法计算';
|
||||
}
|
||||
lastPreviewMetrics = null;
|
||||
updateRRDisplay();
|
||||
checkLotsLimit();
|
||||
return;
|
||||
}
|
||||
lotsCalc.value = data.lots;
|
||||
lotsCalc.placeholder = '填写止损后自动计算';
|
||||
lotsCalc.value = String(data.lots || '');
|
||||
if (lotsInput) lotsInput.value = String(data.lots || '');
|
||||
lotsCalc.placeholder = isAmountMode() ? '填写止损后自动计算' : '—';
|
||||
lastPreviewMetrics = data.metrics || null;
|
||||
updateRRDisplay();
|
||||
checkLotsLimit();
|
||||
scheduleQuote();
|
||||
}).catch(function () {
|
||||
lotsCalc.placeholder = '计算失败';
|
||||
if (isAmountMode()) lotsCalc.placeholder = '计算失败';
|
||||
lastPreviewMetrics = null;
|
||||
updateRRDisplay();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -470,12 +510,16 @@
|
||||
showOrderMsg('开启移动保本须填写止损价', false);
|
||||
return;
|
||||
}
|
||||
if (isRiskMode() && lots <= 0) {
|
||||
showOrderMsg('请填写止损,系统将自动计算手数', false);
|
||||
if (isAmountMode() && lots <= 0) {
|
||||
showOrderMsg('请填写止损,系统将按固定金额自动计算手数', false);
|
||||
return;
|
||||
}
|
||||
if (!isRiskMode() && lots <= 0) {
|
||||
showOrderMsg('请填写手数', false);
|
||||
if (isFixedMode() && lots <= 0) {
|
||||
showOrderMsg('手数无效,请检查系统设置中的固定手数', false);
|
||||
return;
|
||||
}
|
||||
if (lots <= 0) {
|
||||
showOrderMsg('请填写有效手数', false);
|
||||
return;
|
||||
}
|
||||
var maxLots = maxLotsForSymbol(sym);
|
||||
@@ -627,10 +671,13 @@
|
||||
' · 浮盈' +
|
||||
(slTpBtn ? ' · ' + slTpBtn : '') +
|
||||
(row.sl_order_active ? ' · <span class="text-profit">止损监控中</span>' : '') +
|
||||
(row.tp_order_active ? ' · <span class="text-profit">止盈监控中</span>' : '') +
|
||||
(row.trailing_be ? ' · <span class="text-accent">移动保本' +
|
||||
(row.trailing_r_locked ? '(锁' + row.trailing_r_locked + 'R)' : '') + '</span>' : '') + '</div>' +
|
||||
(row.tp_order_active ? ' · <span class="text-profit">止盈监控中</span>' : '') + '</div>' +
|
||||
'<div class="pos-metrics">' +
|
||||
'<div class="cell"><label>移动保本</label><div>' +
|
||||
(row.trailing_be ?
|
||||
'<span class="text-accent">已开启' + (row.trailing_r_locked ? '(锁' + row.trailing_r_locked + 'R)' : '') + '</span>' :
|
||||
'<span class="text-muted">未开启</span>') +
|
||||
'</div></div>' +
|
||||
'<div class="cell"><label>持仓均价</label><div>' + fmtNum(row.entry_price) + '</div></div>' +
|
||||
'<div class="cell"><label>当前价格</label><div>' + (row.current_price != null ? fmtNum(row.current_price) : '--') + '</div></div>' +
|
||||
'<div class="cell"><label>止损</label><div>' + (row.stop_loss != null ? fmtNum(row.stop_loss) : '--') + '</div></div>' +
|
||||
@@ -861,7 +908,7 @@
|
||||
'<td>' + (r.price != null ? r.price : '—') + '</td>' +
|
||||
'<td>' + (r.ref_stop_loss != null ? r.ref_stop_loss : '—') + '</td>' +
|
||||
'<td>' + (r.ref_take_profit != null ? r.ref_take_profit : '—') + '</td>' +
|
||||
'<td>' + (r.margin_one_lot != null ? r.margin_one_lot : '—') + '</td>' +
|
||||
'<td>' + (r.margin_one_lot != null ? r.margin_one_lot + (r.margin_source === 'ctp' ? ' <span class="text-muted">(柜台)</span>' : '') : '—') + '</td>' +
|
||||
'<td>' + (r.open_fee_one_lot != null ? r.open_fee_one_lot : '—') + '</td>' +
|
||||
'<td>' + (r.max_lots != null && r.max_lots > 0 ? r.max_lots : '—') + '</td>' +
|
||||
'<td><span class="badge ' + (r.status === 'ok' ? 'profit' : 'planned') + '">' + (r.status_label || '') + '</span></td>' +
|
||||
@@ -904,10 +951,6 @@
|
||||
checkLotsLimit();
|
||||
});
|
||||
}
|
||||
if (lotsInput) lotsInput.addEventListener('input', function () {
|
||||
scheduleQuote();
|
||||
checkLotsLimit();
|
||||
});
|
||||
if (lotsCalc) lotsCalc.addEventListener('input', checkLotsLimit);
|
||||
if (slInput) {
|
||||
slInput.addEventListener('input', function () {
|
||||
@@ -945,6 +988,10 @@
|
||||
|
||||
runWhenReady(function () {
|
||||
setPriceType('limit');
|
||||
if (isFixedMode() && lotsCalc) {
|
||||
lotsCalc.value = String(window.TRADE_FIXED_LOTS || 1);
|
||||
if (lotsInput) lotsInput.value = lotsCalc.value;
|
||||
}
|
||||
var cached = loadPosCache();
|
||||
if (cached) {
|
||||
applyPositionsData(cached);
|
||||
@@ -965,5 +1012,6 @@
|
||||
updateSessionUi();
|
||||
updateRRDisplay();
|
||||
scheduleQuote();
|
||||
scheduleAutoCalc();
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user