Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e173fd7799 | ||
|
|
3b726311f5 | ||
|
|
c8e916adde | ||
|
|
2b437071a9 | ||
|
|
7c3b3ef616 | ||
|
|
7de2ee4cfc | ||
|
|
0583f90e82 | ||
|
|
0a1a61d8d3 | ||
|
|
5912157ed1 | ||
|
|
c5ff494e13 | ||
|
|
bc99d40713 | ||
|
|
8010a00e4d | ||
|
|
259a630b78 | ||
|
|
f64a3521c4 |
@@ -1,2 +1,4 @@
|
||||
/.env
|
||||
/.worktrees/
|
||||
.DS_Store
|
||||
.idea/
|
||||
Generated
-8
@@ -1,8 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
Generated
-9
@@ -1,9 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="Go" enabled="true" />
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
Generated
-8
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/crypto-price-bot.iml" filepath="$PROJECT_DIR$/.idea/crypto-price-bot.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
-6
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,85 @@
|
||||
# Go filenames consistency design
|
||||
|
||||
## Context
|
||||
|
||||
The repository has inconsistent `.go` filename semantics in multiple areas (domain-oriented names, endpoint/history-oriented names, mixed singular/plural forms).
|
||||
This slows code navigation because filenames do not consistently communicate data dimension or responsibility.
|
||||
|
||||
Goal: refactor `.go` filenames across the whole repository to a consistent naming model, and align declaration placement with filename responsibility, without changing runtime behavior.
|
||||
|
||||
## Scope
|
||||
|
||||
Applies to all `.go` files in the repository.
|
||||
|
||||
In scope:
|
||||
- Rename `.go` files to consistent, responsibility-first names.
|
||||
- Mechanical updates required by rename/move.
|
||||
- Move declarations between files when needed so file responsibility is clear.
|
||||
|
||||
Out of scope:
|
||||
- Any behavior or logic changes.
|
||||
- Non-`.go` files.
|
||||
- New feature work.
|
||||
|
||||
## Requirements
|
||||
|
||||
1. `.go` filename naming is consistent across the repository.
|
||||
2. Filenames follow responsibility-first conventions:
|
||||
- data-dimension names where applicable (`spot_*`, `futures_*`, etc.)
|
||||
- pluralized collection-oriented files (`*_pairs.go`, `*_prices.go`)
|
||||
- avoid endpoint/history-oriented naming that obscures responsibility.
|
||||
3. Declarations in each file match filename responsibility.
|
||||
4. No public/package behavior change.
|
||||
5. Resulting PR remains reviewable by grouping changes mechanically and keeping logic untouched.
|
||||
|
||||
## Design
|
||||
|
||||
### Naming model
|
||||
|
||||
Use a responsibility-first naming model for all `.go` files:
|
||||
- A reader should infer the primary responsibility from filename alone.
|
||||
- Prefer domain/data-dimension names over transport/implementation-history names.
|
||||
- Use consistent singular/plural convention based on file content (entity vs collection).
|
||||
|
||||
### Refactor mechanics
|
||||
|
||||
1. Inventory `.go` files by package and classify naming inconsistencies.
|
||||
2. Define target filename map package-by-package.
|
||||
3. Rename files with git-aware rename operations.
|
||||
4. Move declarations when file content does not match filename responsibility.
|
||||
5. Keep signatures and bodies unchanged.
|
||||
6. Apply only mechanical reference/import updates required by moves.
|
||||
|
||||
### Execution strategy for reviewability
|
||||
|
||||
- Use focused commits by package or subsystem.
|
||||
- Keep each commit behavior-neutral.
|
||||
- Separate any necessary declaration moves from broad rename waves when this improves diff readability.
|
||||
|
||||
## Error handling and behavior
|
||||
|
||||
No new error handling paths are introduced.
|
||||
Existing control flow and error behavior remain unchanged.
|
||||
|
||||
## Testing and verification
|
||||
|
||||
1. Run project tests after each major package/subsystem batch.
|
||||
2. Run full project test command before PR.
|
||||
3. Confirm no functional diff beyond rename/move/mechanical updates.
|
||||
4. Final check: each renamed file contains declarations aligned with its responsibility.
|
||||
|
||||
## Risks and mitigations
|
||||
|
||||
- Risk: accidental behavior change while moving declarations.
|
||||
- Mitigation: keep signatures/bodies unchanged and validate with tests per batch.
|
||||
- Risk: review noise from repo-wide scope.
|
||||
- Mitigation: package-grouped commits, strict mechanical-only edits, and explicit PR summary.
|
||||
- Risk: large PR becomes hard to review.
|
||||
- Mitigation: preserve commit structure and provide a clear rename map in PR description.
|
||||
|
||||
## Success criteria
|
||||
|
||||
- `.go` filenames are consistent and responsibility-first across the repo.
|
||||
- Declarations in each file align with filename responsibility.
|
||||
- Tests pass with no behavior regression.
|
||||
- PR is understandable through structured, behavior-neutral commits.
|
||||
@@ -5,11 +5,11 @@ go 1.25.0
|
||||
require (
|
||||
github.com/adshao/go-binance/v2 v2.8.11
|
||||
github.com/go-resty/resty/v2 v2.17.2
|
||||
github.com/jedib0t/go-pretty/v6 v6.7.10
|
||||
github.com/jedib0t/go-pretty/v6 v6.8.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/rs/zerolog v1.32.0
|
||||
github.com/rs/zerolog v1.35.1
|
||||
github.com/samber/lo v1.53.0
|
||||
golang.org/x/text v0.36.0
|
||||
golang.org/x/text v0.37.0
|
||||
gopkg.in/telebot.v3 v3.3.8
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@ require (
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.3 // indirect
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
|
||||
@@ -99,7 +99,6 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH
|
||||
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -257,8 +256,8 @@ github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpT
|
||||
github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/jedib0t/go-pretty/v6 v6.7.10 h1:B/2qW2Bkv2L6n14PP8o1kx75kWzHOQ3YTluWzg9icac=
|
||||
github.com/jedib0t/go-pretty/v6 v6.7.10/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
|
||||
github.com/jedib0t/go-pretty/v6 v6.8.0 h1:fQOTjATVQl5RhssBro6ZuHANFybCkmJ7FjYPo4b7sEY=
|
||||
github.com/jedib0t/go-pretty/v6 v6.8.0/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
|
||||
@@ -294,16 +293,14 @@ github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
@@ -362,9 +359,8 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
|
||||
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
||||
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
|
||||
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8=
|
||||
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
|
||||
@@ -623,9 +619,7 @@ golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
@@ -639,8 +633,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
@@ -8,7 +8,7 @@ const (
|
||||
|
||||
var Token2StickerIdxMap = map[string]int{
|
||||
"BNB": 3,
|
||||
"TON": 7,
|
||||
"TON": 5,
|
||||
}
|
||||
|
||||
var Sticker2TokenMap = map[string]string{
|
||||
|
||||
@@ -25,17 +25,16 @@ func (ms *MarketData) refreshFuturePairCache() error {
|
||||
continue
|
||||
}
|
||||
futurePairs[s.Symbol] = true
|
||||
token := parseTokenFromSymbolByQuotePriority(s.Symbol)
|
||||
token := tokenKeyFromSymbol(s.Symbol)
|
||||
if token == "" {
|
||||
continue
|
||||
}
|
||||
token = futureCacheTokenKey(token)
|
||||
futureTokenCandidates[token] = append(futureTokenCandidates[token], s.Symbol)
|
||||
}
|
||||
|
||||
futureToken2Symbol := make(map[string]string, len(futureTokenCandidates))
|
||||
for token, candidates := range futureTokenCandidates {
|
||||
futureToken2Symbol[token] = selectCanonicalSymbolByQuotePriority(token, candidates)
|
||||
futureToken2Symbol[token] = selectCanonicalFutureSymbol(token, candidates)
|
||||
}
|
||||
|
||||
ms.pairCacheMutex.Lock()
|
||||
@@ -47,10 +46,6 @@ func (ms *MarketData) refreshFuturePairCache() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func futureCacheTokenKey(token string) string {
|
||||
return strings.ToUpper(token)
|
||||
}
|
||||
|
||||
func (ms *MarketData) IsFuturesPair(symbol string) bool {
|
||||
ms.pairCacheMutex.RLock()
|
||||
defer ms.pairCacheMutex.RUnlock()
|
||||
|
||||
@@ -26,16 +26,39 @@ func TestSelectCanonicalSymbolByQuotePriority_NoPreferredQuote(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFutureCacheTokenKey_PreservesRawFutureToken(t *testing.T) {
|
||||
got := futureCacheTokenKey("LUNA2")
|
||||
func TestSelectCanonicalFutureSymbol_PrefersPrefixThenQuote(t *testing.T) {
|
||||
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" {
|
||||
t.Fatalf("expected LUNA2, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFutureCacheTokenKey_NoOverride(t *testing.T) {
|
||||
got := futureCacheTokenKey("PEPE")
|
||||
func TestTokenKeyFromSymbol_StripsKnownPrefix(t *testing.T) {
|
||||
got := tokenKeyFromSymbol("1000PEPEUSDT")
|
||||
if got != "PEPE" {
|
||||
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
|
||||
}
|
||||
spotPairs[s.Symbol] = true
|
||||
token := parseTokenFromSymbolByQuotePriority(s.Symbol)
|
||||
token := tokenKeyFromSymbol(s.Symbol)
|
||||
if token == "" {
|
||||
continue
|
||||
}
|
||||
@@ -36,7 +36,7 @@ func (ms *MarketData) refreshSpotPairCache() error {
|
||||
|
||||
spotToken2Symbol := make(map[string]string, len(spotTokenCandidates))
|
||||
for token, candidates := range spotTokenCandidates {
|
||||
spotToken2Symbol[token] = selectCanonicalSymbolByQuotePriority(token, candidates)
|
||||
spotToken2Symbol[token] = selectCanonicalSpotSymbol(token, candidates)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"me.thuanle/bbot/internal/configs/binance"
|
||||
"me.thuanle/bbot/internal/data"
|
||||
"me.thuanle/bbot/internal/data/market"
|
||||
"me.thuanle/bbot/internal/utils/stringx"
|
||||
)
|
||||
|
||||
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) IsFuturesPair(symbol string) bool { return m.futuresPairs[symbol] }
|
||||
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
|
||||
return selectCanonicalTestSymbol(token, m.spotPairs, []string{""})
|
||||
}
|
||||
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)
|
||||
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 {
|
||||
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 }
|
||||
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
go run cmd/tele/main.go ─╯
|
||||
Reference in New Issue
Block a user