1
This commit is contained in:
parent
3c8d422b85
commit
1de64df2dc
@ -24,88 +24,75 @@ 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, exchange"),
|
status: Optional[str] = Query(None, description="数据源: trades=成交记录, orders=历史订单, exchange=历史订单"),
|
||||||
limit: int = Query(100, description="返回数量限制")
|
limit: int = Query(100, description="返回数量限制")
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
获取实盘订单列表
|
获取实盘交易历史数据
|
||||||
|
|
||||||
- symbol: 可选,按交易对筛选
|
- symbol: 可选,按交易对筛选
|
||||||
- status: 可选
|
- status: 可选
|
||||||
- active: 本地数据库的活跃订单
|
- trades: 交易所成交记录(包含每笔成交和手续费)
|
||||||
- closed: 本地数据库的历史订单
|
- orders: 交易所历史订单(包含订单状态)
|
||||||
- exchange: 交易所的历史订单(推荐)
|
- exchange: 交易所历史订单(同 orders)
|
||||||
- limit: 返回数量限制,默认100
|
- limit: 返回数量限制,默认100
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 如果请求交易所历史订单,直接从交易所获取
|
trading_api = get_bitget_trading_api()
|
||||||
if status == "exchange":
|
|
||||||
trading_api = get_bitget_trading_api()
|
|
||||||
|
|
||||||
if not 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()
|
|
||||||
|
|
||||||
if not service:
|
|
||||||
return {
|
return {
|
||||||
"success": False,
|
"success": False,
|
||||||
"message": "实盘交易服务未启用",
|
"message": "Bitget API 未配置",
|
||||||
"count": 0,
|
"count": 0,
|
||||||
"orders": []
|
"orders": []
|
||||||
}
|
}
|
||||||
|
|
||||||
if status == "active":
|
# 获取成交记录(推荐,包含盈亏信息)
|
||||||
orders = service.get_active_orders()
|
if status == "trades":
|
||||||
elif status == "closed":
|
orders = trading_api.get_closed_orders(symbol, limit)
|
||||||
# 从数据库获取历史订单
|
return {
|
||||||
from app.services.db_service import db_service
|
"success": True,
|
||||||
from app.models.real_trading import RealOrder
|
"count": len(orders),
|
||||||
from app.models.paper_trading import OrderStatus
|
"orders": orders,
|
||||||
|
"source": "trades"
|
||||||
|
}
|
||||||
|
|
||||||
db = db_service.get_session()
|
# 获取历史订单
|
||||||
|
if status in ["orders", "exchange"]:
|
||||||
try:
|
try:
|
||||||
query = db.query(RealOrder).filter(
|
|
||||||
RealOrder.status.in_([OrderStatus.CLOSED, OrderStatus.CANCELLED])
|
|
||||||
)
|
|
||||||
|
|
||||||
if symbol:
|
if symbol:
|
||||||
query = query.filter(RealOrder.symbol == symbol)
|
ccxt_symbol = trading_api._standardize_symbol(symbol)
|
||||||
|
orders = trading_api.exchange.fetch_closed_orders(ccxt_symbol, limit=limit)
|
||||||
|
else:
|
||||||
|
orders = trading_api.exchange.fetch_closed_orders(limit=limit)
|
||||||
|
|
||||||
orders = [order.to_dict() for order in query.order_by(
|
return {
|
||||||
RealOrder.created_at.desc()
|
"success": True,
|
||||||
).limit(limit).all()]
|
"count": len(orders),
|
||||||
finally:
|
"orders": orders,
|
||||||
db.close()
|
"source": "orders"
|
||||||
else:
|
}
|
||||||
# 返回所有订单
|
except Exception as e:
|
||||||
active = service.get_active_orders()
|
logger.error(f"获取历史订单失败: {e}")
|
||||||
# TODO: 获取历史订单
|
return {
|
||||||
orders = active
|
"success": False,
|
||||||
|
"message": f"获取历史订单失败: {str(e)}",
|
||||||
|
"count": 0,
|
||||||
|
"orders": []
|
||||||
|
}
|
||||||
|
|
||||||
|
# 默认返回成交记录
|
||||||
|
orders = trading_api.get_closed_orders(symbol, limit)
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
"count": len(orders),
|
"count": len(orders),
|
||||||
"orders": orders,
|
"orders": orders,
|
||||||
"source": "database"
|
"source": "trades"
|
||||||
}
|
}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"获取实盘订单列表失败: {e}")
|
logger.error(f"获取实盘交易历史失败: {e}")
|
||||||
raise HTTPException(status_code=500, detail=str(e))
|
raise HTTPException(status_code=500, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -376,21 +376,21 @@ class BitgetTradingAPI:
|
|||||||
历史订单列表
|
历史订单列表
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 使用 CCXT 的 fetch_closed_orders 或 fetch_my_trades
|
# 使用 CCXT 的 fetch_my_trades 获取历史成交记录(包含盈亏信息)
|
||||||
if symbol:
|
if symbol:
|
||||||
ccxt_symbol = self._standardize_symbol(symbol)
|
ccxt_symbol = self._standardize_symbol(symbol)
|
||||||
orders = self.exchange.fetch_closed_orders(ccxt_symbol, limit=limit)
|
trades = self.exchange.fetch_my_trades(ccxt_symbol, limit=limit)
|
||||||
else:
|
else:
|
||||||
orders = self.exchange.fetch_closed_orders(limit=limit)
|
trades = self.exchange.fetch_my_trades(limit=limit)
|
||||||
|
|
||||||
logger.debug(f"查询到 {len(orders)} 条历史订单")
|
logger.debug(f"查询到 {len(trades)} 条历史成交记录")
|
||||||
return orders
|
return trades
|
||||||
|
|
||||||
except ccxt.BaseError as e:
|
except ccxt.BaseError as e:
|
||||||
logger.error(f"❌ 查询历史订单失败: {e}")
|
logger.error(f"❌ 查询历史成交失败: {e}")
|
||||||
return []
|
return []
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"❌ 查询历史订单异常: {e}")
|
logger.error(f"❌ 查询历史成交异常: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# ==================== 账户操作 ====================
|
# ==================== 账户操作 ====================
|
||||||
|
|||||||
@ -462,8 +462,14 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="tab"
|
class="tab"
|
||||||
:class="{ active: currentTab === 'history' }"
|
:class="{ active: currentTab === 'trades' }"
|
||||||
@click="currentTab = 'history'">
|
@click="currentTab = 'trades'">
|
||||||
|
成交记录
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="tab"
|
||||||
|
:class="{ active: currentTab === 'orders' }"
|
||||||
|
@click="currentTab = 'orders'">
|
||||||
历史订单
|
历史订单
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -482,7 +488,14 @@
|
|||||||
<p>暂无持仓</p>
|
<p>暂无持仓</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else-if="currentTab === 'history' && historyOrders.length === 0" class="empty-state">
|
<div v-else-if="currentTab === 'trades' && tradeHistory.length === 0" class="empty-state">
|
||||||
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
|
</svg>
|
||||||
|
<p>暂无成交记录</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="currentTab === 'orders' && orderHistory.length === 0" class="empty-state">
|
||||||
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
||||||
</svg>
|
</svg>
|
||||||
@ -503,14 +516,23 @@
|
|||||||
<th>盈亏比例</th>
|
<th>盈亏比例</th>
|
||||||
<th>强平价格</th>
|
<th>强平价格</th>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr v-else-if="currentTab === 'trades'">
|
||||||
|
<th>交易对</th>
|
||||||
|
<th>方向</th>
|
||||||
|
<th>价格</th>
|
||||||
|
<th>数量</th>
|
||||||
|
<th>成交金额</th>
|
||||||
|
<th>手续费</th>
|
||||||
|
<th>盈亏</th>
|
||||||
|
<th>时间</th>
|
||||||
|
</tr>
|
||||||
<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>
|
||||||
</tr>
|
</tr>
|
||||||
@ -539,9 +561,35 @@
|
|||||||
<td>${{ pos.liquidationPrice ? parseFloat(pos.liquidationPrice).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '-' }}</td>
|
<td>${{ pos.liquidationPrice ? parseFloat(pos.liquidationPrice).toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2}) : '-' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</template>
|
</template>
|
||||||
<!-- 订单表格 -->
|
<!-- 成交记录表格 -->
|
||||||
<template v-else>
|
<template v-if="currentTab === 'trades'">
|
||||||
<tr v-for="order in displayOrders" :key="order.id || order.order_id">
|
<tr v-for="trade in displayOrders" :key="trade.id">
|
||||||
|
<td><strong>{{ formatSymbol(trade.symbol) }}</strong></td>
|
||||||
|
<td>
|
||||||
|
<span class="side-badge" :class="trade.side === 'buy' ? 'side-long' : 'side-short'">
|
||||||
|
{{ trade.side === 'buy' ? '买入' : '卖出' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>${{ trade.price ? parseFloat(trade.price).toLocaleString() : '-' }}</td>
|
||||||
|
<td>{{ trade.amount || trade.filled || '0' }}</td>
|
||||||
|
<td>${{ trade.cost ? parseFloat(trade.cost).toFixed(2) : '-' }}</td>
|
||||||
|
<td>
|
||||||
|
<span :class="getFeeClass(trade)">
|
||||||
|
{{ trade.fee && parseFloat(trade.fee) !== 0 ? (trade.fee > 0 ? '+' : '') + parseFloat(trade.fee).toFixed(4) : '-' }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span v-if="trade.return !== undefined" :class="parseFloat(trade.return || 0) >= 0 ? 'pnl-positive' : 'pnl-negative'">
|
||||||
|
{{ parseFloat(trade.return || 0) >= 0 ? '+' : '' }}${{ parseFloat(trade.return || 0).toFixed(2) }}
|
||||||
|
</span>
|
||||||
|
<span v-else>-</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ formatTime(trade.datetime || trade.timestamp) }}</td>
|
||||||
|
</tr>
|
||||||
|
</template>
|
||||||
|
<!-- 历史订单表格 -->
|
||||||
|
<template v-else-if="currentTab === 'orders'">
|
||||||
|
<tr v-for="order in displayOrders" :key="order.id">
|
||||||
<td><strong>{{ formatSymbol(order.symbol) }}</strong></td>
|
<td><strong>{{ formatSymbol(order.symbol) }}</strong></td>
|
||||||
<td>
|
<td>
|
||||||
<span class="side-badge" :class="order.side === 'buy' ? 'side-long' : 'side-short'">
|
<span class="side-badge" :class="order.side === 'buy' ? 'side-long' : 'side-short'">
|
||||||
@ -550,13 +598,8 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>{{ order.type || 'market' }}</td>
|
<td>{{ order.type || 'market' }}</td>
|
||||||
<td>${{ order.price ? parseFloat(order.price).toLocaleString() : '市价' }}</td>
|
<td>${{ order.price ? parseFloat(order.price).toLocaleString() : '市价' }}</td>
|
||||||
<td>{{ order.amount || order.filled || '0' }}</td>
|
<td>{{ order.amount || '0' }}</td>
|
||||||
<td>${{ order.cost ? parseFloat(order.cost).toFixed(2) : '-' }}</td>
|
<td>{{ order.filled || '0' }}</td>
|
||||||
<td>
|
|
||||||
<span :class="getFeeClass(order)">
|
|
||||||
{{ order.fee && parseFloat(order.fee) !== 0 ? (order.fee > 0 ? '+' : '') + parseFloat(order.fee).toFixed(4) : '-' }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
<td>
|
||||||
<span class="status-badge" :class="'status-' + order.status">
|
<span class="status-badge" :class="'status-' + order.status">
|
||||||
{{ formatOrderStatus(order.status) }}
|
{{ formatOrderStatus(order.status) }}
|
||||||
@ -592,14 +635,16 @@
|
|||||||
total_position_value: 0
|
total_position_value: 0
|
||||||
},
|
},
|
||||||
stats: null,
|
stats: null,
|
||||||
historyOrders: [],
|
tradeHistory: [],
|
||||||
|
orderHistory: [],
|
||||||
exchangePositions: [],
|
exchangePositions: [],
|
||||||
autoRefreshInterval: null
|
autoRefreshInterval: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
displayOrders() {
|
displayOrders() {
|
||||||
if (this.currentTab === 'history') return this.historyOrders;
|
if (this.currentTab === 'trades') return this.tradeHistory;
|
||||||
|
if (this.currentTab === 'orders') return this.orderHistory;
|
||||||
if (this.currentTab === 'positions') return this.exchangePositions;
|
if (this.currentTab === 'positions') return this.exchangePositions;
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -612,9 +657,15 @@
|
|||||||
this.fetchServiceStatus(),
|
this.fetchServiceStatus(),
|
||||||
this.fetchAccountStatus(),
|
this.fetchAccountStatus(),
|
||||||
this.fetchStats(),
|
this.fetchStats(),
|
||||||
this.fetchHistoryOrders(),
|
|
||||||
this.fetchExchangePositions()
|
this.fetchExchangePositions()
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// 根据当前标签页获取对应数据
|
||||||
|
if (this.currentTab === 'trades') {
|
||||||
|
await this.fetchTradeHistory();
|
||||||
|
} else if (this.currentTab === 'orders') {
|
||||||
|
await this.fetchOrderHistory();
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
@ -659,12 +710,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async fetchHistoryOrders() {
|
async fetchTradeHistory() {
|
||||||
try {
|
try {
|
||||||
// 从交易所获取历史订单
|
// 获取成交记录(包含盈亏)
|
||||||
const response = await axios.get('/api/real-trading/orders?status=exchange&limit=50');
|
const response = await axios.get('/api/real-trading/orders?status=trades&limit=100');
|
||||||
if (response.data.success) {
|
if (response.data.success) {
|
||||||
this.historyOrders = response.data.orders;
|
this.tradeHistory = response.data.orders;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取成交记录失败:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async fetchOrderHistory() {
|
||||||
|
try {
|
||||||
|
// 获取历史订单
|
||||||
|
const response = await axios.get('/api/real-trading/orders?status=orders&limit=50');
|
||||||
|
if (response.data.success) {
|
||||||
|
this.orderHistory = response.data.orders;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取历史订单失败:', error);
|
console.error('获取历史订单失败:', error);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user