1
This commit is contained in:
parent
df55f2ff37
commit
37b1dc682d
@ -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;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@ -436,11 +496,12 @@
|
||||
<tr>
|
||||
<th>交易对</th>
|
||||
<th>方向</th>
|
||||
<th>数量</th>
|
||||
<th>挂单价</th>
|
||||
<th>杠杆</th>
|
||||
<th>止损</th>
|
||||
<th>止盈</th>
|
||||
<th>规模</th>
|
||||
<th>挂单信息</th>
|
||||
<th>风控目标</th>
|
||||
<th>仓位占用</th>
|
||||
<th>信号</th>
|
||||
<th>理由</th>
|
||||
<th>时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
@ -453,11 +514,51 @@
|
||||
{{ order.side === 'long' ? '做多' : '做空' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ order.quantity ? order.quantity.toFixed(4) : '0.0000' }}</td>
|
||||
<td>{{ order.entry_price ? '$' + order.entry_price.toFixed(2) : '$0.00' }}</td>
|
||||
<td>{{ order.leverage || 0 }}x</td>
|
||||
<td>{{ order.stop_loss ? '$' + order.stop_loss.toFixed(2) : '-' }}</td>
|
||||
<td>{{ order.take_profit ? '$' + order.take_profit.toFixed(2) : '-' }}</td>
|
||||
<td>
|
||||
<div class="pending-stack">
|
||||
<div class="pending-main">{{ formatNumber(order.quantity, 4) }}</div>
|
||||
<div class="pending-sub">杠杆 {{ order.leverage || 0 }}x</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="pending-stack">
|
||||
<div class="pending-main">{{ formatCurrency(order.display_entry_price) }}</div>
|
||||
<div class="pending-sub">现价 {{ formatCurrency(order.current_price) }}</div>
|
||||
<div class="pending-sub" :class="order.distance_class">{{ order.distance_text }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="pending-stack">
|
||||
<div class="pending-main">{{ formatCurrency(order.stop_loss) }}</div>
|
||||
<div class="pending-sub text-error">止损 {{ formatOptionalSignedPercent(order.stop_loss_percent) }}</div>
|
||||
<div class="pending-main">{{ formatCurrency(order.take_profit) }}</div>
|
||||
<div class="pending-sub text-success">止盈 {{ formatOptionalSignedPercent(order.take_profit_percent) }}</div>
|
||||
<div class="pending-sub">盈亏比 {{ order.risk_reward_text }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="pending-stack">
|
||||
<div class="pending-main">{{ formatCurrency(order.margin) }}</div>
|
||||
<div class="pending-sub">保证金</div>
|
||||
<div class="pending-main">{{ formatCurrency(order.expected_position_value) }}</div>
|
||||
<div class="pending-sub">名义仓位</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="pending-stack pending-signal">
|
||||
<div class="pending-main">{{ order.signal_grade_text }}</div>
|
||||
<div class="pending-chip-row">
|
||||
<span v-if="order.signal_type_text" class="pending-chip">{{ order.signal_type_text }}</span>
|
||||
<span v-if="order.confidence_text" class="pending-chip">{{ order.confidence_text }}</span>
|
||||
<span v-if="order.entry_type_text" class="pending-chip">{{ order.entry_type_text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="pending-stack pending-reason">
|
||||
<div class="pending-main">{{ order.reason_preview }}</div>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ formatTime(order.created_at) }}</td>
|
||||
<td>
|
||||
<button class="btn btn-danger btn-small" @click="cancelOrder(order)">撤单</button>
|
||||
@ -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': '手动',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user