From 9c1a32290186d5df9b14d91a4c72ca9a1eeb20ce Mon Sep 17 00:00:00 2001 From: tinkle-community Date: Mon, 8 Dec 2025 12:49:49 +0800 Subject: [PATCH] fix: OI Top API response parsing and quant data URL validation - Fix OITopAPIResponse struct to use Code int (0=success) instead of Success bool - Add all response fields from actual API (time_range_param, rank_type, limit) - Add {symbol} placeholder validation warning in FetchQuantData - Add API-level validation in strategy create/update to warn about missing {symbol} --- api/strategy.go | 37 ++++++++++++++++++++++++++++++++++--- decision/strategy_engine.go | 8 +++++++- pool/coin_pool.go | 23 +++++++++++++---------- 3 files changed, 54 insertions(+), 14 deletions(-) diff --git a/api/strategy.go b/api/strategy.go index 3eea7266..5eec8b92 100644 --- a/api/strategy.go +++ b/api/strategy.go @@ -8,12 +8,27 @@ import ( "nofx/market" "nofx/mcp" "nofx/store" + "strings" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" ) +// validateStrategyConfig validates strategy configuration and returns warnings +func validateStrategyConfig(config *store.StrategyConfig) []string { + var warnings []string + + // Validate quant data URL if enabled + if config.Indicators.EnableQuantData && config.Indicators.QuantDataAPIURL != "" { + if !strings.Contains(config.Indicators.QuantDataAPIURL, "{symbol}") { + warnings = append(warnings, "Quant data URL does not contain {symbol} placeholder. The same data will be used for all coins, which may not be correct.") + } + } + + return warnings +} + // handleGetStrategies Get strategy list func (s *Server) handleGetStrategies(c *gin.Context) { userID := c.GetString("user_id") @@ -123,10 +138,18 @@ func (s *Server) handleCreateStrategy(c *gin.Context) { return } - c.JSON(http.StatusOK, gin.H{ + // Validate configuration and collect warnings + warnings := validateStrategyConfig(&req.Config) + + response := gin.H{ "id": strategy.ID, "message": "Strategy created successfully", - }) + } + if len(warnings) > 0 { + response["warnings"] = warnings + } + + c.JSON(http.StatusOK, response) } // handleUpdateStrategy Update strategy @@ -181,7 +204,15 @@ func (s *Server) handleUpdateStrategy(c *gin.Context) { return } - c.JSON(http.StatusOK, gin.H{"message": "Strategy updated successfully"}) + // Validate configuration and collect warnings + warnings := validateStrategyConfig(&req.Config) + + response := gin.H{"message": "Strategy updated successfully"} + if len(warnings) > 0 { + response["warnings"] = warnings + } + + c.JSON(http.StatusOK, response) } // handleDeleteStrategy Delete strategy diff --git a/decision/strategy_engine.go b/decision/strategy_engine.go index 559e068d..28649560 100644 --- a/decision/strategy_engine.go +++ b/decision/strategy_engine.go @@ -215,8 +215,14 @@ func (e *StrategyEngine) FetchQuantData(symbol string) (*QuantData, error) { return nil, nil } + // Check if URL contains {symbol} placeholder + apiURL := e.config.Indicators.QuantDataAPIURL + if !strings.Contains(apiURL, "{symbol}") { + logger.Infof("⚠️ Quant data URL does not contain {symbol} placeholder, data may be incorrect for %s", symbol) + } + // Replace {symbol} placeholder - url := strings.Replace(e.config.Indicators.QuantDataAPIURL, "{symbol}", symbol, -1) + url := strings.Replace(apiURL, "{symbol}", symbol, -1) client := &http.Client{Timeout: 10 * time.Second} resp, err := client.Get(url) diff --git a/pool/coin_pool.go b/pool/coin_pool.go index 08169554..b436f96c 100644 --- a/pool/coin_pool.go +++ b/pool/coin_pool.go @@ -392,12 +392,15 @@ type OIPosition struct { // OITopAPIResponse data structure returned by OI Top API type OITopAPIResponse struct { - Success bool `json:"success"` - Data struct { - Positions []OIPosition `json:"positions"` - Count int `json:"count"` - Exchange string `json:"exchange"` - TimeRange string `json:"time_range"` + Code int `json:"code"` // 0 = success + Data struct { + Positions []OIPosition `json:"positions"` + Count int `json:"count"` + Exchange string `json:"exchange"` + TimeRange string `json:"time_range"` + TimeRangeParam string `json:"time_range_param"` + RankType string `json:"rank_type"` + Limit int `json:"limit"` } `json:"data"` } @@ -494,16 +497,16 @@ func fetchOITop() ([]OIPosition, error) { return nil, fmt.Errorf("OI Top JSON parsing failed: %w", err) } - if !response.Success { - return nil, fmt.Errorf("OI Top API returned failure status") + if response.Code != 0 { + return nil, fmt.Errorf("OI Top API returned error code: %d", response.Code) } if len(response.Data.Positions) == 0 { return nil, fmt.Errorf("OI Top position list is empty") } - log.Printf("✓ Successfully fetched %d OI Top coins (time range: %s)", - len(response.Data.Positions), response.Data.TimeRange) + log.Printf("✓ Successfully fetched %d OI Top coins (time range: %s, type: %s)", + len(response.Data.Positions), response.Data.TimeRange, response.Data.RankType) return response.Data.Positions, nil }