diff --git a/frontend/trading.html b/frontend/trading.html
index 5ead61e..bdd623e 100644
--- a/frontend/trading.html
+++ b/frontend/trading.html
@@ -266,6 +266,66 @@
.admin-menu-item:last-child {
border-radius: 0 0 var(--radius-md) var(--radius-md);
}
+
+ .pending-stack {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ min-width: 0;
+ }
+
+ .pending-main {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--text-primary);
+ line-height: 1.4;
+ }
+
+ .pending-sub {
+ font-size: 12px;
+ color: var(--text-secondary);
+ line-height: 1.4;
+ }
+
+ .pending-sub.text-success,
+ .pending-sub.text-error {
+ font-weight: 600;
+ }
+
+ .pending-chip-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+ }
+
+ .pending-chip {
+ display: inline-flex;
+ align-items: center;
+ padding: 2px 8px;
+ border-radius: 999px;
+ background: var(--bg-secondary);
+ border: 1px solid var(--border);
+ font-size: 11px;
+ color: var(--text-secondary);
+ line-height: 1.4;
+ white-space: nowrap;
+ }
+
+ .pending-reason {
+ max-width: 280px;
+ white-space: normal;
+ word-break: break-word;
+ }
+
+ .pending-signal {
+ min-width: 140px;
+ }
+
+ @media (max-width: 1200px) {
+ .pending-reason {
+ max-width: 220px;
+ }
+ }
@@ -436,11 +496,12 @@
| 交易对 |
方向 |
- 数量 |
- 挂单价 |
- 杠杆 |
- 止损 |
- 止盈 |
+ 规模 |
+ 挂单信息 |
+ 风控目标 |
+ 仓位占用 |
+ 信号 |
+ 理由 |
时间 |
操作 |
@@ -453,11 +514,51 @@
{{ order.side === 'long' ? '做多' : '做空' }}
- {{ order.quantity ? order.quantity.toFixed(4) : '0.0000' }} |
- {{ order.entry_price ? '$' + order.entry_price.toFixed(2) : '$0.00' }} |
- {{ order.leverage || 0 }}x |
- {{ order.stop_loss ? '$' + order.stop_loss.toFixed(2) : '-' }} |
- {{ order.take_profit ? '$' + order.take_profit.toFixed(2) : '-' }} |
+
+
+ {{ formatNumber(order.quantity, 4) }}
+ 杠杆 {{ order.leverage || 0 }}x
+
+ |
+
+
+ {{ formatCurrency(order.display_entry_price) }}
+ 现价 {{ formatCurrency(order.current_price) }}
+ {{ order.distance_text }}
+
+ |
+
+
+ {{ formatCurrency(order.stop_loss) }}
+ 止损 {{ formatOptionalSignedPercent(order.stop_loss_percent) }}
+ {{ formatCurrency(order.take_profit) }}
+ 止盈 {{ formatOptionalSignedPercent(order.take_profit_percent) }}
+ 盈亏比 {{ order.risk_reward_text }}
+
+ |
+
+
+ {{ formatCurrency(order.margin) }}
+ 保证金
+ {{ formatCurrency(order.expected_position_value) }}
+ 名义仓位
+
+ |
+
+
+ {{ order.signal_grade_text }}
+
+ {{ order.signal_type_text }}
+ {{ order.confidence_text }}
+ {{ order.entry_type_text }}
+
+
+ |
+
+
+ {{ order.reason_preview }}
+
+ |
{{ formatTime(order.created_at) }} |
@@ -678,7 +779,35 @@
});
},
pendingOrders() {
- return this.orders.filter(order => order.status === 'pending');
+ return this.orders
+ .filter(order => order.status === 'pending')
+ .map(order => {
+ const entryPrice = Number(order.entry_price || 0);
+ const currentPrice = this.resolveOrderCurrentPrice(order);
+ const stopLossPercent = this.calculateOrderTargetPercent(order.side, entryPrice, order.stop_loss);
+ const takeProfitPercent = this.calculateOrderTargetPercent(order.side, entryPrice, order.take_profit);
+ const margin = Number(order.margin || 0);
+ const leverage = Number(order.leverage || 0);
+
+ return {
+ ...order,
+ display_entry_price: entryPrice,
+ current_price: currentPrice || null,
+ distance_percent: this.calculatePendingDistancePercent(currentPrice, entryPrice),
+ distance_text: this.getPendingDistanceText(currentPrice, entryPrice),
+ distance_class: this.getPendingDistanceClass(currentPrice, entryPrice),
+ stop_loss_percent: stopLossPercent,
+ take_profit_percent: takeProfitPercent,
+ risk_reward_ratio: this.calculateRiskRewardRatio(stopLossPercent, takeProfitPercent),
+ risk_reward_text: this.formatRiskRewardRatio(stopLossPercent, takeProfitPercent),
+ expected_position_value: margin > 0 && leverage > 0 ? margin * leverage : 0,
+ signal_grade_text: order.signal_grade || '-',
+ signal_type_text: this.getSignalTypeText(order.signal_type),
+ confidence_text: this.formatConfidence(order.confidence),
+ entry_type_text: this.getEntryTypeText(order.entry_type),
+ reason_preview: this.getPendingReasonPreview(order)
+ };
+ });
},
orderHistory() {
return this.orders.filter(order => {
@@ -865,12 +994,25 @@
return `${number >= 0 ? '+' : '-'}${Math.abs(number).toFixed(2)}%`;
},
+ formatOptionalSignedPercent(value) {
+ const number = Number(value);
+ if (!Number.isFinite(number)) return '-';
+ return this.formatSignedPercent(number);
+ },
+
formatNumber(value, digits = 2) {
const number = Number(value);
if (!Number.isFinite(number)) return (0).toFixed(digits);
return number.toFixed(digits);
},
+ formatConfidence(value) {
+ const number = Number(value);
+ if (!Number.isFinite(number) || number <= 0) return '';
+ const percent = number <= 1 ? number * 100 : number;
+ return `置信度 ${percent.toFixed(1)}%`;
+ },
+
getDisplayEntryPrice(order) {
const price = Number(order.filled_price || order.entry_price || 0);
return Number.isFinite(price) ? price : 0;
@@ -914,6 +1056,114 @@
return positionValue * pnlPercent / 100;
},
+ calculatePendingDistancePercent(currentPrice, entryPrice) {
+ const current = Number(currentPrice);
+ const entry = Number(entryPrice);
+ if (!Number.isFinite(current) || current <= 0 || !Number.isFinite(entry) || entry <= 0) {
+ return null;
+ }
+ return ((entry - current) / current) * 100;
+ },
+
+ getPendingDistanceText(currentPrice, entryPrice) {
+ const distancePercent = this.calculatePendingDistancePercent(currentPrice, entryPrice);
+ if (!Number.isFinite(distancePercent)) {
+ return '等待触发';
+ }
+
+ if (Math.abs(distancePercent) < 0.05) {
+ return '接近触发';
+ }
+
+ const direction = distancePercent > 0 ? '需上涨触发' : '需回落触发';
+ return `${direction} ${this.formatPercent(Math.abs(distancePercent))}`;
+ },
+
+ getPendingDistanceClass(currentPrice, entryPrice) {
+ const distancePercent = this.calculatePendingDistancePercent(currentPrice, entryPrice);
+ if (!Number.isFinite(distancePercent)) {
+ return '';
+ }
+
+ if (Math.abs(distancePercent) < 0.35) {
+ return 'text-success';
+ }
+
+ if (Math.abs(distancePercent) < 1) {
+ return '';
+ }
+
+ return 'text-error';
+ },
+
+ calculateOrderTargetPercent(side, entryPrice, targetPrice) {
+ const entry = Number(entryPrice);
+ const target = Number(targetPrice);
+ if (!Number.isFinite(entry) || entry <= 0 || !Number.isFinite(target) || target <= 0) {
+ return null;
+ }
+
+ if (side === 'long') {
+ return ((target - entry) / entry) * 100;
+ }
+
+ if (side === 'short') {
+ return ((entry - target) / entry) * 100;
+ }
+
+ return null;
+ },
+
+ calculateRiskRewardRatio(stopLossPercent, takeProfitPercent) {
+ const stop = Number(stopLossPercent);
+ const take = Number(takeProfitPercent);
+ if (!Number.isFinite(stop) || !Number.isFinite(take) || stop >= 0 || take <= 0) {
+ return null;
+ }
+ return take / Math.abs(stop);
+ },
+
+ formatRiskRewardRatio(stopLossPercent, takeProfitPercent) {
+ const ratio = this.calculateRiskRewardRatio(stopLossPercent, takeProfitPercent);
+ if (!Number.isFinite(ratio) || ratio <= 0) {
+ return '-';
+ }
+ return `1:${ratio.toFixed(2)}`;
+ },
+
+ formatPercent(value, digits = 2) {
+ const number = Number(value);
+ if (!Number.isFinite(number)) return '-';
+ return `${number.toFixed(digits)}%`;
+ },
+
+ getSignalTypeText(signalType) {
+ const map = {
+ short_term: '日内',
+ medium_term: '中线',
+ long_term: '趋势',
+ swing: '波段'
+ };
+ return map[signalType] || signalType || '';
+ },
+
+ getEntryTypeText(entryType) {
+ const map = {
+ market: '市价',
+ limit: '限价',
+ pending: '挂单'
+ };
+ return map[entryType] || entryType || '';
+ },
+
+ getPendingReasonPreview(order) {
+ const reasons = Array.isArray(order.entry_reasons)
+ ? order.entry_reasons.filter(Boolean)
+ : [];
+ const baseText = reasons.length > 0 ? reasons[0] : '暂无入场理由';
+ return baseText.length > 58 ? `${baseText.slice(0, 58)}...` : baseText;
+ },
+
getCloseReason(reason) {
const map = {
'manual': '手动',
|