mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-05 20:11:13 +08:00
sync fork
This commit is contained in:
302
api/server.go
302
api/server.go
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user