| <template> |
| <div> |
| <el-row :gutter="16" style="margin-bottom: 16px;"> |
| <el-col :span="8"> |
| <el-card shadow="never" style="border: 1px solid var(--border-color);"> |
| <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 8px;">持仓数量</div> |
| <div style="font-size: 28px; font-weight: 700; color: var(--accent-blue);">{{ positions.length }}</div> |
| </el-card> |
| </el-col> |
| <el-col :span="8"> |
| <el-card shadow="never" style="border: 1px solid var(--border-color);"> |
| <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 8px;">持仓盈亏</div> |
| <div :style="{ fontSize: '28px', fontWeight: 700, color: totalPnl >= 0 ? 'var(--accent-green)' : 'var(--accent-red)' }"> |
| {{ totalPnl >= 0 ? '+' : '' }}{{ totalPnl.toFixed(2) }} |
| </div> |
| </el-card> |
| </el-col> |
| <el-col :span="8"> |
| <el-card shadow="never" style="border: 1px solid var(--border-color);"> |
| <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 8px;">占用保证金</div> |
| <div style="font-size: 28px; font-weight: 700; color: var(--accent-yellow);">{{ totalMargin.toFixed(2) }}</div> |
| </el-card> |
| </el-col> |
| </el-row> |
| |
| <el-card shadow="never" style="border: 1px solid var(--border-color); margin-bottom: 16px;"> |
| <template #header> |
| <div style="display: flex; justify-content: space-between; align-items: center;"> |
| <span style="font-weight: 600;">当前持仓</span> |
| <el-button size="small" text @click="loadPositions"> |
| <el-icon><Refresh /></el-icon> 刷新 |
| </el-button> |
| </div> |
| </template> |
| <div v-if="positions.length === 0" style="text-align: center; padding: 50px; color: var(--text-secondary);"> |
| 暂无持仓 |
| </div> |
| <el-table v-else :data="positions" stripe style="width: 100%"> |
| <el-table-column prop="symbol" label="合约" width="100" /> |
| <el-table-column label="方向" width="80"> |
| <template #default="{ row }"> |
| <el-tag :type="row.side === 'LONG' ? 'success' : 'danger'" effect="dark"> |
| {{ row.side === 'LONG' ? '多' : '空' }} |
| </el-tag> |
| </template> |
| </el-table-column> |
| <el-table-column prop="quantity" label="数量" width="80" /> |
| <el-table-column label="开仓均价" width="120"> |
| <template #default="{ row }">{{ row.avg_price?.toFixed(2) }}</template> |
| </el-table-column> |
| <el-table-column label="当前价" width="120"> |
| <template #default="{ row }">{{ row.current_price?.toFixed(2) }}</template> |
| </el-table-column> |
| <el-table-column label="浮动盈亏" width="140"> |
| <template #default="{ row }"> |
| <span :class="row.unrealized_pnl >= 0 ? 'price-up' : 'price-down'" style="font-weight: 600;"> |
| {{ row.unrealized_pnl >= 0 ? '+' : '' }}{{ row.unrealized_pnl?.toFixed(2) }} |
| </span> |
| </template> |
| </el-table-column> |
| <el-table-column label="保证金" width="120"> |
| <template #default="{ row }">{{ row.margin?.toFixed(2) }}</template> |
| </el-table-column> |
| <el-table-column prop="leverage" label="杠杆" width="80"> |
| <template #default="{ row }">{{ row.leverage }}x</template> |
| </el-table-column> |
| <el-table-column label="操作" width="100"> |
| <template #default="{ row }"> |
| <el-button size="small" type="danger" @click="closePosition(row)">平仓</el-button> |
| </template> |
| </el-table-column> |
| </el-table> |
| </el-card> |
| |
| <el-card shadow="never" style="border: 1px solid var(--border-color);"> |
| <template #header><span style="font-weight: 600;">成交历史</span></template> |
| <el-table :data="trades" stripe style="width: 100%" size="small" :max-height="300"> |
| <el-table-column prop="trade_id" label="成交号" width="140" /> |
| <el-table-column prop="symbol" label="合约" width="90" /> |
| <el-table-column label="方向" width="70"> |
| <template #default="{ row }"> |
| <el-tag :type="row.side === 'BUY' ? 'success' : 'danger'" size="small">{{ row.side === 'BUY' ? '买' : '卖' }}</el-tag> |
| </template> |
| </el-table-column> |
| <el-table-column prop="quantity" label="数量" width="60" /> |
| <el-table-column label="成交价" width="100"> |
| <template #default="{ row }">{{ row.price?.toFixed(2) }}</template> |
| </el-table-column> |
| <el-table-column label="盈亏" width="100"> |
| <template #default="{ row }"> |
| <span :class="row.pnl >= 0 ? 'price-up' : 'price-down'">{{ row.pnl?.toFixed(2) }}</span> |
| </template> |
| </el-table-column> |
| <el-table-column prop="strategy_id" label="策略" width="140" /> |
| <el-table-column prop="timestamp" label="时间" /> |
| </el-table> |
| </el-card> |
| </div> |
| </template> |
| |
| <script setup> |
| import { ref, computed, onMounted } from 'vue' |
| import { ElMessage } from 'element-plus' |
| import { positionApi, tradeApi, orderApi } from '../api' |
| import { useTradingStore } from '../stores/trading' |
| |
| const store = useTradingStore() |
| const positions = ref([]) |
| const trades = ref([]) |
| |
| const totalPnl = computed(() => positions.value.reduce((sum, p) => sum + (p.unrealized_pnl || 0), 0)) |
| const totalMargin = computed(() => positions.value.reduce((sum, p) => sum + (p.margin || 0), 0)) |
| |
| async function loadPositions() { |
| try { |
| const { data } = await positionApi.getPositions() |
| positions.value = data |
| } catch (e) { console.error(e) } |
| } |
| |
| async function loadTrades() { |
| try { |
| const { data } = await tradeApi.getTrades() |
| trades.value = data |
| } catch (e) { console.error(e) } |
| } |
| |
| async function closePosition(pos) { |
| try { |
| await orderApi.placeOrder({ |
| symbol: pos.symbol, |
| side: pos.side === 'LONG' ? 'SELL' : 'BUY', |
| order_type: 'MARKET', |
| quantity: pos.quantity, |
| }) |
| ElMessage.success('平仓成功') |
| loadPositions() |
| loadTrades() |
| store.fetchAccount() |
| } catch (e) { ElMessage.error('平仓失败') } |
| } |
| |
| onMounted(() => { loadPositions(); loadTrades() }) |
| </script> |
| |