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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user