From 486ff5e909c3e4985a8256733c49bb757633d5d7 Mon Sep 17 00:00:00 2001 From: Lawrence Liu Date: Mon, 10 Nov 2025 01:20:30 +0800 Subject: [PATCH] =?UTF-8?q?fix(trader):=20=E4=BF=AE=E5=A4=8D=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E4=BA=A4=E6=98=93=E5=91=98=E6=97=B6=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D=E6=A8=A1=E6=9D=BF=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=92=8C=E5=9B=9E=E6=98=BE=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98=20(#841)=20##=20=E9=97=AE=E9=A2=98=E6=8F=8F=E8=BF=B0?= =?UTF-8?q?=201.=20=E2=9A=A0=EF=B8=8F=20**=E6=97=A0=E6=B3=95=E6=9B=B4?= =?UTF-8?q?=E6=96=B0**=EF=BC=88=E6=9C=80=E4=B8=A5=E9=87=8D=EF=BC=89?= =?UTF-8?q?=EF=BC=9A=E7=94=A8=E6=88=B7=E4=BF=AE=E6=94=B9=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D=E6=A8=A1=E6=9D=BF=E5=B9=B6=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E5=90=8E=EF=BC=8C=E6=9B=B4=E6=96=B0=E8=A2=AB=E5=BF=BD?= =?UTF-8?q?=E7=95=A5=EF=BC=8C=E4=BB=8D=E4=BF=9D=E6=8C=81=E6=97=A7=E5=80=BC?= =?UTF-8?q?=202.=20=E7=BC=96=E8=BE=91=E6=97=B6=E6=98=BE=E7=A4=BA=E9=94=99?= =?UTF-8?q?=E8=AF=AF=E7=9A=84=E9=BB=98=E8=AE=A4=E5=80=BC=EF=BC=9A=E6=89=93?= =?UTF-8?q?=E5=BC=80=E7=BC=96=E8=BE=91=E5=AF=B9=E8=AF=9D=E6=A1=86=E6=97=B6?= =?UTF-8?q?=E8=AF=A5=E5=AD=97=E6=AE=B5=E6=98=BE=E7=A4=BA=E4=B8=BA=20Defaul?= =?UTF-8?q?t=20=E8=80=8C=E9=9D=9E=E5=AE=9E=E9=99=85=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E7=9A=84=E5=80=BC=EF=BC=88=E5=A6=82=20nof1=EF=BC=89=20##=20?= =?UTF-8?q?=E6=A0=B9=E6=9C=AC=E5=8E=9F=E5=9B=A0=201.=20UpdateTraderRequest?= =?UTF-8?q?=20=E7=BB=93=E6=9E=84=E4=BD=93=E7=BC=BA=E5=B0=91=20SystemPrompt?= =?UTF-8?q?Template=20=E5=AD=97=E6=AE=B5=20-=20=E5=90=8E=E7=AB=AF=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E6=8E=A5=E6=94=B6=E6=9B=B4=E6=96=B0=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=202.=20handleGetTraderConfig=20=E8=BF=94=E5=9B=9E=E5=80=BC?= =?UTF-8?q?=E4=B8=AD=E7=BC=BA=E5=B0=91=20system=5Fprompt=5Ftemplate=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=20-=20=E5=89=8D=E7=AB=AF=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E8=8E=B7=E5=8F=96=E5=AE=9E=E9=99=85=E5=80=BC=203.=20handleUpda?= =?UTF-8?q?teTrader=20=E5=BC=BA=E5=88=B6=E4=BD=BF=E7=94=A8=E5=8E=9F?= =?UTF-8?q?=E5=80=BC=EF=BC=8C=E4=B8=8D=E6=8E=A5=E5=8F=97=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E6=9B=B4=E6=96=B0=20-=20=E5=8D=B3=E4=BD=BF?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E5=8F=91=E9=80=81=E4=B9=9F=E8=A2=AB=E5=BF=BD?= =?UTF-8?q?=E7=95=A5=20##=20=E4=BF=AE=E5=A4=8D=E5=86=85=E5=AE=B9=201.=20?= =?UTF-8?q?=E5=9C=A8=20UpdateTraderRequest=20=E4=B8=AD=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=20SystemPromptTemplate=20=E5=AD=97=E6=AE=B5=20-=20=E7=8E=B0?= =?UTF-8?q?=E5=9C=A8=E5=8F=AF=E4=BB=A5=E6=8E=A5=E6=94=B6=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=202.=20=E5=9C=A8=20handleUpdateTrader=20=E4=B8=AD=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E4=BB=8E=E8=AF=B7=E6=B1=82=E8=AF=BB=E5=8F=96=E5=B9=B6?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=AF=A5=E5=AD=97=E6=AE=B5=20-=20=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E5=8F=AF=E4=BB=A5=E4=BF=AE=E6=94=B9=E4=BA=86=203.=20?= =?UTF-8?q?=E5=9C=A8=20handleGetTraderConfig=20=E8=BF=94=E5=9B=9E=E5=80=BC?= =?UTF-8?q?=E4=B8=AD=E6=B7=BB=E5=8A=A0=20system=5Fprompt=5Ftemplate=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=20-=20=E5=89=8D=E7=AB=AF=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E6=AD=A3=E7=A1=AE=E6=98=BE=E7=A4=BA=20##=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=20-=20=E6=B7=BB=E5=8A=A0=203=20=E4=B8=AA=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E9=AA=8C=E8=AF=81=E4=BF=AE=E5=A4=8D=20-=20?= =?UTF-8?q?=E6=89=80=E6=9C=89=E6=B5=8B=E8=AF=95=E9=80=9A=E8=BF=87=EF=BC=8C?= =?UTF-8?q?=E6=97=A0=E5=9B=9E=E5=BD=92=20-=20=E8=A6=86=E7=9B=96=20nof1,=20?= =?UTF-8?q?default,=20custom=20=E7=AD=89=E4=B8=8D=E5=90=8C=E6=A8=A1?= =?UTF-8?q?=E6=9D=BF=E5=9C=BA=E6=99=AF=20##=20=E5=BD=B1=E5=93=8D=E8=8C=83?= =?UTF-8?q?=E5=9B=B4=20-=20api/server.go:=20UpdateTraderRequest,=20handleU?= =?UTF-8?q?pdateTrader,=20handleGetTraderConfig=20-=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20api/server=5Ftest.go:=203=20=E4=B8=AA=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=20Closes=20#838?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/server.go | 62 +++++++------ api/server_test.go | 227 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 262 insertions(+), 27 deletions(-) create mode 100644 api/server_test.go diff --git a/api/server.go b/api/server.go index bbe71144..ce86a77c 100644 --- a/api/server.go +++ b/api/server.go @@ -644,17 +644,18 @@ func (s *Server) handleCreateTrader(c *gin.Context) { // UpdateTraderRequest 更新交易员请求 type UpdateTraderRequest 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"` - ScanIntervalMinutes int `json:"scan_interval_minutes"` - BTCETHLeverage int `json:"btc_eth_leverage"` - AltcoinLeverage int `json:"altcoin_leverage"` - TradingSymbols string `json:"trading_symbols"` - CustomPrompt string `json:"custom_prompt"` - OverrideBasePrompt bool `json:"override_base_prompt"` - IsCrossMargin *bool `json:"is_cross_margin"` + 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"` + ScanIntervalMinutes int `json:"scan_interval_minutes"` + BTCETHLeverage int `json:"btc_eth_leverage"` + AltcoinLeverage int `json:"altcoin_leverage"` + TradingSymbols string `json:"trading_symbols"` + CustomPrompt string `json:"custom_prompt"` + OverrideBasePrompt bool `json:"override_base_prompt"` + SystemPromptTemplate string `json:"system_prompt_template"` + IsCrossMargin *bool `json:"is_cross_margin"` } // handleUpdateTrader 更新交易员配置 @@ -712,6 +713,12 @@ func (s *Server) handleUpdateTrader(c *gin.Context) { scanIntervalMinutes = 3 } + // 设置提示词模板,允许更新 + systemPromptTemplate := req.SystemPromptTemplate + if systemPromptTemplate == "" { + systemPromptTemplate = existingTrader.SystemPromptTemplate // 如果请求中没有提供,保持原值 + } + // 更新交易员配置 trader := &config.TraderRecord{ ID: traderID, @@ -725,7 +732,7 @@ func (s *Server) handleUpdateTrader(c *gin.Context) { TradingSymbols: req.TradingSymbols, CustomPrompt: req.CustomPrompt, OverrideBasePrompt: req.OverrideBasePrompt, - SystemPromptTemplate: existingTrader.SystemPromptTemplate, // 保持原值 + SystemPromptTemplate: systemPromptTemplate, IsCrossMargin: isCrossMargin, ScanIntervalMinutes: scanIntervalMinutes, IsRunning: existingTrader.IsRunning, // 保持原值 @@ -1298,21 +1305,22 @@ func (s *Server) handleGetTraderConfig(c *gin.Context) { aiModelID := traderConfig.AIModelID result := map[string]interface{}{ - "trader_id": traderConfig.ID, - "trader_name": traderConfig.Name, - "ai_model": aiModelID, - "exchange_id": traderConfig.ExchangeID, - "initial_balance": traderConfig.InitialBalance, - "scan_interval_minutes": traderConfig.ScanIntervalMinutes, - "btc_eth_leverage": traderConfig.BTCETHLeverage, - "altcoin_leverage": traderConfig.AltcoinLeverage, - "trading_symbols": traderConfig.TradingSymbols, - "custom_prompt": traderConfig.CustomPrompt, - "override_base_prompt": traderConfig.OverrideBasePrompt, - "is_cross_margin": traderConfig.IsCrossMargin, - "use_coin_pool": traderConfig.UseCoinPool, - "use_oi_top": traderConfig.UseOITop, - "is_running": isRunning, + "trader_id": traderConfig.ID, + "trader_name": traderConfig.Name, + "ai_model": aiModelID, + "exchange_id": traderConfig.ExchangeID, + "initial_balance": traderConfig.InitialBalance, + "scan_interval_minutes": traderConfig.ScanIntervalMinutes, + "btc_eth_leverage": traderConfig.BTCETHLeverage, + "altcoin_leverage": traderConfig.AltcoinLeverage, + "trading_symbols": traderConfig.TradingSymbols, + "custom_prompt": traderConfig.CustomPrompt, + "override_base_prompt": traderConfig.OverrideBasePrompt, + "system_prompt_template": traderConfig.SystemPromptTemplate, + "is_cross_margin": traderConfig.IsCrossMargin, + "use_coin_pool": traderConfig.UseCoinPool, + "use_oi_top": traderConfig.UseOITop, + "is_running": isRunning, } c.JSON(http.StatusOK, result) diff --git a/api/server_test.go b/api/server_test.go new file mode 100644 index 00000000..6b6b83c1 --- /dev/null +++ b/api/server_test.go @@ -0,0 +1,227 @@ +package api + +import ( + "encoding/json" + "testing" + + "nofx/config" +) + +// TestUpdateTraderRequest_SystemPromptTemplate 测试更新交易员时 SystemPromptTemplate 字段是否存在 +func TestUpdateTraderRequest_SystemPromptTemplate(t *testing.T) { + tests := []struct { + name string + requestJSON string + expectedPromptTemplate string + }{ + { + name: "更新时应该能接收 system_prompt_template=nof1", + requestJSON: `{ + "name": "Test Trader", + "ai_model_id": "gpt-4", + "exchange_id": "binance", + "initial_balance": 1000, + "scan_interval_minutes": 5, + "btc_eth_leverage": 5, + "altcoin_leverage": 3, + "trading_symbols": "BTC,ETH", + "custom_prompt": "test", + "override_base_prompt": false, + "is_cross_margin": true, + "system_prompt_template": "nof1" + }`, + expectedPromptTemplate: "nof1", + }, + { + name: "更新时应该能接收 system_prompt_template=default", + requestJSON: `{ + "name": "Test Trader", + "ai_model_id": "gpt-4", + "exchange_id": "binance", + "initial_balance": 1000, + "scan_interval_minutes": 5, + "btc_eth_leverage": 5, + "altcoin_leverage": 3, + "trading_symbols": "BTC,ETH", + "custom_prompt": "test", + "override_base_prompt": false, + "is_cross_margin": true, + "system_prompt_template": "default" + }`, + expectedPromptTemplate: "default", + }, + { + name: "更新时应该能接收 system_prompt_template=custom", + requestJSON: `{ + "name": "Test Trader", + "ai_model_id": "gpt-4", + "exchange_id": "binance", + "initial_balance": 1000, + "scan_interval_minutes": 5, + "btc_eth_leverage": 5, + "altcoin_leverage": 3, + "trading_symbols": "BTC,ETH", + "custom_prompt": "test", + "override_base_prompt": false, + "is_cross_margin": true, + "system_prompt_template": "custom" + }`, + expectedPromptTemplate: "custom", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // 测试 UpdateTraderRequest 结构体是否能正确解析 system_prompt_template 字段 + var req UpdateTraderRequest + err := json.Unmarshal([]byte(tt.requestJSON), &req) + if err != nil { + t.Fatalf("Failed to unmarshal JSON: %v", err) + } + + // ✅ 验证 SystemPromptTemplate 字段是否被正确读取 + if req.SystemPromptTemplate != tt.expectedPromptTemplate { + t.Errorf("Expected SystemPromptTemplate=%q, got %q", + tt.expectedPromptTemplate, req.SystemPromptTemplate) + } + + // 验证其他字段也被正确解析 + if req.Name != "Test Trader" { + t.Errorf("Name not parsed correctly") + } + if req.AIModelID != "gpt-4" { + t.Errorf("AIModelID not parsed correctly") + } + }) + } +} + +// TestGetTraderConfigResponse_SystemPromptTemplate 测试获取交易员配置时返回值是否包含 system_prompt_template +func TestGetTraderConfigResponse_SystemPromptTemplate(t *testing.T) { + tests := []struct { + name string + traderConfig *config.TraderRecord + expectedTemplate string + }{ + { + name: "获取配置应该返回 system_prompt_template=nof1", + traderConfig: &config.TraderRecord{ + ID: "trader-123", + UserID: "user-1", + Name: "Test Trader", + AIModelID: "gpt-4", + ExchangeID: "binance", + InitialBalance: 1000, + ScanIntervalMinutes: 5, + BTCETHLeverage: 5, + AltcoinLeverage: 3, + TradingSymbols: "BTC,ETH", + CustomPrompt: "test", + OverrideBasePrompt: false, + SystemPromptTemplate: "nof1", + IsCrossMargin: true, + IsRunning: false, + }, + expectedTemplate: "nof1", + }, + { + name: "获取配置应该返回 system_prompt_template=default", + traderConfig: &config.TraderRecord{ + ID: "trader-456", + UserID: "user-1", + Name: "Test Trader 2", + AIModelID: "gpt-4", + ExchangeID: "binance", + InitialBalance: 2000, + ScanIntervalMinutes: 10, + BTCETHLeverage: 10, + AltcoinLeverage: 5, + TradingSymbols: "BTC", + CustomPrompt: "", + OverrideBasePrompt: false, + SystemPromptTemplate: "default", + IsCrossMargin: false, + IsRunning: false, + }, + expectedTemplate: "default", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // 模拟 handleGetTraderConfig 的返回值构造逻辑(修复后的实现) + result := map[string]interface{}{ + "trader_id": tt.traderConfig.ID, + "trader_name": tt.traderConfig.Name, + "ai_model": tt.traderConfig.AIModelID, + "exchange_id": tt.traderConfig.ExchangeID, + "initial_balance": tt.traderConfig.InitialBalance, + "scan_interval_minutes": tt.traderConfig.ScanIntervalMinutes, + "btc_eth_leverage": tt.traderConfig.BTCETHLeverage, + "altcoin_leverage": tt.traderConfig.AltcoinLeverage, + "trading_symbols": tt.traderConfig.TradingSymbols, + "custom_prompt": tt.traderConfig.CustomPrompt, + "override_base_prompt": tt.traderConfig.OverrideBasePrompt, + "system_prompt_template": tt.traderConfig.SystemPromptTemplate, + "is_cross_margin": tt.traderConfig.IsCrossMargin, + "is_running": tt.traderConfig.IsRunning, + } + + // ✅ 检查响应中是否包含 system_prompt_template + if _, exists := result["system_prompt_template"]; !exists { + t.Errorf("Response is missing 'system_prompt_template' field") + } else { + actualTemplate := result["system_prompt_template"].(string) + if actualTemplate != tt.expectedTemplate { + t.Errorf("Expected system_prompt_template=%q, got %q", + tt.expectedTemplate, actualTemplate) + } + } + + // 验证其他字段是否正确 + if result["trader_id"] != tt.traderConfig.ID { + t.Errorf("trader_id mismatch") + } + if result["trader_name"] != tt.traderConfig.Name { + t.Errorf("trader_name mismatch") + } + }) + } +} + +// TestUpdateTraderRequest_CompleteFields 验证 UpdateTraderRequest 结构体定义完整性 +func TestUpdateTraderRequest_CompleteFields(t *testing.T) { + jsonData := `{ + "name": "Test Trader", + "ai_model_id": "gpt-4", + "exchange_id": "binance", + "initial_balance": 1000, + "scan_interval_minutes": 5, + "btc_eth_leverage": 5, + "altcoin_leverage": 3, + "trading_symbols": "BTC,ETH", + "custom_prompt": "test", + "override_base_prompt": false, + "is_cross_margin": true, + "system_prompt_template": "nof1" + }` + + var req UpdateTraderRequest + err := json.Unmarshal([]byte(jsonData), &req) + if err != nil { + t.Fatalf("Failed to unmarshal JSON: %v", err) + } + + // 验证基本字段是否正确解析 + if req.Name != "Test Trader" { + t.Errorf("Name mismatch: got %q", req.Name) + } + if req.AIModelID != "gpt-4" { + t.Errorf("AIModelID mismatch: got %q", req.AIModelID) + } + + // ✅ 验证 SystemPromptTemplate 字段已正确添加到结构体 + if req.SystemPromptTemplate != "nof1" { + t.Errorf("SystemPromptTemplate mismatch: expected %q, got %q", "nof1", req.SystemPromptTemplate) + } +}