This commit is contained in:
6
go.mod
6
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
|
||||
)
|
||||
|
||||
14
go.sum
14
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=
|
||||
|
||||
@@ -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": "",
|
||||
|
||||
@@ -5,4 +5,5 @@ type IMarket interface {
|
||||
GetAllFundRate() (map[string]float64, map[string]int64)
|
||||
|
||||
GetSpotPrice(symbol string) (float64, bool)
|
||||
GetMarginInterestRates() map[string]float64
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
65
internal/data/market/margin.go
Normal file
65
internal/data/market/margin.go
Normal file
@@ -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
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -7,5 +7,5 @@ import (
|
||||
|
||||
func RenderSymbolInfo(sym string) string {
|
||||
token := binancex.Symbol2Token(sym)
|
||||
return fmt.Sprintf("<a href=\"https://www.binance.com/futures/%s\">%s</a>", sym, token)
|
||||
return fmt.Sprintf("#%s", token)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user