This commit is contained in:
aaron 2026-02-23 00:22:17 +08:00
parent a9f0aa0717
commit 9eb9bc9a49
3 changed files with 106 additions and 31 deletions

View File

@ -24,17 +24,42 @@ class OrderResponse(BaseModel):
@router.get("/orders") @router.get("/orders")
async def get_orders( async def get_orders(
symbol: Optional[str] = Query(None, description="交易对筛选"), symbol: Optional[str] = Query(None, description="交易对筛选"),
status: Optional[str] = Query(None, description="状态筛选: active, closed"), status: Optional[str] = Query(None, description="状态筛选: active, closed, exchange"),
limit: int = Query(100, description="返回数量限制") limit: int = Query(100, description="返回数量限制")
): ):
""" """
获取实盘订单列表 获取实盘订单列表
- symbol: 可选按交易对筛选 - symbol: 可选按交易对筛选
- status: 可选active=活跃订单, closed=已平仓订单 - status: 可选
- active: 本地数据库的活跃订单
- closed: 本地数据库的历史订单
- exchange: 交易所的历史订单推荐
- limit: 返回数量限制默认100 - limit: 返回数量限制默认100
""" """
try: try:
# 如果请求交易所历史订单,直接从交易所获取
if status == "exchange":
trading_api = get_bitget_trading_api()
if not trading_api:
return {
"success": False,
"message": "Bitget API 未配置",
"count": 0,
"orders": []
}
orders = trading_api.get_closed_orders(symbol, limit)
return {
"success": True,
"count": len(orders),
"orders": orders,
"source": "exchange"
}
# 否则从本地数据库获取
service = get_real_trading_service() service = get_real_trading_service()
if not service: if not service:
@ -76,7 +101,8 @@ async def get_orders(
return { return {
"success": True, "success": True,
"count": len(orders), "count": len(orders),
"orders": orders "orders": orders,
"source": "database"
} }
except Exception as e: except Exception as e:
logger.error(f"获取实盘订单列表失败: {e}") logger.error(f"获取实盘订单列表失败: {e}")

View File

@ -364,6 +364,35 @@ class BitgetTradingAPI:
return positions[0] return positions[0]
return None return None
def get_closed_orders(self, symbol: str = None, limit: int = 100) -> List[Dict]:
"""
查询历史成交订单从交易所获取
Args:
symbol: 交易对不传则查询所有
limit: 返回数量
Returns:
历史订单列表
"""
try:
# 使用 CCXT 的 fetch_closed_orders 或 fetch_my_trades
if symbol:
ccxt_symbol = self._standardize_symbol(symbol)
orders = self.exchange.fetch_closed_orders(ccxt_symbol, limit=limit)
else:
orders = self.exchange.fetch_closed_orders(limit=limit)
logger.debug(f"查询到 {len(orders)} 条历史订单")
return orders
except ccxt.BaseError as e:
logger.error(f"❌ 查询历史订单失败: {e}")
return []
except Exception as e:
logger.error(f"❌ 查询历史订单异常: {e}")
return []
# ==================== 账户操作 ==================== # ==================== 账户操作 ====================
def get_balance(self) -> Dict: def get_balance(self) -> Dict:

View File

@ -229,6 +229,14 @@
color: #ff4444; color: #ff4444;
} }
.fee-positive {
color: #00ff41;
}
.fee-negative {
color: #ff4444;
}
.status-badge { .status-badge {
display: inline-block; display: inline-block;
padding: 2px 8px; padding: 2px 8px;
@ -498,14 +506,11 @@
<tr v-else> <tr v-else>
<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>
<th>止盈</th>
<th>盈亏</th>
<th>状态</th> <th>状态</th>
<th>时间</th> <th>时间</th>
</tr> </tr>
@ -536,36 +541,28 @@
</template> </template>
<!-- 订单表格 --> <!-- 订单表格 -->
<template v-else> <template v-else>
<tr v-for="order in displayOrders" :key="order.order_id"> <tr v-for="order in displayOrders" :key="order.id || order.order_id">
<td><strong>{{ order.symbol }}</strong></td> <td><strong>{{ formatSymbol(order.symbol) }}</strong></td>
<td> <td>
<span class="side-badge" :class="'side-' + order.side"> <span class="side-badge" :class="order.side === 'buy' ? 'side-long' : 'side-short'">
{{ order.side === 'long' ? '做多' : '做空' }} {{ order.side === 'buy' ? '买入' : '卖出' }}
</span> </span>
</td> </td>
<td>{{ order.type || 'market' }}</td>
<td>${{ order.price ? parseFloat(order.price).toLocaleString() : '市价' }}</td>
<td>{{ order.amount || order.filled || '0' }}</td>
<td>${{ order.cost ? parseFloat(order.cost).toFixed(2) : '-' }}</td>
<td> <td>
<span class="grade-badge" :class="'grade-' + (order.signal_grade || 'D')"> <span :class="getFeeClass(order)">
{{ order.signal_grade || 'D' }} {{ order.fee && parseFloat(order.fee) !== 0 ? (order.fee > 0 ? '+' : '') + parseFloat(order.fee).toFixed(4) : '-' }}
</span> </span>
</td> </td>
<td>${{ order.entry_price ? order.entry_price.toLocaleString() : '-' }}</td>
<td>${{ order.current_price ? order.current_price.toLocaleString() : '-' }}</td>
<td>${{ order.quantity ? order.quantity.toLocaleString() : '-' }}</td>
<td>{{ order.leverage || 1 }}x</td>
<td>${{ order.stop_loss ? order.stop_loss.toLocaleString() : '-' }}</td>
<td>${{ order.take_profit ? order.take_profit.toLocaleString() : '-' }}</td>
<td>
<span v-if="order.pnl !== undefined" :class="order.pnl >= 0 ? 'pnl-positive' : 'pnl-negative'">
{{ order.pnl >= 0 ? '+' : '' }}${{ order.pnl.toFixed(2) }}
</span>
<span v-else>-</span>
</td>
<td> <td>
<span class="status-badge" :class="'status-' + order.status"> <span class="status-badge" :class="'status-' + order.status">
{{ formatStatus(order.status) }} {{ formatOrderStatus(order.status) }}
</span> </span>
</td> </td>
<td>{{ formatTime(order.created_at) }}</td> <td>{{ formatTime(order.datetime || order.timestamp) }}</td>
</tr> </tr>
</template> </template>
</tbody> </tbody>
@ -664,7 +661,8 @@
async fetchHistoryOrders() { async fetchHistoryOrders() {
try { try {
const response = await axios.get('/api/real-trading/orders?status=closed&limit=50'); // 从交易所获取历史订单
const response = await axios.get('/api/real-trading/orders?status=exchange&limit=50');
if (response.data.success) { if (response.data.success) {
this.historyOrders = response.data.orders; this.historyOrders = response.data.orders;
} }
@ -710,6 +708,28 @@
// 例如BTC/USDT:USDT -> BTCUSDT // 例如BTC/USDT:USDT -> BTCUSDT
if (!symbol) return '-'; if (!symbol) return '-';
return symbol.replace('/', '').replace(':', ''); return symbol.replace('/', '').replace(':', '');
},
formatOrderStatus(status) {
const map = {
'open': '挂单中',
'closed': '已成交',
'canceled': '已取消',
'cancelled': '已取消',
'filled': '已成交',
'partially_filled': '部分成交',
'rejected': '已拒绝',
'expired': '已过期'
};
return map[status] || status;
},
getFeeClass(order) {
const fee = parseFloat(order.fee || 0);
if (fee === 0) return '';
// 如果是 taker (fee > 0),显示为负(花费)
// 如果是 maker (fee < 0)显示为正返还
return fee > 0 ? 'fee-negative' : 'fee-positive';
} }
}, },
mounted() { mounted() {