This commit is contained in:
aaron 2026-02-23 12:06:38 +08:00
parent fe6f7a4b59
commit ceb479d5d3
4 changed files with 166 additions and 85 deletions

View File

@ -261,53 +261,58 @@ async def close_order(order_id: str, exit_price: float = Query(..., description=
async def get_trading_stats(): async def get_trading_stats():
"""获取实盘交易统计""" """获取实盘交易统计"""
try: try:
service = get_real_trading_service() logger.info("[stats] 开始获取统计数据")
if not service:
return {
"success": False,
"message": "实盘交易服务未启用",
"stats": None
}
# 获取账户信息 # 获取账户信息
account = service.get_account_status() account = {}
trading_api = get_bitget_trading_api()
logger.info(f"[stats] trading_api: {trading_api}")
# 获取历史订单统计 if trading_api:
from app.services.db_service import db_service try:
from app.models.real_trading import RealOrder logger.info("[stats] 开始获取账户信息")
from app.models.paper_trading import OrderStatus balance_info = trading_api.get_balance()
logger.info(f"[stats] balance_info: {balance_info}")
usdt_info = balance_info.get('USDT', {})
available = float(usdt_info.get('available', 0))
frozen = float(usdt_info.get('frozen', 0))
locked = float(usdt_info.get('locked', 0))
db = db_service.get_session() # 获取持仓价值
try: logger.info("[stats] 开始获取持仓")
# 获取已平仓订单 positions = trading_api.get_position()
closed_orders = db.query(RealOrder).filter( logger.info(f"[stats] positions count: {len(positions)}")
RealOrder.status == OrderStatus.CLOSED total_position_value = sum(
).all() float(p.get('notional', 0)) for p in positions
)
# 计算统计数据 account = {
total_trades = len(closed_orders) 'current_balance': available + frozen + locked,
winning_trades = len([o for o in closed_orders if o.pnl > 0]) 'available': available,
losing_trades = len([o for o in closed_orders if o.pnl < 0]) 'used_margin': frozen + locked,
total_pnl = sum([o.pnl or 0 for o in closed_orders]) 'total_position_value': total_position_value
win_rate = (winning_trades / total_trades * 100) if total_trades > 0 else 0 }
logger.info(f"[stats] account: {account}")
except Exception as e:
logger.error(f"[stats] 获取账户信息失败: {e}")
import traceback
logger.error(traceback.format_exc())
account = {}
# 计算最大回撤等指标 # 尝试从数据库获取统计
# TODO: 实现更详细的统计 stats = {
"total_trades": 0,
"winning_trades": 0,
"losing_trades": 0,
"win_rate": 0,
"total_pnl": 0,
"current_balance": account.get('current_balance', 0),
"available": account.get('available', 0),
"used_margin": account.get('used_margin', 0),
"total_position_value": account.get('total_position_value', 0),
}
stats = { logger.info(f"[stats] 返回统计数据: {stats}")
"total_trades": total_trades,
"winning_trades": winning_trades,
"losing_trades": losing_trades,
"win_rate": round(win_rate, 2),
"total_pnl": round(total_pnl, 2),
"current_balance": account.get('current_balance', 0),
"available": account.get('available', 0),
"used_margin": account.get('used_margin', 0),
"total_position_value": account.get('total_position_value', 0),
}
finally:
db.close()
return { return {
"success": True, "success": True,
@ -315,6 +320,8 @@ async def get_trading_stats():
} }
except Exception as e: except Exception as e:
logger.error(f"获取实盘交易统计失败: {e}") logger.error(f"获取实盘交易统计失败: {e}")
import traceback
logger.error(traceback.format_exc())
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))

View File

@ -539,8 +539,15 @@ def get_bitget_trading_api() -> Optional[BitgetTradingAPI]:
""" """
global _trading_api global _trading_api
# 如果已有实例,检查它是否仍然有效
if _trading_api: if _trading_api:
return _trading_api try:
# 尝试获取余额来验证连接是否仍然有效
_trading_api.get_balance()
return _trading_api
except Exception as e:
logger.warning(f"Bitget API 实例已失效({e}),将重新创建")
_trading_api = None
from app.config import get_settings from app.config import get_settings

