Files
nofx/provider/nofxos/ai500.go
tinkle-community 0275e23b7e 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
2026-01-04 00:59:07 +08:00

164 lines
4.1 KiB
Go

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
}