package market import ( "context" "sort" "strings" "time" "github.com/rs/zerolog/log" "me.thuanle/bbot/internal/configs/binance" ) func (ms *MarketData) refreshSpotPairCache() error { ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() spotInfo, err := ms.spotClient.NewExchangeInfoService().Do(ctx) if err != nil { log.Error().Err(err).Msg("Failed to fetch spot exchange info") return err } spotPairs := make(map[string]bool, len(spotInfo.Symbols)) spotTokenCandidates := make(map[string][]string) for _, s := range spotInfo.Symbols { if s.Status != "TRADING" { continue } spotPairs[s.Symbol] = true token := parseTokenFromSymbolByQuotePriority(s.Symbol) if token == "" { continue } spotTokenCandidates[token] = append(spotTokenCandidates[token], s.Symbol) } spotToken2Symbol := make(map[string]string, len(spotTokenCandidates)) for token, candidates := range spotTokenCandidates { spotToken2Symbol[token] = selectCanonicalSymbolByQuotePriority(token, candidates) } ms.pairCacheMutex.Lock() ms.spotPairs = spotPairs ms.spotToken2Symbol = spotToken2Symbol ms.lastPairCacheUpdate = time.Now() ms.pairCacheMutex.Unlock() return nil } func parseTokenFromSymbolByQuotePriority(symbol string) string { symbol = strings.ToUpper(symbol) for _, quote := range binance.QuotePriority { quote = strings.ToUpper(quote) if strings.HasSuffix(symbol, quote) { token := strings.TrimSuffix(symbol, quote) if token != "" { return token } } } return "" } func selectCanonicalSymbolByQuotePriority(token string, candidates []string) string { if len(candidates) == 0 { return "" } if len(candidates) == 1 { return strings.ToUpper(candidates[0]) } token = strings.ToUpper(token) normalized := make([]string, 0, len(candidates)) for _, c := range candidates { normalized = append(normalized, strings.ToUpper(c)) } for _, quote := range binance.QuotePriority { target := token + strings.ToUpper(quote) for _, c := range normalized { if c == target { return c } } } sort.Strings(normalized) return normalized[0] } func (ms *MarketData) IsSpotPair(symbol string) bool { ms.pairCacheMutex.RLock() defer ms.pairCacheMutex.RUnlock() return ms.spotPairs[symbol] } func (ms *MarketData) GetSpotSymbolByToken(token string) (string, bool) { ms.pairCacheMutex.RLock() defer ms.pairCacheMutex.RUnlock() sym, ok := ms.spotToken2Symbol[strings.ToUpper(token)] return sym, ok }