View File

@ -516,34 +516,6 @@
</div> </div>
</div> </div>
<!-- 统计卡片 - 只有在实盘交易启用时才显示交易统计 -->
<div class="stats-grid" v-if="stats && serviceEnabled">
<div class="stat-card">
<div class="stat-label">胜率</div>
<div class="stat-value" :class="stats.win_rate >= 50 ? 'positive' : 'negative'">
{{ stats.win_rate ? stats.win_rate.toFixed(1) : '0' }}%
</div>
</div>
<div class="stat-card">
<div class="stat-label">盈利交易</div>
<div class="stat-value positive">
{{ stats.winning_trades || 0 }}
</div>
</div>
<div class="stat-card">
<div class="stat-label">亏损交易</div>
<div class="stat-value negative">
{{ stats.losing_trades || 0 }}
</div>
</div>
<div class="stat-card">
<div class="stat-label">活跃订单</div>
<div class="stat-value">
{{ activeOrders.length }}
</div>
</div>
</div>
<!-- 标签页 --> <!-- 标签页 -->
<div class="tabs"> <div class="tabs">
<button <button
@ -632,6 +604,28 @@
</template> </template>
<!-- 历史订单表格 --> <!-- 历史订单表格 -->
<template v-else> <template v-else>
<tr v-for="order in displayOrders" :key="order.id">
<td><strong>{{ formatSymbol(order.symbol) }}</strong></td>
<td>
<span class="side-badge" :class="order.side === 'buy' ? 'side-long' : 'side-short'">
{{ order.side === 'buy' ? '做多' : '做空' }}
</span>
</td>
<td>
<span class="status-badge" :class="{
'status-open': order.status === 'open',
'status-pending': order.status === 'pending' || order.status === 'partially_filled',
'status-closed': order.status === 'closed' || order.status === 'filled'
}">
{{ formatOrderStatus(order.status) }}
</span>
</td>
<td>${{ order.price ? parseFloat(order.price).toLocaleString() : '-' }}</td>
<td>{{ order.amount || order.filled || '0' }}</td>
<td>{{ order.filled || '0' }}</td>
<td>{{ order.datetime ? formatTime(order.datetime) : '-' }}</td>
</tr>
</template>
</tbody> </tbody>
</table> </table>
</div> </div>
@ -664,7 +658,6 @@
used_margin: 0, used_margin: 0,
total_position_value: 0 total_position_value: 0
}, },
stats: null,
orderHistory: [], orderHistory: [],
exchangePositions: [], exchangePositions: [],
autoRefreshInterval: null autoRefreshInterval: null
@ -700,7 +693,6 @@
await Promise.all([ await Promise.all([
this.fetchServiceStatus(), this.fetchServiceStatus(),
this.fetchAccountStatus(), this.fetchAccountStatus(),
this.fetchStats(),
this.fetchExchangePositions() this.fetchExchangePositions()
]); ]);
@ -735,9 +727,15 @@
autoTradingEnabled: this.autoTradingEnabled, autoTradingEnabled: this.autoTradingEnabled,
account: this.account account: this.account
}); });
} else {
console.error('[fetchServiceStatus] API 返回失败:', response.data.message);
} }
} catch (error) { } catch (error) {
console.error('获取服务状态失败:', error); console.error('[fetchServiceStatus] 获取服务状态失败:', error);
// 如果是网络错误,显示提示
if (error.code === 'ERR_NETWORK') {
console.error('[fetchServiceStatus] 网络错误,请检查后端服务是否启动');
}
} }
}, },
@ -776,20 +774,16 @@
console.log('[fetchAccountStatus] 响应:', response.data); console.log('[fetchAccountStatus] 响应:', response.data);
if (response.data.success) { if (response.data.success) {
this.account = response.data.account; this.account = response.data.account;
} else {
console.error('[fetchAccountStatus] API 返回失败:', response.data.message);
} }
} catch (error) { } catch (error) {
console.error('获取账户状态失败:', error); console.error('[fetchAccountStatus] 获取账户状态失败:', error);
} if (error.response?.status === 500) {
}, console.error('[fetchAccountStatus] 服务器内部错误,请检查后端日志');
} else if (error.code === 'ERR_NETWORK') {
async fetchStats() { console.error('[fetchAccountStatus] 网络错误,请检查后端服务是否启动');
try {
const response = await axios.get('/api/real-trading/stats');
if (response.data.success) {
this.stats = response.data.stats;
} }
} catch (error) {
console.error('获取统计数据失败:', error);
} }
}, },
@ -807,13 +801,22 @@
async fetchOrderHistory() { async fetchOrderHistory() {
try { try {
console.log('[fetchOrderHistory] 开始请求历史订单...');
// 获取历史订单 // 获取历史订单
const response = await axios.get('/api/real-trading/orders?status=orders&limit=50'); const response = await axios.get('/api/real-trading/orders?status=orders&limit=50');
console.log('[fetchOrderHistory] 响应:', response.data);
if (response.data.success) { if (response.data.success) {
this.orderHistory = response.data.orders; this.orderHistory = response.data.orders;
} else {
console.error('[fetchOrderHistory] API 返回失败:', response.data.message);
this.orderHistory = [];
} }
} catch (error) { } catch (error) {
console.error('获取历史订单失败:', error); console.error('[fetchOrderHistory] 获取历史订单失败:', error);
this.orderHistory = [];
if (error.code === 'ERR_NETWORK') {
console.error('[fetchOrderHistory] 网络错误,请检查后端服务是否启动');
}
} }
}, },
@ -824,9 +827,16 @@
console.log('[fetchExchangePositions] 响应:', response.data); console.log('[fetchExchangePositions] 响应:', response.data);
if (response.data.success) { if (response.data.success) {
this.exchangePositions = response.data.positions; this.exchangePositions = response.data.positions;
} else {
console.error('[fetchExchangePositions] API 返回失败:', response.data.message);
this.exchangePositions = [];
} }
} catch (error) { } catch (error) {
console.error('获取交易所持仓失败:', error); console.error('[fetchExchangePositions] 获取交易所持仓失败:', error);
this.exchangePositions = [];
if (error.code === 'ERR_NETWORK') {
console.error('[fetchExchangePositions] 网络错误,请检查后端服务是否启动');
}
} }
}, },

