fix: restore canonical token lookup for market caches
Build Docker Image / build (amd64) (push) Successful in 1m25s
Build Docker Image / build (amd64) (push) Successful in 1m25s
This commit is contained in:
@@ -25,17 +25,16 @@ func (ms *MarketData) refreshFuturePairCache() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
futurePairs[s.Symbol] = true
|
futurePairs[s.Symbol] = true
|
||||||
token := parseTokenFromSymbolByQuotePriority(s.Symbol)
|
token := tokenKeyFromSymbol(s.Symbol)
|
||||||
if token == "" {
|
if token == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
token = futureCacheTokenKey(token)
|
|
||||||
futureTokenCandidates[token] = append(futureTokenCandidates[token], s.Symbol)
|
futureTokenCandidates[token] = append(futureTokenCandidates[token], s.Symbol)
|
||||||
}
|
}
|
||||||
|
|
||||||
futureToken2Symbol := make(map[string]string, len(futureTokenCandidates))
|
futureToken2Symbol := make(map[string]string, len(futureTokenCandidates))
|
||||||
for token, candidates := range futureTokenCandidates {
|
for token, candidates := range futureTokenCandidates {
|
||||||
futureToken2Symbol[token] = selectCanonicalSymbolByQuotePriority(token, candidates)
|
futureToken2Symbol[token] = selectCanonicalFutureSymbol(token, candidates)
|
||||||
}
|
}
|
||||||
|
|
||||||
ms.pairCacheMutex.Lock()
|
ms.pairCacheMutex.Lock()
|
||||||
@@ -47,10 +46,6 @@ func (ms *MarketData) refreshFuturePairCache() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func futureCacheTokenKey(token string) string {
|
|
||||||
return strings.ToUpper(token)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ms *MarketData) IsFuturesPair(symbol string) bool {
|
func (ms *MarketData) IsFuturesPair(symbol string) bool {
|
||||||
ms.pairCacheMutex.RLock()
|
ms.pairCacheMutex.RLock()
|
||||||
defer ms.pairCacheMutex.RUnlock()
|
defer ms.pairCacheMutex.RUnlock()
|
||||||
|
|||||||
@@ -26,16 +26,39 @@ func TestSelectCanonicalSymbolByQuotePriority_NoPreferredQuote(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFutureCacheTokenKey_PreservesRawFutureToken(t *testing.T) {
|
func TestSelectCanonicalFutureSymbol_PrefersPrefixThenQuote(t *testing.T) {
|
||||||
got := futureCacheTokenKey("LUNA2")
|
pairs := []string{"PEPEUSDC", "PEPEUSDT", "1000PEPEUSDC", "1000PEPEUSDT"}
|
||||||
|
got := selectCanonicalFutureSymbol("PEPE", pairs)
|
||||||
|
if got != "1000PEPEUSDT" {
|
||||||
|
t.Fatalf("expected 1000PEPEUSDT, got %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelectCanonicalFutureSymbol_UsesQuoteAbbreviation(t *testing.T) {
|
||||||
|
pairs := []string{"PEPEUSDC"}
|
||||||
|
got := selectCanonicalFutureSymbol("PEPEC", pairs)
|
||||||
|
if got != "PEPEUSDC" {
|
||||||
|
t.Fatalf("expected PEPEUSDC, got %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenKeyFromSymbol_PreservesUnmappedFutureToken(t *testing.T) {
|
||||||
|
got := tokenKeyFromSymbol("LUNA2USDT")
|
||||||
if got != "LUNA2" {
|
if got != "LUNA2" {
|
||||||
t.Fatalf("expected LUNA2, got %q", got)
|
t.Fatalf("expected LUNA2, got %q", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFutureCacheTokenKey_NoOverride(t *testing.T) {
|
func TestTokenKeyFromSymbol_StripsKnownPrefix(t *testing.T) {
|
||||||
got := futureCacheTokenKey("PEPE")
|
got := tokenKeyFromSymbol("1000PEPEUSDT")
|
||||||
if got != "PEPE" {
|
if got != "PEPE" {
|
||||||
t.Fatalf("expected PEPE, got %q", got)
|
t.Fatalf("expected PEPE, got %q", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTokenKeyFromSymbol_UsesQuoteAbbreviation(t *testing.T) {
|
||||||
|
got := tokenKeyFromSymbol("PEPEUSDC")
|
||||||
|
if got != "PEPEC" {
|
||||||
|
t.Fatalf("expected PEPEC, got %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func (ms *MarketData) refreshSpotPairCache() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
spotPairs[s.Symbol] = true
|
spotPairs[s.Symbol] = true
|
||||||
token := parseTokenFromSymbolByQuotePriority(s.Symbol)
|
token := tokenKeyFromSymbol(s.Symbol)
|
||||||
if token == "" {
|
if token == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -36,7 +36,7 @@ func (ms *MarketData) refreshSpotPairCache() error {
|
|||||||
|
|
||||||
spotToken2Symbol := make(map[string]string, len(spotTokenCandidates))
|
spotToken2Symbol := make(map[string]string, len(spotTokenCandidates))
|
||||||
for token, candidates := range spotTokenCandidates {
|
for token, candidates := range spotTokenCandidates {
|
||||||
spotToken2Symbol[token] = selectCanonicalSymbolByQuotePriority(token, candidates)
|
spotToken2Symbol[token] = selectCanonicalSpotSymbol(token, candidates)
|
||||||
}
|
}
|
||||||
|
|
||||||
ms.pairCacheMutex.Lock()
|
ms.pairCacheMutex.Lock()
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package market
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"me.thuanle/bbot/internal/configs/binance"
|
||||||
|
"me.thuanle/bbot/internal/utils/stringx"
|
||||||
|
)
|
||||||
|
|
||||||
|
var futureSymbolPrefixPriority = append(append([]string{}, binance.SymbolPrefixList...), "")
|
||||||
|
|
||||||
|
func tokenKeyFromSymbol(symbol string) string {
|
||||||
|
token := strings.ToUpper(symbol)
|
||||||
|
|
||||||
|
for _, prefix := range binance.SymbolPrefixList {
|
||||||
|
token, _ = strings.CutPrefix(token, strings.ToUpper(prefix))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, suffix := range binance.SymbolSuffixList {
|
||||||
|
replaced, ok := stringx.ReplaceSuffix(token, strings.ToUpper(suffix), strings.ToUpper(binance.SymbolSuffixMap[suffix]))
|
||||||
|
if ok {
|
||||||
|
return replaced
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectCanonicalSpotSymbol(token string, candidates []string) string {
|
||||||
|
return selectCanonicalSymbolByTokenOrder(token, candidates, []string{""})
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectCanonicalFutureSymbol(token string, candidates []string) string {
|
||||||
|
return selectCanonicalSymbolByTokenOrder(token, candidates, futureSymbolPrefixPriority)
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectCanonicalSymbolByTokenOrder(token string, candidates []string, prefixes []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))
|
||||||
|
candidateSet := make(map[string]struct{}, len(candidates))
|
||||||
|
for _, candidate := range candidates {
|
||||||
|
candidate = strings.ToUpper(candidate)
|
||||||
|
normalized = append(normalized, candidate)
|
||||||
|
candidateSet[candidate] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prefix := range prefixes {
|
||||||
|
base := strings.ToUpper(prefix) + token
|
||||||
|
if _, ok := candidateSet[base]; ok {
|
||||||
|
return base
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, suffix := range binance.SymbolSuffixList {
|
||||||
|
target := base + strings.ToUpper(suffix)
|
||||||
|
if _, ok := candidateSet[target]; ok {
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
replaced, ok := stringx.ReplaceSuffix(base, strings.ToUpper(binance.SymbolSuffixMap[suffix]), strings.ToUpper(suffix))
|
||||||
|
if ok {
|
||||||
|
if _, exists := candidateSet[replaced]; exists {
|
||||||
|
return replaced
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(normalized)
|
||||||
|
return normalized[0]
|
||||||
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
package binancex
|
package binancex
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"me.thuanle/bbot/internal/configs/binance"
|
"me.thuanle/bbot/internal/configs/binance"
|
||||||
"me.thuanle/bbot/internal/data"
|
"me.thuanle/bbot/internal/data"
|
||||||
"me.thuanle/bbot/internal/data/market"
|
"me.thuanle/bbot/internal/data/market"
|
||||||
|
"me.thuanle/bbot/internal/utils/stringx"
|
||||||
)
|
)
|
||||||
|
|
||||||
type resolverMarketStub struct {
|
type resolverMarketStub struct {
|
||||||
@@ -32,22 +34,51 @@ func (m *resolverMarketStub) GetAlphaPrice(symbol string) (float64, bool) { retu
|
|||||||
func (m *resolverMarketStub) IsSpotPair(symbol string) bool { return m.spotPairs[symbol] }
|
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) IsFuturesPair(symbol string) bool { return m.futuresPairs[symbol] }
|
||||||
func (m *resolverMarketStub) GetSpotSymbolByToken(token string) (string, bool) {
|
func (m *resolverMarketStub) GetSpotSymbolByToken(token string) (string, bool) {
|
||||||
token = strings.ToUpper(token)
|
return selectCanonicalTestSymbol(token, m.spotPairs, []string{""})
|
||||||
for sym := range m.spotPairs {
|
|
||||||
if strings.ToUpper(Symbol2Token(sym)) == token {
|
|
||||||
return sym, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
}
|
||||||
func (m *resolverMarketStub) GetFutureSymbolByToken(token string) (string, bool) {
|
func (m *resolverMarketStub) GetFutureSymbolByToken(token string) (string, bool) {
|
||||||
|
prefixes := append(append([]string{}, binance.SymbolPrefixList...), "")
|
||||||
|
return selectCanonicalTestSymbol(token, m.futuresPairs, prefixes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectCanonicalTestSymbol(token string, pairs map[string]bool, prefixes []string) (string, bool) {
|
||||||
token = strings.ToUpper(token)
|
token = strings.ToUpper(token)
|
||||||
for sym := range m.futuresPairs {
|
candidates := make([]string, 0, len(pairs))
|
||||||
|
candidateSet := make(map[string]struct{}, len(pairs))
|
||||||
|
for sym := range pairs {
|
||||||
if strings.ToUpper(Symbol2Token(sym)) == token {
|
if strings.ToUpper(Symbol2Token(sym)) == token {
|
||||||
return sym, true
|
sym = strings.ToUpper(sym)
|
||||||
|
candidates = append(candidates, sym)
|
||||||
|
candidateSet[sym] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return "", false
|
if len(candidates) == 0 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prefix := range prefixes {
|
||||||
|
base := strings.ToUpper(prefix) + token
|
||||||
|
if _, ok := candidateSet[base]; ok {
|
||||||
|
return base, true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, quote := range binance.SymbolSuffixList {
|
||||||
|
target := base + strings.ToUpper(quote)
|
||||||
|
if _, ok := candidateSet[target]; ok {
|
||||||
|
return target, true
|
||||||
|
}
|
||||||
|
|
||||||
|
replaced, ok := stringx.ReplaceSuffix(base, strings.ToUpper(binance.SymbolSuffixMap[quote]), strings.ToUpper(quote))
|
||||||
|
if ok {
|
||||||
|
if _, exists := candidateSet[replaced]; exists {
|
||||||
|
return replaced, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(candidates)
|
||||||
|
return candidates[0], true
|
||||||
}
|
}
|
||||||
func (m *resolverMarketStub) RefreshTradingPairCache() error { return nil }
|
func (m *resolverMarketStub) RefreshTradingPairCache() error { return nil }
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user