diff --git a/common/convert/convert.go b/common/convert/convert.go index 42a49104..402d5fcd 100644 --- a/common/convert/convert.go +++ b/common/convert/convert.go @@ -1,6 +1,8 @@ package convert import ( + "bytes" + "errors" "fmt" "math" "strconv" @@ -10,6 +12,10 @@ import ( "github.com/shopspring/decimal" ) +const jsonStringIdent = `"` // immutable byte sequence + +var errUnhandledType = errors.New("unhandled type") + // FloatFromString format func FloatFromString(raw interface{}) (float64, error) { str, ok := raw.(string) @@ -187,3 +193,36 @@ func InterfaceToStringOrZeroValue(r interface{}) string { } return "" } + +// StringToFloat64 is a float64 that unmarshals from a string. This is useful +// for APIs that return numbers as strings and return an empty string instead of +// 0. +type StringToFloat64 float64 + +// UnmarshalJSON implements the json.Unmarshaler interface. +// This implementation is slightly more performant than calling json.Unmarshal +// again. +func (f *StringToFloat64) UnmarshalJSON(data []byte) error { + if !bytes.HasPrefix(data, []byte(jsonStringIdent)) { + return fmt.Errorf("%w: %s", errUnhandledType, string(data)) + } + + data = data[1 : len(data)-1] // Remove quotes + if len(data) == 0 { + *f = StringToFloat64(0) + return nil + } + + val, err := strconv.ParseFloat(string(data), 64) + if err != nil { + return err + } + + *f = StringToFloat64(val) + return nil +} + +// Float64 returns the float64 value of the FloatString. +func (f *StringToFloat64) Float64() float64 { + return float64(*f) +} diff --git a/common/convert/convert_test.go b/common/convert/convert_test.go index 1fdd6f1c..15fd0d0a 100644 --- a/common/convert/convert_test.go +++ b/common/convert/convert_test.go @@ -1,6 +1,8 @@ package convert import ( + "encoding/json" + "errors" "strings" "testing" "time" @@ -315,3 +317,54 @@ func TestInterfaceToStringOrZeroValue(t *testing.T) { t.Errorf("expected meow, got: %v", x) } } + +func TestStringToFloat64(t *testing.T) { + t.Parallel() + resp := struct { + Data StringToFloat64 `json:"data"` + }{} + + err := json.Unmarshal([]byte(`{"data":"0.00000001"}`), &resp) + if err != nil { + t.Fatal(err) + } + + if resp.Data.Float64() != 1e-8 { + t.Fatalf("expected 1e-8, got %v", resp.Data.Float64()) + } + + err = json.Unmarshal([]byte(`{"data":""}`), &resp) + if err != nil { + t.Fatal(err) + } + + err = json.Unmarshal([]byte(`{"data":1337.37}`), &resp) + if !errors.Is(err, errUnhandledType) { + t.Fatalf("received %v but expected %v", err, errUnhandledType) + } + + // Demonstrates that a suffix check is not needed. + err = json.Unmarshal([]byte(`{"data":"1337.37}`), &resp) + if err == nil { + t.Fatal("error cannot be nil") + } + + err = json.Unmarshal([]byte(`{"data":"MEOW"}`), &resp) + if err == nil { + t.Fatal("error cannot be nil") + } +} + +// 2677173 428.9 ns/op 240 B/op 5 allocs/op +func BenchmarkStringToFloat64(b *testing.B) { + resp := struct { + Data StringToFloat64 `json:"data"` + }{} + + for i := 0; i < b.N; i++ { + err := json.Unmarshal([]byte(`{"data":"0.00000001"}`), &resp) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index bf010308..3fb3987f 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -1214,8 +1214,8 @@ func (b *Binance) FetchSpotExchangeLimits(ctx context.Context) ([]order.MinMaxLe l.MultiplierDown = f.MultiplierDown l.AveragePriceMinutes = f.AvgPriceMinutes case lotSizeFilter: - l.MaxAmount = f.MaxQty - l.MinAmount = f.MinQty + l.MaximumBaseAmount = f.MaxQty + l.MinimumBaseAmount = f.MinQty l.AmountStepIncrementSize = f.StepSize case notionalFilter: l.MinNotional = f.MinNotional diff --git a/exchanges/binance/binance_cfutures.go b/exchanges/binance/binance_cfutures.go index 4f9be8a5..72551c86 100644 --- a/exchanges/binance/binance_cfutures.go +++ b/exchanges/binance/binance_cfutures.go @@ -1474,8 +1474,8 @@ func (b *Binance) FetchCoinMarginExchangeLimits(ctx context.Context) ([]order.Mi MinPrice: coinFutures.Symbols[x].Filters[0].MinPrice, MaxPrice: coinFutures.Symbols[x].Filters[0].MaxPrice, PriceStepIncrementSize: coinFutures.Symbols[x].Filters[0].TickSize, - MaxAmount: coinFutures.Symbols[x].Filters[1].MaxQty, - MinAmount: coinFutures.Symbols[x].Filters[1].MinQty, + MaximumBaseAmount: coinFutures.Symbols[x].Filters[1].MaxQty, + MinimumBaseAmount: coinFutures.Symbols[x].Filters[1].MinQty, AmountStepIncrementSize: coinFutures.Symbols[x].Filters[1].StepSize, MarketMinQty: coinFutures.Symbols[x].Filters[2].MinQty, MarketMaxQty: coinFutures.Symbols[x].Filters[2].MaxQty, diff --git a/exchanges/binance/binance_test.go b/exchanges/binance/binance_test.go index a96e102f..69206421 100644 --- a/exchanges/binance/binance_test.go +++ b/exchanges/binance/binance_test.go @@ -2771,11 +2771,11 @@ func TestUpdateOrderExecutionLimits(t *testing.T) { if limits.PriceStepIncrementSize == 0 { t.Errorf("Binance UpdateOrderExecutionLimits empty PriceStepIncrementSize; Asset: %s, Pair: %s, Got: %v", a, p, limits.PriceStepIncrementSize) } - if limits.MinAmount == 0 { - t.Errorf("Binance UpdateOrderExecutionLimits empty MinAmount; Asset: %s, Pair: %s, Got: %v", a, p, limits.MinAmount) + if limits.MinimumBaseAmount == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty MinAmount; Asset: %s, Pair: %s, Got: %v", a, p, limits.MinimumBaseAmount) } - if limits.MaxAmount == 0 { - t.Errorf("Binance UpdateOrderExecutionLimits empty MaxAmount; Asset: %s, Pair: %s, Got: %v", a, p, limits.MaxAmount) + if limits.MaximumBaseAmount == 0 { + t.Errorf("Binance UpdateOrderExecutionLimits empty MaxAmount; Asset: %s, Pair: %s, Got: %v", a, p, limits.MaximumBaseAmount) } if limits.AmountStepIncrementSize == 0 { t.Errorf("Binance UpdateOrderExecutionLimits empty AmountStepIncrementSize; Asset: %s, Pair: %s, Got: %v", a, p, limits.AmountStepIncrementSize) diff --git a/exchanges/binance/binance_ufutures.go b/exchanges/binance/binance_ufutures.go index 09a8dfca..1a95c6e6 100644 --- a/exchanges/binance/binance_ufutures.go +++ b/exchanges/binance/binance_ufutures.go @@ -1145,8 +1145,8 @@ func (b *Binance) FetchUSDTMarginExchangeLimits(ctx context.Context) ([]order.Mi MinPrice: usdtFutures.Symbols[x].Filters[0].MinPrice, MaxPrice: usdtFutures.Symbols[x].Filters[0].MaxPrice, PriceStepIncrementSize: usdtFutures.Symbols[x].Filters[0].TickSize, - MaxAmount: usdtFutures.Symbols[x].Filters[1].MaxQty, - MinAmount: usdtFutures.Symbols[x].Filters[1].MinQty, + MaximumBaseAmount: usdtFutures.Symbols[x].Filters[1].MaxQty, + MinimumBaseAmount: usdtFutures.Symbols[x].Filters[1].MinQty, AmountStepIncrementSize: usdtFutures.Symbols[x].Filters[1].StepSize, MarketMinQty: usdtFutures.Symbols[x].Filters[2].MinQty, MarketMaxQty: usdtFutures.Symbols[x].Filters[2].MaxQty, diff --git a/exchanges/bithumb/bithumb.go b/exchanges/bithumb/bithumb.go index 3759601b..a9c7e59b 100644 --- a/exchanges/bithumb/bithumb.go +++ b/exchanges/bithumb/bithumb.go @@ -696,9 +696,9 @@ func (b *Bithumb) FetchExchangeLimits(ctx context.Context) ([]order.MinMaxLevel, } limits = append(limits, order.MinMaxLevel{ - Pair: cp, - Asset: asset.Spot, - MinAmount: getAmountMinimum(data.ClosingPrice), + Pair: cp, + Asset: asset.Spot, + MinimumBaseAmount: getAmountMinimum(data.ClosingPrice), }) } return limits, nil diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index 487d6dc8..dfc3e57c 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -1125,8 +1125,8 @@ func (b *BTCMarkets) UpdateOrderExecutionLimits(ctx context.Context, a asset.Ite limits[x] = order.MinMaxLevel{ Pair: pair, Asset: asset.Spot, - MinAmount: markets[x].MinOrderAmount, - MaxAmount: markets[x].MaxOrderAmount, + MinimumBaseAmount: markets[x].MinOrderAmount, + MaximumBaseAmount: markets[x].MaxOrderAmount, AmountStepIncrementSize: math.Pow(10, -markets[x].AmountDecimals), PriceStepIncrementSize: math.Pow(10, -markets[x].PriceDecimals), } diff --git a/exchanges/bybit/bybit.go b/exchanges/bybit/bybit.go index a25e05b7..26d5f28e 100644 --- a/exchanges/bybit/bybit.go +++ b/exchanges/bybit/bybit.go @@ -14,6 +14,7 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/common/crypto" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" @@ -42,6 +43,7 @@ const ( bybit24HrsChange = "/spot/quote/v1/ticker/24hr" bybitLastTradedPrice = "/spot/quote/v1/ticker/price" bybitBestBidAskPrice = "/spot/quote/v1/ticker/book_ticker" + bybitGetTickersV5 = "/v5/market/tickers" // Authenticated endpoints bybitSpotOrder = "/spot/v1/order" // create, query, cancel @@ -53,12 +55,18 @@ const ( bybitTradeHistory = "/spot/v1/myTrades" bybitWalletBalance = "/spot/v1/account" bybitServerTime = "/spot/v1/time" + bybitAccountFee = "/v5/account/fee-rate" // Account asset endpoint bybitGetDepositAddress = "/asset/v1/private/deposit/address" bybitWithdrawFund = "/asset/v1/private/withdraw" ) +var ( + errCategoryNotSet = errors.New("category not set") + errBaseNotSet = errors.New("base coin not set when category is option") +) + // GetAllSpotPairs gets all pairs on the exchange func (by *Bybit) GetAllSpotPairs(ctx context.Context) ([]PairData, error) { resp := struct { @@ -153,10 +161,10 @@ func (by *Bybit) GetMergedOrderBook(ctx context.Context, symbol string, scale, d func (by *Bybit) GetTrades(ctx context.Context, symbol string, limit int64) ([]TradeItem, error) { resp := struct { Data []struct { - Price float64 `json:"price,string"` - Time bybitTimeMilliSec `json:"time"` - Quantity float64 `json:"qty,string"` - IsBuyerMaker bool `json:"isBuyerMaker"` + Price convert.StringToFloat64 `json:"price"` + Time bybitTimeMilliSec `json:"time"` + Quantity convert.StringToFloat64 `json:"qty"` + IsBuyerMaker bool `json:"isBuyerMaker"` } `json:"result"` Error }{} @@ -186,9 +194,9 @@ func (by *Bybit) GetTrades(ctx context.Context, symbol string, limit int64) ([]T trades[x] = TradeItem{ CurrencyPair: symbol, - Price: resp.Data[x].Price, + Price: resp.Data[x].Price.Float64(), Side: tradeSide, - Volume: resp.Data[x].Quantity, + Volume: resp.Data[x].Quantity.Float64(), Time: resp.Data[x].Time.Time(), } } @@ -314,23 +322,9 @@ func (by *Bybit) GetKlines(ctx context.Context, symbol, period string, limit int // Get24HrsChange returns price change statistics for the last 24 hours // If symbol not passed then it will return price change statistics for all pairs func (by *Bybit) Get24HrsChange(ctx context.Context, symbol string) ([]PriceChangeStats, error) { - type priceChangeStats struct { - Time bybitTimeMilliSec `json:"time"` - Symbol string `json:"symbol"` - BestBidPrice float64 `json:"bestBidPrice,string"` - BestAskPrice float64 `json:"bestAskPrice,string"` - LastPrice float64 `json:"lastPrice,string"` - OpenPrice float64 `json:"openPrice,string"` - HighPrice float64 `json:"highPrice,string"` - LowPrice float64 `json:"lowPrice,string"` - Volume float64 `json:"volume,string"` - QuoteVolume float64 `json:"quoteVolume,string"` - } - - var stats []PriceChangeStats if symbol != "" { resp := struct { - Data priceChangeStats `json:"result"` + Data PriceChangeStats `json:"result"` Error }{} @@ -341,46 +335,20 @@ func (by *Bybit) Get24HrsChange(ctx context.Context, symbol string) ([]PriceChan if err != nil { return nil, err } - - stats = append(stats, PriceChangeStats{ - resp.Data.Time.Time(), - resp.Data.Symbol, - resp.Data.BestAskPrice, - resp.Data.BestAskPrice, - resp.Data.LastPrice, - resp.Data.OpenPrice, - resp.Data.HighPrice, - resp.Data.LowPrice, - resp.Data.Volume, - resp.Data.QuoteVolume, - }) - } else { - resp := struct { - Data []priceChangeStats `json:"result"` - Error - }{} - - err := by.SendHTTPRequest(ctx, exchange.RestSpot, bybit24HrsChange, publicSpotRate, &resp) - if err != nil { - return nil, err - } - - for x := range resp.Data { - stats = append(stats, PriceChangeStats{ - resp.Data[x].Time.Time(), - resp.Data[x].Symbol, - resp.Data[x].BestAskPrice, - resp.Data[x].BestAskPrice, - resp.Data[x].LastPrice, - resp.Data[x].OpenPrice, - resp.Data[x].HighPrice, - resp.Data[x].LowPrice, - resp.Data[x].Volume, - resp.Data[x].QuoteVolume, - }) - } + return []PriceChangeStats{resp.Data}, nil } - return stats, nil + + resp := struct { + Data []PriceChangeStats `json:"result"` + Error + }{} + + err := by.SendHTTPRequest(ctx, exchange.RestSpot, bybit24HrsChange, publicSpotRate, &resp) + if err != nil { + return nil, err + } + + return resp.Data, nil } // GetLastTradedPrice returns last trading price @@ -427,19 +395,9 @@ func (by *Bybit) GetLastTradedPrice(ctx context.Context, symbol string) ([]LastT // GetBestBidAskPrice returns best BID and ASK price // If symbol not passed then it will return best BID and ASK price for all pairs func (by *Bybit) GetBestBidAskPrice(ctx context.Context, symbol string) ([]TickerData, error) { - type bestTicker struct { - Symbol string `json:"symbol"` - BidPrice float64 `json:"bidPrice,string"` - BidQuantity float64 `json:"bidQty,string"` - AskPrice float64 `json:"askPrice,string"` - AskQuantity float64 `json:"askQty,string"` - Time bybitTimeMilliSec `json:"time"` - } - - var tickers []TickerData if symbol != "" { resp := struct { - Data bestTicker `json:"result"` + Data TickerData `json:"result"` Error }{} @@ -450,36 +408,55 @@ func (by *Bybit) GetBestBidAskPrice(ctx context.Context, symbol string) ([]Ticke if err != nil { return nil, err } - tickers = append(tickers, TickerData{ - resp.Data.Symbol, - resp.Data.BidPrice, - resp.Data.BidQuantity, - resp.Data.AskPrice, - resp.Data.AskQuantity, - resp.Data.Time.Time(), - }) - } else { - resp := struct { - Data []bestTicker `json:"result"` - Error - }{} - - err := by.SendHTTPRequest(ctx, exchange.RestSpot, bybitBestBidAskPrice, publicSpotRate, &resp) - if err != nil { - return nil, err - } - for x := range resp.Data { - tickers = append(tickers, TickerData{ - resp.Data[x].Symbol, - resp.Data[x].BidPrice, - resp.Data[x].BidQuantity, - resp.Data[x].AskPrice, - resp.Data[x].AskQuantity, - resp.Data[x].Time.Time(), - }) - } + return []TickerData{resp.Data}, nil } - return tickers, nil + + resp := struct { + Data []TickerData `json:"result"` + Error + }{} + + err := by.SendHTTPRequest(ctx, exchange.RestSpot, bybitBestBidAskPrice, publicSpotRate, &resp) + if err != nil { + return nil, err + } + + return resp.Data, nil +} + +// GetTickersV5 returns tickers for either "spot", "option" or "inverse". +// Specific symbol is optional. +func (by *Bybit) GetTickersV5(ctx context.Context, category, symbol, baseCoin string) (*ListOfTickers, error) { + if category == "" { + return nil, errCategoryNotSet + } + + if category == "option" && baseCoin == "" { + return nil, errBaseNotSet + } + + val := url.Values{} + val.Set("category", category) + + if symbol != "" { + val.Set("symbol", symbol) + } + + if baseCoin != "" { + val.Set("baseCoin", baseCoin) + } + + result := struct { + Data *ListOfTickers `json:"result"` + Error + }{} + + err := by.SendHTTPRequest(ctx, exchange.RestSpot, bybitGetTickersV5+"?"+val.Encode(), publicSpotRate, &result) + if err != nil { + return nil, err + } + + return result.Data, nil } // CreatePostOrder create and post order @@ -789,6 +766,42 @@ func (by *Bybit) WithdrawFund(ctx context.Context, coin, chain, address, tag, am return resp.Data.ID, by.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, bybitWithdrawFund, nil, params, &resp, privateSpotRate) } +// GetFeeRate returns user account fee +// Valid category: "spot", "linear", "inverse", "option" +func (by *Bybit) GetFeeRate(ctx context.Context, category, symbol, baseCoin string) (*AccountFee, error) { + if category == "" { + return nil, errCategoryNotSet + } + + if !common.StringDataContains(validCategory, category) { + // NOTE: Opted to fail here because if the user passes in an invalid + // category the error returned is this + // `Bybit raw response: {"retCode":10005,"retMsg":"Permission denied, please check your API key permissions.","result":{},"retExtInfo":{},"time":1683694010783}` + return nil, fmt.Errorf("%w, valid category values are %v", errInvalidCategory, validCategory) + } + + params := url.Values{} + params.Set("category", category) + if symbol != "" { + params.Set("symbol", symbol) + } + if baseCoin != "" { + params.Set("baseCoin", baseCoin) + } + + result := struct { + Data *AccountFee `json:"result"` + Error + }{} + + err := by.SendAuthHTTPRequestV5(ctx, exchange.RestSpot, http.MethodGet, bybitAccountFee, params, &result, privateFeeRate) + if err != nil { + return nil, err + } + + return result.Data, nil +} + // SendHTTPRequest sends an unauthenticated request func (by *Bybit) SendHTTPRequest(ctx context.Context, ePath exchange.URL, path string, f request.EndpointLimit, result UnmarshalTo) error { endpointPath, err := by.API.Endpoints.GetURL(ePath) @@ -842,8 +855,8 @@ func (by *Bybit) SendAuthHTTPRequest(ctx context.Context, ePath exchange.URL, me var ( payload []byte hmacSignedStr string + headers = make(map[string]string) ) - headers := make(map[string]string) if jsonPayload != nil { headers["Content-Type"] = "application/json" @@ -893,19 +906,71 @@ func (by *Bybit) SendAuthHTTPRequest(ctx context.Context, ePath exchange.URL, me return result.GetError() } +// SendAuthHTTPRequestV5 sends an authenticated HTTP request +func (by *Bybit) SendAuthHTTPRequestV5(ctx context.Context, ePath exchange.URL, method, path string, params url.Values, result UnmarshalTo, f request.EndpointLimit) error { + creds, err := by.GetCredentials(ctx) + if err != nil { + return err + } + + if result == nil { + result = &Error{} + } + + endpointPath, err := by.API.Endpoints.GetURL(ePath) + if err != nil { + return err + } + + err = by.SendPayload(ctx, f, func() (*request.Item, error) { + timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10) + headers := make(map[string]string) + headers["Content-Type"] = "application/x-www-form-urlencoded" + headers["X-BAPI-TIMESTAMP"] = timestamp + headers["X-BAPI-API-KEY"] = creds.Key + headers["X-BAPI-RECV-WINDOW"] = defaultRecvWindow + + var hmacSignedStr string + hmacSignedStr, err = getSign(timestamp+creds.Key+defaultRecvWindow+params.Encode(), creds.Secret) + if err != nil { + return nil, err + } + headers["X-BAPI-SIGN"] = hmacSignedStr + return &request.Item{ + Method: method, + Path: endpointPath + common.EncodeURLValues(path, params), + Headers: headers, + Result: &result, + AuthRequest: true, + Verbose: by.Verbose, + HTTPDebugging: by.HTTPDebugging, + HTTPRecording: by.HTTPRecording}, nil + }) + if err != nil { + return err + } + + return result.GetError() +} + // Error defines all error information for each request type Error struct { - ReturnCode int64 `json:"ret_code"` - ReturnMsg string `json:"ret_msg"` - ExtCode string `json:"ext_code"` - ExtMsg string `json:"ext_info"` + ReturnCode int64 `json:"ret_code"` + ReturnMsg string `json:"ret_msg"` + ReturnCodeV5 int64 `json:"retCode"` + ReturnMessageV5 string `json:"retMsg"` + ExtCode string `json:"ext_code"` + ExtMsg string `json:"ext_info"` } // GetError checks and returns an error if it is supplied. -func (e Error) GetError() error { +func (e *Error) GetError() error { if e.ReturnCode != 0 && e.ReturnMsg != "" { return errors.New(e.ReturnMsg) } + if e.ReturnCodeV5 != 0 && e.ReturnMessageV5 != "" { + return errors.New(e.ReturnMessageV5) + } if e.ExtCode != "" && e.ExtMsg != "" { return errors.New(e.ExtMsg) } diff --git a/exchanges/bybit/bybit_cfutures.go b/exchanges/bybit/bybit_cfutures.go index 270ccad0..2ebc5590 100644 --- a/exchanges/bybit/bybit_cfutures.go +++ b/exchanges/bybit/bybit_cfutures.go @@ -10,6 +10,7 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" @@ -98,12 +99,12 @@ func (by *Bybit) GetFuturesOrderbook(ctx context.Context, symbol currency.Pair) switch data.Result[x].Side { case sideBuy: resp.Bids = append(resp.Bids, orderbook.Item{ - Price: data.Result[x].Price, + Price: data.Result[x].Price.Float64(), Amount: data.Result[x].Size, }) case sideSell: resp.Asks = append(resp.Asks, orderbook.Item{ - Price: data.Result[x].Price, + Price: data.Result[x].Price.Float64(), Amount: data.Result[x].Size, }) default: @@ -393,7 +394,7 @@ func (by *Bybit) GetLastFundingRate(ctx context.Context, symbol currency.Pair) ( // GetFuturesServerTime returns Bybit server time in seconds func (by *Bybit) GetFuturesServerTime(ctx context.Context) (time.Time, error) { resp := struct { - TimeNow float64 `json:"time_now,string"` + TimeNow convert.StringToFloat64 `json:"time_now"` Error }{} @@ -401,7 +402,7 @@ func (by *Bybit) GetFuturesServerTime(ctx context.Context) (time.Time, error) { if err != nil { return time.Time{}, err } - sec, dec := math.Modf(resp.TimeNow) + sec, dec := math.Modf(resp.TimeNow.Float64()) return time.Unix(int64(sec), int64(dec*(1e9))), nil } @@ -1085,24 +1086,21 @@ func (by *Bybit) ChangeCoinMargin(ctx context.Context, symbol currency.Pair, buy } // GetTradingFeeRate returns trading taker and maker fee rate -func (by *Bybit) GetTradingFeeRate(ctx context.Context, symbol currency.Pair) (takerFee, makerFee float64, err error) { +func (by *Bybit) GetTradingFeeRate(ctx context.Context, symbol currency.Pair) (*CFuturesTradingFeeRate, error) { params := url.Values{} resp := struct { - Result struct { - TakerFeeRate float64 `json:"taker_fee_rate,string"` - MakerFeeRate float64 `json:"maker_fee_rate,string"` - } `json:"result"` + Result *CFuturesTradingFeeRate `json:"result"` Error }{} symbolValue, err := by.FormatSymbol(symbol, asset.CoinMarginedFutures) if err != nil { - return resp.Result.TakerFeeRate, resp.Result.MakerFeeRate, err + return nil, err } params.Set("symbol", symbolValue) - takerFee, makerFee, err = resp.Result.TakerFeeRate, resp.Result.MakerFeeRate, by.SendAuthHTTPRequest(ctx, exchange.RestCoinMargined, http.MethodGet, bybitFuturesAPIVersion+cfuturesGetTradingFeeRate, params, nil, &resp, cFuturesGetTradingFeeRate) - return + return resp.Result, + by.SendAuthHTTPRequest(ctx, exchange.RestCoinMargined, http.MethodGet, bybitFuturesAPIVersion+cfuturesGetTradingFeeRate, params, nil, &resp, cFuturesGetTradingFeeRate) } // SetCoinRiskLimit sets risk limit diff --git a/exchanges/bybit/bybit_test.go b/exchanges/bybit/bybit_test.go index 6cb0deae..d52c147a 100644 --- a/exchanges/bybit/bybit_test.go +++ b/exchanges/bybit/bybit_test.go @@ -19,6 +19,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" + "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -60,6 +61,20 @@ func TestMain(m *testing.M) { log.Fatal(err) } + // Turn on all pairs for testing + supportedAssets := b.GetAssetTypes(false) + for x := range supportedAssets { + avail, err := b.GetAvailablePairs(supportedAssets[x]) + if err != nil { + log.Fatal(err) + } + + err = b.CurrencyPairs.StorePairs(supportedAssets[x], avail, true) + if err != nil { + log.Fatal(err) + } + } + os.Exit(m.Run()) } @@ -1055,10 +1070,18 @@ func TestGetTradingFeeRate(t *testing.T) { t.Fatal(err) } - _, _, err = b.GetTradingFeeRate(context.Background(), pair) + feeRate, err := b.GetTradingFeeRate(context.Background(), pair) if err != nil { t.Error(err) } + + if feeRate.MakerFeeRate == 0 && feeRate.TakerFeeRate == 0 { + t.Error("expected fee rate") + } + + if feeRate.UserID == 0 { + t.Error("expected user id") + } } func TestSetCoinRiskLimit(t *testing.T) { @@ -3309,3 +3332,137 @@ func TestGetUSDCPredictedFundingRate(t *testing.T) { t.Error(err) } } + +func TestUpdateTickers(t *testing.T) { + t.Parallel() + supportedAssets := b.GetAssetTypes(false) + ctx := context.Background() + for x := range supportedAssets { + err := b.UpdateTickers(ctx, supportedAssets[x]) + if err != nil { + t.Fatalf("%v %v\n", supportedAssets[x], err) + } + + avail, err := b.GetAvailablePairs(supportedAssets[x]) + if err != nil { + t.Fatalf("%v %v\n", supportedAssets[x], err) + } + + for y := range avail { + _, err = ticker.GetTicker(b.GetName(), avail[y], supportedAssets[x]) + if err != nil { + t.Fatalf("%v %v %v\n", avail[y], supportedAssets[x], err) + } + } + } +} + +func TestGetTickersV5(t *testing.T) { + t.Parallel() + + _, err := b.GetTickersV5(context.Background(), "bruh", "", "") + if err != nil && err.Error() != "Illegal category" { + t.Error(err) + } + + _, err = b.GetTickersV5(context.Background(), "option", "", "") + if !errors.Is(err, errBaseNotSet) { + t.Fatalf("expected: %v, received: %v", errBaseNotSet, err) + } + + _, err = b.GetTickersV5(context.Background(), "spot", "", "") + if err != nil { + t.Error(err) + } + + _, err = b.GetTickersV5(context.Background(), "option", "", "BTC") + if err != nil { + t.Error(err) + } + + _, err = b.GetTickersV5(context.Background(), "inverse", "", "") + if err != nil { + t.Error(err) + } + + _, err = b.GetTickersV5(context.Background(), "linear", "", "") + if err != nil { + t.Error(err) + } +} + +func TestUpdateOrderExecutionLimits(t *testing.T) { + t.Parallel() + + err := b.UpdateOrderExecutionLimits(context.Background(), asset.USDCMarginedFutures) + if !errors.Is(err, asset.ErrNotSupported) { + t.Fatalf("received: %v expected: %v", err, asset.ErrNotSupported) + } + + err = b.UpdateOrderExecutionLimits(context.Background(), asset.Spot) + if err != nil { + t.Error("Okx UpdateOrderExecutionLimits() error", err) + } + + avail, err := b.GetAvailablePairs(asset.Spot) + if err != nil { + t.Fatal("Okx GetAvailablePairs() error", err) + } + + for x := range avail { + limits, err := b.GetOrderExecutionLimits(asset.Spot, avail[x]) + if err != nil { + t.Fatal("Okx GetOrderExecutionLimits() error", err) + } + if limits == (order.MinMaxLevel{}) { + t.Fatal("Okx GetOrderExecutionLimits() error cannot be nil") + } + } +} + +func TestGetFeeRate(t *testing.T) { + t.Parallel() + + _, err := b.GetFeeRate(context.Background(), "", "", "") + if !errors.Is(err, errCategoryNotSet) { + t.Fatalf("received %v but expected %v", err, errCategoryNotSet) + } + + _, err = b.GetFeeRate(context.Background(), "bruh", "", "") + if !errors.Is(err, errInvalidCategory) { + t.Fatalf("received %v but expected %v", err, errInvalidCategory) + } + + sharedtestvalues.SkipTestIfCredentialsUnset(t, b) + + _, err = b.GetFeeRate(context.Background(), "spot", "", "") + if !errors.Is(err, nil) { + t.Errorf("received %v but expected %v", err, nil) + } + + _, err = b.GetFeeRate(context.Background(), "linear", "", "") + if !errors.Is(err, nil) { + t.Errorf("received %v but expected %v", err, nil) + } + + _, err = b.GetFeeRate(context.Background(), "inverse", "", "") + if !errors.Is(err, nil) { + t.Errorf("received %v but expected %v", err, nil) + } + + _, err = b.GetFeeRate(context.Background(), "option", "", "ETH") + if !errors.Is(err, nil) { + t.Errorf("received %v but expected %v", err, nil) + } +} + +func TestForceFileStandard(t *testing.T) { + t.Parallel() + err := sharedtestvalues.ForceFileStandard(t, sharedtestvalues.EmptyStringPotentialPattern) + if err != nil { + t.Error(err) + } + if t.Failed() { + t.Fatal("Please use convert.StringToFloat64 type instead of `float64` and remove `,string` as strings can be empty in unmarshal process. Then call the Float64() method.") + } +} diff --git a/exchanges/bybit/bybit_types.go b/exchanges/bybit/bybit_types.go index 35111af9..2c6cf09c 100644 --- a/exchanges/bybit/bybit_types.go +++ b/exchanges/bybit/bybit_types.go @@ -6,6 +6,7 @@ import ( "strconv" "time" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" ) @@ -44,6 +45,8 @@ var ( errExpectedOneOrder = errors.New("expected one order") ) +var validCategory = []string{"spot", "linear", "inverse", "option"} + // bybitTimeSec provides an internal conversion helper type bybitTimeSec time.Time @@ -149,34 +152,6 @@ func (b bybitTimeNanoSec) Time() time.Time { return time.Time(b) } -// bybitNumericalValue is a type used for when the API returns an empty or -// numerical string -type bybitNumericalValue float64 - -// UnmarshalJSON is custom type json unmarshaller for bybitNumericalValue -func (b *bybitNumericalValue) UnmarshalJSON(data []byte) error { - var num string - err := json.Unmarshal(data, &num) - if err != nil { - return err - } - - if num == "" { - return nil - } - - v, err := strconv.ParseFloat(num, 64) - if err != nil { - return err - } - - *b = bybitNumericalValue(v) - return nil -} - -// Float64 returns a float64 value for bybitNumericalValue -func (b *bybitNumericalValue) Float64() float64 { return float64(*b) } - // UnmarshalTo acts as interface to exchange API response type UnmarshalTo interface { GetError() error @@ -184,19 +159,19 @@ type UnmarshalTo interface { // PairData stores pair data type PairData struct { - Name string `json:"name"` - Alias string `json:"alias"` - BaseCurrency string `json:"baseCurrency"` - QuoteCurrency string `json:"quoteCurrency"` - BasePrecision float64 `json:"basePrecision,string"` - QuotePrecision float64 `json:"quotePrecision,string"` - MinTradeQuantity float64 `json:"minTradeQuantity,string"` - MinTradeAmount float64 `json:"minTradeAmount,string"` - MinPricePrecision float64 `json:"minPricePrecision,string"` - MaxTradeQuantity float64 `json:"maxTradeQuantity,string"` - MaxTradeAmount float64 `json:"maxTradeAmount,string"` - Category int64 `json:"category"` - ShowStatus bool `json:"showStatus"` + Name string `json:"name"` + Alias string `json:"alias"` + BaseCurrency string `json:"baseCurrency"` + QuoteCurrency string `json:"quoteCurrency"` + BasePrecision convert.StringToFloat64 `json:"basePrecision"` + QuotePrecision convert.StringToFloat64 `json:"quotePrecision"` + MinTradeQuantity convert.StringToFloat64 `json:"minTradeQuantity"` + MinTradeAmount convert.StringToFloat64 `json:"minTradeAmount"` + MinPricePrecision convert.StringToFloat64 `json:"minPricePrecision"` + MaxTradeQuantity convert.StringToFloat64 `json:"maxTradeQuantity"` + MaxTradeAmount convert.StringToFloat64 `json:"maxTradeAmount"` + Category int64 `json:"category"` + ShowStatus bool `json:"showStatus"` } // Orderbook stores the orderbook data @@ -233,32 +208,32 @@ type KlineItem struct { // PriceChangeStats contains statistics for the last 24 hours trade type PriceChangeStats struct { - Time time.Time - Symbol string - BestBidPrice float64 - BestAskPrice float64 - LastPrice float64 - OpenPrice float64 - HighPrice float64 - LowPrice float64 - Volume float64 - QuoteVolume float64 + Time bybitTimeMilliSec `json:"time"` + Symbol string `json:"symbol"` + BestBidPrice convert.StringToFloat64 `json:"bestBidPrice"` + BestAskPrice convert.StringToFloat64 `json:"bestAskPrice"` + LastPrice convert.StringToFloat64 `json:"lastPrice"` + OpenPrice convert.StringToFloat64 `json:"openPrice"` + HighPrice convert.StringToFloat64 `json:"highPrice"` + LowPrice convert.StringToFloat64 `json:"lowPrice"` + Volume convert.StringToFloat64 `json:"volume"` + QuoteVolume convert.StringToFloat64 `json:"quoteVolume"` } // LastTradePrice contains price for last trade type LastTradePrice struct { - Symbol string `json:"symbol"` - Price float64 `json:"price,string"` + Symbol string `json:"symbol"` + Price convert.StringToFloat64 `json:"price"` } // TickerData stores ticker data type TickerData struct { - Symbol string - BidPrice float64 - BidQuantity float64 - AskPrice float64 - AskQuantity float64 - Time time.Time + Symbol string `json:"symbol"` + BidPrice convert.StringToFloat64 `json:"bidPrice"` + BidQuantity convert.StringToFloat64 `json:"bidQty"` + AskPrice convert.StringToFloat64 `json:"askPrice"` + AskQuantity convert.StringToFloat64 `json:"askQty"` + Time bybitTimeMilliSec `json:"time"` } var ( @@ -296,97 +271,97 @@ type PlaceOrderRequest struct { // PlaceOrderResponse store new order response type type PlaceOrderResponse struct { - OrderID string `json:"orderId"` - OrderLinkID string `json:"orderLinkId"` - Symbol string `json:"symbol"` - Time bybitTimeMilliSecStr `json:"transactTime"` - Price float64 `json:"price,string"` - Quantity float64 `json:"origQty,string"` - TradeType string `json:"type"` - Side string `json:"side"` - Status string `json:"status"` - TimeInForce string `json:"timeInForce"` - AccountID string `json:"accountId"` - SymbolName string `json:"symbolName"` - ExecutedQty float64 `json:"executedQty,string"` + OrderID string `json:"orderId"` + OrderLinkID string `json:"orderLinkId"` + Symbol string `json:"symbol"` + Time bybitTimeMilliSecStr `json:"transactTime"` + Price convert.StringToFloat64 `json:"price"` + Quantity convert.StringToFloat64 `json:"origQty"` + TradeType string `json:"type"` + Side string `json:"side"` + Status string `json:"status"` + TimeInForce string `json:"timeInForce"` + AccountID string `json:"accountId"` + SymbolName string `json:"symbolName"` + ExecutedQty convert.StringToFloat64 `json:"executedQty"` } // QueryOrderResponse holds query order data type QueryOrderResponse struct { - AccountID string `json:"accountId"` - ExchangeID string `json:"exchangeId"` - Symbol string `json:"symbol"` - SymbolName string `json:"symbolName"` - OrderLinkID string `json:"orderLinkId"` - OrderID string `json:"orderId"` - Price float64 `json:"price,string"` - Quantity float64 `json:"origQty,string"` - ExecutedQty float64 `json:"executedQty,string"` - CummulativeQuoteQty float64 `json:"cummulativeQuoteQty,string"` - AveragePrice float64 `json:"avgPrice,string"` - Status string `json:"status"` - TimeInForce string `json:"timeInForce"` - TradeType string `json:"type"` - Side string `json:"side"` - StopPrice float64 `json:"stopPrice,string"` - IcebergQty float64 `json:"icebergQty,string"` - Time bybitTimeMilliSecStr `json:"time"` - UpdateTime bybitTimeMilliSecStr `json:"updateTime"` - IsWorking bool `json:"isWorking"` + AccountID string `json:"accountId"` + ExchangeID string `json:"exchangeId"` + Symbol string `json:"symbol"` + SymbolName string `json:"symbolName"` + OrderLinkID string `json:"orderLinkId"` + OrderID string `json:"orderId"` + Price convert.StringToFloat64 `json:"price"` + Quantity convert.StringToFloat64 `json:"origQty"` + ExecutedQty convert.StringToFloat64 `json:"executedQty"` + CummulativeQuoteQty convert.StringToFloat64 `json:"cummulativeQuoteQty"` + AveragePrice convert.StringToFloat64 `json:"avgPrice"` + Status string `json:"status"` + TimeInForce string `json:"timeInForce"` + TradeType string `json:"type"` + Side string `json:"side"` + StopPrice convert.StringToFloat64 `json:"stopPrice"` + IcebergQty convert.StringToFloat64 `json:"icebergQty"` + Time bybitTimeMilliSecStr `json:"time"` + UpdateTime bybitTimeMilliSecStr `json:"updateTime"` + IsWorking bool `json:"isWorking"` } // CancelOrderResponse is the return structured response from the exchange type CancelOrderResponse struct { - OrderID string `json:"orderId"` - OrderLinkID string `json:"orderLinkId"` - Symbol string `json:"symbol"` - Status string `json:"status"` - AccountID string `json:"accountId"` - Time bybitTimeMilliSecStr `json:"transactTime"` - Price float64 `json:"price,string"` - Quantity float64 `json:"origQty,string"` - ExecutedQty float64 `json:"executedQty,string"` - TimeInForce string `json:"timeInForce"` - TradeType string `json:"type"` - Side string `json:"side"` + OrderID string `json:"orderId"` + OrderLinkID string `json:"orderLinkId"` + Symbol string `json:"symbol"` + Status string `json:"status"` + AccountID string `json:"accountId"` + Time bybitTimeMilliSecStr `json:"transactTime"` + Price convert.StringToFloat64 `json:"price"` + Quantity convert.StringToFloat64 `json:"origQty"` + ExecutedQty convert.StringToFloat64 `json:"executedQty"` + TimeInForce string `json:"timeInForce"` + TradeType string `json:"type"` + Side string `json:"side"` } // HistoricalTrade holds recent trade data type HistoricalTrade struct { - Symbol string `json:"symbol"` - ID string `json:"id"` - OrderID string `json:"orderId"` - TicketID string `json:"ticketId"` - Price float64 `json:"price,string"` - Quantity float64 `json:"qty,string"` - Commission float64 `json:"commission,string"` - CommissionAsset float64 `json:"commissionAsset,string"` - Time bybitTimeMilliSecStr `json:"time"` - IsBuyer bool `json:"isBuyer"` - IsMaker bool `json:"isMaker"` - SymbolName string `json:"symbolName"` - MatchOrderID string `json:"matchOrderId"` - Fee FeeData `json:"fee"` - FeeTokenID string `json:"feeTokenId"` - FeeAmount float64 `json:"feeAmount,string"` - MakerRebate float64 `json:"makerRebate,string"` + Symbol string `json:"symbol"` + ID string `json:"id"` + OrderID string `json:"orderId"` + TicketID string `json:"ticketId"` + Price convert.StringToFloat64 `json:"price"` + Quantity convert.StringToFloat64 `json:"qty"` + Commission convert.StringToFloat64 `json:"commission"` + CommissionAsset convert.StringToFloat64 `json:"commissionAsset"` + Time bybitTimeMilliSecStr `json:"time"` + IsBuyer bool `json:"isBuyer"` + IsMaker bool `json:"isMaker"` + SymbolName string `json:"symbolName"` + MatchOrderID string `json:"matchOrderId"` + Fee FeeData `json:"fee"` + FeeTokenID string `json:"feeTokenId"` + FeeAmount convert.StringToFloat64 `json:"feeAmount"` + MakerRebate convert.StringToFloat64 `json:"makerRebate"` } // FeeData store fees data type FeeData struct { - FeeTokenID int64 `json:"feeTokenId"` - FeeTokenName string `json:"feeTokenName"` - Fee float64 `json:"fee,string"` + FeeTokenID int64 `json:"feeTokenId"` + FeeTokenName string `json:"feeTokenName"` + Fee convert.StringToFloat64 `json:"fee"` } // Balance holds wallet balance type Balance struct { - Coin string `json:"coin"` - CoinID string `json:"coinId"` - CoinName string `json:"coinName"` - Total float64 `json:"total,string"` - Free float64 `json:"free,string"` - Locked float64 `json:"locked,string"` + Coin string `json:"coin"` + CoinID string `json:"coinId"` + CoinName string `json:"coinName"` + Total convert.StringToFloat64 `json:"total"` + Free convert.StringToFloat64 `json:"free"` + Locked convert.StringToFloat64 `json:"locked"` } type orderbookResponse struct { @@ -443,12 +418,12 @@ type WsParams struct { // WsSpotTickerData stores ws ticker data type WsSpotTickerData struct { - Symbol string `json:"symbol"` - Bid float64 `json:"bidPrice,string"` - Ask float64 `json:"askPrice,string"` - BidSize float64 `json:"bidQty,string"` - AskSize float64 `json:"askQty,string"` - Time bybitTimeMilliSec `json:"time"` + Symbol string `json:"symbol"` + Bid convert.StringToFloat64 `json:"bidPrice"` + Ask convert.StringToFloat64 `json:"askPrice"` + BidSize convert.StringToFloat64 `json:"bidQty"` + AskSize convert.StringToFloat64 `json:"askQty"` + Time bybitTimeMilliSec `json:"time"` } // WsSpotTicker stores ws ticker data @@ -460,13 +435,13 @@ type WsSpotTicker struct { // KlineStreamData stores ws kline stream data type KlineStreamData struct { - StartTime bybitTimeMilliSec `json:"t"` - Symbol string `json:"s"` - ClosePrice float64 `json:"c,string"` - HighPrice float64 `json:"h,string"` - LowPrice float64 `json:"l,string"` - OpenPrice float64 `json:"o,string"` - Volume float64 `json:"v,string"` + StartTime bybitTimeMilliSec `json:"t"` + Symbol string `json:"s"` + ClosePrice convert.StringToFloat64 `json:"c"` + HighPrice convert.StringToFloat64 `json:"h"` + LowPrice convert.StringToFloat64 `json:"l"` + OpenPrice convert.StringToFloat64 `json:"o"` + Volume convert.StringToFloat64 `json:"v"` } // KlineStream holds the kline stream data @@ -494,11 +469,11 @@ type WsOrderbook struct { // WsTradeData stores ws trade data type WsTradeData struct { - Time bybitTimeMilliSec `json:"t"` - ID string `json:"v"` - Price float64 `json:"p,string"` - Size float64 `json:"q,string"` - Side bool `json:"m"` + Time bybitTimeMilliSec `json:"t"` + ID string `json:"v"` + Price convert.StringToFloat64 `json:"p"` + Size convert.StringToFloat64 `json:"q"` + Side bool `json:"m"` } // WsTrade stores ws trades data @@ -520,65 +495,65 @@ type wsAccount struct { // Currencies stores currencies data type Currencies struct { - Asset string `json:"a"` - Available float64 `json:"f,string"` - Locked float64 `json:"l,string"` + Asset string `json:"a"` + Available convert.StringToFloat64 `json:"f"` + Locked convert.StringToFloat64 `json:"l"` } // wsOrderUpdate defines websocket account order update data type wsOrderUpdate struct { - EventType string `json:"e"` - EventTime string `json:"E"` - Symbol string `json:"s"` - ClientOrderID string `json:"c"` - Side string `json:"S"` - OrderType string `json:"o"` - TimeInForce string `json:"f"` - Quantity float64 `json:"q,string"` - Price float64 `json:"p,string"` - OrderStatus string `json:"X"` - OrderID string `json:"i"` - OpponentOrderID string `json:"M"` - LastExecutedQuantity float64 `json:"l,string"` - CumulativeFilledQuantity float64 `json:"z,string"` - LastExecutedPrice float64 `json:"L,string"` - Commission float64 `json:"n,string"` - CommissionAsset string `json:"N"` - IsNormal bool `json:"u"` - IsOnOrderBook bool `json:"w"` - IsLimitMaker bool `json:"m"` - OrderCreationTime bybitTimeMilliSecStr `json:"O"` - CumulativeQuoteTransactedQuantity float64 `json:"Z,string"` - AccountID string `json:"A"` - IsClose bool `json:"C"` - Leverage float64 `json:"v,string"` + EventType string `json:"e"` + EventTime string `json:"E"` + Symbol string `json:"s"` + ClientOrderID string `json:"c"` + Side string `json:"S"` + OrderType string `json:"o"` + TimeInForce string `json:"f"` + Quantity convert.StringToFloat64 `json:"q"` + Price convert.StringToFloat64 `json:"p"` + OrderStatus string `json:"X"` + OrderID string `json:"i"` + OpponentOrderID string `json:"M"` + LastExecutedQuantity convert.StringToFloat64 `json:"l"` + CumulativeFilledQuantity convert.StringToFloat64 `json:"z"` + LastExecutedPrice convert.StringToFloat64 `json:"L"` + Commission convert.StringToFloat64 `json:"n"` + CommissionAsset string `json:"N"` + IsNormal bool `json:"u"` + IsOnOrderBook bool `json:"w"` + IsLimitMaker bool `json:"m"` + OrderCreationTime bybitTimeMilliSecStr `json:"O"` + CumulativeQuoteTransactedQuantity convert.StringToFloat64 `json:"Z"` + AccountID string `json:"A"` + IsClose bool `json:"C"` + Leverage convert.StringToFloat64 `json:"v"` } // wsOrderFilled defines websocket account order filled data type wsOrderFilled struct { - EventType string `json:"e"` - EventTime string `json:"E"` - Symbol string `json:"s"` - Quantity float64 `json:"q,string"` - Timestamp bybitTimeMilliSecStr `json:"t"` - Price float64 `json:"p,string"` - TradeID string `json:"T"` - OrderID string `json:"o"` - UserGenOrderID string `json:"c"` - OpponentOrderID string `json:"O"` - AccountID string `json:"a"` - OpponentAccountID string `json:"A"` - IsMaker bool `json:"m"` - Side string `json:"S"` + EventType string `json:"e"` + EventTime string `json:"E"` + Symbol string `json:"s"` + Quantity convert.StringToFloat64 `json:"q"` + Timestamp bybitTimeMilliSecStr `json:"t"` + Price convert.StringToFloat64 `json:"p"` + TradeID string `json:"T"` + OrderID string `json:"o"` + UserGenOrderID string `json:"c"` + OpponentOrderID string `json:"O"` + AccountID string `json:"a"` + OpponentAccountID string `json:"A"` + IsMaker bool `json:"m"` + Side string `json:"S"` } // WsFuturesOrderbookData stores ws futures orderbook data type WsFuturesOrderbookData struct { - Price float64 `json:"price,string"` - Symbol string `json:"symbol"` - ID int64 `json:"id"` - Side string `json:"side"` - Size float64 `json:"size"` + Price convert.StringToFloat64 `json:"price"` + Symbol string `json:"symbol"` + ID int64 `json:"id"` + Side string `json:"side"` + Size float64 `json:"size"` } // WsFuturesOrderbook stores ws futures orderbook @@ -661,32 +636,32 @@ type WsInsurance struct { // WsTickerData stores ws ticker data type WsTickerData struct { - ID string `json:"id"` - Symbol string `json:"symbol"` - LastPrice float64 `json:"last_price,string"` - BidPrice float64 `json:"bid1_price"` - AskPrice float64 `json:"ask1_price"` - LastDirection string `json:"last_tick_direction"` - PrevPrice24h float64 `json:"prev_price_24h,string"` - Price24hPercentChange float64 `json:"price_24h_pcnt_e6"` - Price1hPercentChange float64 `json:"price_1h_pcnt_e6"` - HighPrice24h float64 `json:"high_price_24h,string"` - LowPrice24h float64 `json:"low_price_24h,string"` - PrevPrice1h float64 `json:"prev_price_1h,string"` - MarkPrice float64 `json:"mark_price,string"` - IndexPrice float64 `json:"index_price,string"` - OpenInterest float64 `json:"open_interest"` - OpenValue float64 `json:"open_value_e8"` - TotalTurnOver float64 `json:"total_turnover_e8"` - TurnOver24h float64 `json:"turnover_24h_e8"` - TotalVolume float64 `json:"total_volume"` - Volume24h float64 `json:"volume_24h"` - FundingRate int64 `json:"funding_rate_e6"` - PredictedFundingRate float64 `json:"predicted_funding_rate_e6"` - CreatedAt time.Time `json:"created_at"` - UpdateAt time.Time `json:"updated_at"` - NextFundingAt time.Time `json:"next_funding_time"` - CountDownHour int64 `json:"countdown_hour"` + ID string `json:"id"` + Symbol string `json:"symbol"` + LastPrice convert.StringToFloat64 `json:"last_price"` + BidPrice float64 `json:"bid1_price"` + AskPrice float64 `json:"ask1_price"` + LastDirection string `json:"last_tick_direction"` + PrevPrice24h convert.StringToFloat64 `json:"prev_price_24h"` + Price24hPercentChange float64 `json:"price_24h_pcnt_e6"` + Price1hPercentChange float64 `json:"price_1h_pcnt_e6"` + HighPrice24h convert.StringToFloat64 `json:"high_price_24h"` + LowPrice24h convert.StringToFloat64 `json:"low_price_24h"` + PrevPrice1h convert.StringToFloat64 `json:"prev_price_1h"` + MarkPrice convert.StringToFloat64 `json:"mark_price"` + IndexPrice convert.StringToFloat64 `json:"index_price"` + OpenInterest float64 `json:"open_interest"` + OpenValue float64 `json:"open_value_e8"` + TotalTurnOver float64 `json:"total_turnover_e8"` + TurnOver24h float64 `json:"turnover_24h_e8"` + TotalVolume float64 `json:"total_volume"` + Volume24h float64 `json:"volume_24h"` + FundingRate int64 `json:"funding_rate_e6"` + PredictedFundingRate float64 `json:"predicted_funding_rate_e6"` + CreatedAt time.Time `json:"created_at"` + UpdateAt time.Time `json:"updated_at"` + NextFundingAt time.Time `json:"next_funding_time"` + CountDownHour int64 `json:"countdown_hour"` } // WsTicker stores ws ticker @@ -708,45 +683,45 @@ type WsDeltaTicker struct { // WsFuturesTickerData stores ws future ticker data type WsFuturesTickerData struct { - ID string `json:"id"` - Symbol string `json:"symbol"` - SymbolName string `json:"symbol_name"` - SymbolYear int64 `json:"symbol_year"` - ContractType string `json:"contract_type"` - Coin string `json:"coin"` - QuoteSymbol string `json:"quote_symbol"` - Mode string `json:"mode"` - IsUpBorrowable int64 `json:"is_up_borrowable"` - ImportTime bybitTimeNanoSec `json:"import_time_e9"` - StartTradingTime bybitTimeNanoSec `json:"start_trading_time_e9"` - TimeToSettle bybitTimeNanoSec `json:"settle_time_e9"` - SettleFeeRate int64 `json:"settle_fee_rate_e8"` - ContractStatus string `json:"contract_status"` - SystemSubsidy int64 `json:"system_subsidy_e8"` - LastPrice float64 `json:"last_price,string"` - BidPrice float64 `json:"bid1_price"` - AskPrice float64 `json:"ask1_price"` - LastDirection string `json:"last_tick_direction"` - PrevPrice24h float64 `json:"prev_price_24h,string"` - Price24hPercentChange float64 `json:"price_24h_pcnt_e6"` - Price1hPercentChange float64 `json:"price_1h_pcnt_e6"` - HighPrice24h float64 `json:"high_price_24h,string"` - LowPrice24h float64 `json:"low_price_24h,string"` - PrevPrice1h float64 `json:"prev_price_1h,string"` - MarkPrice float64 `json:"mark_price,string"` - IndexPrice float64 `json:"index_price,string"` - OpenInterest float64 `json:"open_interest"` - OpenValue float64 `json:"open_value_e8"` - TotalTurnOver float64 `json:"total_turnover_e8"` - TurnOver24h float64 `json:"turnover_24h_e8"` - TotalVolume float64 `json:"total_volume"` - Volume24h float64 `json:"volume_24h"` - FairBasis float64 `json:"fair_basis_e8"` - FairBasisRate float64 `json:"fair_basis_rate_e8"` - BasisInYear float64 `json:"basis_in_year_e8"` - ExpectPrice float64 `json:"expect_price,string"` - CreatedAt time.Time `json:"created_at"` - UpdateAt time.Time `json:"updated_at"` + ID string `json:"id"` + Symbol string `json:"symbol"` + SymbolName string `json:"symbol_name"` + SymbolYear int64 `json:"symbol_year"` + ContractType string `json:"contract_type"` + Coin string `json:"coin"` + QuoteSymbol string `json:"quote_symbol"` + Mode string `json:"mode"` + IsUpBorrowable int64 `json:"is_up_borrowable"` + ImportTime bybitTimeNanoSec `json:"import_time_e9"` + StartTradingTime bybitTimeNanoSec `json:"start_trading_time_e9"` + TimeToSettle bybitTimeNanoSec `json:"settle_time_e9"` + SettleFeeRate int64 `json:"settle_fee_rate_e8"` + ContractStatus string `json:"contract_status"` + SystemSubsidy int64 `json:"system_subsidy_e8"` + LastPrice convert.StringToFloat64 `json:"last_price"` + BidPrice float64 `json:"bid1_price"` + AskPrice float64 `json:"ask1_price"` + LastDirection string `json:"last_tick_direction"` + PrevPrice24h convert.StringToFloat64 `json:"prev_price_24h"` + Price24hPercentChange float64 `json:"price_24h_pcnt_e6"` + Price1hPercentChange float64 `json:"price_1h_pcnt_e6"` + HighPrice24h convert.StringToFloat64 `json:"high_price_24h"` + LowPrice24h convert.StringToFloat64 `json:"low_price_24h"` + PrevPrice1h convert.StringToFloat64 `json:"prev_price_1h"` + MarkPrice convert.StringToFloat64 `json:"mark_price"` + IndexPrice convert.StringToFloat64 `json:"index_price"` + OpenInterest float64 `json:"open_interest"` + OpenValue float64 `json:"open_value_e8"` + TotalTurnOver float64 `json:"total_turnover_e8"` + TurnOver24h float64 `json:"turnover_24h_e8"` + TotalVolume float64 `json:"total_volume"` + Volume24h float64 `json:"volume_24h"` + FairBasis float64 `json:"fair_basis_e8"` + FairBasisRate float64 `json:"fair_basis_rate_e8"` + BasisInYear float64 `json:"basis_in_year_e8"` + ExpectPrice convert.StringToFloat64 `json:"expect_price"` + CreatedAt time.Time `json:"created_at"` + UpdateAt time.Time `json:"updated_at"` } // WsFuturesTicker stores ws future ticker @@ -768,11 +743,11 @@ type WsDeltaFuturesTicker struct { // WsLiquidationData stores ws liquidation data type WsLiquidationData struct { - Symbol string `json:"symbol"` - Side string `json:"side"` - Price float64 `json:"price,string"` - Qty float64 `json:"qty"` - Timestamp bybitTimeMilliSec `json:"time"` + Symbol string `json:"symbol"` + Side string `json:"side"` + Price convert.StringToFloat64 `json:"price"` + Qty float64 `json:"qty"` + Timestamp bybitTimeMilliSec `json:"time"` } // WsFuturesLiquidation stores ws future liquidation @@ -783,36 +758,36 @@ type WsFuturesLiquidation struct { // WsFuturesPositionData stores ws future position data type WsFuturesPositionData struct { - UserID int64 `json:"user_id"` - Symbol string `json:"symbol"` - Side string `json:"side"` - Size float64 `json:"size"` - PositionID int64 `json:"position_idx"` // present in Futures position struct only - Mode int64 `json:"mode"` // present in Futures position struct only - Isolated bool `json:"isolated"` // present in Futures position struct only - PositionValue float64 `json:"position_value,string"` - EntryPrice float64 `json:"entry_price,string"` - LiquidPrice float64 `json:"liq_price,string"` - BustPrice float64 `json:"bust_price,string"` - Leverage float64 `json:"leverage,string"` - OrderMargin float64 `json:"order_margin,string"` - PositionMargin float64 `json:"position_margin,string"` - AvailableBalance float64 `json:"available_balance,string"` - TakeProfit float64 `json:"take_profit,string"` - TakeProfitTriggerBy string `json:"tp_trigger_by"` - StopLoss float64 `json:"stop_loss,string"` - StopLossTriggerBy string `json:"sl_trigger_by"` - RealisedPNL float64 `json:"realised_pnl,string"` - TrailingStop float64 `json:"trailing_stop,string"` - TrailingActive float64 `json:"trailing_active,string"` - WalletBalance float64 `json:"wallet_balance,string"` - RiskID int64 `json:"risk_id"` - ClosingFee float64 `json:"occ_closing_fee,string"` - FundingFee float64 `json:"occ_funding_fee,string"` - AutoAddMargin int64 `json:"auto_add_margin"` - TotalPNL float64 `json:"cum_realised_pnl,string"` - Status string `json:"position_status"` - Version int64 `json:"position_seq"` + UserID int64 `json:"user_id"` + Symbol string `json:"symbol"` + Side string `json:"side"` + Size float64 `json:"size"` + PositionID int64 `json:"position_idx"` // present in Futures position struct only + Mode int64 `json:"mode"` // present in Futures position struct only + Isolated bool `json:"isolated"` // present in Futures position struct only + PositionValue convert.StringToFloat64 `json:"position_value"` + EntryPrice convert.StringToFloat64 `json:"entry_price"` + LiquidPrice convert.StringToFloat64 `json:"liq_price"` + BustPrice convert.StringToFloat64 `json:"bust_price"` + Leverage convert.StringToFloat64 `json:"leverage"` + OrderMargin convert.StringToFloat64 `json:"order_margin"` + PositionMargin convert.StringToFloat64 `json:"position_margin"` + AvailableBalance convert.StringToFloat64 `json:"available_balance"` + TakeProfit convert.StringToFloat64 `json:"take_profit"` + TakeProfitTriggerBy string `json:"tp_trigger_by"` + StopLoss convert.StringToFloat64 `json:"stop_loss"` + StopLossTriggerBy string `json:"sl_trigger_by"` + RealisedPNL convert.StringToFloat64 `json:"realised_pnl"` + TrailingStop convert.StringToFloat64 `json:"trailing_stop"` + TrailingActive convert.StringToFloat64 `json:"trailing_active"` + WalletBalance convert.StringToFloat64 `json:"wallet_balance"` + RiskID int64 `json:"risk_id"` + ClosingFee convert.StringToFloat64 `json:"occ_closing_fee"` + FundingFee convert.StringToFloat64 `json:"occ_funding_fee"` + AutoAddMargin int64 `json:"auto_add_margin"` + TotalPNL convert.StringToFloat64 `json:"cum_realised_pnl"` + Status string `json:"position_status"` + Version int64 `json:"position_seq"` } // WsFuturesPosition stores ws future position @@ -824,19 +799,19 @@ type WsFuturesPosition struct { // WsFuturesExecutionData stores ws future execution data type WsFuturesExecutionData struct { - Symbol string `json:"symbol"` - Side string `json:"side"` - OrderID string `json:"order_id"` - ExecutionID string `json:"exec_id"` - OrderLinkID string `json:"order_link_id"` - Price float64 `json:"price,string"` - OrderQty float64 `json:"order_qty"` - ExecutionType string `json:"exec_type"` - ExecutionQty float64 `json:"exec_qty"` - ExecutionFee float64 `json:"exec_fee,string"` - LeavesQty float64 `json:"leaves_qty"` - IsMaker bool `json:"is_maker"` - Time time.Time `json:"trade_time"` + Symbol string `json:"symbol"` + Side string `json:"side"` + OrderID string `json:"order_id"` + ExecutionID string `json:"exec_id"` + OrderLinkID string `json:"order_link_id"` + Price convert.StringToFloat64 `json:"price"` + OrderQty float64 `json:"order_qty"` + ExecutionType string `json:"exec_type"` + ExecutionQty float64 `json:"exec_qty"` + ExecutionFee convert.StringToFloat64 `json:"exec_fee"` + LeavesQty float64 `json:"leaves_qty"` + IsMaker bool `json:"is_maker"` + Time time.Time `json:"trade_time"` } // WsFuturesExecution stores ws future execution @@ -847,31 +822,31 @@ type WsFuturesExecution struct { // WsOrderData stores ws order data type WsOrderData struct { - OrderID string `json:"order_id"` - OrderLinkID string `json:"order_link_id"` - Symbol string `json:"symbol"` - Side string `json:"side"` - OrderType string `json:"order_type"` - Price float64 `json:"price,string"` - OrderQty float64 `json:"qty"` - TimeInForce string `json:"time_in_force"` - CreateType string `json:"create_type"` - CancelType string `json:"cancel_type"` - OrderStatus string `json:"order_status"` - LeavesQty float64 `json:"leaves_qty"` - CummulativeExecQty float64 `json:"cum_exec_qty"` - CummulativeExecValue float64 `json:"cum_exec_value,string"` - CummulativeExecFee float64 `json:"cum_exec_fee,string"` - TakeProfit float64 `json:"take_profit,string"` - StopLoss float64 `json:"stop_loss,string"` - TrailingStop float64 `json:"trailing_stop,string"` - TrailingActive float64 `json:"trailing_active,string"` - LastExecPrice float64 `json:"last_exec_price,string"` - ReduceOnly bool `json:"reduce_only"` - CloseOnTrigger bool `json:"close_on_trigger"` - Time time.Time `json:"timestamp"` // present in CoinMarginedFutures and Futures only - CreateTime time.Time `json:"create_time"` // present in USDTMarginedFutures only - UpdateTime time.Time `json:"update_time"` // present in USDTMarginedFutures only + OrderID string `json:"order_id"` + OrderLinkID string `json:"order_link_id"` + Symbol string `json:"symbol"` + Side string `json:"side"` + OrderType string `json:"order_type"` + Price convert.StringToFloat64 `json:"price"` + OrderQty float64 `json:"qty"` + TimeInForce string `json:"time_in_force"` + CreateType string `json:"create_type"` + CancelType string `json:"cancel_type"` + OrderStatus string `json:"order_status"` + LeavesQty float64 `json:"leaves_qty"` + CummulativeExecQty float64 `json:"cum_exec_qty"` + CummulativeExecValue convert.StringToFloat64 `json:"cum_exec_value"` + CummulativeExecFee convert.StringToFloat64 `json:"cum_exec_fee"` + TakeProfit convert.StringToFloat64 `json:"take_profit"` + StopLoss convert.StringToFloat64 `json:"stop_loss"` + TrailingStop convert.StringToFloat64 `json:"trailing_stop"` + TrailingActive convert.StringToFloat64 `json:"trailing_active"` + LastExecPrice convert.StringToFloat64 `json:"last_exec_price"` + ReduceOnly bool `json:"reduce_only"` + CloseOnTrigger bool `json:"close_on_trigger"` + Time time.Time `json:"timestamp"` // present in CoinMarginedFutures and Futures only + CreateTime time.Time `json:"create_time"` // present in USDTMarginedFutures only + UpdateTime time.Time `json:"update_time"` // present in USDTMarginedFutures only } // WsOrder stores ws order @@ -882,23 +857,23 @@ type WsOrder struct { // WsStopOrderData stores ws stop order data type WsStopOrderData struct { - OrderID string `json:"order_id"` - OrderLinkID string `json:"order_link_id"` - UserID int64 `json:"user_id"` - Symbol string `json:"symbol"` - Side string `json:"side"` - OrderType string `json:"order_type"` - Price float64 `json:"price,string"` - OrderQty float64 `json:"qty"` - TimeInForce string `json:"time_in_force"` - CreateType string `json:"create_type"` - CancelType string `json:"cancel_type"` - OrderStatus string `json:"order_status"` - StopOrderType string `json:"stop_order_type"` - TriggerBy string `json:"trigger_by"` - TriggerPrice float64 `json:"trigger_price,string"` - Time time.Time `json:"timestamp"` - CloseOnTrigger bool `json:"close_on_trigger"` + OrderID string `json:"order_id"` + OrderLinkID string `json:"order_link_id"` + UserID int64 `json:"user_id"` + Symbol string `json:"symbol"` + Side string `json:"side"` + OrderType string `json:"order_type"` + Price convert.StringToFloat64 `json:"price"` + OrderQty float64 `json:"qty"` + TimeInForce string `json:"time_in_force"` + CreateType string `json:"create_type"` + CancelType string `json:"cancel_type"` + OrderStatus string `json:"order_status"` + StopOrderType string `json:"stop_order_type"` + TriggerBy string `json:"trigger_by"` + TriggerPrice convert.StringToFloat64 `json:"trigger_price"` + Time time.Time `json:"timestamp"` + CloseOnTrigger bool `json:"close_on_trigger"` } // WsFuturesStopOrder stores ws future stop order @@ -909,23 +884,23 @@ type WsFuturesStopOrder struct { // WsUSDTStopOrderData stores ws USDT stop order data type WsUSDTStopOrderData struct { - OrderID string `json:"stop_order_id"` - OrderLinkID string `json:"order_link_id"` - UserID int64 `json:"user_id"` - Symbol string `json:"symbol"` - Side string `json:"side"` - OrderType string `json:"order_type"` - Price float64 `json:"price,string"` - OrderQty float64 `json:"qty"` - TimeInForce string `json:"time_in_force"` - OrderStatus string `json:"order_status"` - StopOrderType string `json:"stop_order_type"` - TriggerBy string `json:"trigger_by"` - TriggerPrice float64 `json:"trigger_price,string"` - ReduceOnly bool `json:"reduce_only"` - CloseOnTrigger bool `json:"close_on_trigger"` - CreateTime time.Time `json:"create_time"` - UpdateTime time.Time `json:"update_time"` + OrderID string `json:"stop_order_id"` + OrderLinkID string `json:"order_link_id"` + UserID int64 `json:"user_id"` + Symbol string `json:"symbol"` + Side string `json:"side"` + OrderType string `json:"order_type"` + Price convert.StringToFloat64 `json:"price"` + OrderQty float64 `json:"qty"` + TimeInForce string `json:"time_in_force"` + OrderStatus string `json:"order_status"` + StopOrderType string `json:"stop_order_type"` + TriggerBy string `json:"trigger_by"` + TriggerPrice convert.StringToFloat64 `json:"trigger_price"` + ReduceOnly bool `json:"reduce_only"` + CloseOnTrigger bool `json:"close_on_trigger"` + CreateTime time.Time `json:"create_time"` + UpdateTime time.Time `json:"update_time"` } // WsUSDTFuturesStopOrder stores ws USDT stop order @@ -945,3 +920,68 @@ type WsFuturesWallet struct { Topic string `json:"topic"` Data []WsFuturesWalletData `json:"data"` } + +// Ticker holds ticker information +type Ticker struct { + // Spot fields + Symbol string `json:"symbol"` + TopBidPrice convert.StringToFloat64 `json:"bid1Price"` + TopBidSize convert.StringToFloat64 `json:"bid1Size"` + TopAskPrice convert.StringToFloat64 `json:"ask1Price"` + TopAskSize convert.StringToFloat64 `json:"ask1Size"` + LastPrice convert.StringToFloat64 `json:"lastPrice"` + PreviousPrice24Hr convert.StringToFloat64 `json:"prevPrice24h"` + Price24HrPcnt convert.StringToFloat64 `json:"price24hPcnt"` + HighPrice24Hr convert.StringToFloat64 `json:"highPrice24h"` + LowPrice24Hr convert.StringToFloat64 `json:"lowPrice24h"` + Turnover24Hr convert.StringToFloat64 `json:"turnover24h"` + Volume24Hr convert.StringToFloat64 `json:"volume24h"` + USDIndexPrice convert.StringToFloat64 `json:"usdIndexPrice"` + + // Option fields + TopBidImpliedVolatility convert.StringToFloat64 `json:"bid1Iv"` + TopAskImpliedVolatility convert.StringToFloat64 `json:"ask1Iv"` + MarkPrice convert.StringToFloat64 `json:"markPrice"` + IndexPrice convert.StringToFloat64 `json:"indexPrice"` + MarkImpliedVolatility convert.StringToFloat64 `json:"markIv"` + UnderlyingPrice convert.StringToFloat64 `json:"underlyingPrice"` + OpenInterest convert.StringToFloat64 `json:"openInterest"` + TotalVolume convert.StringToFloat64 `json:"totalVolume"` + TotalTurnover convert.StringToFloat64 `json:"totalTurnover"` + Delta convert.StringToFloat64 `json:"delta"` + Gamma convert.StringToFloat64 `json:"gamma"` + Vega convert.StringToFloat64 `json:"vega"` + Theta convert.StringToFloat64 `json:"theta"` + PredictedDeliveryPrice convert.StringToFloat64 `json:"predictedDeliveryPrice"` + Change24h convert.StringToFloat64 `json:"change24h"` + + // Inverse/linear fields + PrevPrice1h convert.StringToFloat64 `json:"prevPrice1h"` + OpenInterestValue convert.StringToFloat64 `json:"openInterestValue"` + FundingRate convert.StringToFloat64 `json:"fundingRate"` + NextFundingTime convert.StringToFloat64 `json:"nextFundingTime"` + BasisRate convert.StringToFloat64 `json:"basisRate"` + DeliveryFeeRate convert.StringToFloat64 `json:"deliveryFeeRate"` + DeliveryTime convert.StringToFloat64 `json:"deliveryTime"` + Basis convert.StringToFloat64 `json:"basis"` +} + +// Fee holds fee information +type Fee struct { + BaseCoin string `json:"baseCoin"` + Symbol string `json:"symbol"` + Taker convert.StringToFloat64 `json:"takerFeeRate"` + Maker convert.StringToFloat64 `json:"makerFeeRate"` +} + +// AccountFee holds account fee information +type AccountFee struct { + Category string `json:"category"` + List []Fee `json:"list"` +} + +// ListOfTickers holds list of tickers +type ListOfTickers struct { + Category string `json:"category"` + List []Ticker `json:"list"` +} diff --git a/exchanges/bybit/bybit_usdcfutures.go b/exchanges/bybit/bybit_usdcfutures.go index 97b3c643..801aa970 100644 --- a/exchanges/bybit/bybit_usdcfutures.go +++ b/exchanges/bybit/bybit_usdcfutures.go @@ -11,6 +11,7 @@ import ( "time" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -79,13 +80,13 @@ func (by *Bybit) GetUSDCFuturesOrderbook(ctx context.Context, symbol currency.Pa switch data.Result[x].Side { case sideBuy: resp.Bids = append(resp.Bids, orderbook.Item{ - Price: data.Result[x].Price, - Amount: data.Result[x].Size, + Price: data.Result[x].Price.Float64(), + Amount: data.Result[x].Size.Float64(), }) case sideSell: resp.Asks = append(resp.Asks, orderbook.Item{ - Price: data.Result[x].Price, - Amount: data.Result[x].Size, + Price: data.Result[x].Price.Float64(), + Amount: data.Result[x].Size.Float64(), }) default: return nil, errInvalidSide @@ -884,7 +885,7 @@ func (by *Bybit) GetUSDCPosition(ctx context.Context, symbol currency.Pair, cate func (by *Bybit) SetUSDCLeverage(ctx context.Context, symbol currency.Pair, leverage float64) (float64, error) { resp := struct { Result struct { - Leverage float64 `json:"leverage,string"` + Leverage convert.StringToFloat64 `json:"leverage"` } `json:"result"` USDCError }{} @@ -895,7 +896,7 @@ func (by *Bybit) SetUSDCLeverage(ctx context.Context, symbol currency.Pair, leve } symbolValue, err := by.FormatSymbol(symbol, asset.USDCMarginedFutures) if err != nil { - return resp.Result.Leverage, err + return 0, err } req["symbol"] = symbolValue @@ -904,7 +905,8 @@ func (by *Bybit) SetUSDCLeverage(ctx context.Context, symbol currency.Pair, leve } req["leverage"] = strconv.FormatFloat(leverage, 'f', -1, 64) - return resp.Result.Leverage, by.SendUSDCAuthHTTPRequest(ctx, exchange.RestUSDCMargined, http.MethodPost, usdcfuturesSetLeverage, req, &resp, usdcSetLeverageRate) + return resp.Result.Leverage.Float64(), + by.SendUSDCAuthHTTPRequest(ctx, exchange.RestUSDCMargined, http.MethodPost, usdcfuturesSetLeverage, req, &resp, usdcSetLeverageRate) } // GetUSDCSettlementHistory gets USDC settlement history with support of last 30 days. @@ -1014,8 +1016,8 @@ func (by *Bybit) GetUSDCLastFundingRate(ctx context.Context, symbol currency.Pai func (by *Bybit) GetUSDCPredictedFundingRate(ctx context.Context, symbol currency.Pair) (predictedFundingRate, predictedFundingFee float64, err error) { resp := struct { Result struct { - PredictedFundingRate float64 `json:"predictedFundingRate,string"` - PredictedFundingFee float64 `json:"predictedFundingFee,string"` + PredictedFundingRate convert.StringToFloat64 `json:"predictedFundingRate"` + PredictedFundingFee convert.StringToFloat64 `json:"predictedFundingFee"` } `json:"result"` USDCError }{} @@ -1023,17 +1025,17 @@ func (by *Bybit) GetUSDCPredictedFundingRate(ctx context.Context, symbol currenc req := make(map[string]interface{}) var symbolValue string if symbol.IsEmpty() { - return resp.Result.PredictedFundingRate, resp.Result.PredictedFundingFee, errSymbolMissing + return 0, 0, errSymbolMissing } symbolValue, err = by.FormatSymbol(symbol, asset.USDCMarginedFutures) if err != nil { - return resp.Result.PredictedFundingRate, resp.Result.PredictedFundingFee, err + return 0, 0, err } req["symbol"] = symbolValue err = by.SendUSDCAuthHTTPRequest(ctx, exchange.RestUSDCMargined, http.MethodPost, usdcfuturesGetPredictedFundingRate, req, &resp, usdcGetPredictedFundingRate) - predictedFundingRate = resp.Result.PredictedFundingRate - predictedFundingFee = resp.Result.PredictedFundingFee + predictedFundingRate = resp.Result.PredictedFundingRate.Float64() + predictedFundingFee = resp.Result.PredictedFundingFee.Float64() return } diff --git a/exchanges/bybit/bybit_websocket.go b/exchanges/bybit/bybit_websocket.go index 5b372d78..d84fb5da 100644 --- a/exchanges/bybit/bybit_websocket.go +++ b/exchanges/bybit/bybit_websocket.go @@ -301,8 +301,8 @@ func (by *Bybit) wsHandleData(respRaw []byte) error { CurrencyPair: p, AssetType: asset.Spot, Exchange: by.Name, - Price: data.TradeData.Price, - Amount: data.TradeData.Size, + Price: data.TradeData.Price.Float64(), + Amount: data.TradeData.Size.Float64(), Side: side, TID: data.TradeData.ID, }) @@ -320,8 +320,8 @@ func (by *Bybit) wsHandleData(respRaw []byte) error { by.Websocket.DataHandler <- &ticker.Price{ ExchangeName: by.Name, - Bid: data.Ticker.Bid, - Ask: data.Ticker.Ask, + Bid: data.Ticker.Bid.Float64(), + Ask: data.Ticker.Ask.Float64(), LastUpdated: data.Ticker.Time.Time(), AssetType: asset.Spot, Pair: p, @@ -345,11 +345,11 @@ func (by *Bybit) wsHandleData(respRaw []byte) error { Exchange: by.Name, StartTime: data.Kline.StartTime.Time(), Interval: data.Parameters.KlineType, - OpenPrice: data.Kline.OpenPrice, - ClosePrice: data.Kline.ClosePrice, - HighPrice: data.Kline.HighPrice, - LowPrice: data.Kline.LowPrice, - Volume: data.Kline.Volume, + OpenPrice: data.Kline.OpenPrice.Float64(), + ClosePrice: data.Kline.ClosePrice.Float64(), + HighPrice: data.Kline.HighPrice.Float64(), + LowPrice: data.Kline.LowPrice.Float64(), + Volume: data.Kline.Volume.Float64(), } return nil default: @@ -431,10 +431,10 @@ func (by *Bybit) wsHandleData(respRaw []byte) error { } by.Websocket.DataHandler <- order.Detail{ - Price: data[j].Price, - Amount: data[j].Quantity, - ExecutedAmount: data[j].CumulativeFilledQuantity, - RemainingAmount: data[j].Quantity - data[j].CumulativeFilledQuantity, + Price: data[j].Price.Float64(), + Amount: data[j].Quantity.Float64(), + ExecutedAmount: data[j].CumulativeFilledQuantity.Float64(), + RemainingAmount: data[j].Quantity.Float64() - data[j].CumulativeFilledQuantity.Float64(), Exchange: by.Name, OrderID: data[j].OrderID, Type: oType, @@ -446,8 +446,8 @@ func (by *Bybit) wsHandleData(respRaw []byte) error { ClientOrderID: data[j].ClientOrderID, Trades: []order.TradeHistory{ { - Price: data[j].Price, - Amount: data[j].Quantity, + Price: data[j].Price.Float64(), + Amount: data[j].Quantity.Float64(), Exchange: by.Name, Timestamp: data[j].OrderCreationTime.Time(), }, @@ -486,13 +486,13 @@ func (by *Bybit) wsHandleData(respRaw []byte) error { Side: oSide, AssetType: asset.Spot, Pair: p, - Price: data[j].Price, - Amount: data[j].Quantity, + Price: data[j].Price.Float64(), + Amount: data[j].Quantity.Float64(), Date: data[j].Timestamp.Time(), Trades: []order.TradeHistory{ { - Price: data[j].Price, - Amount: data[j].Quantity, + Price: data[j].Price.Float64(), + Amount: data[j].Quantity.Float64(), Exchange: by.Name, Timestamp: data[j].Timestamp.Time(), TID: data[j].TradeID, diff --git a/exchanges/bybit/bybit_wrapper.go b/exchanges/bybit/bybit_wrapper.go index d08bbfa6..38be7397 100644 --- a/exchanges/bybit/bybit_wrapper.go +++ b/exchanges/bybit/bybit_wrapper.go @@ -381,13 +381,16 @@ func (by *Bybit) FetchTradablePairs(ctx context.Context, a asset.Item) (currency if err != nil { return nil, err } - pairs := make([]currency.Pair, len(allPairs)) + pairs := make([]currency.Pair, 0, len(allPairs)) for x := range allPairs { + if allPairs[x].Status != "ONLINE" { + continue + } pair, err = currency.NewPairFromStrings(allPairs[x].BaseCoin, "PERP") if err != nil { return nil, err } - pairs[x] = pair + pairs = append(pairs, pair) } return pairs, nil } @@ -413,58 +416,62 @@ func (by *Bybit) UpdateTradablePairs(ctx context.Context, forceUpdate bool) erro // UpdateTickers updates the ticker for all currency pairs of a given asset type func (by *Bybit) UpdateTickers(ctx context.Context, assetType asset.Item) error { - allPairs, err := by.GetEnabledPairs(assetType) + avail, err := by.GetAvailablePairs(assetType) if err != nil { return err } + + enabled, err := by.GetEnabledPairs(assetType) + if err != nil { + return err + } + switch assetType { case asset.Spot: - tick, err := by.Get24HrsChange(ctx, "") + ticks, err := by.GetTickersV5(ctx, "spot", "", "") if err != nil { return err } - for p := range allPairs { - formattedPair, err := by.FormatExchangeCurrency(allPairs[p], assetType) + + for x := range ticks.List { + pair, err := avail.DeriveFrom(ticks.List[x].Symbol) if err != nil { + // These symbols below do not have a spot market but are in fact + // perpetuals. + if ticks.List[x].Symbol == "ZECUSDT" || ticks.List[x].Symbol == "DASHUSDT" { + continue + } return err } - for y := range tick { - if tick[y].Symbol != formattedPair.String() { - continue - } + if !enabled.Contains(pair, true) { + continue + } - cp, err := by.extractCurrencyPair(tick[y].Symbol, assetType) - if err != nil { - return err - } - err = ticker.ProcessTicker(&ticker.Price{ - Last: tick[y].LastPrice, - High: tick[y].HighPrice, - Low: tick[y].LowPrice, - Bid: tick[y].BestBidPrice, - Ask: tick[y].BestAskPrice, - Volume: tick[y].Volume, - QuoteVolume: tick[y].QuoteVolume, - Open: tick[y].OpenPrice, - Pair: cp, - LastUpdated: tick[y].Time, - ExchangeName: by.Name, - AssetType: assetType}) - if err != nil { - return err - } + err = ticker.ProcessTicker(&ticker.Price{ + Last: ticks.List[x].LastPrice.Float64(), + High: ticks.List[x].HighPrice24Hr.Float64(), + Low: ticks.List[x].LowPrice24Hr.Float64(), + Bid: ticks.List[x].TopBidPrice.Float64(), + BidSize: ticks.List[x].TopBidSize.Float64(), + Ask: ticks.List[x].TopAskPrice.Float64(), + AskSize: ticks.List[x].TopAskSize.Float64(), + Volume: ticks.List[x].Volume24Hr.Float64(), + Pair: pair, + ExchangeName: by.Name, + AssetType: assetType}) + if err != nil { + return err } } - case asset.CoinMarginedFutures, asset.USDTMarginedFutures, asset.Futures: tick, err := by.GetFuturesSymbolPriceTicker(ctx, currency.Pair{}) if err != nil { return err } - for p := range allPairs { - formattedPair, err := by.FormatExchangeCurrency(allPairs[p], assetType) + for p := range enabled { + formattedPair, err := by.FormatExchangeCurrency(enabled[p], assetType) if err != nil { return err } @@ -478,11 +485,11 @@ func (by *Bybit) UpdateTickers(ctx context.Context, assetType asset.Item) error return err } err = ticker.ProcessTicker(&ticker.Price{ - Last: tick[y].LastPrice, - High: tick[y].HighPrice24h, - Low: tick[y].LowPrice24h, - Bid: tick[y].BidPrice, - Ask: tick[y].AskPrice, + Last: tick[y].LastPrice.Float64(), + High: tick[y].HighPrice24h.Float64(), + Low: tick[y].LowPrice24h.Float64(), + Bid: tick[y].BidPrice.Float64(), + Ask: tick[y].AskPrice.Float64(), Volume: tick[y].Volume24h, Open: tick[y].OpenValue.Float64(), Pair: cp, @@ -493,10 +500,9 @@ func (by *Bybit) UpdateTickers(ctx context.Context, assetType asset.Item) error } } } - case asset.USDCMarginedFutures: - for p := range allPairs { - formattedPair, err := by.FormatExchangeCurrency(allPairs[p], assetType) + for x := range enabled { + formattedPair, err := by.FormatExchangeCurrency(enabled[x], assetType) if err != nil { return err } @@ -511,12 +517,12 @@ func (by *Bybit) UpdateTickers(ctx context.Context, assetType asset.Item) error return err } err = ticker.ProcessTicker(&ticker.Price{ - Last: tick.LastPrice, - High: tick.High24h, - Low: tick.Low24h, - Bid: tick.Bid, - Ask: tick.Ask, - Volume: tick.Volume24h, + Last: tick.LastPrice.Float64(), + High: tick.High24h.Float64(), + Low: tick.Low24h.Float64(), + Bid: tick.Bid.Float64(), + Ask: tick.Ask.Float64(), + Volume: tick.Volume24h.Float64(), Pair: cp, ExchangeName: by.Name, AssetType: assetType}) @@ -524,11 +530,9 @@ func (by *Bybit) UpdateTickers(ctx context.Context, assetType asset.Item) error return err } } - default: return fmt.Errorf("%s %w", assetType, asset.ErrNotSupported) } - return nil } @@ -552,16 +556,16 @@ func (by *Bybit) UpdateTicker(ctx context.Context, p currency.Pair, assetType as return nil, err } err = ticker.ProcessTicker(&ticker.Price{ - Last: tick[y].LastPrice, - High: tick[y].HighPrice, - Low: tick[y].LowPrice, - Bid: tick[y].BestBidPrice, - Ask: tick[y].BestAskPrice, - Volume: tick[y].Volume, - QuoteVolume: tick[y].QuoteVolume, - Open: tick[y].OpenPrice, + Last: tick[y].LastPrice.Float64(), + High: tick[y].HighPrice.Float64(), + Low: tick[y].LowPrice.Float64(), + Bid: tick[y].BestBidPrice.Float64(), + Ask: tick[y].BestAskPrice.Float64(), + Volume: tick[y].Volume.Float64(), + QuoteVolume: tick[y].QuoteVolume.Float64(), + Open: tick[y].OpenPrice.Float64(), Pair: cp, - LastUpdated: tick[y].Time, + LastUpdated: tick[y].Time.Time(), ExchangeName: by.Name, AssetType: assetType}) if err != nil { @@ -581,11 +585,11 @@ func (by *Bybit) UpdateTicker(ctx context.Context, p currency.Pair, assetType as return nil, err } err = ticker.ProcessTicker(&ticker.Price{ - Last: tick[y].LastPrice, - High: tick[y].HighPrice24h, - Low: tick[y].LowPrice24h, - Bid: tick[y].BidPrice, - Ask: tick[y].AskPrice, + Last: tick[y].LastPrice.Float64(), + High: tick[y].HighPrice24h.Float64(), + Low: tick[y].LowPrice24h.Float64(), + Bid: tick[y].BidPrice.Float64(), + Ask: tick[y].AskPrice.Float64(), Volume: tick[y].Volume24h, Open: tick[y].OpenValue.Float64(), Pair: cp, @@ -607,12 +611,12 @@ func (by *Bybit) UpdateTicker(ctx context.Context, p currency.Pair, assetType as return nil, err } err = ticker.ProcessTicker(&ticker.Price{ - Last: tick.LastPrice, - High: tick.High24h, - Low: tick.Low24h, - Bid: tick.Bid, - Ask: tick.Ask, - Volume: tick.Volume24h, + Last: tick.LastPrice.Float64(), + High: tick.High24h.Float64(), + Low: tick.Low24h.Float64(), + Bid: tick.Bid.Float64(), + Ask: tick.Ask.Float64(), + Volume: tick.Volume24h.Float64(), Pair: cp, ExchangeName: by.Name, AssetType: assetType}) @@ -719,9 +723,9 @@ func (by *Bybit) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a for i := range balances { currencyBalance[i] = account.Balance{ Currency: currency.NewCode(balances[i].CoinName), - Total: balances[i].Total, - Hold: balances[i].Locked, - Free: balances[i].Total - balances[i].Locked, + Total: balances[i].Total.Float64(), + Hold: balances[i].Locked.Float64(), + Free: balances[i].Total.Float64() - balances[i].Locked.Float64(), } } @@ -756,9 +760,9 @@ func (by *Bybit) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a acc.Currencies = []account.Balance{ { Currency: currency.USD, - Total: balance.WalletBalance, - Hold: balance.WalletBalance - balance.AvailableBalance, - Free: balance.AvailableBalance, + Total: balance.WalletBalance.Float64(), + Hold: balance.WalletBalance.Float64() - balance.AvailableBalance.Float64(), + Free: balance.AvailableBalance.Float64(), }, } @@ -813,7 +817,7 @@ func (by *Bybit) GetWithdrawalsHistory(ctx context.Context, c currency.Code, a a Status: w[i].Status, TransferID: strconv.FormatInt(w[i].ID, 10), Currency: w[i].Coin, - Amount: w[i].Amount, + Amount: w[i].Amount.Float64(), Fee: w[i].Fee, CryptoToAddress: w[i].Address, CryptoTxID: w[i].TxID, @@ -898,8 +902,8 @@ func (by *Bybit) GetRecentTrades(ctx context.Context, p currency.Pair, assetType Exchange: by.Name, CurrencyPair: p, AssetType: assetType, - Price: tradeData[i].OrderPrice, - Amount: tradeData[i].OrderQty, + Price: tradeData[i].OrderPrice.Float64(), + Amount: tradeData[i].OrderQty.Float64(), Timestamp: tradeData[i].Timestamp.Time(), }) } @@ -1230,18 +1234,18 @@ func (by *Bybit) GetOrderInfo(ctx context.Context, orderID string, pair currency } return order.Detail{ - Amount: resp.Quantity, + Amount: resp.Quantity.Float64(), Exchange: by.Name, OrderID: resp.OrderID, ClientOrderID: resp.OrderLinkID, Side: getSide(resp.Side), Type: getTradeType(resp.TradeType), Pair: pair, - Cost: resp.CummulativeQuoteQty, + Cost: resp.CummulativeQuoteQty.Float64(), AssetType: assetType, Status: getOrderStatus(resp.Status), - Price: resp.Price, - ExecutedAmount: resp.ExecutedQty, + Price: resp.Price.Float64(), + ExecutedAmount: resp.ExecutedQty.Float64(), Date: resp.Time.Time(), LastUpdated: resp.UpdateTime.Time(), }, nil @@ -1264,7 +1268,7 @@ func (by *Bybit) GetOrderInfo(ctx context.Context, orderID string, pair currency Side: getSide(resp[0].Side), Type: getTradeType(resp[0].OrderType), Pair: pair, - Cost: resp[0].CumulativeQty, + Cost: resp[0].CumulativeQty.Float64(), AssetType: assetType, Status: getOrderStatus(resp[0].OrderStatus), Price: resp[0].Price, @@ -1291,7 +1295,7 @@ func (by *Bybit) GetOrderInfo(ctx context.Context, orderID string, pair currency Side: getSide(resp[0].Side), Type: getTradeType(resp[0].OrderType), Pair: pair, - Cost: resp[0].CumulativeQty, + Cost: resp[0].CumulativeQty.Float64(), AssetType: assetType, Status: getOrderStatus(resp[0].OrderStatus), Price: resp[0].Price, @@ -1318,7 +1322,7 @@ func (by *Bybit) GetOrderInfo(ctx context.Context, orderID string, pair currency Side: getSide(resp[0].Side), Type: getTradeType(resp[0].OrderType), Pair: pair, - Cost: resp[0].CumulativeQty, + Cost: resp[0].CumulativeQty.Float64(), AssetType: assetType, Status: getOrderStatus(resp[0].OrderStatus), Price: resp[0].Price, @@ -1338,18 +1342,18 @@ func (by *Bybit) GetOrderInfo(ctx context.Context, orderID string, pair currency } return order.Detail{ - Amount: resp[0].Qty, + Amount: resp[0].Qty.Float64(), Exchange: by.Name, OrderID: resp[0].ID, ClientOrderID: resp[0].OrderLinkID, Side: getSide(resp[0].Side), Type: getTradeType(resp[0].OrderType), Pair: pair, - Cost: resp[0].TotalOrderValue, + Cost: resp[0].TotalOrderValue.Float64(), AssetType: assetType, Status: getOrderStatus(resp[0].OrderStatus), - Price: resp[0].Price, - ExecutedAmount: resp[0].TotalFilledQty, + Price: resp[0].Price.Float64(), + ExecutedAmount: resp[0].TotalFilledQty.Float64(), Date: resp[0].CreatedAt.Time(), }, nil @@ -1453,14 +1457,14 @@ func (by *Bybit) GetActiveOrders(ctx context.Context, req *order.GetOrdersReques for i := range req.Pairs { if req.Pairs[i].String() == openOrders[x].SymbolName { orders = append(orders, order.Detail{ - Amount: openOrders[x].Quantity, + Amount: openOrders[x].Quantity.Float64(), Date: openOrders[x].Time.Time(), Exchange: by.Name, OrderID: openOrders[x].OrderID, ClientOrderID: openOrders[x].OrderLinkID, Side: getSide(openOrders[x].Side), Type: getTradeType(openOrders[x].TradeType), - Price: openOrders[x].Price, + Price: openOrders[x].Price.Float64(), Status: getOrderStatus(openOrders[x].Status), Pair: req.Pairs[i], AssetType: req.AssetType, @@ -1557,11 +1561,11 @@ func (by *Bybit) GetActiveOrders(ctx context.Context, req *order.GetOrdersReques for i := range req.Pairs { if req.Pairs[i].String() == openOrders[x].Symbol { orders = append(orders, order.Detail{ - Price: openOrders[x].Price, - Amount: openOrders[x].Qty, - ExecutedAmount: openOrders[x].TotalFilledQty, - RemainingAmount: openOrders[x].Qty - openOrders[x].TotalFilledQty, - Fee: openOrders[x].TotalFee, + Price: openOrders[x].Price.Float64(), + Amount: openOrders[x].Qty.Float64(), + ExecutedAmount: openOrders[x].TotalFilledQty.Float64(), + RemainingAmount: openOrders[x].Qty.Float64() - openOrders[x].TotalFilledQty.Float64(), + Fee: openOrders[x].TotalFee.Float64(), Exchange: by.Name, OrderID: openOrders[x].ID, ClientOrderID: openOrders[x].OrderLinkID, @@ -1610,17 +1614,17 @@ func (by *Bybit) GetOrderHistory(ctx context.Context, req *order.GetOrdersReques return nil, err } detail := order.Detail{ - Amount: resp[i].Quantity, - ExecutedAmount: resp[i].ExecutedQty, - RemainingAmount: resp[i].Quantity - resp[i].ExecutedQty, - Cost: resp[i].CummulativeQuoteQty, + Amount: resp[i].Quantity.Float64(), + ExecutedAmount: resp[i].ExecutedQty.Float64(), + RemainingAmount: resp[i].Quantity.Float64() - resp[i].ExecutedQty.Float64(), + Cost: resp[i].CummulativeQuoteQty.Float64(), Date: resp[i].Time.Time(), LastUpdated: resp[i].UpdateTime.Time(), Exchange: by.Name, OrderID: resp[i].OrderID, Side: side, Type: getTradeType(resp[i].TradeType), - Price: resp[i].Price, + Price: resp[i].Price.Float64(), Pair: pair, Status: getOrderStatus(resp[i].Status), } @@ -1731,16 +1735,16 @@ func (by *Bybit) GetOrderHistory(ctx context.Context, req *order.GetOrdersReques return nil, err } detail := order.Detail{ - Amount: resp[i].Qty, - ExecutedAmount: resp[i].TotalFilledQty, - RemainingAmount: resp[i].LeavesQty, + Amount: resp[i].Qty.Float64(), + ExecutedAmount: resp[i].TotalFilledQty.Float64(), + RemainingAmount: resp[i].LeavesQty.Float64(), Date: resp[i].CreatedAt.Time(), LastUpdated: resp[i].UpdatedAt.Time(), Exchange: by.Name, OrderID: resp[i].ID, Side: getSide(resp[i].Side), Type: orderType, - Price: resp[i].Price, + Price: resp[i].Price.Float64(), Pair: pair, Status: orderStatus, } @@ -1879,11 +1883,11 @@ func (by *Bybit) GetHistoricCandles(ctx context.Context, pair currency.Pair, a a for x := range candles { timeSeries[x] = kline.Candle{ Time: time.Unix(candles[x].OpenTime, 0), - Open: candles[x].Open, - High: candles[x].High, - Low: candles[x].Low, - Close: candles[x].Close, - Volume: candles[x].Volume, + Open: candles[x].Open.Float64(), + High: candles[x].High.Float64(), + Low: candles[x].Low.Float64(), + Close: candles[x].Close.Float64(), + Volume: candles[x].Volume.Float64(), } } case asset.USDTMarginedFutures: @@ -1923,11 +1927,11 @@ func (by *Bybit) GetHistoricCandles(ctx context.Context, pair currency.Pair, a a for x := range candles { timeSeries[x] = kline.Candle{ Time: candles[x].OpenTime.Time(), - Open: candles[x].Open, - High: candles[x].High, - Low: candles[x].Low, - Close: candles[x].Close, - Volume: candles[x].Volume, + Open: candles[x].Open.Float64(), + High: candles[x].High.Float64(), + Low: candles[x].Low.Float64(), + Close: candles[x].Close.Float64(), + Volume: candles[x].Volume.Float64(), } } default: @@ -1982,11 +1986,11 @@ func (by *Bybit) GetHistoricCandlesExtended(ctx context.Context, pair currency.P for i := range candles { timeSeries = append(timeSeries, kline.Candle{ Time: time.Unix(candles[i].OpenTime, 0), - Open: candles[i].Open, - High: candles[i].High, - Low: candles[i].Low, - Close: candles[i].Close, - Volume: candles[i].Volume, + Open: candles[i].Open.Float64(), + High: candles[i].High.Float64(), + Low: candles[i].Low.Float64(), + Close: candles[i].Close.Float64(), + Volume: candles[i].Volume.Float64(), }) } case asset.USDTMarginedFutures: @@ -2024,11 +2028,11 @@ func (by *Bybit) GetHistoricCandlesExtended(ctx context.Context, pair currency.P for x := range candles { timeSeries = append(timeSeries, kline.Candle{ Time: candles[x].OpenTime.Time(), - Open: candles[x].Open, - High: candles[x].High, - Low: candles[x].Low, - Close: candles[x].Close, - Volume: candles[x].Volume, + Open: candles[x].Open.Float64(), + High: candles[x].High.Float64(), + Low: candles[x].Low.Float64(), + Close: candles[x].Close.Float64(), + Volume: candles[x].Volume.Float64(), }) } default: @@ -2064,3 +2068,46 @@ func (by *Bybit) extractCurrencyPair(symbol string, item asset.Item) (currency.P } return pairs.DeriveFrom(symbol) } + +// UpdateOrderExecutionLimits sets exchange executions for a required asset type +func (by *Bybit) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error { + avail, err := by.GetAvailablePairs(a) + if err != nil { + return err + } + + var limits []order.MinMaxLevel + switch a { + case asset.Spot: + var pairsData []PairData + pairsData, err = by.GetAllSpotPairs(ctx) + if err != nil { + return err + } + + limits = make([]order.MinMaxLevel, 0, len(pairsData)) + for x := range pairsData { + var pair currency.Pair + pair, err = avail.DeriveFrom(pairsData[x].Name) + if err != nil { + return err + } + + limits = append(limits, order.MinMaxLevel{ + Asset: a, + Pair: pair, + AmountStepIncrementSize: pairsData[x].BasePrecision.Float64(), + QuoteStepIncrementSize: pairsData[x].QuotePrecision.Float64(), + MinimumBaseAmount: pairsData[x].MinTradeQuantity.Float64(), + MaximumBaseAmount: pairsData[x].MaxTradeQuantity.Float64(), + MinimumQuoteAmount: pairsData[x].MinTradeAmount.Float64(), + MaximumQuoteAmount: pairsData[x].MaxTradeAmount.Float64(), + PriceStepIncrementSize: pairsData[x].MinPricePrecision.Float64(), + }) + } + default: + // TODO: Add in other assets + return fmt.Errorf("%s %w", a, asset.ErrNotSupported) + } + return by.LoadLimits(limits) +} diff --git a/exchanges/bybit/bybit_ws_cfutures.go b/exchanges/bybit/bybit_ws_cfutures.go index e44da011..ec63ada3 100644 --- a/exchanges/bybit/bybit_ws_cfutures.go +++ b/exchanges/bybit/bybit_ws_cfutures.go @@ -389,13 +389,13 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { by.Websocket.DataHandler <- &ticker.Price{ ExchangeName: by.Name, - Last: response.Ticker.LastPrice, - High: response.Ticker.HighPrice24h, - Low: response.Ticker.LowPrice24h, + Last: response.Ticker.LastPrice.Float64(), + High: response.Ticker.HighPrice24h.Float64(), + Low: response.Ticker.LowPrice24h.Float64(), Bid: response.Ticker.BidPrice, Ask: response.Ticker.AskPrice, Volume: response.Ticker.Volume24h, - Close: response.Ticker.PrevPrice24h, + Close: response.Ticker.PrevPrice24h.Float64(), LastUpdated: response.Ticker.UpdateAt, AssetType: asset.CoinMarginedFutures, Pair: p, @@ -418,13 +418,13 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { by.Websocket.DataHandler <- &ticker.Price{ ExchangeName: by.Name, - Last: response.Data.Delete[x].LastPrice, - High: response.Data.Delete[x].HighPrice24h, - Low: response.Data.Delete[x].LowPrice24h, + Last: response.Data.Delete[x].LastPrice.Float64(), + High: response.Data.Delete[x].HighPrice24h.Float64(), + Low: response.Data.Delete[x].LowPrice24h.Float64(), Bid: response.Data.Delete[x].BidPrice, Ask: response.Data.Delete[x].AskPrice, Volume: response.Data.Delete[x].Volume24h, - Close: response.Data.Delete[x].PrevPrice24h, + Close: response.Data.Delete[x].PrevPrice24h.Float64(), LastUpdated: response.Data.Delete[x].UpdateAt, AssetType: asset.CoinMarginedFutures, Pair: p, @@ -442,13 +442,13 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { by.Websocket.DataHandler <- &ticker.Price{ ExchangeName: by.Name, - Last: response.Data.Update[x].LastPrice, - High: response.Data.Update[x].HighPrice24h, - Low: response.Data.Update[x].LowPrice24h, + Last: response.Data.Update[x].LastPrice.Float64(), + High: response.Data.Update[x].HighPrice24h.Float64(), + Low: response.Data.Update[x].LowPrice24h.Float64(), Bid: response.Data.Update[x].BidPrice, Ask: response.Data.Update[x].AskPrice, Volume: response.Data.Update[x].Volume24h, - Close: response.Data.Update[x].PrevPrice24h, + Close: response.Data.Update[x].PrevPrice24h.Float64(), LastUpdated: response.Data.Update[x].UpdateAt, AssetType: asset.CoinMarginedFutures, Pair: p, @@ -466,13 +466,13 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { by.Websocket.DataHandler <- &ticker.Price{ ExchangeName: by.Name, - Last: response.Data.Insert[x].LastPrice, - High: response.Data.Insert[x].HighPrice24h, - Low: response.Data.Insert[x].LowPrice24h, + Last: response.Data.Insert[x].LastPrice.Float64(), + High: response.Data.Insert[x].HighPrice24h.Float64(), + Low: response.Data.Insert[x].LowPrice24h.Float64(), Bid: response.Data.Insert[x].BidPrice, Ask: response.Data.Insert[x].AskPrice, Volume: response.Data.Insert[x].Volume24h, - Close: response.Data.Insert[x].PrevPrice24h, + Close: response.Data.Insert[x].PrevPrice24h.Float64(), LastUpdated: response.Data.Insert[x].UpdateAt, AssetType: asset.CoinMarginedFutures, Pair: p, @@ -540,13 +540,13 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { OrderID: response.Data[i].OrderID, AssetType: asset.CoinMarginedFutures, Pair: p, - Price: response.Data[i].Price, + Price: response.Data[i].Price.Float64(), Amount: response.Data[i].OrderQty, Side: oSide, Status: oStatus, Trades: []order.TradeHistory{ { - Price: response.Data[i].Price, + Price: response.Data[i].Price.Float64(), Amount: response.Data[i].OrderQty, Exchange: by.Name, Side: oSide, @@ -598,7 +598,7 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { } } by.Websocket.DataHandler <- &order.Detail{ - Price: response.Data[x].Price, + Price: response.Data[x].Price.Float64(), Amount: response.Data[x].OrderQty, Exchange: by.Name, OrderID: response.Data[x].OrderID, @@ -610,7 +610,7 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { Pair: p, Trades: []order.TradeHistory{ { - Price: response.Data[x].Price, + Price: response.Data[x].Price.Float64(), Amount: response.Data[x].OrderQty, Exchange: by.Name, Side: oSide, @@ -660,7 +660,7 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { } } by.Websocket.DataHandler <- &order.Detail{ - Price: response.Data[x].Price, + Price: response.Data[x].Price.Float64(), Amount: response.Data[x].OrderQty, Exchange: by.Name, OrderID: response.Data[x].OrderID, @@ -673,7 +673,7 @@ func (by *Bybit) wsCoinHandleData(respRaw []byte) error { Pair: p, Trades: []order.TradeHistory{ { - Price: response.Data[x].Price, + Price: response.Data[x].Price.Float64(), Amount: response.Data[x].OrderQty, Exchange: by.Name, Side: oSide, @@ -709,7 +709,7 @@ func (by *Bybit) processOrderbook(data []WsFuturesOrderbookData, action string, var book orderbook.Base for i := range data { item := orderbook.Item{ - Price: data[i].Price, + Price: data[i].Price.Float64(), Amount: data[i].Size, ID: data[i].ID, } @@ -741,7 +741,7 @@ func (by *Bybit) processOrderbook(data []WsFuturesOrderbookData, action string, var asks, bids []orderbook.Item for i := range data { item := orderbook.Item{ - Price: data[i].Price, + Price: data[i].Price.Float64(), Amount: data[i].Size, ID: data[i].ID, } diff --git a/exchanges/bybit/bybit_ws_futures.go b/exchanges/bybit/bybit_ws_futures.go index fc68e48c..8eb6fe51 100644 --- a/exchanges/bybit/bybit_ws_futures.go +++ b/exchanges/bybit/bybit_ws_futures.go @@ -334,13 +334,13 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { by.Websocket.DataHandler <- &ticker.Price{ ExchangeName: by.Name, - Last: response.Ticker.LastPrice, - High: response.Ticker.HighPrice24h, - Low: response.Ticker.LowPrice24h, + Last: response.Ticker.LastPrice.Float64(), + High: response.Ticker.HighPrice24h.Float64(), + Low: response.Ticker.LowPrice24h.Float64(), Bid: response.Ticker.BidPrice, Ask: response.Ticker.AskPrice, Volume: response.Ticker.Volume24h, - Close: response.Ticker.PrevPrice24h, + Close: response.Ticker.PrevPrice24h.Float64(), LastUpdated: response.Ticker.UpdateAt, AssetType: asset.Futures, Pair: p, @@ -363,13 +363,13 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { by.Websocket.DataHandler <- &ticker.Price{ ExchangeName: by.Name, - Last: response.Data.Delete[x].LastPrice, - High: response.Data.Delete[x].HighPrice24h, - Low: response.Data.Delete[x].LowPrice24h, + Last: response.Data.Delete[x].LastPrice.Float64(), + High: response.Data.Delete[x].HighPrice24h.Float64(), + Low: response.Data.Delete[x].LowPrice24h.Float64(), Bid: response.Data.Delete[x].BidPrice, Ask: response.Data.Delete[x].AskPrice, Volume: response.Data.Delete[x].Volume24h, - Close: response.Data.Delete[x].PrevPrice24h, + Close: response.Data.Delete[x].PrevPrice24h.Float64(), LastUpdated: response.Data.Delete[x].UpdateAt, AssetType: asset.Futures, Pair: p, @@ -387,13 +387,13 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { by.Websocket.DataHandler <- &ticker.Price{ ExchangeName: by.Name, - Last: response.Data.Update[x].LastPrice, - High: response.Data.Update[x].HighPrice24h, - Low: response.Data.Update[x].LowPrice24h, + Last: response.Data.Update[x].LastPrice.Float64(), + High: response.Data.Update[x].HighPrice24h.Float64(), + Low: response.Data.Update[x].LowPrice24h.Float64(), Bid: response.Data.Update[x].BidPrice, Ask: response.Data.Update[x].AskPrice, Volume: response.Data.Update[x].Volume24h, - Close: response.Data.Update[x].PrevPrice24h, + Close: response.Data.Update[x].PrevPrice24h.Float64(), LastUpdated: response.Data.Update[x].UpdateAt, AssetType: asset.Futures, Pair: p, @@ -411,13 +411,13 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { by.Websocket.DataHandler <- &ticker.Price{ ExchangeName: by.Name, - Last: response.Data.Insert[x].LastPrice, - High: response.Data.Insert[x].HighPrice24h, - Low: response.Data.Insert[x].LowPrice24h, + Last: response.Data.Insert[x].LastPrice.Float64(), + High: response.Data.Insert[x].HighPrice24h.Float64(), + Low: response.Data.Insert[x].LowPrice24h.Float64(), Bid: response.Data.Insert[x].BidPrice, Ask: response.Data.Insert[x].AskPrice, Volume: response.Data.Insert[x].Volume24h, - Close: response.Data.Insert[x].PrevPrice24h, + Close: response.Data.Insert[x].PrevPrice24h.Float64(), LastUpdated: response.Data.Insert[x].UpdateAt, AssetType: asset.Futures, Pair: p, @@ -485,13 +485,13 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { OrderID: response.Data[i].OrderID, AssetType: asset.Futures, Pair: p, - Price: response.Data[i].Price, + Price: response.Data[i].Price.Float64(), Amount: response.Data[i].OrderQty, Side: oSide, Status: oStatus, Trades: []order.TradeHistory{ { - Price: response.Data[i].Price, + Price: response.Data[i].Price.Float64(), Amount: response.Data[i].OrderQty, Exchange: by.Name, Side: oSide, @@ -541,7 +541,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { } } by.Websocket.DataHandler <- &order.Detail{ - Price: response.Data[x].Price, + Price: response.Data[x].Price.Float64(), Amount: response.Data[x].OrderQty, Exchange: by.Name, OrderID: response.Data[x].OrderID, @@ -553,7 +553,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { Pair: p, Trades: []order.TradeHistory{ { - Price: response.Data[x].Price, + Price: response.Data[x].Price.Float64(), Amount: response.Data[x].OrderQty, Exchange: by.Name, Side: oSide, @@ -603,7 +603,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { } } by.Websocket.DataHandler <- &order.Detail{ - Price: response.Data[x].Price, + Price: response.Data[x].Price.Float64(), Amount: response.Data[x].OrderQty, Exchange: by.Name, OrderID: response.Data[x].OrderID, @@ -616,7 +616,7 @@ func (by *Bybit) wsFuturesHandleData(respRaw []byte) error { Pair: p, Trades: []order.TradeHistory{ { - Price: response.Data[x].Price, + Price: response.Data[x].Price.Float64(), Amount: response.Data[x].OrderQty, Exchange: by.Name, Side: oSide, diff --git a/exchanges/bybit/bybit_ws_ufutures.go b/exchanges/bybit/bybit_ws_ufutures.go index 6adafe6e..2a42ec18 100644 --- a/exchanges/bybit/bybit_ws_ufutures.go +++ b/exchanges/bybit/bybit_ws_ufutures.go @@ -340,13 +340,13 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { by.Websocket.DataHandler <- &ticker.Price{ ExchangeName: by.Name, - Last: response.Ticker.LastPrice, - High: response.Ticker.HighPrice24h, - Low: response.Ticker.LowPrice24h, + Last: response.Ticker.LastPrice.Float64(), + High: response.Ticker.HighPrice24h.Float64(), + Low: response.Ticker.LowPrice24h.Float64(), Bid: response.Ticker.BidPrice, Ask: response.Ticker.AskPrice, Volume: response.Ticker.Volume24h, - Close: response.Ticker.PrevPrice24h, + Close: response.Ticker.PrevPrice24h.Float64(), LastUpdated: response.Ticker.UpdateAt, AssetType: asset.USDTMarginedFutures, Pair: p, @@ -369,13 +369,13 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { by.Websocket.DataHandler <- &ticker.Price{ ExchangeName: by.Name, - Last: response.Data.Delete[x].LastPrice, - High: response.Data.Delete[x].HighPrice24h, - Low: response.Data.Delete[x].LowPrice24h, + Last: response.Data.Delete[x].LastPrice.Float64(), + High: response.Data.Delete[x].HighPrice24h.Float64(), + Low: response.Data.Delete[x].LowPrice24h.Float64(), Bid: response.Data.Delete[x].BidPrice, Ask: response.Data.Delete[x].AskPrice, Volume: response.Data.Delete[x].Volume24h, - Close: response.Data.Delete[x].PrevPrice24h, + Close: response.Data.Delete[x].PrevPrice24h.Float64(), LastUpdated: response.Data.Delete[x].UpdateAt, AssetType: asset.USDTMarginedFutures, Pair: p, @@ -393,13 +393,13 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { by.Websocket.DataHandler <- &ticker.Price{ ExchangeName: by.Name, - Last: response.Data.Update[x].LastPrice, - High: response.Data.Update[x].HighPrice24h, - Low: response.Data.Update[x].LowPrice24h, + Last: response.Data.Update[x].LastPrice.Float64(), + High: response.Data.Update[x].HighPrice24h.Float64(), + Low: response.Data.Update[x].LowPrice24h.Float64(), Bid: response.Data.Update[x].BidPrice, Ask: response.Data.Update[x].AskPrice, Volume: response.Data.Update[x].Volume24h, - Close: response.Data.Update[x].PrevPrice24h, + Close: response.Data.Update[x].PrevPrice24h.Float64(), LastUpdated: response.Data.Update[x].UpdateAt, AssetType: asset.USDTMarginedFutures, Pair: p, @@ -417,13 +417,13 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { by.Websocket.DataHandler <- &ticker.Price{ ExchangeName: by.Name, - Last: response.Data.Insert[x].LastPrice, - High: response.Data.Insert[x].HighPrice24h, - Low: response.Data.Insert[x].LowPrice24h, + Last: response.Data.Insert[x].LastPrice.Float64(), + High: response.Data.Insert[x].HighPrice24h.Float64(), + Low: response.Data.Insert[x].LowPrice24h.Float64(), Bid: response.Data.Insert[x].BidPrice, Ask: response.Data.Insert[x].AskPrice, Volume: response.Data.Insert[x].Volume24h, - Close: response.Data.Insert[x].PrevPrice24h, + Close: response.Data.Insert[x].PrevPrice24h.Float64(), LastUpdated: response.Data.Insert[x].UpdateAt, AssetType: asset.USDTMarginedFutures, Pair: p, @@ -493,11 +493,11 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { Pair: p, Side: oSide, Status: oStatus, - Price: response.Data[i].Price, + Price: response.Data[i].Price.Float64(), Amount: response.Data[i].OrderQty, Trades: []order.TradeHistory{ { - Price: response.Data[i].Price, + Price: response.Data[i].Price.Float64(), Amount: response.Data[i].OrderQty, Exchange: by.Name, Side: oSide, @@ -547,7 +547,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { } } by.Websocket.DataHandler <- &order.Detail{ - Price: response.Data[x].Price, + Price: response.Data[x].Price.Float64(), Amount: response.Data[x].OrderQty, Exchange: by.Name, OrderID: response.Data[x].OrderID, @@ -559,7 +559,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { Pair: p, Trades: []order.TradeHistory{ { - Price: response.Data[x].Price, + Price: response.Data[x].Price.Float64(), Amount: response.Data[x].OrderQty, Exchange: by.Name, Side: oSide, @@ -609,7 +609,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { } } by.Websocket.DataHandler <- &order.Detail{ - Price: response.Data[x].Price, + Price: response.Data[x].Price.Float64(), Amount: response.Data[x].OrderQty, Exchange: by.Name, OrderID: response.Data[x].OrderID, @@ -622,7 +622,7 @@ func (by *Bybit) wsUSDTHandleData(respRaw []byte) error { Pair: p, Trades: []order.TradeHistory{ { - Price: response.Data[x].Price, + Price: response.Data[x].Price.Float64(), Amount: response.Data[x].OrderQty, Exchange: by.Name, Side: oSide, diff --git a/exchanges/bybit/futures_type.go b/exchanges/bybit/futures_type.go index 2f365671..b7836893 100644 --- a/exchanges/bybit/futures_type.go +++ b/exchanges/bybit/futures_type.go @@ -1,6 +1,10 @@ package bybit -import "time" +import ( + "time" + + "github.com/thrasher-corp/gocryptotrader/common/convert" +) var ( validFuturesIntervals = []string{ @@ -15,10 +19,10 @@ var ( // OrderbookData stores ob data for cmargined futures type OrderbookData struct { - Symbol string `json:"symbol"` - Price float64 `json:"price,string"` - Size float64 `json:"size"` - Side string `json:"side"` + Symbol string `json:"symbol"` + Price convert.StringToFloat64 `json:"price"` + Size float64 `json:"size"` + Side string `json:"side"` } // FuturesCandleStick holds kline data @@ -37,46 +41,46 @@ type FuturesCandleStick struct { // FuturesCandleStickWithStringParam holds kline data type FuturesCandleStickWithStringParam struct { - ID int64 `json:"id"` - Symbol string `json:"symbol"` - Interval string `json:"interval"` - OpenTime int64 `json:"open_time"` - Open float64 `json:"open,string"` - High float64 `json:"high,string"` - Low float64 `json:"low,string"` - Close float64 `json:"close,string"` - Volume float64 `json:"volume,string"` - TurnOver float64 `json:"turnover,string"` + ID int64 `json:"id"` + Symbol string `json:"symbol"` + Interval string `json:"interval"` + OpenTime int64 `json:"open_time"` + Open convert.StringToFloat64 `json:"open"` + High convert.StringToFloat64 `json:"high"` + Low convert.StringToFloat64 `json:"low"` + Close convert.StringToFloat64 `json:"close"` + Volume convert.StringToFloat64 `json:"volume"` + TurnOver convert.StringToFloat64 `json:"turnover"` } // SymbolPriceTicker stores ticker price stats type SymbolPriceTicker struct { - Symbol string `json:"symbol"` - BidPrice float64 `json:"bid_price,string"` - AskPrice float64 `json:"ask_price,string"` - LastPrice float64 `json:"last_price,string"` - LastTickDirection string `json:"last_tick_direction"` - Price24hAgo float64 `json:"prev_price_24h,string"` - PricePcntChange24h float64 `json:"price_24h_pcnt,string"` - HighPrice24h float64 `json:"high_price_24h,string"` - LowPrice24h float64 `json:"low_price_24h,string"` - Price1hAgo float64 `json:"prev_price_1h,string"` - PricePcntChange1h bybitNumericalValue `json:"price_1h_pcnt"` - MarkPrice float64 `json:"mark_price,string"` - IndexPrice float64 `json:"index_price,string"` - OpenInterest float64 `json:"open_interest"` - OpenValue bybitNumericalValue `json:"open_value"` - TotalTurnover bybitNumericalValue `json:"total_turnover"` - Turnover24h float64 `json:"turnover_24h,string"` - TotalVolume float64 `json:"total_volume"` - Volume24h float64 `json:"volume_24h"` - FundingRate float64 `json:"funding_rate,string"` - PredictedFundingRate bybitNumericalValue `json:"predicted_funding_rate"` - NextFundingTime string `json:"next_funding_time"` - CountdownHour int64 `json:"countdown_hour"` - DeliveryFeeRate bybitNumericalValue `json:"delivery_fee_rate"` - PredictedDeliveryPrice bybitNumericalValue `json:"predicted_delivery_price"` - DeliveryTime string `json:"delivery_time"` + Symbol string `json:"symbol"` + BidPrice convert.StringToFloat64 `json:"bid_price"` + AskPrice convert.StringToFloat64 `json:"ask_price"` + LastPrice convert.StringToFloat64 `json:"last_price"` + LastTickDirection string `json:"last_tick_direction"` + Price24hAgo convert.StringToFloat64 `json:"prev_price_24h"` + PricePcntChange24h convert.StringToFloat64 `json:"price_24h_pcnt"` + HighPrice24h convert.StringToFloat64 `json:"high_price_24h"` + LowPrice24h convert.StringToFloat64 `json:"low_price_24h"` + Price1hAgo convert.StringToFloat64 `json:"prev_price_1h"` + PricePcntChange1h convert.StringToFloat64 `json:"price_1h_pcnt"` + MarkPrice convert.StringToFloat64 `json:"mark_price"` + IndexPrice convert.StringToFloat64 `json:"index_price"` + OpenInterest float64 `json:"open_interest"` + OpenValue convert.StringToFloat64 `json:"open_value"` + TotalTurnover convert.StringToFloat64 `json:"total_turnover"` + Turnover24h convert.StringToFloat64 `json:"turnover_24h"` + TotalVolume float64 `json:"total_volume"` + Volume24h float64 `json:"volume_24h"` + FundingRate convert.StringToFloat64 `json:"funding_rate"` + PredictedFundingRate convert.StringToFloat64 `json:"predicted_funding_rate"` + NextFundingTime string `json:"next_funding_time"` + CountdownHour int64 `json:"countdown_hour"` + DeliveryFeeRate convert.StringToFloat64 `json:"delivery_fee_rate"` + PredictedDeliveryPrice convert.StringToFloat64 `json:"predicted_delivery_price"` + DeliveryTime string `json:"delivery_time"` } // FuturesPublicTradesData stores recent public trades for futures @@ -101,14 +105,14 @@ type SymbolInfo struct { MakerFee string `json:"maker_fee"` FundingFeeInterval int64 `json:"funding_interval"` LeverageFilter struct { - MinLeverage float64 `json:"min_leverage"` - MaxLeverage float64 `json:"max_leverage"` - LeverageStep float64 `json:"leverage_step,string"` + MinLeverage float64 `json:"min_leverage"` + MaxLeverage float64 `json:"max_leverage"` + LeverageStep convert.StringToFloat64 `json:"leverage_step"` } `json:"leverage_filter"` PriceFilter struct { - MinPrice float64 `json:"min_price,string"` - MaxPrice float64 `json:"max_price,string"` - TickSize float64 `json:"tick_size,string"` + MinPrice convert.StringToFloat64 `json:"min_price"` + MaxPrice convert.StringToFloat64 `json:"max_price"` + TickSize convert.StringToFloat64 `json:"tick_size"` } `json:"price_filter"` LotSizeFilter struct { MinTradeQty float64 `json:"min_trading_qty"` @@ -131,13 +135,13 @@ type MarkPriceKlineData struct { // IndexPriceKlineData stores index price kline data type IndexPriceKlineData struct { - Symbol string `json:"symbol"` - Interval string `json:"period"` - StartAt int64 `json:"open_time"` - Open float64 `json:"open,string"` - High float64 `json:"high,string"` - Low float64 `json:"low,string"` - Close float64 `json:"close,string"` + Symbol string `json:"symbol"` + Interval string `json:"period"` + StartAt int64 `json:"open_time"` + Open convert.StringToFloat64 `json:"open"` + High convert.StringToFloat64 `json:"high"` + Low convert.StringToFloat64 `json:"low"` + Close convert.StringToFloat64 `json:"close"` } // OpenInterestData stores open interest data @@ -242,35 +246,35 @@ type FuturesRealtimeOrderData struct { // FuturesActiveRealtimeOrder stores future active realtime order type FuturesActiveRealtimeOrder struct { FuturesRealtimeOrderData - ExtensionField map[string]interface{} `json:"ext_fields"` - LastExecutionTime string `json:"last_exec_time"` - LastExecutionPrice float64 `json:"last_exec_price"` - LeavesQty float64 `json:"leaves_qty"` - LeaveValue float64 `json:"leaves_value,string"` - CumulativeQty float64 `json:"cum_exec_qty,string"` - CumulativeValue float64 `json:"cum_exec_value,string"` - CumulativeFee float64 `json:"cum_exec_fee,string"` - RejectReason string `json:"reject_reason"` - CancelType string `json:"cancel_type"` - CreatedAt time.Time `json:"create_at"` - UpdatedAt time.Time `json:"updated_at"` - OrderID string `json:"order_id"` + ExtensionField map[string]interface{} `json:"ext_fields"` + LastExecutionTime string `json:"last_exec_time"` + LastExecutionPrice float64 `json:"last_exec_price"` + LeavesQty float64 `json:"leaves_qty"` + LeaveValue convert.StringToFloat64 `json:"leaves_value"` + CumulativeQty convert.StringToFloat64 `json:"cum_exec_qty"` + CumulativeValue convert.StringToFloat64 `json:"cum_exec_value"` + CumulativeFee convert.StringToFloat64 `json:"cum_exec_fee"` + RejectReason string `json:"reject_reason"` + CancelType string `json:"cancel_type"` + CreatedAt time.Time `json:"create_at"` + UpdatedAt time.Time `json:"updated_at"` + OrderID string `json:"order_id"` } // CoinFuturesConditionalRealtimeOrder stores CMF future coinditional realtime order type CoinFuturesConditionalRealtimeOrder struct { FuturesRealtimeOrderData - ExtensionField map[string]interface{} `json:"ext_fields"` - LeavesQty float64 `json:"leaves_qty"` - LeaveValue float64 `json:"leaves_value,string"` - CumulativeQty float64 `json:"cum_exec_qty,string"` - CumulativeValue float64 `json:"cum_exec_value,string"` - CumulativeFee float64 `json:"cum_exec_fee,string"` - RejectReason string `json:"reject_reason"` - CancelType string `json:"cancel_type"` - CreatedAt string `json:"create_at"` - UpdatedAt string `json:"updated_at"` - OrderID string `json:"order_id"` + ExtensionField map[string]interface{} `json:"ext_fields"` + LeavesQty float64 `json:"leaves_qty"` + LeaveValue convert.StringToFloat64 `json:"leaves_value"` + CumulativeQty convert.StringToFloat64 `json:"cum_exec_qty"` + CumulativeValue convert.StringToFloat64 `json:"cum_exec_value"` + CumulativeFee convert.StringToFloat64 `json:"cum_exec_fee"` + RejectReason string `json:"reject_reason"` + CancelType string `json:"cancel_type"` + CreatedAt string `json:"create_at"` + UpdatedAt string `json:"updated_at"` + OrderID string `json:"order_id"` } // FuturesConditionalRealtimeOrder stores future conditional realtime order @@ -375,10 +379,10 @@ type FuturesCancelOrderData struct { // FuturesCancelOrderResp stores future cancel order response type FuturesCancelOrderResp struct { FuturesCancelOrderData - StopOrderType string `json:"stop_order_type"` - TriggerBy string `json:"trigger_by"` - BasePrice float64 `json:"base_price,string"` - ExpectedDirection string `json:"expected_direction"` + StopOrderType string `json:"stop_order_type"` + TriggerBy string `json:"trigger_by"` + BasePrice convert.StringToFloat64 `json:"base_price"` + ExpectedDirection string `json:"expected_direction"` } // RiskInfo stores risk information @@ -397,23 +401,23 @@ type RiskInfo struct { // RiskInfoWithStringParam stores risk information where string params type RiskInfoWithStringParam struct { - ID int64 `json:"id"` - Symbol string `json:"symbol"` - Limit int64 `json:"limit"` - MaintainMargin float64 `json:"maintain_margin,string"` - StartingMargin float64 `json:"starting_margin,string"` - Section []string `json:"section"` - IsLowestRisk int64 `json:"is_lowest_risk"` - CreatedAt string `json:"create_at"` - UpdateAt string `json:"updated_at"` - MaxLeverage float64 `json:"max_leverage,string"` + ID int64 `json:"id"` + Symbol string `json:"symbol"` + Limit int64 `json:"limit"` + MaintainMargin convert.StringToFloat64 `json:"maintain_margin"` + StartingMargin convert.StringToFloat64 `json:"starting_margin"` + Section []string `json:"section"` + IsLowestRisk int64 `json:"is_lowest_risk"` + CreatedAt string `json:"create_at"` + UpdateAt string `json:"updated_at"` + MaxLeverage convert.StringToFloat64 `json:"max_leverage"` } // FundingInfo stores funding information type FundingInfo struct { - Symbol string `json:"symbol"` - FundingRate float64 `json:"funding_rate,string"` - FundingRateTimestamp int64 `json:"funding_rate_timestamp"` + Symbol string `json:"symbol"` + FundingRate convert.StringToFloat64 `json:"funding_rate"` + FundingRateTimestamp int64 `json:"funding_rate_timestamp"` } // USDTFundingInfo stores USDT funding information @@ -451,19 +455,19 @@ type Position struct { // PositionWithStringParam stores position with string params type PositionWithStringParam struct { - UserID int64 `json:"user_id"` - Symbol string `json:"symbol"` - Side string `json:"side"` - Size float64 `json:"size"` - PositionValue float64 `json:"position_value,string"` - EntryPrice float64 `json:"entry_price,string"` - LiquidationPrice float64 `json:"liq_price,string"` - BankruptcyPrice float64 `json:"bust_price,string"` - Leverage float64 `json:"leverage,string"` - PositionMargin float64 `json:"position_margin,string"` - OccupiedClosingFee float64 `json:"occ_closing_fee,string"` - RealisedPNL float64 `json:"realised_pnl,string"` - AccumulatedRealisedPNL float64 `json:"cum_realised_pnl,string"` + UserID int64 `json:"user_id"` + Symbol string `json:"symbol"` + Side string `json:"side"` + Size float64 `json:"size"` + PositionValue convert.StringToFloat64 `json:"position_value"` + EntryPrice convert.StringToFloat64 `json:"entry_price"` + LiquidationPrice convert.StringToFloat64 `json:"liq_price"` + BankruptcyPrice convert.StringToFloat64 `json:"bust_price"` + Leverage convert.StringToFloat64 `json:"leverage"` + PositionMargin convert.StringToFloat64 `json:"position_margin"` + OccupiedClosingFee convert.StringToFloat64 `json:"occ_closing_fee"` + RealisedPNL convert.StringToFloat64 `json:"realised_pnl"` + AccumulatedRealisedPNL convert.StringToFloat64 `json:"cum_realised_pnl"` } // PositionData stores position data @@ -482,54 +486,54 @@ type PositionData struct { // PositionDataWithStringParam stores position data with string params type PositionDataWithStringParam struct { PositionWithStringParam - IsIsolated bool `json:"is_isolated"` - AutoAddMargin int64 `json:"auto_add_margin"` - UnrealisedPNL float64 `json:"unrealised_pnl"` - DeleverageIndicator int64 `json:"deleverage_indicator"` - RiskID int64 `json:"risk_id"` - TakeProfit float64 `json:"take_profit,string"` - StopLoss float64 `json:"stop_loss,string"` - TrailingStop float64 `json:"trailing_stop,string"` + IsIsolated bool `json:"is_isolated"` + AutoAddMargin int64 `json:"auto_add_margin"` + UnrealisedPNL float64 `json:"unrealised_pnl"` + DeleverageIndicator int64 `json:"deleverage_indicator"` + RiskID int64 `json:"risk_id"` + TakeProfit convert.StringToFloat64 `json:"take_profit"` + StopLoss convert.StringToFloat64 `json:"stop_loss"` + TrailingStop convert.StringToFloat64 `json:"trailing_stop"` } // PositionResp stores position response type PositionResp struct { PositionDataWithStringParam - PositionID int64 `json:"position_idx"` - Mode int64 `json:"mode"` - ID int64 `json:"id"` - EffectiveLeverage float64 `json:"effective_leverage,string"` - OccupiedFundingFee float64 `json:"occ_funding_fee,string"` - PositionStatus string `json:"position_status"` - CalculatedData string `json:"oc_calc_data"` - OrderMargin float64 `json:"order_margin,string"` - WalletBalance float64 `json:"wallet_balance,string"` - CrossSequence int64 `json:"cross_seq"` - PositionSequence int64 `json:"position_seq"` - TakeProfitStopLossMode string `json:"tp_sl_mode"` - CreatedAt string `json:"created_at"` - UpdateAt string `json:"updated_at"` + PositionID int64 `json:"position_idx"` + Mode int64 `json:"mode"` + ID int64 `json:"id"` + EffectiveLeverage convert.StringToFloat64 `json:"effective_leverage"` + OccupiedFundingFee convert.StringToFloat64 `json:"occ_funding_fee"` + PositionStatus string `json:"position_status"` + CalculatedData string `json:"oc_calc_data"` + OrderMargin convert.StringToFloat64 `json:"order_margin"` + WalletBalance convert.StringToFloat64 `json:"wallet_balance"` + CrossSequence int64 `json:"cross_seq"` + PositionSequence int64 `json:"position_seq"` + TakeProfitStopLossMode string `json:"tp_sl_mode"` + CreatedAt string `json:"created_at"` + UpdateAt string `json:"updated_at"` } // SetTradingAndStopResp stores set trading and stop response type SetTradingAndStopResp struct { PositionData - ID int64 `json:"id"` - RiskID int64 `json:"risk_id"` - AutoAddMargin int64 `json:"auto_add_margin"` - OccupiedFundingFee float64 `json:"occ_funding_fee,string"` - TakeProfit float64 `json:"take_profit,string"` - StopLoss float64 `json:"stop_loss,string"` - PositionStatus string `json:"position_status"` - DeleverageIndicator int64 `json:"deleverage_indicator"` - CalculatedData string `json:"oc_calc_data"` - OrderMargin float64 `json:"order_margin,string"` - WalletBalance float64 `json:"wallet_balance,string"` - CrossSequence int64 `json:"cross_seq"` - PositionSequence int64 `json:"position_seq"` - CreatedAt string `json:"created_at"` - UpdateAt string `json:"updated_at"` - ExtensionField map[string]interface{} `json:"ext_fields"` + ID int64 `json:"id"` + RiskID int64 `json:"risk_id"` + AutoAddMargin int64 `json:"auto_add_margin"` + OccupiedFundingFee convert.StringToFloat64 `json:"occ_funding_fee"` + TakeProfit convert.StringToFloat64 `json:"take_profit"` + StopLoss convert.StringToFloat64 `json:"stop_loss"` + PositionStatus string `json:"position_status"` + DeleverageIndicator int64 `json:"deleverage_indicator"` + CalculatedData string `json:"oc_calc_data"` + OrderMargin convert.StringToFloat64 `json:"order_margin"` + WalletBalance convert.StringToFloat64 `json:"wallet_balance"` + CrossSequence int64 `json:"cross_seq"` + PositionSequence int64 `json:"position_seq"` + CreatedAt string `json:"created_at"` + UpdateAt string `json:"updated_at"` + ExtensionField map[string]interface{} `json:"ext_fields"` } // USDTPositionResp stores USDT position response @@ -547,24 +551,24 @@ type UpdateMarginResp struct { // TradeData stores trade data type TradeData struct { - OrderID string `json:"order_id"` - OrderLinkedID string `json:"order_link_id"` - OrderSide string `json:"side"` - Symbol string `json:"symbol"` - ExecutionID string `json:"exec_id"` - OrderPrice float64 `json:"order_price"` - OrderQty float64 `json:"order_qty"` - OrderType string `json:"order_type"` - FeeRate float64 `json:"fee_rate"` - ExecutionFee float64 `json:"exec_fee,string"` - ExecutionPrice float64 `json:"exec_price,string"` - ExecutionQty float64 `json:"exec_qty"` - ExecutionType string `json:"exec_type"` - ExecutionValue float64 `json:"exec_value,string"` - LeavesQty float64 `json:"leaves_qty"` - ClosedSize float64 `json:"closed_size"` - LastLiquidity string `json:"last_liquidity_ind"` - TradeTimeMs int64 `json:"trade_time_ms"` + OrderID string `json:"order_id"` + OrderLinkedID string `json:"order_link_id"` + OrderSide string `json:"side"` + Symbol string `json:"symbol"` + ExecutionID string `json:"exec_id"` + OrderPrice float64 `json:"order_price"` + OrderQty float64 `json:"order_qty"` + OrderType string `json:"order_type"` + FeeRate float64 `json:"fee_rate"` + ExecutionFee convert.StringToFloat64 `json:"exec_fee"` + ExecutionPrice convert.StringToFloat64 `json:"exec_price"` + ExecutionQty float64 `json:"exec_qty"` + ExecutionType string `json:"exec_type"` + ExecutionValue convert.StringToFloat64 `json:"exec_value"` + LeavesQty float64 `json:"leaves_qty"` + ClosedSize float64 `json:"closed_size"` + LastLiquidity string `json:"last_liquidity_ind"` + TradeTimeMs int64 `json:"trade_time_ms"` } // TradeResp stores trade response @@ -650,30 +654,30 @@ type WalletData struct { // FundRecord stores funding records type FundRecord struct { - ID int64 `json:"id"` - UserID int64 `json:"user_id"` - Coin string `json:"coin"` - Type string `json:"type"` - Amount float64 `json:"amount,string"` - TxID string `json:"tx_id"` - Address string `json:"address"` - WalletBalance float64 `json:"wallet_balance,string"` - ExecutionTime string `json:"exec_time"` - CrossSequence int64 `json:"cross_seq"` + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + Coin string `json:"coin"` + Type string `json:"type"` + Amount convert.StringToFloat64 `json:"amount"` + TxID string `json:"tx_id"` + Address string `json:"address"` + WalletBalance convert.StringToFloat64 `json:"wallet_balance"` + ExecutionTime string `json:"exec_time"` + CrossSequence int64 `json:"cross_seq"` } // FundWithdrawalRecord stores funding withdrawal records type FundWithdrawalRecord struct { - ID int64 `json:"id"` - UserID int64 `json:"user_id"` - Coin string `json:"coin"` - Status string `json:"status"` - Amount float64 `json:"amount,string"` - Fee float64 `json:"fee"` - Address string `json:"address"` - TxID string `json:"tx_id"` - SubmittedAt time.Time `json:"submited_at"` - UpdatedAt time.Time `json:"updated_at"` + ID int64 `json:"id"` + UserID int64 `json:"user_id"` + Coin string `json:"coin"` + Status string `json:"status"` + Amount convert.StringToFloat64 `json:"amount"` + Fee float64 `json:"fee"` + Address string `json:"address"` + TxID string `json:"tx_id"` + SubmittedAt time.Time `json:"submited_at"` + UpdatedAt time.Time `json:"updated_at"` } // AssetExchangeRecord stores asset exchange records @@ -690,79 +694,79 @@ type AssetExchangeRecord struct { // USDCOrderbookData stores orderbook data for USDCMarginedFutures type USDCOrderbookData struct { - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - Side string `json:"side"` + Price convert.StringToFloat64 `json:"price"` + Size convert.StringToFloat64 `json:"size"` + Side string `json:"side"` } // USDCContract stores contract data type USDCContract struct { - Symbol string `json:"symbol"` - Status string `json:"status"` - BaseCoin string `json:"baseCoin"` - QuoteCoin string `json:"quoteCoin"` - TakerFeeRate float64 `json:"takerFeeRate,string"` - MakerFeeRate float64 `json:"makerFeeRate,string"` - MinLeverage float64 `json:"minLeverage,string"` - MaxLeverage float64 `json:"maxLeverage,string"` - LeverageStep float64 `json:"leverageStep,string"` - MinPrice float64 `json:"minPrice,string"` - MaxPrice float64 `json:"maxPrice,string"` - TickSize float64 `json:"tickSize,string"` - MaxTradingQty float64 `json:"maxTradingQty,string"` - MinTradingQty float64 `json:"minTradingQty,string"` - QtyStep float64 `json:"qtyStep,string"` - DeliveryTime bybitTimeMilliSecStr `json:"deliveryTime"` + Symbol string `json:"symbol"` + Status string `json:"status"` + BaseCoin string `json:"baseCoin"` + QuoteCoin string `json:"quoteCoin"` + TakerFeeRate convert.StringToFloat64 `json:"takerFeeRate"` + MakerFeeRate convert.StringToFloat64 `json:"makerFeeRate"` + MinLeverage convert.StringToFloat64 `json:"minLeverage"` + MaxLeverage convert.StringToFloat64 `json:"maxLeverage"` + LeverageStep convert.StringToFloat64 `json:"leverageStep"` + MinPrice convert.StringToFloat64 `json:"minPrice"` + MaxPrice convert.StringToFloat64 `json:"maxPrice"` + TickSize convert.StringToFloat64 `json:"tickSize"` + MaxTradingQty convert.StringToFloat64 `json:"maxTradingQty"` + MinTradingQty convert.StringToFloat64 `json:"minTradingQty"` + QtyStep convert.StringToFloat64 `json:"qtyStep"` + DeliveryTime bybitTimeMilliSecStr `json:"deliveryTime"` } // USDCSymbol stores symbol data type USDCSymbol struct { - Symbol string `json:"symbol"` - NextFundingTime string `json:"nextFundingTime"` - Bid float64 `json:"bid,string"` - BidSize float64 `json:"bidSize,string"` - Ask float64 `json:"ask,string"` - AskSize float64 `json:"askSize,string"` - LastPrice float64 `json:"lastPrice,string"` - OpenInterest float64 `json:"openInterest,string"` - IndexPrice float64 `json:"indexPrice,string"` - MarkPrice float64 `json:"markPrice,string"` - Change24h float64 `json:"change24h,string"` - High24h float64 `json:"high24h,string"` - Low24h float64 `json:"low24h,string"` - Volume24h float64 `json:"volume24h,string"` - Turnover24h float64 `json:"turnover24h,string"` - TotalVolume float64 `json:"totalVolume,string"` - TotalTurnover float64 `json:"totalTurnover,string"` - FundingRate float64 `json:"fundingRate,string"` - PredictedFundingRate float64 `json:"predictedFundingRate,string"` - CountdownHour float64 `json:"countdownHour,string"` - UnderlyingPrice string `json:"underlyingPrice"` + Symbol string `json:"symbol"` + NextFundingTime string `json:"nextFundingTime"` + Bid convert.StringToFloat64 `json:"bid"` + BidSize convert.StringToFloat64 `json:"bidSize"` + Ask convert.StringToFloat64 `json:"ask"` + AskSize convert.StringToFloat64 `json:"askSize"` + LastPrice convert.StringToFloat64 `json:"lastPrice"` + OpenInterest convert.StringToFloat64 `json:"openInterest"` + IndexPrice convert.StringToFloat64 `json:"indexPrice"` + MarkPrice convert.StringToFloat64 `json:"markPrice"` + Change24h convert.StringToFloat64 `json:"change24h"` + High24h convert.StringToFloat64 `json:"high24h"` + Low24h convert.StringToFloat64 `json:"low24h"` + Volume24h convert.StringToFloat64 `json:"volume24h"` + Turnover24h convert.StringToFloat64 `json:"turnover24h"` + TotalVolume convert.StringToFloat64 `json:"totalVolume"` + TotalTurnover convert.StringToFloat64 `json:"totalTurnover"` + FundingRate convert.StringToFloat64 `json:"fundingRate"` + PredictedFundingRate convert.StringToFloat64 `json:"predictedFundingRate"` + CountdownHour convert.StringToFloat64 `json:"countdownHour"` + UnderlyingPrice string `json:"underlyingPrice"` } // USDCKlineBase stores Kline Base type USDCKlineBase struct { - Symbol string `json:"symbol"` - Period string `json:"period"` - OpenTime bybitTimeSecStr `json:"openTime"` - Open float64 `json:"open,string"` - High float64 `json:"high,string"` - Low float64 `json:"low,string"` - Close float64 `json:"close,string"` + Symbol string `json:"symbol"` + Period string `json:"period"` + OpenTime bybitTimeSecStr `json:"openTime"` + Open convert.StringToFloat64 `json:"open"` + High convert.StringToFloat64 `json:"high"` + Low convert.StringToFloat64 `json:"low"` + Close convert.StringToFloat64 `json:"close"` } // USDCKline stores kline data type USDCKline struct { USDCKlineBase - Volume float64 `json:"volume,string"` - Turnover float64 `json:"turnover,string"` + Volume convert.StringToFloat64 `json:"volume"` + Turnover convert.StringToFloat64 `json:"turnover"` } // USDCOpenInterest stores open interest data type USDCOpenInterest struct { - Symbol string `json:"symbol"` - Timestamp bybitTimeMilliSecStr `json:"timestamp"` - OpenInterest float64 `json:"openInterest,string"` + Symbol string `json:"symbol"` + Timestamp bybitTimeMilliSecStr `json:"timestamp"` + OpenInterest convert.StringToFloat64 `json:"openInterest"` } // USDCLargeOrder stores large order data @@ -783,184 +787,191 @@ type USDCAccountRatio struct { // USDCTrade stores trade data type USDCTrade struct { - ID string `json:"id"` - Symbol string `json:"symbol"` - OrderPrice float64 `json:"orderPrice,string"` - OrderQty float64 `json:"orderQty,string"` - Side string `json:"side"` - Timestamp bybitTimeMilliSecStr `json:"time"` + ID string `json:"id"` + Symbol string `json:"symbol"` + OrderPrice convert.StringToFloat64 `json:"orderPrice"` + OrderQty convert.StringToFloat64 `json:"orderQty"` + Side string `json:"side"` + Timestamp bybitTimeMilliSecStr `json:"time"` } // USDCCreateOrderResp stores create order response type USDCCreateOrderResp struct { - ID string `json:"orderId"` - OrderLinkID string `json:"orderLinkId"` - Symbol string `json:"symbol"` - OrderPrice float64 `json:"orderPrice,string"` - OrderQty float64 `json:"orderQty,string"` - OrderType string `json:"orderType"` - Side string `json:"side"` + ID string `json:"orderId"` + OrderLinkID string `json:"orderLinkId"` + Symbol string `json:"symbol"` + OrderPrice convert.StringToFloat64 `json:"orderPrice"` + OrderQty convert.StringToFloat64 `json:"orderQty"` + OrderType string `json:"orderType"` + Side string `json:"side"` } // USDCOrder store order data type USDCOrder struct { - ID string `json:"orderId"` - OrderLinkID string `json:"orderLinkId"` - Symbol string `json:"symbol"` - OrderType string `json:"orderType"` - Side string `json:"side"` - Qty float64 `json:"qty,string"` - Price float64 `json:"price,string"` - TimeInForce string `json:"timeInForce"` - TotalOrderValue float64 `json:"cumExecValue,string"` - TotalFilledQty float64 `json:"cumExecQty,string"` - TotalFee float64 `json:"cumExecFee,string"` - InitialMargin string `json:"orderIM"` - OrderStatus string `json:"orderStatus"` - TakeProfit float64 `json:"takeProfit,string"` - StopLoss float64 `json:"stopLoss,string"` - TPTriggerBy string `json:"tpTriggerBy"` - SLTriggerBy string `json:"slTriggerBy"` - LastExecPrice float64 `json:"lastExecPrice"` - BasePrice string `json:"basePrice"` - TriggerPrice float64 `json:"triggerPrice,string"` - TriggerBy string `json:"triggerBy"` - ReduceOnly bool `json:"reduceOnly"` - StopOrderType string `json:"stopOrderType"` - CloseOnTrigger string `json:"closeOnTrigger"` - CreatedAt bybitTimeMilliSecStr `json:"createdAt"` + ID string `json:"orderId"` + OrderLinkID string `json:"orderLinkId"` + Symbol string `json:"symbol"` + OrderType string `json:"orderType"` + Side string `json:"side"` + Qty convert.StringToFloat64 `json:"qty"` + Price convert.StringToFloat64 `json:"price"` + TimeInForce string `json:"timeInForce"` + TotalOrderValue convert.StringToFloat64 `json:"cumExecValue"` + TotalFilledQty convert.StringToFloat64 `json:"cumExecQty"` + TotalFee convert.StringToFloat64 `json:"cumExecFee"` + InitialMargin string `json:"orderIM"` + OrderStatus string `json:"orderStatus"` + TakeProfit convert.StringToFloat64 `json:"takeProfit"` + StopLoss convert.StringToFloat64 `json:"stopLoss"` + TPTriggerBy string `json:"tpTriggerBy"` + SLTriggerBy string `json:"slTriggerBy"` + LastExecPrice float64 `json:"lastExecPrice"` + BasePrice string `json:"basePrice"` + TriggerPrice convert.StringToFloat64 `json:"triggerPrice"` + TriggerBy string `json:"triggerBy"` + ReduceOnly bool `json:"reduceOnly"` + StopOrderType string `json:"stopOrderType"` + CloseOnTrigger string `json:"closeOnTrigger"` + CreatedAt bybitTimeMilliSecStr `json:"createdAt"` } // USDCOrderHistory stores order history type USDCOrderHistory struct { USDCOrder - LeavesQty float64 `json:"leavesQty,string"` // Est. unfilled order qty - CashFlow string `json:"cashFlow"` - RealisedPnl float64 `json:"realisedPnl,string"` - UpdatedAt bybitTimeMilliSecStr `json:"updatedAt"` + LeavesQty convert.StringToFloat64 `json:"leavesQty"` // Est. unfilled order qty + CashFlow string `json:"cashFlow"` + RealisedPnl convert.StringToFloat64 `json:"realisedPnl"` + UpdatedAt bybitTimeMilliSecStr `json:"updatedAt"` } // USDCTradeHistory stores trade history type USDCTradeHistory struct { - ID string `json:"orderId"` - OrderLinkID string `json:"orderLinkId"` - Symbol string `json:"symbol"` - Side string `json:"side"` - TradeID string `json:"tradeId"` - ExecPrice float64 `json:"execPrice,string"` - ExecQty float64 `json:"execQty,string"` - ExecFee float64 `json:"execFee,string"` - FeeRate float64 `json:"feeRate,string"` - ExecType string `json:"execType"` - ExecValue float64 `json:"execValue,string"` - TradeTime bybitTimeMilliSecStr `json:"tradeTime"` - LastLiquidityInd string `json:"lastLiquidityInd"` + ID string `json:"orderId"` + OrderLinkID string `json:"orderLinkId"` + Symbol string `json:"symbol"` + Side string `json:"side"` + TradeID string `json:"tradeId"` + ExecPrice convert.StringToFloat64 `json:"execPrice"` + ExecQty convert.StringToFloat64 `json:"execQty"` + ExecFee convert.StringToFloat64 `json:"execFee"` + FeeRate convert.StringToFloat64 `json:"feeRate"` + ExecType string `json:"execType"` + ExecValue convert.StringToFloat64 `json:"execValue"` + TradeTime bybitTimeMilliSecStr `json:"tradeTime"` + LastLiquidityInd string `json:"lastLiquidityInd"` } // USDCTxLog stores transaction log data type USDCTxLog struct { - TxTime bybitTimeMilliSecStr `json:"transactionTime"` - Symbol string `json:"symbol"` - Type string `json:"type"` - Side string `json:"side"` - Quantity float64 `json:"qty,string"` - Size float64 `json:"size,string"` - TradePrice float64 `json:"tradePrice,string"` - Funding float64 `json:"funding,string"` - Fee float64 `json:"fee,string"` - CashFlow string `json:"cashFlow"` - Change float64 `json:"change,string"` - WalletBalance float64 `json:"walletBalance,string"` - FeeRate float64 `json:"feeRate,string"` - TradeID string `json:"tradeId"` - OrderID string `json:"orderId"` - OrderLinkID string `json:"orderLinkId"` - Info string `json:"info"` + TxTime bybitTimeMilliSecStr `json:"transactionTime"` + Symbol string `json:"symbol"` + Type string `json:"type"` + Side string `json:"side"` + Quantity convert.StringToFloat64 `json:"qty"` + Size convert.StringToFloat64 `json:"size"` + TradePrice convert.StringToFloat64 `json:"tradePrice"` + Funding convert.StringToFloat64 `json:"funding"` + Fee convert.StringToFloat64 `json:"fee"` + CashFlow string `json:"cashFlow"` + Change convert.StringToFloat64 `json:"change"` + WalletBalance convert.StringToFloat64 `json:"walletBalance"` + FeeRate convert.StringToFloat64 `json:"feeRate"` + TradeID string `json:"tradeId"` + OrderID string `json:"orderId"` + OrderLinkID string `json:"orderLinkId"` + Info string `json:"info"` } // USDCWalletBalance store USDC wallet balance type USDCWalletBalance struct { - Equity float64 `json:"equity,string"` - WalletBalance float64 `json:"walletBalance,string"` - AvailableBalance float64 `json:"availableBalance,string"` - AccountIM float64 `json:"accountIM,string"` - AccountMM float64 `json:"accountMM,string"` - TotalRPL float64 `json:"totalRPL,string"` - TotalSessionUPL float64 `json:"totalSessionUPL,string"` - TotalSessionRPL float64 `json:"totalSessionRPL,string"` + Equity convert.StringToFloat64 `json:"equity"` + WalletBalance convert.StringToFloat64 `json:"walletBalance"` + AvailableBalance convert.StringToFloat64 `json:"availableBalance"` + AccountIM convert.StringToFloat64 `json:"accountIM"` + AccountMM convert.StringToFloat64 `json:"accountMM"` + TotalRPL convert.StringToFloat64 `json:"totalRPL"` + TotalSessionUPL convert.StringToFloat64 `json:"totalSessionUPL"` + TotalSessionRPL convert.StringToFloat64 `json:"totalSessionRPL"` } // USDCAssetInfo stores USDC asset data type USDCAssetInfo struct { - BaseCoin string `json:"baseCoin"` - TotalDelta float64 `json:"totalDelta,string"` - TotalGamma float64 `json:"totalGamma,string"` - TotalVega float64 `json:"totalVega,string"` - TotalTheta float64 `json:"totalTheta,string"` - TotalRPL float64 `json:"totalRPL,string"` - SessionUPL float64 `json:"sessionUPL,string"` - SessionRPL float64 `json:"sessionRPL,string"` - IM float64 `json:"im,string"` - MM float64 `json:"mm,string"` + BaseCoin string `json:"baseCoin"` + TotalDelta convert.StringToFloat64 `json:"totalDelta"` + TotalGamma convert.StringToFloat64 `json:"totalGamma"` + TotalVega convert.StringToFloat64 `json:"totalVega"` + TotalTheta convert.StringToFloat64 `json:"totalTheta"` + TotalRPL convert.StringToFloat64 `json:"totalRPL"` + SessionUPL convert.StringToFloat64 `json:"sessionUPL"` + SessionRPL convert.StringToFloat64 `json:"sessionRPL"` + IM convert.StringToFloat64 `json:"im"` + MM convert.StringToFloat64 `json:"mm"` } // USDCPosition store USDC position data type USDCPosition struct { - Symbol string `json:"symbol"` - Leverage float64 `json:"leverage,string"` - ClosingFee float64 `json:"occClosingFee,string"` - LiquidPrice string `json:"liqPrice"` - Position float64 `json:"positionValue"` - TakeProfit float64 `json:"takeProfit,string"` - RiskID string `json:"riskId"` - TrailingStop float64 `json:"trailingStop,string"` - UnrealisedPnl float64 `json:"unrealisedPnl,string"` - MarkPrice float64 `json:"markPrice,string"` - CumRealisedPnl float64 `json:"cumRealisedPnl,string"` - PositionMM float64 `json:"positionMM,string"` - PositionIM float64 `json:"positionIM,string"` - EntryPrice float64 `json:"entryPrice,string"` - Size float64 `json:"size,string"` - SessionRPL float64 `json:"sessionRPL,string"` - SessionUPL float64 `json:"sessionUPL,string"` - StopLoss float64 `json:"stopLoss,string"` - OrderMargin float64 `json:"orderMargin,string"` - SessionAvgPrice float64 `json:"sessionAvgPrice,string"` - CreatedAt bybitTimeMilliSecStr `json:"createdAt"` - UpdatedAt bybitTimeMilliSecStr `json:"updatedAt"` - TpSLMode string `json:"tpSLMode"` - Side string `json:"side"` - BustPrice string `json:"bustPrice"` - PositionStatus string `json:"positionStatus"` - DeleverageIndicator int64 `json:"deleverageIndicator"` + Symbol string `json:"symbol"` + Leverage convert.StringToFloat64 `json:"leverage"` + ClosingFee convert.StringToFloat64 `json:"occClosingFee"` + LiquidPrice string `json:"liqPrice"` + Position float64 `json:"positionValue"` + TakeProfit convert.StringToFloat64 `json:"takeProfit"` + RiskID string `json:"riskId"` + TrailingStop convert.StringToFloat64 `json:"trailingStop"` + UnrealisedPnl convert.StringToFloat64 `json:"unrealisedPnl"` + MarkPrice convert.StringToFloat64 `json:"markPrice"` + CumRealisedPnl convert.StringToFloat64 `json:"cumRealisedPnl"` + PositionMM convert.StringToFloat64 `json:"positionMM"` + PositionIM convert.StringToFloat64 `json:"positionIM"` + EntryPrice convert.StringToFloat64 `json:"entryPrice"` + Size convert.StringToFloat64 `json:"size"` + SessionRPL convert.StringToFloat64 `json:"sessionRPL"` + SessionUPL convert.StringToFloat64 `json:"sessionUPL"` + StopLoss convert.StringToFloat64 `json:"stopLoss"` + OrderMargin convert.StringToFloat64 `json:"orderMargin"` + SessionAvgPrice convert.StringToFloat64 `json:"sessionAvgPrice"` + CreatedAt bybitTimeMilliSecStr `json:"createdAt"` + UpdatedAt bybitTimeMilliSecStr `json:"updatedAt"` + TpSLMode string `json:"tpSLMode"` + Side string `json:"side"` + BustPrice string `json:"bustPrice"` + PositionStatus string `json:"positionStatus"` + DeleverageIndicator int64 `json:"deleverageIndicator"` } // USDCSettlementHistory store USDC settlement history data type USDCSettlementHistory struct { - Symbol string `json:"symbol"` - Side string `json:"side"` - Time bybitTimeMilliSecStr `json:"time"` - Size float64 `json:"size,string"` - SessionAvgPrice float64 `json:"sessionAvgPrice,string"` - MarkPrice float64 `json:"markPrice,string"` - SessionRpl float64 `json:"sessionRpl,string"` + Symbol string `json:"symbol"` + Side string `json:"side"` + Time bybitTimeMilliSecStr `json:"time"` + Size convert.StringToFloat64 `json:"size"` + SessionAvgPrice convert.StringToFloat64 `json:"sessionAvgPrice"` + MarkPrice convert.StringToFloat64 `json:"markPrice"` + SessionRpl convert.StringToFloat64 `json:"sessionRpl"` } // USDCRiskLimit store USDC risk limit data type USDCRiskLimit struct { - RiskID string `json:"riskId"` - Symbol string `json:"symbol"` - Limit string `json:"limit"` - Section []string `json:"section"` - StartingMargin float64 `json:"startingMargin,string"` - MaintainMargin float64 `json:"maintainMargin,string"` - IsLowestRisk bool `json:"isLowestRisk"` - MaxLeverage float64 `json:"maxLeverage,string"` + RiskID string `json:"riskId"` + Symbol string `json:"symbol"` + Limit string `json:"limit"` + Section []string `json:"section"` + StartingMargin convert.StringToFloat64 `json:"startingMargin"` + MaintainMargin convert.StringToFloat64 `json:"maintainMargin"` + IsLowestRisk bool `json:"isLowestRisk"` + MaxLeverage convert.StringToFloat64 `json:"maxLeverage"` } // USDCFundingInfo store USDC funding data type USDCFundingInfo struct { - Symbol string `json:"symbol"` - Time bybitTimeMilliSecStr `json:"fundingRateTimestamp"` - Rate float64 `json:"fundingRate,string"` + Symbol string `json:"symbol"` + Time bybitTimeMilliSecStr `json:"fundingRateTimestamp"` + Rate convert.StringToFloat64 `json:"fundingRate"` +} + +// CFuturesTradingFeeRate stores trading fee rate +type CFuturesTradingFeeRate struct { + TakerFeeRate convert.StringToFloat64 `json:"taker_fee_rate"` + MakerFeeRate convert.StringToFloat64 `json:"maker_fee_rate"` + UserID int64 `json:"user_id"` } diff --git a/exchanges/bybit/ratelimit.go b/exchanges/bybit/ratelimit.go index 05cc7f51..01bd7c89 100644 --- a/exchanges/bybit/ratelimit.go +++ b/exchanges/bybit/ratelimit.go @@ -10,22 +10,26 @@ import ( ) const ( - spotInterval = time.Second - spotRequestRate = 70 + // See: https://bybit-exchange.github.io/docs/v5/rate-limit + spotInterval = time.Second * 5 + spotRequestRate = 120 + futuresPublicInterval = time.Second futuresRequestRate = 50 - spotPrivateRequestRate = 20 - futuresInterval = time.Minute - futuresDefaultRateCount = 100 - futuresOrderRate = 100 - futuresOrderListRate = 600 - futuresExecutionRate = 120 - futuresPositionRateCount = 75 - futuresPositionListRate = 120 - futuresFundingRate = 120 - futuresWalletRate = 120 - futuresAccountRate = 600 + spotPrivateInterval = time.Second + spotPrivateRequestRate = 20 + spotPrivateFeeRequestRate = 10 + futuresInterval = time.Minute + futuresDefaultRateCount = 100 + futuresOrderRate = 100 + futuresOrderListRate = 600 + futuresExecutionRate = 120 + futuresPositionRateCount = 75 + futuresPositionListRate = 120 + futuresFundingRate = 120 + futuresWalletRate = 120 + futuresAccountRate = 600 usdcPerpetualPublicRate = 50 usdcPerpetualCancelAllRate = 1 @@ -37,7 +41,7 @@ const ( publicSpotRate request.EndpointLimit = iota publicFuturesRate privateSpotRate - + privateFeeRate cFuturesDefaultRate cFuturesCancelActiveOrderRate @@ -153,6 +157,7 @@ type RateLimit struct { SpotRate *rate.Limiter FuturesRate *rate.Limiter PrivateSpotRate *rate.Limiter + PrivateFeeRate *rate.Limiter CMFuturesDefaultRate *rate.Limiter CMFuturesOrderRate *rate.Limiter CMFuturesOrderListRate *rate.Limiter @@ -202,131 +207,92 @@ func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { limiter, tokens = r.SpotRate, 1 case privateSpotRate: limiter, tokens = r.PrivateSpotRate, 1 - + case privateFeeRate: + limiter, tokens = r.PrivateFeeRate, 1 case cFuturesDefaultRate: limiter, tokens = r.CMFuturesDefaultRate, 1 - case cFuturesCancelActiveOrderRate, cFuturesCreateConditionalOrderRate, cFuturesCancelConditionalOrderRate, cFuturesReplaceActiveOrderRate, cFuturesReplaceConditionalOrderRate, cFuturesCreateOrderRate: limiter, tokens = r.CMFuturesOrderRate, 1 case cFuturesCancelAllActiveOrderRate, cFuturesCancelAllConditionalOrderRate: limiter, tokens = r.CMFuturesOrderRate, 10 - case cFuturesGetActiveOrderRate, cFuturesGetConditionalOrderRate, cFuturesGetRealtimeOrderRate: limiter, tokens = r.CMFuturesOrderListRate, 1 - case cFuturesTradeRate: limiter, tokens = r.CMFuturesExecutionRate, 1 - case cFuturesSetLeverageRate, cFuturesUpdateMarginRate, cFuturesSetTradingRate, cFuturesSwitchPositionRate, cFuturesGetTradingFeeRate: limiter, tokens = r.CMFuturesPositionRate, 1 - case cFuturesPositionRate, cFuturesWalletBalanceRate: limiter, tokens = r.CMFuturesPositionListRate, 1 - case cFuturesLastFundingFeeRate, cFuturesPredictFundingRate: limiter, tokens = r.CMFuturesFundingRate, 1 - case cFuturesWalletFundRecordRate, cFuturesWalletWithdrawalRate: limiter, tokens = r.CMFuturesWalletRate, 1 - case cFuturesAPIKeyInfoRate: limiter, tokens = r.CMFuturesAccountRate, 1 - case uFuturesDefaultRate: limiter, tokens = r.UFuturesDefaultRate, 1 - case uFuturesCreateOrderRate, uFuturesCancelOrderRate, uFuturesCreateConditionalOrderRate, uFuturesCancelConditionalOrderRate: limiter, tokens = r.UFuturesOrderRate, 1 - case uFuturesCancelAllOrderRate, uFuturesCancelAllConditionalOrderRate: limiter, tokens = r.UFuturesOrderRate, 10 - case uFuturesSetLeverageRate, uFuturesSwitchMargin, uFuturesSwitchPosition, uFuturesSetMarginRate, uFuturesSetTradingStopRate, uFuturesUpdateMarginRate: limiter, tokens = r.UFuturesPositionRate, 1 - case uFuturesPositionRate, uFuturesGetClosedTradesRate, uFuturesGetTradesRate: limiter, tokens = r.UFuturesPositionListRate, 1 - case uFuturesGetActiveOrderRate, uFuturesGetActiveRealtimeOrderRate, uFuturesGetConditionalOrderRate, uFuturesGetConditionalRealtimeOrderRate: limiter, tokens = r.UFuturesOrderListRate, 1 - case uFuturesGetMyLastFundingFeeRate, uFuturesPredictFundingRate: limiter, tokens = r.UFuturesFundingRate, 1 - case futuresDefaultRate: limiter, tokens = r.FuturesDefaultRate, 1 - case futuresCancelOrderRate, futuresCreateOrderRate, futuresReplaceOrderRate, futuresReplaceConditionalOrderRate, futuresCancelConditionalOrderRate, futuresCreateConditionalOrderRate: limiter, tokens = r.FuturesOrderRate, 1 - case futuresCancelAllOrderRate, futuresCancelAllConditionalOrderRate: limiter, tokens = r.FuturesOrderRate, 10 - case futuresGetActiveOrderRate, futuresGetConditionalOrderRate, futuresGetActiveRealtimeOrderRate, futuresGetConditionalRealtimeOrderRate: limiter, tokens = r.FuturesOrderListRate, 1 - case futuresGetTradeRate: limiter, tokens = r.FuturesExecutionRate, 1 - case futuresSetLeverageRate, futuresUpdateMarginRate, futuresSetTradingStopRate, futuresSwitchPositionModeRate, futuresSwitchMarginRate, futuresSwitchPositionRate: limiter, tokens = r.FuturesPositionRate, 1 - case futuresPositionRate: limiter, tokens = r.FuturesPositionListRate, 1 - case usdcPublicRate: limiter, tokens = r.USDCPublic, 1 - case usdcCancelAllOrderRate: limiter, tokens = r.USDCCancelAllOrderRate, 1 - case usdcPlaceOrderRate: limiter, tokens = r.USDCPlaceOrderRate, 1 - case usdcModifyOrderRate: limiter, tokens = r.USDCModifyOrderRate, 1 - case usdcCancelOrderRate: limiter, tokens = r.USDCCancelOrderRate, 1 - case usdcGetOrderRate: limiter, tokens = r.USDCGetOrderRate, 1 - case usdcGetOrderHistoryRate: limiter, tokens = r.USDCGetOrderHistoryRate, 1 - case usdcGetTradeHistoryRate: limiter, tokens = r.USDCGetTradeHistoryRate, 1 - case usdcGetTransactionRate: limiter, tokens = r.USDCGetTransactionRate, 1 - case usdcGetWalletRate: limiter, tokens = r.USDCGetWalletRate, 1 - case usdcGetAssetRate: limiter, tokens = r.USDCGetAssetRate, 1 - case usdcGetMarginRate: limiter, tokens = r.USDCGetMarginRate, 1 - case usdcGetPositionRate: limiter, tokens = r.USDCGetPositionRate, 1 - case usdcSetLeverageRate: limiter, tokens = r.USDCSetLeverageRate, 1 - case usdcGetSettlementRate: limiter, tokens = r.USDCGetSettlementRate, 1 - case usdcSetRiskRate: limiter, tokens = r.USDCSetRiskRate, 1 - case usdcGetPredictedFundingRate: limiter, tokens = r.USDCGetPredictedFundingRate, 1 - default: limiter, tokens = r.SpotRate, 1 } @@ -360,7 +326,8 @@ func SetRateLimit() *RateLimit { return &RateLimit{ SpotRate: request.NewRateLimit(spotInterval, spotRequestRate), FuturesRate: request.NewRateLimit(futuresPublicInterval, futuresRequestRate), - PrivateSpotRate: request.NewRateLimit(spotInterval, spotPrivateRequestRate), + PrivateSpotRate: request.NewRateLimit(spotPrivateInterval, spotPrivateRequestRate), + PrivateFeeRate: request.NewRateLimit(spotPrivateInterval, spotPrivateFeeRequestRate), CMFuturesDefaultRate: request.NewRateLimit(futuresInterval, futuresDefaultRateCount), CMFuturesOrderRate: request.NewRateLimit(futuresInterval, futuresOrderRate), CMFuturesOrderListRate: request.NewRateLimit(futuresInterval, futuresOrderListRate), diff --git a/exchanges/okx/okx_test.go b/exchanges/okx/okx_test.go index b3319826..a377d05b 100644 --- a/exchanges/okx/okx_test.go +++ b/exchanges/okx/okx_test.go @@ -1907,7 +1907,7 @@ func TestUpdateOrderExecutionLimits(t *testing.T) { t.Errorf("Okx UpdateOrderExecutionLimits wrong PriceStepIncrementSize; Asset: %s Pair: %s Expected: %v Got: %v", a, tt.pair, tt.step, got) } - if got := limits.MinAmount; got != tt.min { + if got := limits.MinimumBaseAmount; got != tt.min { t.Errorf("Okx UpdateOrderExecutionLimits wrong MinAmount; Pair: %s Expected: %v Got: %v", tt.pair, tt.min, got) } } diff --git a/exchanges/okx/okx_wrapper.go b/exchanges/okx/okx_wrapper.go index 4568a8e3..cfccc599 100644 --- a/exchanges/okx/okx_wrapper.go +++ b/exchanges/okx/okx_wrapper.go @@ -342,7 +342,7 @@ func (ok *Okx) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) err Pair: pair, Asset: a, PriceStepIncrementSize: insts[x].TickSize, - MinAmount: insts[x].MinimumOrderSize, + MinimumBaseAmount: insts[x].MinimumOrderSize, } } diff --git a/exchanges/order/limits.go b/exchanges/order/limits.go index 60fda4a1..c187a569 100644 --- a/exchanges/order/limits.go +++ b/exchanges/order/limits.go @@ -52,6 +52,7 @@ var ( errCannotLoadLimit = errors.New("cannot load limit, levels not supplied") errInvalidPriceLevels = errors.New("invalid price levels, cannot load limits") errInvalidAmountLevels = errors.New("invalid amount levels, cannot load limits") + errInvalidQuoteLevels = errors.New("invalid quote levels, cannot load limits") ) // ExecutionLimits defines minimum and maximum values in relation to @@ -74,9 +75,12 @@ type MinMaxLevel struct { MultiplierDown float64 MultiplierDecimal float64 AveragePriceMinutes int64 - MinAmount float64 - MaxAmount float64 + MinimumBaseAmount float64 + MaximumBaseAmount float64 + MinimumQuoteAmount float64 + MaximumQuoteAmount float64 AmountStepIncrementSize float64 + QuoteStepIncrementSize float64 MinNotional float64 MaxIcebergParts int64 MarketMinQty float64 @@ -130,15 +134,26 @@ func (e *ExecutionLimits) LoadLimits(levels []MinMaxLevel) error { levels[x].MaxPrice) } - if levels[x].MinAmount > 0 && - levels[x].MaxAmount > 0 && - levels[x].MinAmount > levels[x].MaxAmount { + if levels[x].MinimumBaseAmount > 0 && + levels[x].MaximumBaseAmount > 0 && + levels[x].MinimumBaseAmount > levels[x].MaximumBaseAmount { return fmt.Errorf("%w for %s %s supplied min: %f max: %f", errInvalidAmountLevels, levels[x].Asset, levels[x].Pair, - levels[x].MinAmount, - levels[x].MaxAmount) + levels[x].MinimumBaseAmount, + levels[x].MaximumBaseAmount) + } + + if levels[x].MinimumQuoteAmount > 0 && + levels[x].MaximumQuoteAmount > 0 && + levels[x].MinimumQuoteAmount > levels[x].MaximumQuoteAmount { + return fmt.Errorf("%w for %s %s supplied min: %f max: %f", + errInvalidQuoteLevels, + levels[x].Asset, + levels[x].Pair, + levels[x].MinimumQuoteAmount, + levels[x].MaximumQuoteAmount) } m2[levels[x].Pair.Quote.Item] = levels[x] @@ -209,25 +224,26 @@ func (e *ExecutionLimits) CheckOrderExecutionLimits(a asset.Item, cp currency.Pa // Conforms checks outbound parameters func (m *MinMaxLevel) Conforms(price, amount float64, orderType Type) error { + // TODO: Update to take in account Quote amounts as well as Base amounts. if m == nil { return nil } - if m.MinAmount != 0 && amount < m.MinAmount { + if m.MinimumBaseAmount != 0 && amount < m.MinimumBaseAmount { return fmt.Errorf("%w min: %.8f supplied %.8f", ErrAmountBelowMin, - m.MinAmount, + m.MinimumBaseAmount, amount) } - if m.MaxAmount != 0 && amount > m.MaxAmount { + if m.MaximumBaseAmount != 0 && amount > m.MaximumBaseAmount { return fmt.Errorf("%w min: %.8f supplied %.8f", ErrAmountExceedsMax, - m.MaxAmount, + m.MaximumBaseAmount, amount) } if m.AmountStepIncrementSize != 0 { dAmount := decimal.NewFromFloat(amount) - dMinAmount := decimal.NewFromFloat(m.MinAmount) + dMinAmount := decimal.NewFromFloat(m.MaximumBaseAmount) dStep := decimal.NewFromFloat(m.AmountStepIncrementSize) if !dAmount.Sub(dMinAmount).Mod(dStep).IsZero() { return fmt.Errorf("%w stepSize: %.8f supplied %.8f", @@ -288,7 +304,7 @@ func (m *MinMaxLevel) Conforms(price, amount float64, orderType Type) error { } if m.MarketMinQty != 0 && - m.MinAmount < m.MarketMinQty && + m.MinimumBaseAmount < m.MarketMinQty && amount < m.MarketMinQty { return fmt.Errorf("%w min: %.8f supplied %.8f", ErrMarketAmountBelowMin, @@ -296,7 +312,7 @@ func (m *MinMaxLevel) Conforms(price, amount float64, orderType Type) error { amount) } if m.MarketMaxQty != 0 && - m.MaxAmount > m.MarketMaxQty && + m.MaximumBaseAmount > m.MarketMaxQty && amount > m.MarketMaxQty { return fmt.Errorf("%w max: %.8f supplied %.8f", ErrMarketAmountExceedsMax, diff --git a/exchanges/order/limits_test.go b/exchanges/order/limits_test.go index bc6bc8e8..b743b27b 100644 --- a/exchanges/order/limits_test.go +++ b/exchanges/order/limits_test.go @@ -25,11 +25,11 @@ func TestLoadLimits(t *testing.T) { invalidAsset := []MinMaxLevel{ { - Pair: btcusd, - MinPrice: 100000, - MaxPrice: 1000000, - MinAmount: 1, - MaxAmount: 10, + Pair: btcusd, + MinPrice: 100000, + MaxPrice: 1000000, + MinimumBaseAmount: 1, + MaximumBaseAmount: 10, }, } err = e.LoadLimits(invalidAsset) @@ -41,11 +41,11 @@ func TestLoadLimits(t *testing.T) { invalidPairLoading := []MinMaxLevel{ { - Asset: asset.Spot, - MinPrice: 100000, - MaxPrice: 1000000, - MinAmount: 1, - MaxAmount: 10, + Asset: asset.Spot, + MinPrice: 100000, + MaxPrice: 1000000, + MinimumBaseAmount: 1, + MaximumBaseAmount: 10, }, } @@ -56,12 +56,12 @@ func TestLoadLimits(t *testing.T) { newLimits := []MinMaxLevel{ { - Pair: btcusd, - Asset: asset.Spot, - MinPrice: 100000, - MaxPrice: 1000000, - MinAmount: 1, - MaxAmount: 10, + Pair: btcusd, + Asset: asset.Spot, + MinPrice: 100000, + MaxPrice: 1000000, + MinimumBaseAmount: 1, + MaximumBaseAmount: 10, }, } @@ -72,12 +72,12 @@ func TestLoadLimits(t *testing.T) { badLimit := []MinMaxLevel{ { - Pair: btcusd, - Asset: asset.Spot, - MinPrice: 2, - MaxPrice: 1, - MinAmount: 1, - MaxAmount: 10, + Pair: btcusd, + Asset: asset.Spot, + MinPrice: 2, + MaxPrice: 1, + MinimumBaseAmount: 1, + MaximumBaseAmount: 10, }, } @@ -88,12 +88,12 @@ func TestLoadLimits(t *testing.T) { badLimit = []MinMaxLevel{ { - Pair: btcusd, - Asset: asset.Spot, - MinPrice: 1, - MaxPrice: 2, - MinAmount: 10, - MaxAmount: 9, + Pair: btcusd, + Asset: asset.Spot, + MinPrice: 1, + MaxPrice: 2, + MinimumBaseAmount: 10, + MaximumBaseAmount: 9, }, } @@ -116,9 +116,9 @@ func TestLoadLimits(t *testing.T) { noCompare := []MinMaxLevel{ { - Pair: btcusd, - Asset: asset.Spot, - MinAmount: 10, + Pair: btcusd, + Asset: asset.Spot, + MinimumBaseAmount: 10, }, } @@ -151,12 +151,12 @@ func TestGetOrderExecutionLimits(t *testing.T) { newLimits := []MinMaxLevel{ { - Pair: btcusd, - Asset: asset.Spot, - MinPrice: 100000, - MaxPrice: 1000000, - MinAmount: 1, - MaxAmount: 10, + Pair: btcusd, + Asset: asset.Spot, + MinPrice: 100000, + MaxPrice: 1000000, + MinimumBaseAmount: 1, + MaximumBaseAmount: 10, }, } @@ -185,8 +185,8 @@ func TestGetOrderExecutionLimits(t *testing.T) { t.Fatalf("expected error %v but received %v", nil, err) } - if tt.MaxAmount != newLimits[0].MaxAmount || - tt.MinAmount != newLimits[0].MinAmount || + if tt.MaximumBaseAmount != newLimits[0].MaximumBaseAmount || + tt.MinimumBaseAmount != newLimits[0].MinimumBaseAmount || tt.MaxPrice != newLimits[0].MaxPrice || tt.MinPrice != newLimits[0].MinPrice { t.Fatal("unexpected values") @@ -203,12 +203,12 @@ func TestCheckLimit(t *testing.T) { newLimits := []MinMaxLevel{ { - Pair: btcusd, - Asset: asset.Spot, - MinPrice: 100000, - MaxPrice: 1000000, - MinAmount: 1, - MaxAmount: 10, + Pair: btcusd, + Asset: asset.Spot, + MinPrice: 100000, + MaxPrice: 1000000, + MinimumBaseAmount: 1, + MaximumBaseAmount: 10, }, } @@ -305,8 +305,8 @@ func TestConforms(t *testing.T) { t.Fatalf("expected error %v but received %v", nil, err) } - tt.MinAmount = 1 - tt.MaxAmount = 10 + tt.MinimumBaseAmount = 1 + tt.MaximumBaseAmount = 10 tt.MarketMinQty = 1.1 tt.MarketMaxQty = 9.9 diff --git a/exchanges/sharedtestvalues/sharedtestvalues.go b/exchanges/sharedtestvalues/sharedtestvalues.go index 1f74b8ef..792d0335 100644 --- a/exchanges/sharedtestvalues/sharedtestvalues.go +++ b/exchanges/sharedtestvalues/sharedtestvalues.go @@ -1,6 +1,11 @@ package sharedtestvalues import ( + "bytes" + "fmt" + "os" + "path/filepath" + "regexp" "strings" "testing" "time" @@ -103,3 +108,42 @@ func SkipTestIfCannotManipulateOrders(t *testing.T, exch exchange.IBotExchange, func AreAPICredentialsSet(exch exchange.IBotExchange) bool { return exch.VerifyAPICredentials(exch.GetDefaultCredentials()) == nil } + +// EmptyStringPotentialPattern is a regular expression pattern for a potential +// empty string into float64 +var EmptyStringPotentialPattern = `.*float64.*json:"[^"]*,string".*` + +// ForceFileStandard will check all files in the current directory for a regular +// expression pattern. If the pattern is found the test will fail. +func ForceFileStandard(t *testing.T, pattern string) error { + t.Helper() + + r := regexp.MustCompile(pattern) + + root := "." // Specify the root directory to start walking from + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if !info.IsDir() && strings.HasSuffix(path, ".go") { + fileContents, err := os.ReadFile(path) + if err != nil { + t.Fatalf("Failed to read file: %v", err) + } + + lines := bytes.Split(fileContents, []byte("\n")) + for x, line := range lines { + if r.Match(line) { + t.Errorf("File: %s line contains pattern [%s] match with [%s] at line %d", path, pattern, string(line), x+1) + } + } + } + return nil + }) + + if err != nil { + return fmt.Errorf("failed to walk directory: %w", err) + } + return nil +} diff --git a/exchanges/ticker/ticker.go b/exchanges/ticker/ticker.go index 51e5ac78..4cabd21b 100644 --- a/exchanges/ticker/ticker.go +++ b/exchanges/ticker/ticker.go @@ -16,6 +16,8 @@ var ( errInvalidTicker = errors.New("invalid ticker") errTickerNotFound = errors.New("ticker not found") errExchangeNameIsEmpty = errors.New("exchange name is empty") + errBidEqualsAsk = errors.New("bid equals ask this is a crossed or locked market") + errBidGreaterThanAsk = errors.New("bid greater than ask this is a crossed or locked market") ) func init() { @@ -129,6 +131,27 @@ func ProcessTicker(p *Price) error { return fmt.Errorf("%s %s", p.ExchangeName, errPairNotSet) } + if p.Bid != 0 && p.Ask != 0 { + switch { + case p.ExchangeName == "Bitfinex" && p.AssetType == asset.MarginFunding: + // Margin funding books can be crossed see Bitfinex. + default: + if p.Bid == p.Ask { + return fmt.Errorf("%s %s %w", + p.ExchangeName, + p.Pair, + errBidEqualsAsk) + } + + if p.Bid > p.Ask { + return fmt.Errorf("%s %s %w", + p.ExchangeName, + p.Pair, + errBidGreaterThanAsk) + } + } + } + if p.AssetType == asset.Empty { return fmt.Errorf("%s %s %s", p.ExchangeName, diff --git a/exchanges/ticker/ticker_test.go b/exchanges/ticker/ticker_test.go index 183fc8ae..66cd6b30 100644 --- a/exchanges/ticker/ticker_test.go +++ b/exchanges/ticker/ticker_test.go @@ -281,6 +281,39 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers t.Fatal("TestProcessTicker pair mismatch") } + err = ProcessTicker(&Price{ + ExchangeName: "Bitfinex", + Pair: currency.NewPair(currency.BTC, currency.USD), + AssetType: asset.Margin, + Bid: 1337, + Ask: 1337, + }) + if !errors.Is(err, errBidEqualsAsk) { + t.Errorf("received: %v but expected: %v", err, errBidEqualsAsk) + } + + err = ProcessTicker(&Price{ + ExchangeName: "Bitfinex", + Pair: currency.NewPair(currency.BTC, currency.USD), + AssetType: asset.Margin, + Bid: 1338, + Ask: 1336, + }) + if !errors.Is(err, errBidGreaterThanAsk) { + t.Errorf("received: %v but expected: %v", err, errBidGreaterThanAsk) + } + + err = ProcessTicker(&Price{ + ExchangeName: "Bitfinex", + Pair: currency.NewPair(currency.BTC, currency.USD), + AssetType: asset.MarginFunding, + Bid: 1338, + Ask: 1336, + }) + if !errors.Is(err, nil) { + t.Errorf("received: %v but expected: %v", err, nil) + } + // now test for processing a pair with a different quote currency newPair, err = currency.NewPairFromStrings("BTC", "AUD") if err != nil {