View File

@ -0,0 +1,57 @@
"""
测试实盘交易 API 端点
"""
import requests
import json
BASE_URL = "http://localhost:8000"
def test_api():
print("=" * 60)
print("测试实盘交易 API")
print("=" * 60)
# 测试 /api/real-trading/status
print("\n1. 测试 /api/real-trading/status")
try:
response = requests.get(f"{BASE_URL}/api/real-trading/status")
print(f" 状态码: {response.status_code}")
print(f" 响应: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
except Exception as e:
print(f" 错误: {e}")
# 测试 /api/real-trading/account
print("\n2. 测试 /api/real-trading/account")
try:
response = requests.get(f"{BASE_URL}/api/real-trading/account")
print(f" 状态码: {response.status_code}")
print(f" 响应: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
except Exception as e:
print(f" 错误: {e}")
# 测试 /api/real-trading/positions
print("\n3. 测试 /api/real-trading/positions")
try:
response = requests.get(f"{BASE_URL}/api/real-trading/positions")
print(f" 状态码: {response.status_code}")
print(f" 响应: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
except Exception as e:
print(f" 错误: {e}")
# 测试 /api/real-trading/orders
print("\n4. 测试 /api/real-trading/orders?status=orders&limit=10")
try:
response = requests.get(f"{BASE_URL}/api/real-trading/orders", params={"status": "orders", "limit": 10})
print(f" 状态码: {response.status_code}")
data = response.json()
print(f" Success: {data.get('success')}")
print(f" Count: {data.get('count')}")
if data.get('orders'):
print(f" Orders: {len(data.get('orders', []))}")
except Exception as e:
print(f" 错误: {e}")
print("\n" + "=" * 60)
if __name__ == "__main__":
test_api()