(function () { var list = document.getElementById('position-live-list'); var pollTimer = null; function fmtNum(v, digits) { if (v === null || v === undefined) return '--'; return Number(v).toFixed(digits === undefined ? 2 : digits); } 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); var dirBadge = row.direction_label || (row.direction === 'long' ? '做多' : '做空'); var closeBtn = ''; if (row.close_url) { closeBtn = '
' + '
'; } else if (row.can_close) { closeBtn = ''; } var metaParts = ['来源 ' + (row.source_label || row.source) + '']; if (row.risk_pct != null) { metaParts.push('风险 ' + fmtNum(row.risk_pct) + '%≈' + fmtNum(row.risk_amount) + '元'); } if (row.tick_value_total != null) { metaParts.push('每跳 ' + fmtNum(row.tick_value_total) + '元'); } var slTp = '
' + (row.stop_loss != null ? fmtNum(row.stop_loss) : '--') + '
' + '
' + (row.take_profit != null ? fmtNum(row.take_profit) : '--') + '
'; var footerParts = ['张数 ' + row.lots]; if (row.margin != null) footerParts.push('保证金 ' + fmtNum(row.margin) + '元'); if (row.position_pct != null) footerParts.push('仓位占比 ' + fmtNum(row.position_pct) + '%'); if (openT) footerParts.push('开仓 ' + openT); if (row.holding_duration) footerParts.push('持仓 ' + row.holding_duration); if (row.est_fee != null) footerParts.push('手续费(估) ' + fmtNum(row.est_fee) + '元'); return ( '
' + '
' + '
' + row.symbol + ' ' + dirBadge + '
' + (row.symbol_code && row.symbol_code !== row.symbol ? '
' + row.symbol_code + '
' : '') + '
' + closeBtn + '
' + '
' + metaParts.join(' · ') + '
' + '
' + '
' + fmtNum(row.entry_price) + '
' + slTp + '
' + rr + '
' + '
' + (row.mark_price != null ? fmtNum(row.mark_price) : '--') + '
' + '
' + pnlText + '
' + (row.est_fee != null ? '
' + fmtNum(row.est_fee) + '元
' + '
' + '
' + (row.est_pnl_net != null ? fmtNum(row.est_pnl_net) + '元' : '--') + '
' : '') + '
' + '' + '
' ); } function closePosition(payload) { var price = payload.mark_price; if (!price || price <= 0) { alert('无法获取现价,请稍后重试'); return; } if (!confirm('确认以 ' + price + ' 限价平仓 ' + payload.lots + ' 手?')) return; fetch('/api/trading/close', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ source: payload.source, symbol_code: payload.symbol_code, direction: payload.direction, lots: payload.lots, price: price, monitor_id: payload.monitor_id }) }).then(function (r) { return r.json(); }).then(function (d) { if (!d.ok) { alert(d.error || '平仓失败'); return; } pollPositions(); }).catch(function () { alert('平仓请求失败'); }); } function pollPositions() { if (!list) return; fetch('/api/trading/live') .then(function (r) { return r.json(); }) .then(function (data) { var cap = document.getElementById('cap-display'); if (cap && data.capital != null) cap.textContent = Number(data.capital).toFixed(2); var recCap = document.getElementById('rec-capital'); if (recCap && data.capital != null) recCap.textContent = Number(data.capital).toFixed(2); var riskBadge = document.getElementById('risk-badge'); if (riskBadge && data.risk_status) { riskBadge.textContent = data.risk_status.status_label; riskBadge.className = 'badge ' + (data.risk_status.can_trade ? 'profit' : 'loss'); } var ctpBadge = document.getElementById('ctp-badge'); if (ctpBadge && data.ctp_status) { ctpBadge.textContent = data.ctp_status.connected ? 'CTP 已连接' : 'CTP 未连接'; ctpBadge.className = 'badge ' + (data.ctp_status.connected ? 'profit' : 'planned'); } var rows = data.rows || []; if (!rows.length) { list.innerHTML = '
暂无持仓。请通过上方「期货下单 → 策略交易」开仓,或连接 CTP 同步柜台持仓。
'; return; } list.innerHTML = rows.map(buildPosCard).join(''); list.querySelectorAll('[data-close]').forEach(function (btn) { btn.addEventListener('click', function () { closePosition(JSON.parse(btn.getAttribute('data-close'))); }); }); }) .catch(function () { if (list.innerHTML.indexOf('pos-card') < 0) { list.innerHTML = '
加载失败,请刷新页面
'; } }); } var btnConnect = document.getElementById('btn-ctp-connect'); if (btnConnect) { btnConnect.addEventListener('click', function () { btnConnect.disabled = true; btnConnect.textContent = '连接中…'; fetch('/api/ctp/connect', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' }) .then(function (r) { return r.json(); }) .then(function (d) { if (!d.ok) { alert(d.error || '连接失败'); return; } location.reload(); }) .finally(function () { btnConnect.disabled = false; btnConnect.textContent = '连接 CTP'; }); }); } document.addEventListener('DOMContentLoaded', function () { pollPositions(); pollTimer = setInterval(pollPositions, 2000); }); })();