From 94a11e8d68e75e00fa75fed063f87a042ce578b1 Mon Sep 17 00:00:00 2001 From: thuanle Date: Thu, 24 Oct 2024 10:58:32 +0700 Subject: [PATCH] add margin --- go.mod | 6 ++- go.sum | 14 +++-- internal/configs/binance/asset.go | 10 ---- internal/data/imarket.go | 1 + internal/data/market/future_price.go | 10 ++-- internal/data/market/margin.go | 65 ++++++++++++++++++++++++ internal/data/market/spot_price.go | 8 +-- internal/services/tele/commands/token.go | 4 +- internal/services/tele/view/price.go | 8 +-- internal/services/tele/view/symbol.go | 2 +- 10 files changed, 98 insertions(+), 30 deletions(-) create mode 100644 internal/data/market/margin.go diff --git a/go.mod b/go.mod index 9d1377a..4171455 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,12 @@ go 1.22 require ( github.com/adshao/go-binance/v2 v2.5.0 + github.com/go-resty/resty/v2 v2.15.3 github.com/jedib0t/go-pretty/v6 v6.5.5 github.com/joho/godotenv v1.5.1 github.com/rs/zerolog v1.32.0 github.com/samber/lo v1.39.0 - golang.org/x/text v0.14.0 + golang.org/x/text v0.16.0 gopkg.in/telebot.v3 v3.2.1 ) @@ -23,5 +24,6 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/rivo/uniseg v0.2.0 // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect - golang.org/x/sys v0.18.0 // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/sys v0.22.0 // indirect ) diff --git a/go.sum b/go.sum index 68caf20..6843da1 100644 --- a/go.sum +++ b/go.sum @@ -135,6 +135,8 @@ github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvSc github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-resty/resty/v2 v2.15.3 h1:bqff+hcqAflpiF591hhJzNdkRsFhlB96CYfBwSFvql8= +github.com/go-resty/resty/v2 v2.15.3/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -507,6 +509,8 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -620,8 +624,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.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.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= @@ -633,11 +637,13 @@ 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.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 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= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/internal/configs/binance/asset.go b/internal/configs/binance/asset.go index 60208b6..eb31a26 100644 --- a/internal/configs/binance/asset.go +++ b/internal/configs/binance/asset.go @@ -1,15 +1,5 @@ package binance -const ( - AssetUsdt = "USDT" - AssetUsdc = "USDC" - AssetFdusd = "FDUSD" - AssetBtc = "BTC" - AssetBnb = "BNB" - AssetEth = "ETH" - AssetXrp = "XRP" -) - var SymbolPrefixList = []string{"1000"} var SymbolSuffixMap = map[string]string{ "USDT": "", diff --git a/internal/data/imarket.go b/internal/data/imarket.go index 52519c3..d0fa699 100644 --- a/internal/data/imarket.go +++ b/internal/data/imarket.go @@ -5,4 +5,5 @@ type IMarket interface { GetAllFundRate() (map[string]float64, map[string]int64) GetSpotPrice(symbol string) (float64, bool) + GetMarginInterestRates() map[string]float64 } diff --git a/internal/data/market/future_price.go b/internal/data/market/future_price.go index 46851a0..67aec61 100644 --- a/internal/data/market/future_price.go +++ b/internal/data/market/future_price.go @@ -6,7 +6,7 @@ import ( "strconv" ) -func (ms MarketData) GetFuturePrice(symbol string) (float64, float64, int64, bool) { +func (ms *MarketData) GetFuturePrice(symbol string) (float64, float64, int64, bool) { p, ok := ms.futureMarkPrice[symbol] if !ok { return 0, 0, 0, false @@ -14,7 +14,7 @@ func (ms MarketData) GetFuturePrice(symbol string) (float64, float64, int64, boo return p, ms.futureFundingRate[symbol], ms.futureNextFundingTime[symbol], true } -func (ms MarketData) StartFutureWsMarkPrice() error { +func (ms *MarketData) StartFutureWsMarkPrice() error { _, _, err := futures.WsAllMarkPriceServe(ms.futureWsMarkPriceHandler, ms.futureWsErrHandler) if err != nil { return err @@ -22,7 +22,7 @@ func (ms MarketData) StartFutureWsMarkPrice() error { return nil } -func (ms MarketData) futureWsMarkPriceHandler(event futures.WsAllMarkPriceEvent) { +func (ms *MarketData) futureWsMarkPriceHandler(event futures.WsAllMarkPriceEvent) { for _, priceEvent := range event { price, err := strconv.ParseFloat(priceEvent.MarkPrice, 64) if err != nil { @@ -40,11 +40,11 @@ func (ms MarketData) futureWsMarkPriceHandler(event futures.WsAllMarkPriceEvent) } } -func (ms MarketData) futureWsErrHandler(err error) { +func (ms *MarketData) futureWsErrHandler(err error) { log.Debug().Err(err).Msg("Ws Error. Restart socket") _ = ms.StartFutureWsMarkPrice() } -func (ms MarketData) GetAllFundRate() (map[string]float64, map[string]int64) { +func (ms *MarketData) GetAllFundRate() (map[string]float64, map[string]int64) { return ms.futureFundingRate, ms.futureNextFundingTime } diff --git a/internal/data/market/margin.go b/internal/data/market/margin.go new file mode 100644 index 0000000..e09e3b1 --- /dev/null +++ b/internal/data/market/margin.go @@ -0,0 +1,65 @@ +package market + +import ( + "github.com/go-resty/resty/v2" + "github.com/rs/zerolog/log" + "me.thuanle/bbot/internal/utils/convertx" +) + +// Structs to parse the JSON response +type MarginInterestResponse struct { + Code string `json:"code"` + Message string `json:"message"` + Data []Asset `json:"data"` +} + +type Asset struct { + AssetName string `json:"assetName"` + Specs []Spec `json:"specs"` +} + +type Spec struct { + VipLevel string `json:"vipLevel"` + DailyInterestRate string `json:"dailyInterestRate"` +} + +func (ms *MarketData) GetMarginInterestRates() map[string]float64 { + data := fetchMarginInterestRate() + + // Map to store the interest rates for the requested tokens + rates := make(map[string]float64) + + // Iterate over the fetched data and match tokens with VIP Level 0 interest rates + for _, asset := range data { + for _, spec := range asset.Specs { + if spec.VipLevel == "0" { + rateDaily := convertx.String2Float64(spec.DailyInterestRate, 0) + rates[asset.AssetName] = rateDaily + } + } + } + return rates +} + +func fetchMarginInterestRate() []Asset { + url := "https://www.binance.com/bapi/margin/v1/friendly/margin/vip/spec/list-all" + + // Create a new Resty client + client := resty.New() + + // Send the GET request to the API + var response MarginInterestResponse + + resp, err := client.R(). + SetResult(&response). + Get(url) + + if err != nil { + log.Err(err). + Str("status", resp.Status()). + Msg("Failed to fetch margin interest rates") + return nil + } + + return response.Data +} diff --git a/internal/data/market/spot_price.go b/internal/data/market/spot_price.go index 9cacca6..cf51795 100644 --- a/internal/data/market/spot_price.go +++ b/internal/data/market/spot_price.go @@ -6,12 +6,12 @@ import ( "strconv" ) -func (ms MarketData) GetSpotPrice(symbol string) (float64, bool) { +func (ms *MarketData) GetSpotPrice(symbol string) (float64, bool) { p, ok := ms.spotPrice[symbol] return p, ok } -func (ms MarketData) StartSpotWsMarkPrice() error { +func (ms *MarketData) StartSpotWsMarkPrice() error { _, _, err := binance.WsAllMarketsStatServe(ms.spotWsAllMarketsStatHandler, ms.spotWsErrHandler) //.WsAllMarkPriceServe(ms.futureWsMarkPriceHandler, ms.futureWsErrHandler) if err != nil { @@ -20,7 +20,7 @@ func (ms MarketData) StartSpotWsMarkPrice() error { return nil } -func (ms MarketData) spotWsAllMarketsStatHandler(event binance.WsAllMarketsStatEvent) { +func (ms *MarketData) spotWsAllMarketsStatHandler(event binance.WsAllMarketsStatEvent) { for _, priceEvent := range event { price, err := strconv.ParseFloat(priceEvent.LastPrice, 64) if err != nil { @@ -30,7 +30,7 @@ func (ms MarketData) spotWsAllMarketsStatHandler(event binance.WsAllMarketsStatE } } -func (ms MarketData) spotWsErrHandler(err error) { +func (ms *MarketData) spotWsErrHandler(err error) { log.Debug().Err(err).Msg("Spot Ws Error. Restart socket") _ = ms.StartSpotWsMarkPrice() } diff --git a/internal/services/tele/commands/token.go b/internal/services/tele/commands/token.go index 2ac265f..52518ac 100644 --- a/internal/services/tele/commands/token.go +++ b/internal/services/tele/commands/token.go @@ -31,13 +31,15 @@ func OnTokenInfoByToken(context telebot.Context, token string) error { showStickerMode(context, token) fp, fundRate, fundTime, ok := data.Market.GetFuturePrice(symbols[0]) + marginRates := data.Market.GetMarginInterestRates() + tokenInterestRate := marginRates[strings.ToUpper(token)] if !ok { return nil } sSymbol := binancex.Future2SpotSymbol(symbols[0]) sp, _ := data.Market.GetSpotPrice(sSymbol) - _ = chat.ReplyMessage(context, view.RenderOnPriceMessage(symbols[0], sp, fp, fundRate, fundTime)) + _ = chat.ReplyMessage(context, view.RenderOnPriceMessage(symbols[0], sp, fp, fundRate, fundTime, tokenInterestRate)) return nil } diff --git a/internal/services/tele/view/price.go b/internal/services/tele/view/price.go index 279b3b6..afe7f64 100644 --- a/internal/services/tele/view/price.go +++ b/internal/services/tele/view/price.go @@ -35,19 +35,21 @@ func RenderPrice(price float64) string { } } -func RenderOnPriceMessage(symbol string, spotPrice float64, futurePrice float64, fundrate float64, fundtime int64) string { +func RenderOnPriceMessage(symbol string, spotPrice float64, futurePrice float64, fundrate float64, fundtime int64, marginRate float64) string { return fmt.Sprintf( "%s: %s\n"+ "Fund rate: %.4f%% %s in %s\n"+ - "Spot: %s", + "Spot: %s\n"+ + "Margin rate: %.3f%%", RenderSymbolInfo(symbol), RenderPrice(futurePrice), fundrate*100, IconOfFundingFeeDirection(fundrate), timex.CdMinuteStringTime(time.UnixMilli(fundtime)), RenderPrice(spotPrice), + marginRate*365, ) } func RenderOnGetTopPricesMessage(symbols []string, price []float64, fundRate []float64) string { - t := helper.NewNoBorderTableWriter("", "Price", "Rate") + t := helper.NewNoBorderTableWriter("", "Price", "Fund") mFmt := message.NewPrinter(language.AmericanEnglish) for i, symbol := range symbols { t.AppendRow(table.Row{ diff --git a/internal/services/tele/view/symbol.go b/internal/services/tele/view/symbol.go index 8aa3753..5f1bb05 100644 --- a/internal/services/tele/view/symbol.go +++ b/internal/services/tele/view/symbol.go @@ -7,5 +7,5 @@ import ( func RenderSymbolInfo(sym string) string { token := binancex.Symbol2Token(sym) - return fmt.Sprintf("%s", sym, token) + return fmt.Sprintf("#%s", token) }