持仓监控平仓自动记入交易记录,新增交易记录页与实盘资金设置
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+74
-9
@@ -1,12 +1,18 @@
|
||||
(function () {
|
||||
var timer = null;
|
||||
var keyTimer = null;
|
||||
var posTimer = null;
|
||||
|
||||
function fmtDist(v) {
|
||||
if (v === null || v === undefined) return '--';
|
||||
return v.toFixed(2);
|
||||
return Number(v).toFixed(2);
|
||||
}
|
||||
|
||||
function pollPrices() {
|
||||
function fmtNum(v, digits) {
|
||||
if (v === null || v === undefined) return '--';
|
||||
return Number(v).toFixed(digits === undefined ? 2 : digits);
|
||||
}
|
||||
|
||||
function pollKeyPrices() {
|
||||
var list = document.getElementById('key-monitor-list');
|
||||
if (!list || !list.querySelector('.key-item')) return;
|
||||
|
||||
@@ -19,9 +25,7 @@
|
||||
var priceEl = el.querySelector('.live-price');
|
||||
var upEl = el.querySelector('.dist-up');
|
||||
var downEl = el.querySelector('.dist-down');
|
||||
if (priceEl) {
|
||||
priceEl.textContent = row.price != null ? row.price : '--';
|
||||
}
|
||||
if (priceEl) priceEl.textContent = row.price != null ? row.price : '--';
|
||||
if (upEl) upEl.textContent = fmtDist(row.dist_upper);
|
||||
if (downEl) downEl.textContent = fmtDist(row.dist_lower);
|
||||
});
|
||||
@@ -29,10 +33,71 @@
|
||||
.catch(function () { /* ignore */ });
|
||||
}
|
||||
|
||||
function buildPosCard(row) {
|
||||
var pnlClass = '';
|
||||
if (row.float_pnl > 0) pnlClass = 'pnl-pos';
|
||||
if (row.float_pnl < 0) pnlClass = 'pnl-neg';
|
||||
var pnlText = '--';
|
||||
if (row.float_pnl != null) {
|
||||
var sign = row.float_pnl >= 0 ? '+' : '';
|
||||
pnlText = sign + fmtNum(row.float_pnl) + '元';
|
||||
if (row.float_pct != null) {
|
||||
pnlText += ' (' + sign + fmtNum(row.float_pct) + '%)';
|
||||
}
|
||||
}
|
||||
var rr = row.rr_ratio != null ? row.rr_ratio + ':1' : '--';
|
||||
var openT = (row.open_time || '').replace('T', ' ').slice(0, 16);
|
||||
|
||||
return (
|
||||
'<div class="pos-card" data-pos-id="' + row.id + '">' +
|
||||
'<div class="pos-card-head">' +
|
||||
'<div><div class="title">' + row.symbol + ' <span class="badge dir">' + row.direction + '</span></div></div>' +
|
||||
'<form method="post" action="/close_position/' + row.id + '" style="display:inline" onsubmit="return confirm(\'确认平仓?\')">' +
|
||||
'<button type="submit" class="btn-del pos-del">平仓</button></form>' +
|
||||
'</div>' +
|
||||
'<div class="pos-card-meta">来源 <strong>手动输入</strong> · 风险 <strong>' +
|
||||
fmtNum(row.risk_pct) + '%≈' + fmtNum(row.risk_amount) + '元</strong></div>' +
|
||||
'<div class="pos-metrics">' +
|
||||
'<div class="cell"><label>成交价</label><div>' + fmtNum(row.entry_price) + '</div></div>' +
|
||||
'<div class="cell"><label>止损</label><div>' + fmtNum(row.stop_loss) + '</div></div>' +
|
||||
'<div class="cell"><label>止盈</label><div>' + fmtNum(row.take_profit) + '</div></div>' +
|
||||
'<div class="cell"><label>盈亏比</label><div>' + rr + '</div></div>' +
|
||||
'<div class="cell"><label>标记价</label><div>' + (row.mark_price != null ? fmtNum(row.mark_price) : '--') + '</div></div>' +
|
||||
'<div class="cell ' + pnlClass + '"><label>浮盈亏</label><div>' + pnlText + '</div></div>' +
|
||||
'</div>' +
|
||||
'<div class="pos-footer">' +
|
||||
'<span>保证金 ' + fmtNum(row.margin) + '元</span>' +
|
||||
'<span>仓位占比 ' + fmtNum(row.position_pct) + '%</span>' +
|
||||
'<span>开仓 ' + (openT || '--') + '</span>' +
|
||||
'<span>持仓 ' + (row.holding_duration || '--') + '</span>' +
|
||||
'<span>张数 ' + row.lots + '</span>' +
|
||||
'</div></div>'
|
||||
);
|
||||
}
|
||||
|
||||
function pollPositions() {
|
||||
var list = document.getElementById('position-live-list');
|
||||
if (!list) return;
|
||||
|
||||
fetch('/api/position_live')
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (rows) {
|
||||
if (!rows.length) {
|
||||
list.innerHTML = '<div class="empty-hint">暂无持仓,左侧录入后显示</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = rows.map(buildPosCard).join('');
|
||||
})
|
||||
.catch(function () { /* ignore */ });
|
||||
}
|
||||
|
||||
function startPolling() {
|
||||
if (timer) clearInterval(timer);
|
||||
pollPrices();
|
||||
timer = setInterval(pollPrices, 1000);
|
||||
if (keyTimer) clearInterval(keyTimer);
|
||||
if (posTimer) clearInterval(posTimer);
|
||||
pollKeyPrices();
|
||||
pollPositions();
|
||||
keyTimer = setInterval(pollKeyPrices, 1000);
|
||||
posTimer = setInterval(pollPositions, 1000);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', startPolling);
|
||||
|
||||
+3
-3
@@ -25,10 +25,10 @@
|
||||
}
|
||||
if (row.in_zone && distEl) {
|
||||
distEl.innerHTML = '<span class="text-profit" style="font-weight:600">在区间内</span>';
|
||||
} else if (distEl && upEl && downEl) {
|
||||
} else if (distEl) {
|
||||
distEl.innerHTML =
|
||||
'距上 <span class="dist-up">' + fmtDist(row.dist_upper) + '</span>' +
|
||||
' · 距下 <span class="dist-down">' + fmtDist(row.dist_lower) + '</span>';
|
||||
'距上<span class="dist-up">' + fmtDist(row.dist_upper) + '</span> ' +
|
||||
'距下<span class="dist-down">' + fmtDist(row.dist_lower) + '</span>';
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
@@ -162,4 +162,6 @@
|
||||
bindModal();
|
||||
recalc();
|
||||
});
|
||||
|
||||
window.recalc = recalc;
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
(function () {
|
||||
var switchEl = document.getElementById('trade-edit-switch');
|
||||
if (!switchEl) return;
|
||||
|
||||
function setEditMode(on) {
|
||||
document.querySelectorAll('.cell-edit-hide').forEach(function (el) {
|
||||
el.style.display = on ? 'none' : '';
|
||||
});
|
||||
document.querySelectorAll('.cell-edit-show').forEach(function (el) {
|
||||
if (el.type === 'hidden') return;
|
||||
el.style.display = on ? '' : 'none';
|
||||
});
|
||||
document.querySelectorAll('.trade-save-btn').forEach(function (btn) {
|
||||
btn.disabled = !on;
|
||||
});
|
||||
}
|
||||
|
||||
switchEl.addEventListener('change', function () {
|
||||
setEditMode(switchEl.checked);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.trade-save-btn').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var row = btn.closest('tr[data-trade-id]');
|
||||
if (!row) return;
|
||||
var id = row.getAttribute('data-trade-id');
|
||||
var form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/update_trade/' + id;
|
||||
row.querySelectorAll('.cell-edit-show').forEach(function (el) {
|
||||
if (!el.name) return;
|
||||
var input = document.createElement('input');
|
||||
input.type = 'hidden';
|
||||
input.name = el.name;
|
||||
input.value = el.value;
|
||||
form.appendChild(input);
|
||||
});
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
});
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user