1
This commit is contained in:
parent
fe6f7a4b59
commit
ceb479d5d3
@ -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
|
||||
|
||||
db = db_service.get_session()
|
||||
if trading_api:
|
||||
try:
|
||||
# 获取已平仓订单
|
||||
closed_orders = db.query(RealOrder).filter(
|
||||
RealOrder.status == OrderStatus.CLOSED
|
||||
).all()
|
||||
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))
|
||||
|
||||
# 计算统计数据
|
||||
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
|
||||
# 获取持仓价值
|
||||
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
|
||||
)
|
||||
|
||||
# 计算最大回撤等指标
|
||||
# TODO: 实现更详细的统计
|
||||
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 = {}
|
||||
|
||||
# 尝试从数据库获取统计
|
||||
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),
|
||||
"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),
|
||||
}
|
||||
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))
|
||||
|
||||
|
||||
|
||||
@ -539,8 +539,15 @@ def get_bitget_trading_api() -> Optional[BitgetTradingAPI]:
|
||||
"""
|
||||
global _trading_api
|
||||
|
||||
# 如果已有实例,检查它是否仍然有效
|
||||
if _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
|
||||
|
||||
|
||||
@ -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);
|
||||
console.error('[fetchAccountStatus] 获取账户状态失败:', error);
|
||||
if (error.response?.status === 500) {
|
||||
console.error('[fetchAccountStatus] 服务器内部错误,请检查后端日志');
|
||||
} else if (error.code === 'ERR_NETWORK') {
|
||||
console.error('[fetchAccountStatus] 网络错误,请检查后端服务是否启动');
|
||||
}
|
||||
},
|
||||
|
||||
async fetchStats() {
|
||||
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() {
|
||||
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] 网络错误,请检查后端服务是否启动');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
57
scripts/test_real_trading_api.py
Normal file
57
scripts/test_real_trading_api.py
Normal 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()
|
||||
Loading…
Reference in New Issue
Block a user