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}
This commit is contained in:
tinkle-community
2025-12-08 12:49:49 +08:00
parent ce3f62cb50
commit 9c1a322901
3 changed files with 54 additions and 14 deletions

View File

@@ -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

View File

@@ -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)

View File

@@ -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
}