sync fork

This commit is contained in:
icy
2025-10-30 20:51:22 +08:00
parent cd0166896d
commit 4f5b8b250a
20 changed files with 2337 additions and 1042 deletions

View File

@@ -4,7 +4,9 @@ import (
"fmt"
"log"
"net/http"
"nofx/config"
"nofx/manager"
"time"
"github.com/gin-gonic/gin"
)
@@ -13,11 +15,12 @@ import (
type Server struct {
router *gin.Engine
traderManager *manager.TraderManager
database *config.Database
port int
}
// NewServer 创建API服务器
func NewServer(traderManager *manager.TraderManager, port int) *Server {
func NewServer(traderManager *manager.TraderManager, database *config.Database, port int) *Server {
// 设置为Release模式减少日志输出
gin.SetMode(gin.ReleaseMode)
@@ -29,6 +32,7 @@ func NewServer(traderManager *manager.TraderManager, port int) *Server {
s := &Server{
router: router,
traderManager: traderManager,
database: database,
port: port,
}
@@ -62,11 +66,20 @@ func (s *Server) setupRoutes() {
// API路由组
api := s.router.Group("/api")
{
// 竞赛总览
api.GET("/competition", s.handleCompetition)
// Trader列表
// AI交易员管理
api.GET("/traders", s.handleTraderList)
api.POST("/traders", s.handleCreateTrader)
api.DELETE("/traders/:id", s.handleDeleteTrader)
api.POST("/traders/:id/start", s.handleStartTrader)
api.POST("/traders/:id/stop", s.handleStopTrader)
// AI模型配置
api.GET("/models", s.handleGetModelConfigs)
api.PUT("/models", s.handleUpdateModelConfigs)
// 交易所配置
api.GET("/exchanges", s.handleGetExchangeConfigs)
api.PUT("/exchanges", s.handleUpdateExchangeConfigs)
// 指定trader的数据使用query参数 ?trader_id=xxx
api.GET("/status", s.handleStatus)
@@ -102,28 +115,266 @@ func (s *Server) getTraderFromQuery(c *gin.Context) (*manager.TraderManager, str
return s.traderManager, traderID, nil
}
// handleCompetition 竞赛总览对比所有trader
func (s *Server) handleCompetition(c *gin.Context) {
comparison, err := s.traderManager.GetComparisonData()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": fmt.Sprintf("获取对比数据失败: %v", err),
})
// AI交易员管理相关结构体
type CreateTraderRequest struct {
Name string `json:"name" binding:"required"`
AIModelID string `json:"ai_model_id" binding:"required"`
ExchangeID string `json:"exchange_id" binding:"required"`
InitialBalance float64 `json:"initial_balance"`
}
type ModelConfig struct {
ID string `json:"id"`
Name string `json:"name"`
Provider string `json:"provider"`
Enabled bool `json:"enabled"`
APIKey string `json:"apiKey,omitempty"`
}
type ExchangeConfig struct {
ID string `json:"id"`
Name string `json:"name"`
Type string `json:"type"` // "cex" or "dex"
Enabled bool `json:"enabled"`
APIKey string `json:"apiKey,omitempty"`
SecretKey string `json:"secretKey,omitempty"`
Testnet bool `json:"testnet,omitempty"`
}
type UpdateModelConfigRequest struct {
Models map[string]struct {
Enabled bool `json:"enabled"`
APIKey string `json:"api_key"`
} `json:"models"`
}
type UpdateExchangeConfigRequest struct {
Exchanges map[string]struct {
Enabled bool `json:"enabled"`
APIKey string `json:"api_key"`
SecretKey string `json:"secret_key"`
Testnet bool `json:"testnet"`
} `json:"exchanges"`
}
// handleCreateTrader 创建新的AI交易员
func (s *Server) handleCreateTrader(c *gin.Context) {
var req CreateTraderRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, comparison)
// 生成交易员ID
traderID := fmt.Sprintf("%s_%s_%d", req.ExchangeID, req.AIModelID, time.Now().Unix())
// 创建交易员配置
trader := &config.TraderConfig{
ID: traderID,
Name: req.Name,
AIModelID: req.AIModelID,
ExchangeID: req.ExchangeID,
InitialBalance: req.InitialBalance,
ScanIntervalMinutes: 3, // 默认3分钟
IsRunning: false,
}
// 保存到数据库
err := s.database.CreateTrader(trader)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("创建交易员失败: %v", err)})
return
}
log.Printf("✓ 创建交易员成功: %s (模型: %s, 交易所: %s)", req.Name, req.AIModelID, req.ExchangeID)
c.JSON(http.StatusCreated, gin.H{
"trader_id": traderID,
"trader_name": req.Name,
"ai_model": req.AIModelID,
"is_running": false,
})
}
// handleDeleteTrader 删除交易员
func (s *Server) handleDeleteTrader(c *gin.Context) {
traderID := c.Param("id")
// 从数据库删除
err := s.database.DeleteTrader(traderID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("删除交易员失败: %v", err)})
return
}
// 如果交易员正在运行,先停止它
if trader, err := s.traderManager.GetTrader(traderID); err == nil {
status := trader.GetStatus()
if isRunning, ok := status["is_running"].(bool); ok && isRunning {
trader.Stop()
log.Printf("⏹ 已停止运行中的交易员: %s", traderID)
}
}
log.Printf("✓ 交易员已删除: %s", traderID)
c.JSON(http.StatusOK, gin.H{"message": "交易员已删除"})
}
// handleStartTrader 启动交易员
func (s *Server) handleStartTrader(c *gin.Context) {
traderID := c.Param("id")
trader, err := s.traderManager.GetTrader(traderID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在"})
return
}
// 检查交易员是否已经在运行
status := trader.GetStatus()
if isRunning, ok := status["is_running"].(bool); ok && isRunning {
c.JSON(http.StatusBadRequest, gin.H{"error": "交易员已在运行中"})
return
}
// 启动交易员
go func() {
log.Printf("▶️ 启动交易员 %s (%s)", traderID, trader.GetName())
if err := trader.Run(); err != nil {
log.Printf("❌ 交易员 %s 运行错误: %v", trader.GetName(), err)
}
}()
// 更新数据库中的运行状态
err = s.database.UpdateTraderStatus(traderID, true)
if err != nil {
log.Printf("⚠️ 更新交易员状态失败: %v", err)
}
log.Printf("✓ 交易员 %s 已启动", trader.GetName())
c.JSON(http.StatusOK, gin.H{"message": "交易员已启动"})
}
// handleStopTrader 停止交易员
func (s *Server) handleStopTrader(c *gin.Context) {
traderID := c.Param("id")
trader, err := s.traderManager.GetTrader(traderID)
if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在"})
return
}
// 检查交易员是否正在运行
status := trader.GetStatus()
if isRunning, ok := status["is_running"].(bool); ok && !isRunning {
c.JSON(http.StatusBadRequest, gin.H{"error": "交易员已停止"})
return
}
// 停止交易员
trader.Stop()
// 更新数据库中的运行状态
err = s.database.UpdateTraderStatus(traderID, false)
if err != nil {
log.Printf("⚠️ 更新交易员状态失败: %v", err)
}
log.Printf("⏹ 交易员 %s 已停止", trader.GetName())
c.JSON(http.StatusOK, gin.H{"message": "交易员已停止"})
}
// handleGetModelConfigs 获取AI模型配置
func (s *Server) handleGetModelConfigs(c *gin.Context) {
models, err := s.database.GetAIModels()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("获取AI模型配置失败: %v", err)})
return
}
c.JSON(http.StatusOK, models)
}
// handleUpdateModelConfigs 更新AI模型配置
func (s *Server) handleUpdateModelConfigs(c *gin.Context) {
var req UpdateModelConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 更新每个模型的配置
for modelID, modelData := range req.Models {
err := s.database.UpdateAIModel(modelID, modelData.Enabled, modelData.APIKey)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("更新模型 %s 失败: %v", modelID, err)})
return
}
}
log.Printf("✓ AI模型配置已更新: %+v", req.Models)
c.JSON(http.StatusOK, gin.H{"message": "模型配置已更新"})
}
// handleGetExchangeConfigs 获取交易所配置
func (s *Server) handleGetExchangeConfigs(c *gin.Context) {
exchanges, err := s.database.GetExchanges()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("获取交易所配置失败: %v", err)})
return
}
c.JSON(http.StatusOK, exchanges)
}
// handleUpdateExchangeConfigs 更新交易所配置
func (s *Server) handleUpdateExchangeConfigs(c *gin.Context) {
var req UpdateExchangeConfigRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 更新每个交易所的配置
for exchangeID, exchangeData := range req.Exchanges {
err := s.database.UpdateExchange(exchangeID, exchangeData.Enabled, exchangeData.APIKey, exchangeData.SecretKey, exchangeData.Testnet)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("更新交易所 %s 失败: %v", exchangeID, err)})
return
}
}
log.Printf("✓ 交易所配置已更新: %+v", req.Exchanges)
c.JSON(http.StatusOK, gin.H{"message": "交易所配置已更新"})
}
// handleTraderList trader列表
func (s *Server) handleTraderList(c *gin.Context) {
traders := s.traderManager.GetAllTraders()
result := make([]map[string]interface{}, 0, len(traders))
traders, err := s.database.GetTraders()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("获取交易员列表失败: %v", err)})
return
}
result := make([]map[string]interface{}, 0, len(traders))
for _, trader := range traders {
// 获取实时运行状态
isRunning := trader.IsRunning
if at, err := s.traderManager.GetTrader(trader.ID); err == nil {
status := at.GetStatus()
if running, ok := status["is_running"].(bool); ok {
isRunning = running
}
}
for _, t := range traders {
result = append(result, map[string]interface{}{
"trader_id": t.GetID(),
"trader_name": t.GetName(),
"ai_model": t.GetAIModel(),
"trader_id": trader.ID,
"trader_name": trader.Name,
"ai_model": trader.AIModelID,
"exchange_id": trader.ExchangeID,
"is_running": isRunning,
"initial_balance": trader.InitialBalance,
})
}
@@ -405,8 +656,16 @@ func (s *Server) Start() error {
addr := fmt.Sprintf(":%d", s.port)
log.Printf("🌐 API服务器启动在 http://localhost%s", addr)
log.Printf("📊 API文档:")
log.Printf(" • GET /api/competition - 竞赛总览对比所有trader")
log.Printf(" • GET /api/traders - Trader列表")
log.Printf(" • GET /health - 健康检查")
log.Printf(" • GET /api/traders - AI交易员列表")
log.Printf(" • POST /api/traders - 创建新的AI交易员")
log.Printf(" • DELETE /api/traders/:id - 删除AI交易员")
log.Printf(" • POST /api/traders/:id/start - 启动AI交易员")
log.Printf(" • POST /api/traders/:id/stop - 停止AI交易员")
log.Printf(" • GET /api/models - 获取AI模型配置")
log.Printf(" • PUT /api/models - 更新AI模型配置")
log.Printf(" • GET /api/exchanges - 获取交易所配置")
log.Printf(" • PUT /api/exchanges - 更新交易所配置")
log.Printf(" • GET /api/status?trader_id=xxx - 指定trader的系统状态")
log.Printf(" • GET /api/account?trader_id=xxx - 指定trader的账户信息")
log.Printf(" • GET /api/positions?trader_id=xxx - 指定trader的持仓列表")
@@ -415,7 +674,6 @@ func (s *Server) Start() error {
log.Printf(" • GET /api/statistics?trader_id=xxx - 指定trader的统计信息")
log.Printf(" • GET /api/equity-history?trader_id=xxx - 指定trader的收益率历史数据")
log.Printf(" • GET /api/performance?trader_id=xxx - 指定trader的AI学习表现分析")
log.Printf(" • GET /health - 健康检查")
log.Println()
return s.router.Run(addr)