feat: 盈亏比与亏损额度展示,市价FAK报单,修复止盈止损保存失败

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-25 14:33:05 +08:00
parent 367f32dd82
commit 63beda3c71
5 changed files with 125 additions and 88 deletions
+78 -7
View File
@@ -208,6 +208,43 @@
return parseFloat(priceInput && priceInput.value) || 0;
}
function calcRR(direction, entry, sl, tp) {
entry = parseFloat(entry);
sl = parseFloat(sl);
tp = parseFloat(tp);
if (!entry || !sl || !tp) return null;
var risk, reward;
if (direction === 'long') {
risk = entry - sl;
reward = tp - entry;
} else if (direction === 'short') {
risk = sl - entry;
reward = entry - tp;
} else {
return null;
}
if (risk <= 0 || reward <= 0) return null;
return (reward / risk).toFixed(2);
}
function updateRRDisplay() {
var el = document.getElementById('trade-rr-hint');
if (!el) return;
var dir = dirSelect ? dirSelect.value : 'long';
var entry = entryPrice();
var sl = slInput && slInput.value ? parseFloat(slInput.value) : 0;
var tp = tpInput && tpInput.value ? parseFloat(tpInput.value) : 0;
var rr = calcRR(dir, entry, sl, tp);
if (rr) {
el.textContent = '盈亏比 ' + rr + ':1';
el.hidden = false;
} else {
el.textContent = '';
el.hidden = true;
}
}
function setPriceType(type) {
priceType = type === 'market' ? 'market' : 'limit';
document.querySelectorAll('.price-tab').forEach(function (btn) {
@@ -218,6 +255,7 @@
if (priceType === 'market' && lastQuotePrice) priceInput.value = lastQuotePrice;
}
if (marketHint) marketHint.hidden = priceType !== 'market';
updateRRDisplay();
}
function updateCtpBadge(connected, connecting) {
@@ -547,7 +585,7 @@
'<button type="button" class="pos-dismiss-btn pos-sl-btn" data-sl-tp="' +
encodeURIComponent(JSON.stringify({
symbol_code: row.symbol_code, direction: row.direction,
lots: row.lots, entry_price: row.entry_price
lots: row.lots, entry_price: row.entry_price, monitor_id: row.monitor_id || null
})) + '">设置止盈止损</button>' : '';
var orderBtn = '';
if (row.monitor_id && (row.stop_loss != null || row.take_profit != null) && row.can_place_orders) {
@@ -561,12 +599,19 @@
'<button type="button" class="pos-close-btn" data-close="' + closePayload + '">平仓</button>' : '';
var actionBtns = (orderBtn || closeBtn) ?
'<div class="pos-card-actions">' + orderBtn + closeBtn + '</div>' : '';
var riskMeta = '';
if (row.rr_ratio != null) {
riskMeta += ' · 盈亏比 <strong>' + row.rr_ratio + ':1</strong>';
}
if (row.risk_amount != null) {
riskMeta += ' · 亏损额度 <strong>' + fmtNum(row.risk_amount) + ' 元</strong>';
}
return (
'<div class="pos-card">' +
'<div class="pos-card-head"><div><div class="title">' + row.symbol + ' <span class="badge dir">' + dirBadge + '</span></div>' +
'<div class="text-muted" style="font-size:.72rem">' + (row.symbol_code || '') + '</div></div>' +
actionBtns + '</div>' +
'<div class="pos-card-meta">来源 <strong>' + (row.source_label || 'CTP') + '</strong>' +
'<div class="pos-card-meta">来源 <strong>' + (row.source_label || 'CTP') + '</strong>' + riskMeta +
(row.sync_pending ? ' · <span class="text-muted">同步柜台中…</span>' : '') +
' · 浮盈' +
(slTpBtn ? ' · ' + slTpBtn : '') +
@@ -652,22 +697,33 @@
fetch('/api/trading/monitor/upsert', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'same-origin',
body: JSON.stringify({
symbol_code: payload.symbol_code,
direction: payload.direction,
lots: payload.lots,
entry_price: payload.entry_price,
monitor_id: payload.monitor_id || null,
stop_loss: sl,
take_profit: tp
})
})
.then(function (r) { return r.json(); })
.then(function (r) {
if (!r.ok) {
return r.json().catch(function () { return {}; }).then(function (d) {
throw new Error(d.error || ('HTTP ' + r.status));
});
}
return r.json();
})
.then(function (d) {
if (!d.ok) throw new Error(d.error || '保存失败');
pollPositions();
})
.catch(function (e) {
alert(e.message || '保存失败');
var msg = e.message || '保存失败';
if (msg === 'Failed to fetch') msg = '网络请求失败,请检查服务是否运行';
alert(msg);
if (btn) {
btn.disabled = false;
btn.textContent = '设置止盈止损';
@@ -830,13 +886,27 @@
checkLotsLimit();
});
if (lotsCalc) lotsCalc.addEventListener('input', checkLotsLimit);
if (slInput) slInput.addEventListener('input', scheduleAutoCalc);
if (tpInput) tpInput.addEventListener('input', scheduleAutoCalc);
if (dirSelect) dirSelect.addEventListener('change', scheduleAutoCalc);
if (slInput) {
slInput.addEventListener('input', function () {
scheduleAutoCalc();
updateRRDisplay();
});
}
if (tpInput) {
tpInput.addEventListener('input', function () {
scheduleAutoCalc();
updateRRDisplay();
});
}
if (dirSelect) dirSelect.addEventListener('change', function () {
scheduleAutoCalc();
updateRRDisplay();
});
if (priceInput) {
priceInput.addEventListener('input', function () {
if (priceType === 'limit') priceInput.dataset.manual = '1';
scheduleAutoCalc();
updateRRDisplay();
});
}
@@ -869,6 +939,7 @@
}
});
updateSessionUi();
updateRRDisplay();
scheduleQuote();
});
})();