refactor: use canonical token-to-symbol maps for market lookup

Build canonical spot/future token maps with quote priority, unify cache refresh scheduling, and switch resolver/token tests to map-based token lookups.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-27 04:44:30 +07:00
parent 06c40ef30f
commit 711721c1ee
9 changed files with 296 additions and 108 deletions
+27 -40
View File
@@ -9,41 +9,25 @@ import (
)
func Token2FutureSymbols(token string) []string {
var syms []string
if !stringx.IsAlphaNumeric(token) {
return syms
return nil
}
token = strings.ToUpper(token)
if mapped, ok := data.Market.GetFutureSymbolByToken(token); ok {
return []string{mapped}
}
for _, prefix := range checkingPrefixList {
prefix = strings.ToUpper(prefix)
s := prefix + token
if data.Market.IsFuturesPair(s) {
syms = append(syms, s)
for futureToken, spotToken := range binance.FutureToken2SpotTokenMap {
if strings.ToUpper(spotToken) != token {
continue
}
for _, suffix := range binance.SymbolSuffixList {
suffix = strings.ToUpper(suffix)
abbr := strings.ToUpper(binance.SymbolSuffixMap[suffix])
sym := s + suffix
if data.Market.IsFuturesPair(sym) {
syms = append(syms, sym)
continue
}
symAbr, found := stringx.ReplaceSuffix(s, abbr, suffix)
if found && data.Market.IsFuturesPair(symAbr) {
syms = append(syms, symAbr)
continue
}
if mapped, ok := data.Market.GetFutureSymbolByToken(strings.ToUpper(futureToken)); ok {
return []string{mapped}
}
}
return syms
return nil
}
func Token2SpotSymbols(token string) []string {
@@ -51,21 +35,31 @@ func Token2SpotSymbols(token string) []string {
return nil
}
spotOnly := strings.ToUpper(token) + "USDT"
if data.Market.IsSpotPair(spotOnly) {
return []string{spotOnly}
token = strings.ToUpper(token)
if mapped, ok := data.Market.GetSpotSymbolByToken(token); ok {
return []string{mapped}
}
return nil
}
func Token2RelatedSpotSymbols(token string) []string {
futureSymbols := Token2FutureSymbols(token)
spots := make([]string, 0, len(futureSymbols)+1)
seen := make(map[string]struct{}, len(futureSymbols)+1)
token = strings.ToUpper(token)
seen := make(map[string]struct{}, 2)
spots := make([]string, 0, 2)
for _, futureSymbol := range futureSymbols {
for _, futureSymbol := range Token2FutureSymbols(token) {
spotSymbol := Future2SpotSymbol(futureSymbol)
if _, ok := seen[spotSymbol]; ok {
continue
}
if data.Market.IsSpotPair(spotSymbol) {
seen[spotSymbol] = struct{}{}
spots = append(spots, spotSymbol)
}
}
for _, spotSymbol := range Token2SpotSymbols(token) {
if _, ok := seen[spotSymbol]; ok {
continue
}
@@ -73,12 +67,5 @@ func Token2RelatedSpotSymbols(token string) []string {
spots = append(spots, spotSymbol)
}
spotOnly := strings.ToUpper(token) + "USDT"
if data.Market.IsSpotPair(spotOnly) {
if _, ok := seen[spotOnly]; !ok {
spots = append(spots, spotOnly)
}
}
return spots
}
+28 -27
View File
@@ -1,7 +1,7 @@
package binancex
import (
"reflect"
"strings"
"testing"
"me.thuanle/bbot/internal/configs/binance"
@@ -31,7 +31,25 @@ func (m *resolverMarketStub) GetAlphaToken(symbol string) (market.AlphaTokenInfo
func (m *resolverMarketStub) GetAlphaPrice(symbol string) (float64, bool) { return 0, false }
func (m *resolverMarketStub) IsSpotPair(symbol string) bool { return m.spotPairs[symbol] }
func (m *resolverMarketStub) IsFuturesPair(symbol string) bool { return m.futuresPairs[symbol] }
func (m *resolverMarketStub) RefreshTradingPairCache() error { return nil }
func (m *resolverMarketStub) GetSpotSymbolByToken(token string) (string, bool) {
token = strings.ToUpper(token)
for sym := range m.spotPairs {
if strings.ToUpper(Symbol2Token(sym)) == token {
return sym, true
}
}
return "", false
}
func (m *resolverMarketStub) GetFutureSymbolByToken(token string) (string, bool) {
token = strings.ToUpper(token)
for sym := range m.futuresPairs {
if strings.ToUpper(Symbol2Token(sym)) == token {
return sym, true
}
}
return "", false
}
func (m *resolverMarketStub) RefreshTradingPairCache() error { return nil }
func withResolverMarketStub(t *testing.T, marketStub *resolverMarketStub) {
t.Helper()
@@ -40,7 +58,7 @@ func withResolverMarketStub(t *testing.T, marketStub *resolverMarketStub) {
t.Cleanup(func() { data.Market = orig })
}
func TestToken2FutureSymbols_ResolvesPrefixAndSuffix(t *testing.T) {
func TestToken2FutureSymbols_ReturnsCanonicalFutureSymbol(t *testing.T) {
withResolverMarketStub(t, &resolverMarketStub{
futuresPairs: map[string]bool{
"1000PEPEUSDT": true,
@@ -49,27 +67,12 @@ func TestToken2FutureSymbols_ResolvesPrefixAndSuffix(t *testing.T) {
})
syms := Token2FutureSymbols("pepe")
if len(syms) == 0 {
t.Fatalf("expected future symbols for PEPE")
}
foundUSDT := false
foundUSDC := false
for _, sym := range syms {
if sym == "1000PEPEUSDT" {
foundUSDT = true
}
if sym == "1000PEPEUSDC" {
foundUSDC = true
}
}
if !foundUSDT || !foundUSDC {
t.Fatalf("expected both 1000PEPEUSDT and 1000PEPEUSDC, got %+v", syms)
if len(syms) != 1 || syms[0] != "1000PEPEUSDT" {
t.Fatalf("expected canonical [1000PEPEUSDT], got %+v", syms)
}
}
func TestToken2FutureSymbols_UsesDeterministicCandidateOrder(t *testing.T) {
func TestToken2FutureSymbols_UsesCanonicalQuotePriority(t *testing.T) {
withResolverMarketStub(t, &resolverMarketStub{
futuresPairs: map[string]bool{
"1000PEPEUSDT": true,
@@ -80,9 +83,8 @@ func TestToken2FutureSymbols_UsesDeterministicCandidateOrder(t *testing.T) {
})
syms := Token2FutureSymbols("pepe")
expected := []string{"1000PEPEUSDT", "1000PEPEUSDC", "PEPEUSDT", "PEPEUSDC"}
if !reflect.DeepEqual(syms, expected) {
t.Fatalf("expected deterministic order %+v, got %+v", expected, syms)
if len(syms) != 1 || syms[0] != "1000PEPEUSDT" {
t.Fatalf("expected canonical [1000PEPEUSDT], got %+v", syms)
}
}
@@ -92,9 +94,8 @@ func TestToken2FutureSymbols_ResolvesUSDCAbbreviation(t *testing.T) {
})
syms := Token2FutureSymbols("pepec")
expected := []string{"PEPEUSDC"}
if !reflect.DeepEqual(syms, expected) {
t.Fatalf("expected USDC abbreviation resolution %+v, got %+v", expected, syms)
if len(syms) != 1 || syms[0] != "PEPEUSDC" {
t.Fatalf("expected [PEPEUSDC], got %+v", syms)
}
}
+4 -4
View File
@@ -51,9 +51,9 @@ func Future2SpotSymbol(sym string) string {
}
}
spotSym, ok := binance.Future2SpotSymbolMap[sym]
if !ok {
return sym
token := Symbol2Token(sym)
if mapped, ok := binance.FutureToken2SpotTokenMap[token]; ok {
token = strings.ToUpper(mapped)
}
return spotSym
return token + "USDT"
}