mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 09:58:22 +08:00
Enhance leaderboard and security for trader management
Features: - Limit leaderboard to top 50 traders sorted by PnL percentage - Add top 10 traders endpoint for performance comparison - Create batch equity history endpoint to optimize frontend performance - Add public trader config endpoint without sensitive data Security: - Add user ownership validation for start/stop trader operations - Prevent users from controlling other users' traders - Maintain consistent error messages for security Performance: - Reduce API calls from 10 to 1 for performance comparison page - Add data limits and error handling for batch operations - Sort traders by performance across all endpoints API Changes: - GET /api/traders - now returns top 50 sorted traders - GET /api/top-traders - new endpoint for top 10 traders - GET /api/equity-history-batch - batch endpoint for multiple trader histories - GET /api/traders/:id/public-config - public config without secrets - POST /api/traders/:id/start - now validates user ownership - POST /api/traders/:id/stop - now validates user ownership 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: tinkle-community <tinklefund@gmail.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"log"
|
||||
"nofx/config"
|
||||
"nofx/trader"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -525,12 +526,108 @@ func (tm *TraderManager) GetCompetitionData() (map[string]interface{}, error) {
|
||||
|
||||
traders = append(traders, traderData)
|
||||
}
|
||||
|
||||
// 按收益率排序(降序)
|
||||
sort.Slice(traders, func(i, j int) bool {
|
||||
pnlPctI, okI := traders[i]["total_pnl_pct"].(float64)
|
||||
pnlPctJ, okJ := traders[j]["total_pnl_pct"].(float64)
|
||||
if !okI {
|
||||
pnlPctI = 0
|
||||
}
|
||||
if !okJ {
|
||||
pnlPctJ = 0
|
||||
}
|
||||
return pnlPctI > pnlPctJ
|
||||
})
|
||||
|
||||
// 限制返回前50名
|
||||
totalCount := len(traders)
|
||||
limit := 50
|
||||
if len(traders) > limit {
|
||||
traders = traders[:limit]
|
||||
}
|
||||
|
||||
comparison["traders"] = traders
|
||||
comparison["count"] = len(traders)
|
||||
comparison["total_count"] = totalCount // 总交易员数量
|
||||
|
||||
return comparison, nil
|
||||
}
|
||||
|
||||
// GetTopTradersData 获取前10名交易员数据(用于表现对比)
|
||||
func (tm *TraderManager) GetTopTradersData() (map[string]interface{}, error) {
|
||||
tm.mu.RLock()
|
||||
defer tm.mu.RUnlock()
|
||||
|
||||
traders := make([]map[string]interface{}, 0)
|
||||
|
||||
// 获取全平台所有交易员
|
||||
for _, t := range tm.traders {
|
||||
account, err := t.GetAccountInfo()
|
||||
status := t.GetStatus()
|
||||
|
||||
var traderData map[string]interface{}
|
||||
|
||||
if err != nil {
|
||||
// 如果获取账户信息失败,使用默认值
|
||||
traderData = map[string]interface{}{
|
||||
"trader_id": t.GetID(),
|
||||
"trader_name": t.GetName(),
|
||||
"ai_model": t.GetAIModel(),
|
||||
"exchange": t.GetExchange(),
|
||||
"total_equity": 0.0,
|
||||
"total_pnl": 0.0,
|
||||
"total_pnl_pct": 0.0,
|
||||
"position_count": 0,
|
||||
"margin_used_pct": 0.0,
|
||||
"is_running": status["is_running"],
|
||||
}
|
||||
} else {
|
||||
// 正常情况下使用真实账户数据
|
||||
traderData = map[string]interface{}{
|
||||
"trader_id": t.GetID(),
|
||||
"trader_name": t.GetName(),
|
||||
"ai_model": t.GetAIModel(),
|
||||
"exchange": t.GetExchange(),
|
||||
"total_equity": account["total_equity"],
|
||||
"total_pnl": account["total_pnl"],
|
||||
"total_pnl_pct": account["total_pnl_pct"],
|
||||
"position_count": account["position_count"],
|
||||
"margin_used_pct": account["margin_used_pct"],
|
||||
"is_running": status["is_running"],
|
||||
}
|
||||
}
|
||||
|
||||
traders = append(traders, traderData)
|
||||
}
|
||||
|
||||
// 按收益率排序(降序)
|
||||
sort.Slice(traders, func(i, j int) bool {
|
||||
pnlPctI, okI := traders[i]["total_pnl_pct"].(float64)
|
||||
pnlPctJ, okJ := traders[j]["total_pnl_pct"].(float64)
|
||||
if !okI {
|
||||
pnlPctI = 0
|
||||
}
|
||||
if !okJ {
|
||||
pnlPctJ = 0
|
||||
}
|
||||
return pnlPctI > pnlPctJ
|
||||
})
|
||||
|
||||
// 限制返回前10名
|
||||
limit := 10
|
||||
if len(traders) > limit {
|
||||
traders = traders[:limit]
|
||||
}
|
||||
|
||||
result := map[string]interface{}{
|
||||
"traders": traders,
|
||||
"count": len(traders),
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// isUserTrader 检查trader是否属于指定用户
|
||||
func isUserTrader(traderID, userID string) bool {
|
||||
// trader ID格式: userID_traderName 或 randomUUID_modelName
|
||||
|
||||
Reference in New Issue
Block a user