mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-15 00:36:56 +08:00
refactor: standardize code comments
This commit is contained in:
@@ -12,7 +12,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// defaultMainstreamCoins 默认主流币种池(从配置文件读取)
|
||||
// defaultMainstreamCoins default mainstream coin pool (read from config file)
|
||||
var defaultMainstreamCoins = []string{
|
||||
"BTCUSDT",
|
||||
"ETHUSDT",
|
||||
@@ -24,42 +24,42 @@ var defaultMainstreamCoins = []string{
|
||||
"HYPEUSDT",
|
||||
}
|
||||
|
||||
// CoinPoolConfig 币种池配置
|
||||
// CoinPoolConfig coin pool configuration
|
||||
type CoinPoolConfig struct {
|
||||
APIURL string
|
||||
Timeout time.Duration
|
||||
CacheDir string
|
||||
UseDefaultCoins bool // 是否使用默认主流币种
|
||||
UseDefaultCoins bool // Whether to use default mainstream coins
|
||||
}
|
||||
|
||||
var coinPoolConfig = CoinPoolConfig{
|
||||
APIURL: "",
|
||||
Timeout: 30 * time.Second, // 增加到30秒
|
||||
Timeout: 30 * time.Second, // Increased to 30 seconds
|
||||
CacheDir: "coin_pool_cache",
|
||||
UseDefaultCoins: false, // 默认不使用
|
||||
UseDefaultCoins: false, // Default is not to use
|
||||
}
|
||||
|
||||
// CoinPoolCache 币种池缓存
|
||||
// CoinPoolCache coin pool cache
|
||||
type CoinPoolCache struct {
|
||||
Coins []CoinInfo `json:"coins"`
|
||||
FetchedAt time.Time `json:"fetched_at"`
|
||||
SourceType string `json:"source_type"` // "api" or "cache"
|
||||
}
|
||||
|
||||
// CoinInfo 币种信息
|
||||
// CoinInfo coin information
|
||||
type CoinInfo struct {
|
||||
Pair string `json:"pair"` // 交易对符号(例如:BTCUSDT)
|
||||
Score float64 `json:"score"` // 当前评分
|
||||
StartTime int64 `json:"start_time"` // 开始时间(Unix时间戳)
|
||||
StartPrice float64 `json:"start_price"` // 开始价格
|
||||
LastScore float64 `json:"last_score"` // 最新评分
|
||||
MaxScore float64 `json:"max_score"` // 最高评分
|
||||
MaxPrice float64 `json:"max_price"` // 最高价格
|
||||
IncreasePercent float64 `json:"increase_percent"` // 涨幅百分比
|
||||
IsAvailable bool `json:"-"` // 是否可交易(内部使用)
|
||||
Pair string `json:"pair"` // Trading pair symbol (e.g.: BTCUSDT)
|
||||
Score float64 `json:"score"` // Current score
|
||||
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
|
||||
IsAvailable bool `json:"-"` // Whether tradable (internal use)
|
||||
}
|
||||
|
||||
// CoinPoolAPIResponse API返回的原始数据结构
|
||||
// CoinPoolAPIResponse raw data structure returned by API
|
||||
type CoinPoolAPIResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Data struct {
|
||||
@@ -68,85 +68,85 @@ type CoinPoolAPIResponse struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// SetCoinPoolAPI 设置币种池API
|
||||
// SetCoinPoolAPI sets coin pool API
|
||||
func SetCoinPoolAPI(apiURL string) {
|
||||
coinPoolConfig.APIURL = apiURL
|
||||
}
|
||||
|
||||
// SetOITopAPI 设置OI Top API
|
||||
// SetOITopAPI sets OI Top API
|
||||
func SetOITopAPI(apiURL string) {
|
||||
oiTopConfig.APIURL = apiURL
|
||||
}
|
||||
|
||||
// SetUseDefaultCoins 设置是否使用默认主流币种
|
||||
// SetUseDefaultCoins sets whether to use default mainstream coins
|
||||
func SetUseDefaultCoins(useDefault bool) {
|
||||
coinPoolConfig.UseDefaultCoins = useDefault
|
||||
}
|
||||
|
||||
// SetDefaultCoins 设置默认主流币种列表
|
||||
// SetDefaultCoins sets default mainstream coin list
|
||||
func SetDefaultCoins(coins []string) {
|
||||
if len(coins) > 0 {
|
||||
defaultMainstreamCoins = coins
|
||||
log.Printf("✓ 已设置默认币种池(共%d个币种): %v", len(coins), coins)
|
||||
log.Printf("✓ Default coin pool set (%d coins): %v", len(coins), coins)
|
||||
}
|
||||
}
|
||||
|
||||
// GetCoinPool 获取币种池列表(带重试和缓存机制)
|
||||
// GetCoinPool retrieves coin pool list (with retry and cache mechanism)
|
||||
func GetCoinPool() ([]CoinInfo, error) {
|
||||
// 优先检查是否启用默认币种列表
|
||||
// First check if default coin list is enabled
|
||||
if coinPoolConfig.UseDefaultCoins {
|
||||
log.Printf("✓ 已启用默认主流币种列表")
|
||||
log.Printf("✓ Default mainstream coin list enabled")
|
||||
return convertSymbolsToCoins(defaultMainstreamCoins), nil
|
||||
}
|
||||
|
||||
// 检查API URL是否配置
|
||||
// Check if API URL is configured
|
||||
if strings.TrimSpace(coinPoolConfig.APIURL) == "" {
|
||||
log.Printf("⚠️ 未配置币种池API URL,使用默认主流币种列表")
|
||||
log.Printf("⚠️ Coin pool API URL not configured, using default mainstream coin list")
|
||||
return convertSymbolsToCoins(defaultMainstreamCoins), nil
|
||||
}
|
||||
|
||||
maxRetries := 3
|
||||
var lastErr error
|
||||
|
||||
// 尝试从API获取
|
||||
// Try to fetch from API
|
||||
for attempt := 1; attempt <= maxRetries; attempt++ {
|
||||
if attempt > 1 {
|
||||
log.Printf("⚠️ 第%d次重试获取币种池(共%d次)...", attempt, maxRetries)
|
||||
time.Sleep(2 * time.Second) // 重试前等待2秒
|
||||
log.Printf("⚠️ Retry attempt %d of %d to fetch coin pool...", attempt, maxRetries)
|
||||
time.Sleep(2 * time.Second) // Wait 2 seconds before retry
|
||||
}
|
||||
|
||||
coins, err := fetchCoinPool()
|
||||
if err == nil {
|
||||
if attempt > 1 {
|
||||
log.Printf("✓ 第%d次重试成功", attempt)
|
||||
log.Printf("✓ Retry attempt %d succeeded", attempt)
|
||||
}
|
||||
// 成功获取后保存到缓存
|
||||
// Save to cache after successful fetch
|
||||
if err := saveCoinPoolCache(coins); err != nil {
|
||||
log.Printf("⚠️ 保存币种池缓存失败: %v", err)
|
||||
log.Printf("⚠️ Failed to save coin pool cache: %v", err)
|
||||
}
|
||||
return coins, nil
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
log.Printf("❌ 第%d次请求失败: %v", attempt, err)
|
||||
log.Printf("❌ Request attempt %d failed: %v", attempt, err)
|
||||
}
|
||||
|
||||
// API获取失败,尝试使用缓存
|
||||
log.Printf("⚠️ API请求全部失败,尝试使用历史缓存数据...")
|
||||
// API fetch failed, try to use cache
|
||||
log.Printf("⚠️ All API requests failed, trying to use historical cache data...")
|
||||
cachedCoins, err := loadCoinPoolCache()
|
||||
if err == nil {
|
||||
log.Printf("✓ 使用历史缓存数据(共%d个币种)", len(cachedCoins))
|
||||
log.Printf("✓ Using historical cache data (%d coins)", len(cachedCoins))
|
||||
return cachedCoins, nil
|
||||
}
|
||||
|
||||
// 缓存也失败,使用默认主流币种
|
||||
log.Printf("⚠️ 无法加载缓存数据(最后错误: %v),使用默认主流币种列表", lastErr)
|
||||
// Cache also failed, use default mainstream coins
|
||||
log.Printf("⚠️ Unable to load cache data (last error: %v), using default mainstream coin list", lastErr)
|
||||
return convertSymbolsToCoins(defaultMainstreamCoins), nil
|
||||
}
|
||||
|
||||
// fetchCoinPool 实际执行币种池请求
|
||||
// fetchCoinPool actually executes coin pool request
|
||||
func fetchCoinPool() ([]CoinInfo, error) {
|
||||
log.Printf("🔄 正在请求AI500币种池...")
|
||||
log.Printf("🔄 Requesting AI500 coin pool...")
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: coinPoolConfig.Timeout,
|
||||
@@ -154,48 +154,48 @@ func fetchCoinPool() ([]CoinInfo, error) {
|
||||
|
||||
resp, err := client.Get(coinPoolConfig.APIURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求币种池API失败: %w", err)
|
||||
return nil, fmt.Errorf("failed to request coin pool API: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取响应失败: %w", err)
|
||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("API返回错误 (status %d): %s", resp.StatusCode, string(body))
|
||||
return nil, fmt.Errorf("API returned error (status %d): %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// 解析API响应
|
||||
// Parse API response
|
||||
var response CoinPoolAPIResponse
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return nil, fmt.Errorf("JSON解析失败: %w", err)
|
||||
return nil, fmt.Errorf("JSON parsing failed: %w", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
return nil, fmt.Errorf("API返回失败状态")
|
||||
return nil, fmt.Errorf("API returned failure status")
|
||||
}
|
||||
|
||||
if len(response.Data.Coins) == 0 {
|
||||
return nil, fmt.Errorf("币种列表为空")
|
||||
return nil, fmt.Errorf("coin list is empty")
|
||||
}
|
||||
|
||||
// 设置IsAvailable标志
|
||||
// Set IsAvailable flag
|
||||
coins := response.Data.Coins
|
||||
for i := range coins {
|
||||
coins[i].IsAvailable = true
|
||||
}
|
||||
|
||||
log.Printf("✓ 成功获取%d个币种", len(coins))
|
||||
log.Printf("✓ Successfully fetched %d coins", len(coins))
|
||||
return coins, nil
|
||||
}
|
||||
|
||||
// saveCoinPoolCache 保存币种池到缓存文件
|
||||
// saveCoinPoolCache saves coin pool to cache file
|
||||
func saveCoinPoolCache(coins []CoinInfo) error {
|
||||
// 确保缓存目录存在
|
||||
// Ensure cache directory exists
|
||||
if err := os.MkdirAll(coinPoolConfig.CacheDir, 0755); err != nil {
|
||||
return fmt.Errorf("创建缓存目录失败: %w", err)
|
||||
return fmt.Errorf("failed to create cache directory: %w", err)
|
||||
}
|
||||
|
||||
cache := CoinPoolCache{
|
||||
@@ -206,43 +206,43 @@ func saveCoinPoolCache(coins []CoinInfo) error {
|
||||
|
||||
data, err := json.MarshalIndent(cache, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化缓存数据失败: %w", err)
|
||||
return fmt.Errorf("failed to serialize cache data: %w", err)
|
||||
}
|
||||
|
||||
cachePath := filepath.Join(coinPoolConfig.CacheDir, "latest.json")
|
||||
if err := ioutil.WriteFile(cachePath, data, 0644); err != nil {
|
||||
return fmt.Errorf("写入缓存文件失败: %w", err)
|
||||
return fmt.Errorf("failed to write cache file: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("💾 已保存币种池缓存(%d个币种)", len(coins))
|
||||
log.Printf("💾 Coin pool cache saved (%d coins)", len(coins))
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadCoinPoolCache 从缓存文件加载币种池
|
||||
// loadCoinPoolCache loads coin pool from cache file
|
||||
func loadCoinPoolCache() ([]CoinInfo, error) {
|
||||
cachePath := filepath.Join(coinPoolConfig.CacheDir, "latest.json")
|
||||
|
||||
// 检查文件是否存在
|
||||
// Check if file exists
|
||||
if _, err := os.Stat(cachePath); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("缓存文件不存在")
|
||||
return nil, fmt.Errorf("cache file does not exist")
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(cachePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取缓存文件失败: %w", err)
|
||||
return nil, fmt.Errorf("failed to read cache file: %w", err)
|
||||
}
|
||||
|
||||
var cache CoinPoolCache
|
||||
if err := json.Unmarshal(data, &cache); err != nil {
|
||||
return nil, fmt.Errorf("解析缓存数据失败: %w", err)
|
||||
return nil, fmt.Errorf("failed to parse cache data: %w", err)
|
||||
}
|
||||
|
||||
// 检查缓存年龄
|
||||
// Check cache age
|
||||
cacheAge := time.Since(cache.FetchedAt)
|
||||
if cacheAge > 24*time.Hour {
|
||||
log.Printf("⚠️ 缓存数据较旧(%.1f小时前),但仍可使用", cacheAge.Hours())
|
||||
log.Printf("⚠️ Cache data is old (%.1f hours ago), but still usable", cacheAge.Hours())
|
||||
} else {
|
||||
log.Printf("📂 缓存数据时间: %s(%.1f分钟前)",
|
||||
log.Printf("📂 Cache data timestamp: %s (%.1f minutes ago)",
|
||||
cache.FetchedAt.Format("2006-01-02 15:04:05"),
|
||||
cacheAge.Minutes())
|
||||
}
|
||||
@@ -250,7 +250,7 @@ func loadCoinPoolCache() ([]CoinInfo, error) {
|
||||
return cache.Coins, nil
|
||||
}
|
||||
|
||||
// GetAvailableCoins 获取可用的币种列表(过滤不可用的)
|
||||
// GetAvailableCoins retrieves available coin list (filters out unavailable ones)
|
||||
func GetAvailableCoins() ([]string, error) {
|
||||
coins, err := GetCoinPool()
|
||||
if err != nil {
|
||||
@@ -260,27 +260,27 @@ func GetAvailableCoins() ([]string, error) {
|
||||
var symbols []string
|
||||
for _, coin := range coins {
|
||||
if coin.IsAvailable {
|
||||
// 确保symbol格式正确(转为大写USDT交易对)
|
||||
// Ensure symbol format is correct (convert to uppercase USDT pair)
|
||||
symbol := normalizeSymbol(coin.Pair)
|
||||
symbols = append(symbols, symbol)
|
||||
}
|
||||
}
|
||||
|
||||
if len(symbols) == 0 {
|
||||
return nil, fmt.Errorf("没有可用的币种")
|
||||
return nil, fmt.Errorf("no available coins")
|
||||
}
|
||||
|
||||
return symbols, nil
|
||||
}
|
||||
|
||||
// GetTopRatedCoins 获取评分最高的N个币种(按评分从大到小排序)
|
||||
// GetTopRatedCoins retrieves top N coins by score (sorted by score descending)
|
||||
func GetTopRatedCoins(limit int) ([]string, error) {
|
||||
coins, err := GetCoinPool()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 过滤可用的币种
|
||||
// Filter available coins
|
||||
var availableCoins []CoinInfo
|
||||
for _, coin := range coins {
|
||||
if coin.IsAvailable {
|
||||
@@ -289,10 +289,10 @@ func GetTopRatedCoins(limit int) ([]string, error) {
|
||||
}
|
||||
|
||||
if len(availableCoins) == 0 {
|
||||
return nil, fmt.Errorf("没有可用的币种")
|
||||
return nil, fmt.Errorf("no available coins")
|
||||
}
|
||||
|
||||
// 按Score降序排序(冒泡排序)
|
||||
// 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 {
|
||||
@@ -301,7 +301,7 @@ func GetTopRatedCoins(limit int) ([]string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// 取前N个
|
||||
// Take top N
|
||||
maxCount := limit
|
||||
if len(availableCoins) < maxCount {
|
||||
maxCount = len(availableCoins)
|
||||
@@ -316,15 +316,15 @@ func GetTopRatedCoins(limit int) ([]string, error) {
|
||||
return symbols, nil
|
||||
}
|
||||
|
||||
// normalizeSymbol 标准化币种符号
|
||||
// normalizeSymbol normalizes coin symbol
|
||||
func normalizeSymbol(symbol string) string {
|
||||
// 移除空格
|
||||
// Remove spaces
|
||||
symbol = trimSpaces(symbol)
|
||||
|
||||
// 转为大写
|
||||
// Convert to uppercase
|
||||
symbol = toUpper(symbol)
|
||||
|
||||
// 确保以USDT结尾
|
||||
// Ensure ends with USDT
|
||||
if !endsWith(symbol, "USDT") {
|
||||
symbol = symbol + "USDT"
|
||||
}
|
||||
@@ -332,7 +332,7 @@ func normalizeSymbol(symbol string) string {
|
||||
return symbol
|
||||
}
|
||||
|
||||
// 辅助函数
|
||||
// Helper functions
|
||||
func trimSpaces(s string) string {
|
||||
result := ""
|
||||
for i := 0; i < len(s); i++ {
|
||||
@@ -362,7 +362,7 @@ func endsWith(s, suffix string) bool {
|
||||
return s[len(s)-len(suffix):] == suffix
|
||||
}
|
||||
|
||||
// convertSymbolsToCoins 将币种符号列表转换为CoinInfo列表
|
||||
// convertSymbolsToCoins converts symbol list to CoinInfo list
|
||||
func convertSymbolsToCoins(symbols []string) []CoinInfo {
|
||||
coins := make([]CoinInfo, 0, len(symbols))
|
||||
for _, symbol := range symbols {
|
||||
@@ -375,22 +375,22 @@ func convertSymbolsToCoins(symbols []string) []CoinInfo {
|
||||
return coins
|
||||
}
|
||||
|
||||
// ========== OI Top(持仓量增长Top20)数据 ==========
|
||||
// ========== OI Top (Open Interest Growth Top 20) Data ==========
|
||||
|
||||
// OIPosition 持仓量数据
|
||||
// OIPosition open interest data
|
||||
type OIPosition struct {
|
||||
Symbol string `json:"symbol"`
|
||||
Rank int `json:"rank"`
|
||||
CurrentOI float64 `json:"current_oi"` // 当前持仓量
|
||||
OIDelta float64 `json:"oi_delta"` // 持仓量变化
|
||||
OIDeltaPercent float64 `json:"oi_delta_percent"` // 持仓量变化百分比
|
||||
OIDeltaValue float64 `json:"oi_delta_value"` // 持仓量变化价值
|
||||
PriceDeltaPercent float64 `json:"price_delta_percent"` // 价格变化百分比
|
||||
NetLong float64 `json:"net_long"` // 净多仓
|
||||
NetShort float64 `json:"net_short"` // 净空仓
|
||||
CurrentOI float64 `json:"current_oi"` // Current open interest
|
||||
OIDelta float64 `json:"oi_delta"` // Open interest change
|
||||
OIDeltaPercent float64 `json:"oi_delta_percent"` // Open interest change percentage
|
||||
OIDeltaValue float64 `json:"oi_delta_value"` // Open interest change value
|
||||
PriceDeltaPercent float64 `json:"price_delta_percent"` // Price change percentage
|
||||
NetLong float64 `json:"net_long"` // Net long position
|
||||
NetShort float64 `json:"net_short"` // Net short position
|
||||
}
|
||||
|
||||
// OITopAPIResponse OI Top API返回的数据结构
|
||||
// OITopAPIResponse data structure returned by OI Top API
|
||||
type OITopAPIResponse struct {
|
||||
Success bool `json:"success"`
|
||||
Data struct {
|
||||
@@ -401,7 +401,7 @@ type OITopAPIResponse struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
// OITopCache OI Top 缓存
|
||||
// OITopCache OI Top cache
|
||||
type OITopCache struct {
|
||||
Positions []OIPosition `json:"positions"`
|
||||
FetchedAt time.Time `json:"fetched_at"`
|
||||
@@ -418,56 +418,56 @@ var oiTopConfig = struct {
|
||||
CacheDir: "coin_pool_cache",
|
||||
}
|
||||
|
||||
// GetOITopPositions 获取持仓量增长Top20数据(带重试和缓存)
|
||||
// GetOITopPositions retrieves OI Top 20 data (with retry and cache)
|
||||
func GetOITopPositions() ([]OIPosition, error) {
|
||||
// 检查API URL是否配置
|
||||
// Check if API URL is configured
|
||||
if strings.TrimSpace(oiTopConfig.APIURL) == "" {
|
||||
log.Printf("⚠️ 未配置OI Top API URL,跳过OI Top数据获取")
|
||||
return []OIPosition{}, nil // 返回空列表,不是错误
|
||||
log.Printf("⚠️ OI Top API URL not configured, skipping OI Top data fetch")
|
||||
return []OIPosition{}, nil // Return empty list, not an error
|
||||
}
|
||||
|
||||
maxRetries := 3
|
||||
var lastErr error
|
||||
|
||||
// 尝试从API获取
|
||||
// Try to fetch from API
|
||||
for attempt := 1; attempt <= maxRetries; attempt++ {
|
||||
if attempt > 1 {
|
||||
log.Printf("⚠️ 第%d次重试获取OI Top数据(共%d次)...", attempt, maxRetries)
|
||||
log.Printf("⚠️ Retry attempt %d of %d to fetch OI Top data...", attempt, maxRetries)
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
|
||||
positions, err := fetchOITop()
|
||||
if err == nil {
|
||||
if attempt > 1 {
|
||||
log.Printf("✓ 第%d次重试成功", attempt)
|
||||
log.Printf("✓ Retry attempt %d succeeded", attempt)
|
||||
}
|
||||
// 成功获取后保存到缓存
|
||||
// Save to cache after successful fetch
|
||||
if err := saveOITopCache(positions); err != nil {
|
||||
log.Printf("⚠️ 保存OI Top缓存失败: %v", err)
|
||||
log.Printf("⚠️ Failed to save OI Top cache: %v", err)
|
||||
}
|
||||
return positions, nil
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
log.Printf("❌ 第%d次请求OI Top失败: %v", attempt, err)
|
||||
log.Printf("❌ OI Top request attempt %d failed: %v", attempt, err)
|
||||
}
|
||||
|
||||
// API获取失败,尝试使用缓存
|
||||
log.Printf("⚠️ OI Top API请求全部失败,尝试使用历史缓存数据...")
|
||||
// API fetch failed, try to use cache
|
||||
log.Printf("⚠️ All OI Top API requests failed, trying to use historical cache data...")
|
||||
cachedPositions, err := loadOITopCache()
|
||||
if err == nil {
|
||||
log.Printf("✓ 使用历史OI Top缓存数据(共%d个币种)", len(cachedPositions))
|
||||
log.Printf("✓ Using historical OI Top cache data (%d coins)", len(cachedPositions))
|
||||
return cachedPositions, nil
|
||||
}
|
||||
|
||||
// 缓存也失败,返回空列表(OI Top是可选的)
|
||||
log.Printf("⚠️ 无法加载OI Top缓存数据(最后错误: %v),跳过OI Top数据", lastErr)
|
||||
// Cache also failed, return empty list (OI Top is optional)
|
||||
log.Printf("⚠️ Unable to load OI Top cache data (last error: %v), skipping OI Top data", lastErr)
|
||||
return []OIPosition{}, nil
|
||||
}
|
||||
|
||||
// fetchOITop 实际执行OI Top请求
|
||||
// fetchOITop actually executes OI Top request
|
||||
func fetchOITop() ([]OIPosition, error) {
|
||||
log.Printf("🔄 正在请求OI Top数据...")
|
||||
log.Printf("🔄 Requesting OI Top data...")
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: oiTopConfig.Timeout,
|
||||
@@ -475,42 +475,42 @@ func fetchOITop() ([]OIPosition, error) {
|
||||
|
||||
resp, err := client.Get(oiTopConfig.APIURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("请求OI Top API失败: %w", err)
|
||||
return nil, fmt.Errorf("failed to request OI Top API: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取OI Top响应失败: %w", err)
|
||||
return nil, fmt.Errorf("failed to read OI Top response: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("OI Top API返回错误 (status %d): %s", resp.StatusCode, string(body))
|
||||
return nil, fmt.Errorf("OI Top API returned error (status %d): %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// 解析API响应
|
||||
// Parse API response
|
||||
var response OITopAPIResponse
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return nil, fmt.Errorf("OI Top JSON解析失败: %w", err)
|
||||
return nil, fmt.Errorf("OI Top JSON parsing failed: %w", err)
|
||||
}
|
||||
|
||||
if !response.Success {
|
||||
return nil, fmt.Errorf("OI Top API返回失败状态")
|
||||
return nil, fmt.Errorf("OI Top API returned failure status")
|
||||
}
|
||||
|
||||
if len(response.Data.Positions) == 0 {
|
||||
return nil, fmt.Errorf("OI Top持仓列表为空")
|
||||
return nil, fmt.Errorf("OI Top position list is empty")
|
||||
}
|
||||
|
||||
log.Printf("✓ 成功获取%d个OI Top币种(时间范围: %s)",
|
||||
log.Printf("✓ Successfully fetched %d OI Top coins (time range: %s)",
|
||||
len(response.Data.Positions), response.Data.TimeRange)
|
||||
return response.Data.Positions, nil
|
||||
}
|
||||
|
||||
// saveOITopCache 保存OI Top数据到缓存
|
||||
// saveOITopCache saves OI Top data to cache
|
||||
func saveOITopCache(positions []OIPosition) error {
|
||||
if err := os.MkdirAll(oiTopConfig.CacheDir, 0755); err != nil {
|
||||
return fmt.Errorf("创建缓存目录失败: %w", err)
|
||||
return fmt.Errorf("failed to create cache directory: %w", err)
|
||||
}
|
||||
|
||||
cache := OITopCache{
|
||||
@@ -521,41 +521,41 @@ func saveOITopCache(positions []OIPosition) error {
|
||||
|
||||
data, err := json.MarshalIndent(cache, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化OI Top缓存数据失败: %w", err)
|
||||
return fmt.Errorf("failed to serialize OI Top cache data: %w", err)
|
||||
}
|
||||
|
||||
cachePath := filepath.Join(oiTopConfig.CacheDir, "oi_top_latest.json")
|
||||
if err := ioutil.WriteFile(cachePath, data, 0644); err != nil {
|
||||
return fmt.Errorf("写入OI Top缓存文件失败: %w", err)
|
||||
return fmt.Errorf("failed to write OI Top cache file: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("💾 已保存OI Top缓存(%d个币种)", len(positions))
|
||||
log.Printf("💾 OI Top cache saved (%d coins)", len(positions))
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadOITopCache 从缓存加载OI Top数据
|
||||
// loadOITopCache loads OI Top data from cache
|
||||
func loadOITopCache() ([]OIPosition, error) {
|
||||
cachePath := filepath.Join(oiTopConfig.CacheDir, "oi_top_latest.json")
|
||||
|
||||
if _, err := os.Stat(cachePath); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("OI Top缓存文件不存在")
|
||||
return nil, fmt.Errorf("OI Top cache file does not exist")
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(cachePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取OI Top缓存文件失败: %w", err)
|
||||
return nil, fmt.Errorf("failed to read OI Top cache file: %w", err)
|
||||
}
|
||||
|
||||
var cache OITopCache
|
||||
if err := json.Unmarshal(data, &cache); err != nil {
|
||||
return nil, fmt.Errorf("解析OI Top缓存数据失败: %w", err)
|
||||
return nil, fmt.Errorf("failed to parse OI Top cache data: %w", err)
|
||||
}
|
||||
|
||||
cacheAge := time.Since(cache.FetchedAt)
|
||||
if cacheAge > 24*time.Hour {
|
||||
log.Printf("⚠️ OI Top缓存数据较旧(%.1f小时前),但仍可使用", cacheAge.Hours())
|
||||
log.Printf("⚠️ OI Top cache data is old (%.1f hours ago), but still usable", cacheAge.Hours())
|
||||
} else {
|
||||
log.Printf("📂 OI Top缓存数据时间: %s(%.1f分钟前)",
|
||||
log.Printf("📂 OI Top cache data timestamp: %s (%.1f minutes ago)",
|
||||
cache.FetchedAt.Format("2006-01-02 15:04:05"),
|
||||
cacheAge.Minutes())
|
||||
}
|
||||
@@ -563,7 +563,7 @@ func loadOITopCache() ([]OIPosition, error) {
|
||||
return cache.Positions, nil
|
||||
}
|
||||
|
||||
// GetOITopSymbols 获取OI Top的币种符号列表
|
||||
// GetOITopSymbols retrieves OI Top coin symbol list
|
||||
func GetOITopSymbols() ([]string, error) {
|
||||
positions, err := GetOITopPositions()
|
||||
if err != nil {
|
||||
@@ -579,41 +579,41 @@ func GetOITopSymbols() ([]string, error) {
|
||||
return symbols, nil
|
||||
}
|
||||
|
||||
// MergedCoinPool 合并的币种池(AI500 + OI Top)
|
||||
// MergedCoinPool merged coin pool (AI500 + OI Top)
|
||||
type MergedCoinPool struct {
|
||||
AI500Coins []CoinInfo // AI500评分币种
|
||||
OITopCoins []OIPosition // 持仓量增长Top20
|
||||
AllSymbols []string // 所有不重复的币种符号
|
||||
SymbolSources map[string][]string // 每个币种的来源("ai500"/"oi_top")
|
||||
AI500Coins []CoinInfo // AI500 score coins
|
||||
OITopCoins []OIPosition // Open interest growth Top 20
|
||||
AllSymbols []string // All unique coin symbols
|
||||
SymbolSources map[string][]string // Source of each coin ("ai500"/"oi_top")
|
||||
}
|
||||
|
||||
// GetMergedCoinPool 获取合并后的币种池(AI500 + OI Top,去重)
|
||||
// GetMergedCoinPool retrieves merged coin pool (AI500 + OI Top, deduplicated)
|
||||
func GetMergedCoinPool(ai500Limit int) (*MergedCoinPool, error) {
|
||||
// 1. 获取AI500数据
|
||||
// 1. Get AI500 data
|
||||
ai500TopSymbols, err := GetTopRatedCoins(ai500Limit)
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 获取AI500数据失败: %v", err)
|
||||
ai500TopSymbols = []string{} // 失败时用空列表
|
||||
log.Printf("⚠️ Failed to get AI500 data: %v", err)
|
||||
ai500TopSymbols = []string{} // Use empty list on failure
|
||||
}
|
||||
|
||||
// 2. 获取OI Top数据
|
||||
// 2. Get OI Top data
|
||||
oiTopSymbols, err := GetOITopSymbols()
|
||||
if err != nil {
|
||||
log.Printf("⚠️ 获取OI Top数据失败: %v", err)
|
||||
oiTopSymbols = []string{} // 失败时用空列表
|
||||
log.Printf("⚠️ Failed to get OI Top data: %v", err)
|
||||
oiTopSymbols = []string{} // Use empty list on failure
|
||||
}
|
||||
|
||||
// 3. 合并并去重
|
||||
// 3. Merge and deduplicate
|
||||
symbolSet := make(map[string]bool)
|
||||
symbolSources := make(map[string][]string)
|
||||
|
||||
// 添加AI500币种
|
||||
// Add AI500 coins
|
||||
for _, symbol := range ai500TopSymbols {
|
||||
symbolSet[symbol] = true
|
||||
symbolSources[symbol] = append(symbolSources[symbol], "ai500")
|
||||
}
|
||||
|
||||
// 添加OI Top币种
|
||||
// Add OI Top coins
|
||||
for _, symbol := range oiTopSymbols {
|
||||
if !symbolSet[symbol] {
|
||||
symbolSet[symbol] = true
|
||||
@@ -621,13 +621,13 @@ func GetMergedCoinPool(ai500Limit int) (*MergedCoinPool, error) {
|
||||
symbolSources[symbol] = append(symbolSources[symbol], "oi_top")
|
||||
}
|
||||
|
||||
// 转换为数组
|
||||
// Convert to array
|
||||
var allSymbols []string
|
||||
for symbol := range symbolSet {
|
||||
allSymbols = append(allSymbols, symbol)
|
||||
}
|
||||
|
||||
// 获取完整数据
|
||||
// Get complete data
|
||||
ai500Coins, _ := GetCoinPool()
|
||||
oiTopPositions, _ := GetOITopPositions()
|
||||
|
||||
@@ -638,7 +638,7 @@ func GetMergedCoinPool(ai500Limit int) (*MergedCoinPool, error) {
|
||||
SymbolSources: symbolSources,
|
||||
}
|
||||
|
||||
log.Printf("📊 币种池合并完成: AI500=%d, OI_Top=%d, 总计(去重)=%d",
|
||||
log.Printf("📊 Coin pool merge complete: AI500=%d, OI_Top=%d, Total(deduplicated)=%d",
|
||||
len(ai500TopSymbols), len(oiTopSymbols), len(allSymbols))
|
||||
|
||||
return merged, nil
|
||||
|
||||
Reference in New Issue
Block a user