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():
"""获取实盘交易统计"""
try:
service = get_real_trading_service()
if not service:
return {
"success": False,
"message": "实盘交易服务未启用",
"stats": None
}
logger.info("[stats] 开始获取统计数据")
# 获取账户信息
account = service.get_account_status()
account = {}
trading_api = get_bitget_trading_api()
logger.info(f"[stats] trading_api: {trading_api}")
# 获取历史订单统计
from app.services.db_service import db_service
from app.models.real_trading import RealOrder
from app.models.paper_trading import OrderStatus
if trading_api:
try:
logger.info("[stats] 开始获取账户信息")
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:
# 获取已平仓订单
closed_orders = db.query(RealOrder).filter(
RealOrder.status == OrderStatus.CLOSED
).all()
# 获取持仓价值
logger.info("[stats] 开始获取持仓")
positions = trading_api.get_position()
logger.info(f"[stats] positions count: {len(positions)}")
total_position_value = sum(
float(p.get('notional', 0)) for p in positions
)
# 计算统计数据
total_trades = len(closed_orders)
winning_trades = len([o for o in closed_orders if o.pnl > 0])
losing_trades = len([o for o in closed_orders if o.pnl < 0])
total_pnl = sum([o.pnl or 0 for o in closed_orders])
win_rate = (winning_trades / total_trades * 100) if total_trades > 0 else 0
account = {
'current_balance': available + frozen + locked,
'available': available,
'used_margin': frozen + locked,
'total_position_value': total_position_value
}
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 = {
"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()
logger.info(f"[stats] 返回统计数据: {stats}")
return {
"success": True,
@ -315,6 +320,8 @@ async def get_trading_stats():
}
except Exception as e:
logger.error(f"获取实盘交易统计失败: {e}")
import traceback
logger.error(traceback.format_exc())
raise HTTPException(status_code=500, detail=str(e))

View File

@ -539,8 +539,15 @@ def get_bitget_trading_api() -> Optional[BitgetTradingAPI]:
"""
global _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

View File

@ -516,34 +516,6 @@
</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">
<button
@ -632,6 +604,28 @@
</template>
<!-- 历史订单表格 -->
<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>
</table>
</div>
@ -664,7 +658,6 @@
used_margin: 0,
total_position_value: 0
},
stats: null,
orderHistory: [],
exchangePositions: [],
autoRefreshInterval: null
@ -700,7 +693,6 @@
await Promise.all([
this.fetchServiceStatus(),
this.fetchAccountStatus(),
this.fetchStats(),
this.fetchExchangePositions()
]);
@ -735,9 +727,15 @@
autoTradingEnabled: this.autoTradingEnabled,
account: this.account
});
} else {
console.error('[fetchServiceStatus] API 返回失败:', response.data.message);
}
} 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);
if (response.data.success) {
this.account = response.data.account;
} else {
console.error('[fetchAccountStatus] API 返回失败:', response.data.message);
}
} catch (error) {
console.error('获取账户状态失败:', error);
}
},
async fetchStats() {
try {
const response = await axios.get('/api/real-trading/stats');
if (response.data.success) {
this.stats = response.data.stats;
console.error('[fetchAccountStatus] 获取账户状态失败:', error);
if (error.response?.status === 500) {
console.error('[fetchAccountStatus] 服务器内部错误,请检查后端日志');
} else if (error.code === 'ERR_NETWORK') {
console.error('[fetchAccountStatus] 网络错误,请检查后端服务是否启动');
}
} catch (error) {
console.error('获取统计数据失败:', error);
}
},
@ -807,13 +801,22 @@
async fetchOrderHistory() {
try {
console.log('[fetchOrderHistory] 开始请求历史订单...');
// 获取历史订单
const response = await axios.get('/api/real-trading/orders?status=orders&limit=50');
console.log('[fetchOrderHistory] 响应:', response.data);
if (response.data.success) {
this.orderHistory = response.data.orders;
} else {
console.error('[fetchOrderHistory] API 返回失败:', response.data.message);
this.orderHistory = [];
}
} 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);
if (response.data.success) {
this.exchangePositions = response.data.positions;
} else {
console.error('[fetchExchangePositions] API 返回失败:', response.data.message);
this.exchangePositions = [];
}
} 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()