mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-18 18:04:32 +08:00
feat: unify NofxOS data provider and fix language consistency
- Add unified NofxOS API key configuration in IndicatorEditor - Add language field to StrategyConfig for consistent prompt generation - Auto-update prompt sections when interface language changes - Remove scattered URL inputs from CoinSourceEditor and IndicatorEditor - Create nofxos provider package with formatted data output - Update kernel engine to use config-based language setting
This commit is contained in:
163
provider/nofxos/ai500.go
Normal file
163
provider/nofxos/ai500.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package nofxos
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CoinData represents AI500 coin information
|
||||
type CoinData struct {
|
||||
Pair string `json:"pair"` // Trading pair symbol (e.g.: BTCUSDT)
|
||||
Score float64 `json:"score"` // Current AI score (0-100)
|
||||
StartTime int64 `json:"start_time"` // Start time (Unix timestamp)
|
||||
StartPrice float64 `json:"start_price"` // Start price
|
||||
LastScore float64 `json:"last_score"` // Latest score
|
||||
MaxScore float64 `json:"max_score"` // Highest score
|
||||
MaxPrice float64 `json:"max_price"` // Highest price
|
||||
IncreasePercent float64 `json:"increase_percent"` // Increase percentage (already x100)
|
||||
IsAvailable bool `json:"-"` // Whether tradable (internal use)
|
||||
}
|
||||
|
||||
// AI500Response is the API response structure
|
||||
type AI500Response struct {
|
||||
Success bool `json:"success"`
|
||||
Data struct {
|
||||
Coins []CoinData `json:"coins"`
|
||||
Count int `json:"count"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// GetAI500List retrieves AI500 coin list with retry mechanism
|
||||
func (c *Client) GetAI500List() ([]CoinData, error) {
|
||||
maxRetries := 3
|
||||
var lastErr error
|
||||
|
||||
for attempt := 1; attempt <= maxRetries; attempt++ {
|
||||
if attempt > 1 {
|
||||
log.Printf("⚠️ Retry attempt %d of %d to fetch AI500 data...", attempt, maxRetries)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
coins, err := c.fetchAI500()
|
||||
if err == nil {
|
||||
if attempt > 1 {
|
||||
log.Printf("✓ Retry attempt %d succeeded", attempt)
|
||||
}
|
||||
return coins, nil
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
log.Printf("❌ AI500 request attempt %d failed: %v", attempt, err)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("all AI500 API requests failed: %w", lastErr)
|
||||
}
|
||||
|
||||
func (c *Client) fetchAI500() ([]CoinData, error) {
|
||||
log.Printf("🔄 Requesting AI500 data from %s...", c.GetBaseURL())
|
||||
|
||||
body, err := c.doRequest("/api/ai500/list")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to request AI500 API: %w", err)
|
||||
}
|
||||
|
||||
var response AI500Response
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return nil, fmt.Errorf("JSON parsing failed: %w", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
return nil, fmt.Errorf("API returned failure status")
|
||||
}
|
||||
|
||||
if len(response.Data.Coins) == 0 {
|
||||
return nil, fmt.Errorf("coin list is empty")
|
||||
}
|
||||
|
||||
// Set IsAvailable flag
|
||||
coins := response.Data.Coins
|
||||
for i := range coins {
|
||||
coins[i].IsAvailable = true
|
||||
}
|
||||
|
||||
log.Printf("✓ Successfully fetched %d AI500 coins", len(coins))
|
||||
return coins, nil
|
||||
}
|
||||
|
||||
// GetTopRatedCoins retrieves top N coins by score (sorted descending)
|
||||
func (c *Client) GetTopRatedCoins(limit int) ([]string, error) {
|
||||
coins, err := c.GetAI500List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Filter available coins
|
||||
var availableCoins []CoinData
|
||||
for _, coin := range coins {
|
||||
if coin.IsAvailable {
|
||||
availableCoins = append(availableCoins, coin)
|
||||
}
|
||||
}
|
||||
|
||||
if len(availableCoins) == 0 {
|
||||
return nil, fmt.Errorf("no available coins")
|
||||
}
|
||||
|
||||
// Sort by Score descending (bubble sort)
|
||||
for i := 0; i < len(availableCoins); i++ {
|
||||
for j := i + 1; j < len(availableCoins); j++ {
|
||||
if availableCoins[i].Score < availableCoins[j].Score {
|
||||
availableCoins[i], availableCoins[j] = availableCoins[j], availableCoins[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Take top N
|
||||
maxCount := limit
|
||||
if len(availableCoins) < maxCount {
|
||||
maxCount = len(availableCoins)
|
||||
}
|
||||
|
||||
var symbols []string
|
||||
for i := 0; i < maxCount; i++ {
|
||||
symbol := NormalizeSymbol(availableCoins[i].Pair)
|
||||
symbols = append(symbols, symbol)
|
||||
}
|
||||
|
||||
return symbols, nil
|
||||
}
|
||||
|
||||
// GetAvailableCoins retrieves all available coin symbols
|
||||
func (c *Client) GetAvailableCoins() ([]string, error) {
|
||||
coins, err := c.GetAI500List()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var symbols []string
|
||||
for _, coin := range coins {
|
||||
if coin.IsAvailable {
|
||||
symbol := NormalizeSymbol(coin.Pair)
|
||||
symbols = append(symbols, symbol)
|
||||
}
|
||||
}
|
||||
|
||||
if len(symbols) == 0 {
|
||||
return nil, fmt.Errorf("no available coins")
|
||||
}
|
||||
|
||||
return symbols, nil
|
||||
}
|
||||
|
||||
// NormalizeSymbol normalizes coin symbol to XXXUSDT format
|
||||
func NormalizeSymbol(symbol string) string {
|
||||
symbol = strings.TrimSpace(symbol)
|
||||
symbol = strings.ToUpper(symbol)
|
||||
if !strings.HasSuffix(symbol, "USDT") {
|
||||
symbol = symbol + "USDT"
|
||||
}
|
||||
return symbol
|
||||
}
|
||||
Reference in New Issue
Block a user