Compare commits
8 Commits
41c1bdfa22
...
0579a1163c
| Author | SHA1 | Date | |
|---|---|---|---|
| 0579a1163c | |||
| ee6c181cd6 | |||
| 4cab108549 | |||
| 733cf48e41 | |||
| 914beea5ce | |||
| c7128ff516 | |||
| 59da49c17f | |||
| 9c39423315 |
@@ -5,7 +5,7 @@ go 1.25.0
|
||||
require (
|
||||
github.com/adshao/go-binance/v2 v2.8.11
|
||||
github.com/go-resty/resty/v2 v2.16.5
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.5
|
||||
github.com/jedib0t/go-pretty/v6 v6.7.10
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/rs/zerolog v1.32.0
|
||||
github.com/samber/lo v1.53.0
|
||||
@@ -20,9 +20,9 @@ require (
|
||||
github.com/jpillora/backoff v1.0.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.28.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
)
|
||||
|
||||
@@ -257,8 +257,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.5.5 h1:PpIU8lOjxvVYGGKule0QxxJfNysUSbC9lggQU2cpZJc=
|
||||
github.com/jedib0t/go-pretty/v6 v6.5.5/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg=
|
||||
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/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=
|
||||
@@ -306,8 +306,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
||||
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.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
@@ -355,8 +355,9 @@ github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsT
|
||||
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
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=
|
||||
@@ -394,8 +395,8 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@@ -625,8 +626,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
||||
@@ -4,4 +4,5 @@ const (
|
||||
LogEnv = "LOG_ENV"
|
||||
|
||||
TelegramToken = "TELEGRAM_TOKEN"
|
||||
AdminChatID = "ADMIN_CHAT_ID"
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@ import "me.thuanle/bbot/internal/data/market"
|
||||
|
||||
type IMarket interface {
|
||||
GetFuturePrice(symbol string) (float64, float64, int64, bool)
|
||||
GetAllPremiumIndex() (map[string]market.PremiumIndex, error)
|
||||
GetAllFundRate() (map[string]float64, map[string]int64)
|
||||
|
||||
GetSpotPrice(symbol string) (float64, bool)
|
||||
@@ -12,4 +13,9 @@ type IMarket interface {
|
||||
// Alpha token methods
|
||||
IsAlphaToken(symbol string) bool
|
||||
GetAlphaToken(symbol string) (market.AlphaTokenInfo, bool)
|
||||
|
||||
// Trading pair methods
|
||||
IsSpotPair(symbol string) bool
|
||||
IsFuturesPair(symbol string) bool
|
||||
RefreshTradingPairCache() error
|
||||
}
|
||||
|
||||
@@ -1,64 +1,85 @@
|
||||
package market
|
||||
|
||||
import (
|
||||
"github.com/adshao/go-binance/v2/futures"
|
||||
"github.com/rs/zerolog/log"
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (ms *MarketData) GetFuturePrice(symbol string) (float64, float64, int64, bool) {
|
||||
ms.mu.RLock()
|
||||
defer ms.mu.RUnlock()
|
||||
p, ok := ms.futureMarkPrice[symbol]
|
||||
if !ok {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
premiums, err := ms.futuresClient.NewPremiumIndexService().Symbol(symbol).Do(ctx)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("symbol", symbol).Msg("Failed to fetch futures premium index")
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
return p, ms.futureFundingRate[symbol], ms.futureNextFundingTime[symbol], true
|
||||
|
||||
if len(premiums) == 0 {
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
|
||||
func (ms *MarketData) StartFutureWsMarkPrice() error {
|
||||
_, _, err := futures.WsAllMarkPriceServe(ms.futureWsMarkPriceHandler, ms.futureWsErrHandler)
|
||||
p := premiums[0]
|
||||
markPrice, err := strconv.ParseFloat(p.MarkPrice, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
|
||||
func (ms *MarketData) futureWsMarkPriceHandler(event futures.WsAllMarkPriceEvent) {
|
||||
ms.mu.Lock()
|
||||
defer ms.mu.Unlock()
|
||||
for _, priceEvent := range event {
|
||||
price, err := strconv.ParseFloat(priceEvent.MarkPrice, 64)
|
||||
fundingRate, err := strconv.ParseFloat(p.LastFundingRate, 64)
|
||||
if err != nil {
|
||||
fundingRate = 0
|
||||
}
|
||||
|
||||
return markPrice, fundingRate, p.NextFundingTime, true
|
||||
}
|
||||
|
||||
type PremiumIndex struct {
|
||||
MarkPrice float64
|
||||
FundingRate float64
|
||||
NextFundingTime int64
|
||||
}
|
||||
|
||||
func (ms *MarketData) GetAllPremiumIndex() (map[string]PremiumIndex, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
premiums, err := ms.futuresClient.NewPremiumIndexService().Do(ctx)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to fetch all futures premium index")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make(map[string]PremiumIndex, len(premiums))
|
||||
for _, p := range premiums {
|
||||
markPrice, err := strconv.ParseFloat(p.MarkPrice, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fundingRate, err := strconv.ParseFloat(priceEvent.FundingRate, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ms.futureMarkPrice[priceEvent.Symbol] = price
|
||||
ms.futureFundingRate[priceEvent.Symbol] = fundingRate
|
||||
ms.futureNextFundingTime[priceEvent.Symbol] = priceEvent.NextFundingTime
|
||||
rate, _ := strconv.ParseFloat(p.LastFundingRate, 64)
|
||||
result[p.Symbol] = PremiumIndex{
|
||||
MarkPrice: markPrice,
|
||||
FundingRate: rate,
|
||||
NextFundingTime: p.NextFundingTime,
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *MarketData) futureWsErrHandler(err error) {
|
||||
log.Debug().Err(err).Msg("Ws Error. Restart socket")
|
||||
_ = ms.StartFutureWsMarkPrice()
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (ms *MarketData) GetAllFundRate() (map[string]float64, map[string]int64) {
|
||||
ms.mu.RLock()
|
||||
defer ms.mu.RUnlock()
|
||||
rates := make(map[string]float64, len(ms.futureFundingRate))
|
||||
for k, v := range ms.futureFundingRate {
|
||||
rates[k] = v
|
||||
all, err := ms.GetAllPremiumIndex()
|
||||
if err != nil {
|
||||
return make(map[string]float64), make(map[string]int64)
|
||||
}
|
||||
times := make(map[string]int64, len(ms.futureNextFundingTime))
|
||||
for k, v := range ms.futureNextFundingTime {
|
||||
times[k] = v
|
||||
|
||||
rates := make(map[string]float64, len(all))
|
||||
times := make(map[string]int64, len(all))
|
||||
for sym, p := range all {
|
||||
rates[sym] = p.FundingRate
|
||||
times[sym] = p.NextFundingTime
|
||||
}
|
||||
return rates, times
|
||||
}
|
||||
|
||||
@@ -4,37 +4,42 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/adshao/go-binance/v2"
|
||||
"github.com/adshao/go-binance/v2/futures"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type MarketData struct {
|
||||
mu sync.RWMutex
|
||||
futureMarkPrice map[string]float64
|
||||
futureFundingRate map[string]float64
|
||||
futureNextFundingTime map[string]int64
|
||||
|
||||
spotPrice map[string]float64
|
||||
// Trading pair caches
|
||||
spotPairs map[string]bool
|
||||
futuresPairs map[string]bool
|
||||
pairCacheMutex sync.RWMutex
|
||||
lastPairCacheUpdate time.Time
|
||||
|
||||
// Alpha token cache
|
||||
alphaTokens map[string]AlphaTokenInfo
|
||||
alphaCacheMutex sync.RWMutex
|
||||
lastAlphaCacheUpdate time.Time
|
||||
|
||||
// Binance REST clients
|
||||
spotClient *binance.Client
|
||||
futuresClient *futures.Client
|
||||
}
|
||||
|
||||
func NewMarketData() *MarketData {
|
||||
log.Info().Msg("Start market service")
|
||||
ms := &MarketData{
|
||||
futureMarkPrice: make(map[string]float64),
|
||||
futureFundingRate: make(map[string]float64),
|
||||
futureNextFundingTime: make(map[string]int64),
|
||||
|
||||
spotPrice: make(map[string]float64),
|
||||
spotPairs: make(map[string]bool),
|
||||
futuresPairs: make(map[string]bool),
|
||||
alphaTokens: make(map[string]AlphaTokenInfo),
|
||||
spotClient: binance.NewClient("", ""),
|
||||
futuresClient: futures.NewClient("", ""),
|
||||
}
|
||||
_ = ms.StartFutureWsMarkPrice()
|
||||
_ = ms.StartSpotWsMarkPrice()
|
||||
|
||||
// Initialize Alpha token cache and refresh every hour
|
||||
if err := ms.refreshTradingPairCache(); err != nil {
|
||||
log.Error().Err(err).Msg("Failed initial trading pair cache load")
|
||||
}
|
||||
go ms.pairCacheRefreshLoop()
|
||||
go ms.alphaCacheRefreshLoop()
|
||||
|
||||
return ms
|
||||
|
||||
@@ -1,21 +1,36 @@
|
||||
package market
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/adshao/go-binance/v2"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (ms *MarketData) GetSpotPrice(symbol string) (float64, bool) {
|
||||
ms.mu.RLock()
|
||||
p, ok := ms.spotPrice[symbol]
|
||||
ms.mu.RUnlock()
|
||||
if ok {
|
||||
return p, true
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
prices, err := ms.spotClient.NewListPricesService().Symbol(symbol).Do(ctx)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("symbol", symbol).Msg("Failed to fetch spot price")
|
||||
return ms.getAlphaPrice(symbol)
|
||||
}
|
||||
|
||||
// If not found, check if it's an Alpha token
|
||||
if len(prices) == 0 {
|
||||
return ms.getAlphaPrice(symbol)
|
||||
}
|
||||
|
||||
price, err := strconv.ParseFloat(prices[0].Price, 64)
|
||||
if err != nil || price == 0 {
|
||||
return ms.getAlphaPrice(symbol)
|
||||
}
|
||||
|
||||
return price, true
|
||||
}
|
||||
|
||||
func (ms *MarketData) getAlphaPrice(symbol string) (float64, bool) {
|
||||
if ms.IsAlphaToken(symbol) {
|
||||
if alphaToken, exists := ms.GetAlphaToken(symbol); exists {
|
||||
if price := alphaToken.GetPrice(); price > 0 {
|
||||
@@ -23,32 +38,5 @@ func (ms *MarketData) GetSpotPrice(symbol string) (float64, bool) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (ms *MarketData) StartSpotWsMarkPrice() error {
|
||||
_, _, err := binance.WsAllMarketsStatServe(ms.spotWsAllMarketsStatHandler, ms.spotWsErrHandler) //.WsAllMarkPriceServe(ms.futureWsMarkPriceHandler, ms.futureWsErrHandler)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *MarketData) spotWsAllMarketsStatHandler(event binance.WsAllMarketsStatEvent) {
|
||||
ms.mu.Lock()
|
||||
defer ms.mu.Unlock()
|
||||
for _, priceEvent := range event {
|
||||
price, err := strconv.ParseFloat(priceEvent.LastPrice, 64)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ms.spotPrice[priceEvent.Symbol] = price
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *MarketData) spotWsErrHandler(err error) {
|
||||
log.Debug().Err(err).Msg("Spot Ws Error. Restart socket")
|
||||
_ = ms.StartSpotWsMarkPrice()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
package market
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
func (ms *MarketData) refreshTradingPairCache() 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
|
||||
}
|
||||
|
||||
futuresInfo, err := ms.futuresClient.NewExchangeInfoService().Do(ctx)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to fetch futures exchange info")
|
||||
return err
|
||||
}
|
||||
|
||||
ms.pairCacheMutex.Lock()
|
||||
defer ms.pairCacheMutex.Unlock()
|
||||
|
||||
ms.spotPairs = make(map[string]bool, len(spotInfo.Symbols))
|
||||
for _, s := range spotInfo.Symbols {
|
||||
if s.Status == "TRADING" {
|
||||
ms.spotPairs[s.Symbol] = true
|
||||
}
|
||||
}
|
||||
|
||||
ms.futuresPairs = make(map[string]bool, len(futuresInfo.Symbols))
|
||||
for _, s := range futuresInfo.Symbols {
|
||||
if s.Status == "TRADING" {
|
||||
ms.futuresPairs[s.Symbol] = true
|
||||
}
|
||||
}
|
||||
|
||||
ms.lastPairCacheUpdate = time.Now()
|
||||
log.Info().
|
||||
Int("spot", len(ms.spotPairs)).
|
||||
Int("futures", len(ms.futuresPairs)).
|
||||
Msg("Trading pair cache refreshed")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ms *MarketData) pairCacheRefreshLoop() {
|
||||
ms.refreshTradingPairCache()
|
||||
ticker := time.NewTicker(time.Hour)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
ms.refreshTradingPairCache()
|
||||
}
|
||||
}
|
||||
|
||||
func (ms *MarketData) IsSpotPair(symbol string) bool {
|
||||
ms.pairCacheMutex.RLock()
|
||||
defer ms.pairCacheMutex.RUnlock()
|
||||
return ms.spotPairs[symbol]
|
||||
}
|
||||
|
||||
func (ms *MarketData) IsFuturesPair(symbol string) bool {
|
||||
ms.pairCacheMutex.RLock()
|
||||
defer ms.pairCacheMutex.RUnlock()
|
||||
return ms.futuresPairs[symbol]
|
||||
}
|
||||
|
||||
func (ms *MarketData) RefreshTradingPairCache() error {
|
||||
return ms.refreshTradingPairCache()
|
||||
}
|
||||
@@ -29,8 +29,7 @@ var (
|
||||
)
|
||||
|
||||
func testSym(sym string) bool {
|
||||
_, _, _, test := data.Market.GetFuturePrice(sym)
|
||||
return test
|
||||
return data.Market.IsFuturesPair(sym)
|
||||
}
|
||||
|
||||
func Token2Symbols(token string) []string {
|
||||
|
||||
@@ -13,15 +13,19 @@ func GetTopPrices() ([]string, []float64, []float64) {
|
||||
topPrice := make([]float64, n)
|
||||
topRate := make([]float64, n)
|
||||
|
||||
all, err := data.Market.GetAllPremiumIndex()
|
||||
if err != nil {
|
||||
return topSym, topPrice, topRate
|
||||
}
|
||||
|
||||
for i, sym := range strategy.TopPriceSymbols {
|
||||
price, rate, _, ok := data.Market.GetFuturePrice(sym)
|
||||
p, ok := all[sym]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
topSym[i] = sym
|
||||
topPrice[i] = price
|
||||
topRate[i] = rate
|
||||
|
||||
topPrice[i] = p.MarkPrice
|
||||
topRate[i] = p.FundingRate
|
||||
}
|
||||
return topSym, topPrice, topRate
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ var commandList = []telebot.Command{
|
||||
Text: "fee",
|
||||
Description: "(f) - show top funding fee",
|
||||
},
|
||||
{
|
||||
Text: "refresh",
|
||||
Description: "Refresh trading pair cache",
|
||||
},
|
||||
}
|
||||
|
||||
func setupCommands(b *telebot.Bot) error {
|
||||
@@ -36,6 +40,7 @@ func setupCommands(b *telebot.Bot) error {
|
||||
//info
|
||||
b.Handle("/p", commands.OnGetTopPrices)
|
||||
b.Handle("/fee", commands.OnGetTopFundingFee)
|
||||
b.Handle("/refresh", commands.OnRefreshPairCache)
|
||||
|
||||
//any text
|
||||
b.Handle(telebot.OnText, commands.OnChatHandler)
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"gopkg.in/telebot.v3"
|
||||
"me.thuanle/bbot/internal/configs/key"
|
||||
"me.thuanle/bbot/internal/data"
|
||||
"me.thuanle/bbot/internal/services/controllers"
|
||||
"me.thuanle/bbot/internal/services/tele/chat"
|
||||
"me.thuanle/bbot/internal/services/tele/view"
|
||||
@@ -16,3 +21,14 @@ func OnGetTopFundingFee(context telebot.Context) error {
|
||||
fee, float64s, cds := controllers.GetTopFundingFee()
|
||||
return chat.ReplyMessagePre(context, view.RenderOnGetTopFundingFeeMessage(fee, float64s, cds))
|
||||
}
|
||||
|
||||
func OnRefreshPairCache(context telebot.Context) error {
|
||||
adminID, err := strconv.ParseInt(os.Getenv(key.AdminChatID), 10, 64)
|
||||
if err != nil || adminID == 0 || context.Sender().ID != adminID {
|
||||
return nil
|
||||
}
|
||||
if err := data.Market.RefreshTradingPairCache(); err != nil {
|
||||
return chat.ReplyMessage(context, "Failed to refresh trading pair cache")
|
||||
}
|
||||
return chat.ReplyMessage(context, "Trading pair cache refreshed")
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ package commands
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/text/message"
|
||||
"gopkg.in/telebot.v3"
|
||||
"me.thuanle/bbot/internal/configs/tele"
|
||||
"me.thuanle/bbot/internal/data"
|
||||
@@ -13,8 +11,6 @@ import (
|
||||
"me.thuanle/bbot/internal/services/tele/view"
|
||||
)
|
||||
|
||||
var lastEthPrice float64
|
||||
|
||||
func showStickerMode(context telebot.Context, token string) {
|
||||
token = strings.ToUpper(token)
|
||||
stickerIdx, ok := tele.Token2StickerIdxMap[token]
|
||||
@@ -60,53 +56,6 @@ func OnTokenInfoByToken(context telebot.Context, token string) error {
|
||||
sp, _ := data.Market.GetSpotPrice(sSymbol)
|
||||
|
||||
_ = chat.ReplyMessage(context, view.RenderOnPriceMessage(symbols[0], sp, fp, fundRate, fundTime, tokenInterestRate))
|
||||
if strings.ToUpper(token) == "ETH" {
|
||||
mFmt := message.NewPrinter(language.AmericanEnglish)
|
||||
realAmount := 35.
|
||||
trangBucAmount := 14.
|
||||
basePrice := 2500.0
|
||||
baseTotal := realAmount * basePrice
|
||||
trangBucTotal := trangBucAmount * basePrice
|
||||
realCurTotal := realAmount * sp
|
||||
trangBucCurTotal := trangBucAmount * sp
|
||||
|
||||
lastDelta := ""
|
||||
if lastEthPrice == 0 {
|
||||
lastEthPrice = sp
|
||||
} else {
|
||||
lastDelta = mFmt.Sprintf(
|
||||
"Δ price: $%+.0f\n"+
|
||||
"Δ Usdt: $%+.0f\n",
|
||||
sp-lastEthPrice,
|
||||
(sp-lastEthPrice)*realAmount,
|
||||
)
|
||||
lastEthPrice = sp
|
||||
}
|
||||
|
||||
msg := mFmt.Sprintf(
|
||||
"🎉🎊🎊🦈🦈🦈 @th13vn Real 🦈🦈🦈🎊🎊🎉\n"+
|
||||
"∑ USDT: $%.0f\n"+
|
||||
"Lợi nhuận: $%.0f\n"+
|
||||
"%s\n"+
|
||||
"\n"+
|
||||
"🚀🚀🚀🚀🚀 Road to 5k 🚀🚀🚀🚀🚀: \n"+
|
||||
"- Δ Price: $%0.0f\n"+
|
||||
"- Δ Vol: $%0.0f\n"+
|
||||
"\n"+
|
||||
"💸💸💸💸💸 Trang Bức balance 💸💸💸💸💸\n"+
|
||||
"∑ USDT: $%.0f\n"+
|
||||
"Lợi nhuận: $%.0f\n",
|
||||
realCurTotal,
|
||||
realCurTotal-baseTotal,
|
||||
lastDelta,
|
||||
5000-sp,
|
||||
5000*realAmount-realCurTotal,
|
||||
trangBucCurTotal,
|
||||
trangBucCurTotal-trangBucTotal,
|
||||
)
|
||||
|
||||
_ = chat.ReplyMessage(context, msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user