From 02aaf3d09879bd9e6956a07324a9e36769e433b2 Mon Sep 17 00:00:00 2001 From: isther Date: Thu, 4 Jan 2024 21:20:40 +0800 Subject: [PATCH] Update market request --- bybit_api_client.go | 110 +++- market.go | 296 --------- market_klines.go | 67 -- market_service.go | 1167 +++++++++++++++++++++++++++++++++ market_service_test.go | 1334 ++++++++++++++++++++++++++++++++++++++ market_test.go | 69 -- models/marketResponse.go | 111 +++- 7 files changed, 2676 insertions(+), 478 deletions(-) delete mode 100644 market.go delete mode 100644 market_klines.go create mode 100644 market_service.go create mode 100644 market_service_test.go delete mode 100644 market_test.go diff --git a/bybit_api_client.go b/bybit_api_client.go index 5a5d7f3..568b8c9 100644 --- a/bybit_api_client.go +++ b/bybit_api_client.go @@ -6,17 +6,21 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/hex" - "encoding/json" "fmt" - "github.com/wuhewuhe/bybit.go.api/handlers" "io" "log" "net/http" "os" "strconv" "time" + + "github.com/bitly/go-simplejson" + jsoniter "github.com/json-iterator/go" + "github.com/wuhewuhe/bybit.go.api/handlers" ) +var json = jsoniter.ConfigCompatibleWithStandardLibrary + type ServerResponse struct { RetCode int `json:"retCode"` RetMsg string `json:"retMsg"` @@ -77,6 +81,14 @@ func GetCurrentTime() int64 { return timeStamp } +func newJSON(data []byte) (j *simplejson.Json, err error) { + j, err = simplejson.NewJson(data) + if err != nil { + return nil, err + } + return j, nil +} + // NewBybitHttpClient NewClient Create client function for initialising new Bybit client func NewBybitHttpClient(apiKey string, APISecret string, options ...ClientOption) *Client { c := &Client{ @@ -152,6 +164,9 @@ func (c *Client) parseRequest(r *request, opts ...RequestOption) (err error) { func (c *Client) callAPI(ctx context.Context, r *request, opts ...RequestOption) (data []byte, err error) { err = c.parseRequest(r, opts...) + if err != nil { + return nil, err + } req, err := http.NewRequest(r.method, r.fullURL, r.body) if err != nil { return []byte{}, err @@ -196,36 +211,79 @@ func (c *Client) callAPI(ctx context.Context, r *request, opts ...RequestOption) return data, nil } -// NewMarketKlineService Market Endpoints -func (c *Client) NewMarketKlineService(klineType, category, symbol, interval string) *Klines { - return &Klines{ - c: c, - category: category, - symbol: symbol, - interval: interval, - klineType: klineType, - } +func (c *Client) NewInstrumentsInfoService() *InstrumentsInfoService { + return &InstrumentsInfoService{c: c} } -func (c *Client) NewMarketKLinesService(klineType string, params map[string]interface{}) *MarketClient { - return &MarketClient{ - c: c, - klineType: klineType, - params: params, - } +// NewMarketKlineService Market Kline Endpoints +func (c *Client) NewMarketKlineService() *MarketKlinesService { + return &MarketKlinesService{c: c} } -func (c *Client) NewMarketInfoServiceNoParams() *MarketClient { - return &MarketClient{ - c: c, - } +// NewMarketMarkPriceKlineService Market Mark Price Kline Endpoints +func (c *Client) NewMarketMarkPriceKlineService() *MarketMarkPriceKlineService { + return &MarketMarkPriceKlineService{c: c} } -func (c *Client) NewMarketInfoService(params map[string]interface{}) *MarketClient { - return &MarketClient{ - c: c, - params: params, - } +// NewMarketIndexPriceKlineService Market Index Price Kline Endpoints +func (c *Client) NewMarketIndexPriceKlineService() *MarketIndexPriceKlineService { + return &MarketIndexPriceKlineService{c: c} +} + +// NewMarketPremiumIndexPriceKlineService Market Premium Index Price Kline Endpoints +func (c *Client) NewMarketPremiumIndexPriceKlineService() *MarketPremiumIndexPriceKlineService { + return &MarketPremiumIndexPriceKlineService{c: c} +} + +func (c *Client) NewOrderBookService() *MarketOrderBookService { + return &MarketOrderBookService{c: c} +} + +func (c *Client) NewTickersService() *MarketTickersService { + return &MarketTickersService{c: c} +} + +func (c *Client) NewFundingTatesService() *MarketFundingRatesService { + return &MarketFundingRatesService{c: c} +} + +func (c *Client) NewGetPublicRecentTradesService() *GetPublicRecentTradesService { + return &GetPublicRecentTradesService{c: c} +} + +// GetOpenInterestsServicdde +func (c *Client) NewGetOpenInterestsService() *GetOpenInterestsService { + return &GetOpenInterestsService{c: c} +} + +// GetHistoricalVolatilityService +func (c *Client) NewGetHistoricalVolatilityService() *GetHistoricalVolatilityService { + return &GetHistoricalVolatilityService{c: c} +} + +// GetInsuranceInfoService +func (c *Client) NewGetInsuranceInfoService() *GetInsuranceInfoService { + return &GetInsuranceInfoService{c: c} +} + +// GetRiskLimitService +func (c *Client) NewGetRiskLimitService() *GetRiskLimitService { + return &GetRiskLimitService{c: c} +} + +// GetDeliveryPriceService +func (c *Client) NewGetDeliveryPriceService() *GetDeliveryPriceService { + return &GetDeliveryPriceService{c: c} +} + +// GetMarketLSRatioService +func (c *Client) NewGetMarketLSRatioService() *GetMarketLSRatioService { + return &GetMarketLSRatioService{c: c} +} + +// GetServerTimeService +func (c *Client) NewGetServerTimeService() *GetServerTimeService { + return &GetServerTimeService{c: c} } // NewPlaceOrderService Trade Endpoints diff --git a/market.go b/market.go deleted file mode 100644 index 55f7996..0000000 --- a/market.go +++ /dev/null @@ -1,296 +0,0 @@ -package bybit_connector - -import ( - "context" - "encoding/json" - "github.com/wuhewuhe/bybit.go.api/handlers" - "net/http" -) - -type MarketClient struct { - c *Client - klineType string - params map[string]interface{} -} - -func (s *MarketClient) GetServerTime(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { - r := &request{ - method: http.MethodGet, - endpoint: "/v5/market/time", - secType: secTypeNone, - } - data, err := s.c.callAPI(ctx, r, opts...) - if err != nil { - return nil, err - } - res = new(ServerResponse) - err = json.Unmarshal(data, res) - if err != nil { - return nil, err - } - return res, nil -} - -func (s *MarketClient) GetMarketKline(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { - if err = handlers.ValidateParams(s.params); err != nil { - return nil, err - } - r := &request{ - method: http.MethodGet, - endpoint: "/v5/market/" + s.klineType, - secType: secTypeNone, - } - r.setParams(s.params) - data, err := s.c.callAPI(ctx, r, opts...) - if err != nil { - return nil, err - } - res = new(ServerResponse) - err = json.Unmarshal(data, res) - if err != nil { - return nil, err - } - return res, nil -} - -func (s *MarketClient) GetInstrumentInfo(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { - if err = handlers.ValidateParams(s.params); err != nil { - return nil, err - } - r := &request{ - method: http.MethodGet, - endpoint: "/v5/market/instruments-info", - secType: secTypeNone, - } - r.setParams(s.params) - data, err := s.c.callAPI(ctx, r, opts...) - if err != nil { - return nil, err - } - res = new(ServerResponse) - err = json.Unmarshal(data, res) - if err != nil { - return nil, err - } - return res, nil -} - -func (s *MarketClient) GetOrderBookInfo(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { - if err = handlers.ValidateParams(s.params); err != nil { - return nil, err - } - r := &request{ - method: http.MethodGet, - endpoint: "/v5/market/orderbook", - secType: secTypeNone, - } - r.setParams(s.params) - data, err := s.c.callAPI(ctx, r, opts...) - if err != nil { - return nil, err - } - res = new(ServerResponse) - err = json.Unmarshal(data, res) - if err != nil { - return nil, err - } - return res, nil -} - -func (s *MarketClient) GetMarketTickers(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { - if err = handlers.ValidateParams(s.params); err != nil { - return nil, err - } - r := &request{ - method: http.MethodGet, - endpoint: "/v5/market/tickers", - secType: secTypeNone, - } - r.setParams(s.params) - data, err := s.c.callAPI(ctx, r, opts...) - if err != nil { - return nil, err - } - res = new(ServerResponse) - err = json.Unmarshal(data, res) - if err != nil { - return nil, err - } - return res, nil -} - -func (s *MarketClient) GetFundingRates(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { - if err = handlers.ValidateParams(s.params); err != nil { - return nil, err - } - r := &request{ - method: http.MethodGet, - endpoint: "/v5/market/tickers", - secType: secTypeNone, - } - r.setParams(s.params) - data, err := s.c.callAPI(ctx, r, opts...) - if err != nil { - return nil, err - } - res = new(ServerResponse) - err = json.Unmarshal(data, res) - if err != nil { - return nil, err - } - return res, nil -} - -func (s *MarketClient) GetPublicRecentTrades(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { - if err = handlers.ValidateParams(s.params); err != nil { - return nil, err - } - r := &request{ - method: http.MethodGet, - endpoint: "/v5/market/recent-trade", - secType: secTypeNone, - } - r.setParams(s.params) - data, err := s.c.callAPI(ctx, r, opts...) - if err != nil { - return nil, err - } - res = new(ServerResponse) - err = json.Unmarshal(data, res) - if err != nil { - return nil, err - } - return res, nil -} - -func (s *MarketClient) GetOpenInterests(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { - if err = handlers.ValidateParams(s.params); err != nil { - return nil, err - } - r := &request{ - method: http.MethodGet, - endpoint: "/v5/market/open-interest", - secType: secTypeNone, - } - r.setParams(s.params) - data, err := s.c.callAPI(ctx, r, opts...) - if err != nil { - return nil, err - } - res = new(ServerResponse) - err = json.Unmarshal(data, res) - if err != nil { - return nil, err - } - return res, nil -} - -func (s *MarketClient) GetHistoricalVolatility(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { - if err = handlers.ValidateParams(s.params); err != nil { - return nil, err - } - r := &request{ - method: http.MethodGet, - endpoint: "/v5/market/historical-volatility", - secType: secTypeNone, - } - r.setParams(s.params) - data, err := s.c.callAPI(ctx, r, opts...) - if err != nil { - return nil, err - } - res = new(ServerResponse) - err = json.Unmarshal(data, res) - if err != nil { - return nil, err - } - return res, nil -} - -func (s *MarketClient) GetInsuranceInfo(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { - if err = handlers.ValidateParams(s.params); err != nil { - return nil, err - } - r := &request{ - method: http.MethodGet, - endpoint: "/v5/market/insurance", - secType: secTypeNone, - } - r.setParams(s.params) - data, err := s.c.callAPI(ctx, r, opts...) - if err != nil { - return nil, err - } - res = new(ServerResponse) - err = json.Unmarshal(data, res) - if err != nil { - return nil, err - } - return res, nil -} - -func (s *MarketClient) GetRiskLimit(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { - if err = handlers.ValidateParams(s.params); err != nil { - return nil, err - } - r := &request{ - method: http.MethodGet, - endpoint: "/v5/market/risk-limit", - secType: secTypeNone, - } - r.setParams(s.params) - data, err := s.c.callAPI(ctx, r, opts...) - if err != nil { - return nil, err - } - res = new(ServerResponse) - err = json.Unmarshal(data, res) - if err != nil { - return nil, err - } - return res, nil -} - -func (s *MarketClient) GetDeliveryPrice(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { - if err = handlers.ValidateParams(s.params); err != nil { - return nil, err - } - r := &request{ - method: http.MethodGet, - endpoint: "/v5/market/delivery-price", - secType: secTypeNone, - } - r.setParams(s.params) - data, err := s.c.callAPI(ctx, r, opts...) - if err != nil { - return nil, err - } - res = new(ServerResponse) - err = json.Unmarshal(data, res) - if err != nil { - return nil, err - } - return res, nil -} - -func (s *MarketClient) GetMarketLSRatio(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { - if err = handlers.ValidateParams(s.params); err != nil { - return nil, err - } - r := &request{ - method: http.MethodGet, - endpoint: "/v5/market/account-ratio", - secType: secTypeNone, - } - r.setParams(s.params) - data, err := s.c.callAPI(ctx, r, opts...) - if err != nil { - return nil, err - } - res = new(ServerResponse) - err = json.Unmarshal(data, res) - if err != nil { - return nil, err - } - return res, nil -} diff --git a/market_klines.go b/market_klines.go deleted file mode 100644 index c0929d8..0000000 --- a/market_klines.go +++ /dev/null @@ -1,67 +0,0 @@ -package bybit_connector - -import ( - "context" - "encoding/json" - "net/http" -) - -// Klines Market Kline (GET /v5/market/kline) -type Klines struct { - c *Client - klineType string - category string - symbol string - interval string - limit *int - start *uint64 - end *uint64 -} - -// Limit set limit -func (s *Klines) Limit(limit int) *Klines { - s.limit = &limit - return s -} - -// Start set startTime -func (s *Klines) Start(startTime uint64) *Klines { - s.start = &startTime - return s -} - -// End set endTime -func (s *Klines) End(endTime uint64) *Klines { - s.end = &endTime - return s -} - -func (s *Klines) Do(ctx context.Context, opts ...RequestOption) (res *ServerResponse, err error) { - r := &request{ - method: http.MethodGet, - endpoint: "/v5/market/" + s.klineType, - secType: secTypeNone, - } - r.setParam("category", s.category) - r.setParam("symbol", s.symbol) - r.setParam("interval", s.interval) - if s.limit != nil { - r.setParam("limit", *s.limit) - } - if s.start != nil { - r.setParam("start", *s.start) - } - if s.end != nil { - r.setParam("end", *s.end) - } - data, err := s.c.callAPI(ctx, r, opts...) - if err != nil { - return nil, err - } - res = new(ServerResponse) - err = json.Unmarshal(data, res) - if err != nil { - return nil, err - } - return res, nil -} diff --git a/market_service.go b/market_service.go new file mode 100644 index 0000000..a660dc1 --- /dev/null +++ b/market_service.go @@ -0,0 +1,1167 @@ +package bybit_connector + +import ( + "context" + "fmt" + "net/http" + + "github.com/wuhewuhe/bybit.go.api/models" +) + +// MarketKlinesService Market Kline (GET /v5/market/kline) +type MarketKlinesService struct { + c *Client + category *models.Category + symbol string + interval string + start *uint64 + end *uint64 + limit *int +} + +// Category set category +func (s *MarketKlinesService) Category(category models.Category) *MarketKlinesService { + s.category = &category + return s +} + +// Symbol set symbol +func (s *MarketKlinesService) Symbol(symbol string) *MarketKlinesService { + s.symbol = symbol + return s +} + +// Interval set interval +func (s *MarketKlinesService) Interval(interval string) *MarketKlinesService { + s.interval = interval + return s +} + +// Limit set limit +func (s *MarketKlinesService) Limit(limit int) *MarketKlinesService { + s.limit = &limit + return s +} + +// Start set startTime +func (s *MarketKlinesService) Start(startTime uint64) *MarketKlinesService { + s.start = &startTime + return s +} + +// End set endTime +func (s *MarketKlinesService) End(endTime uint64) *MarketKlinesService { + s.end = &endTime + return s +} + +func (s *MarketKlinesService) Do(ctx context.Context, opts ...RequestOption) (res *models.MarketKlineResponse, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/kline", + secType: secTypeNone, + } + if s.category != nil { + r.setParam("category", *s.category) + } + r.setParam("symbol", s.symbol) + r.setParam("interval", s.interval) + if s.start != nil { + r.setParam("start", *s.start) + } + if s.end != nil { + r.setParam("end", *s.end) + } + if s.limit != nil { + r.setParam("limit", *s.limit) + } + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + + j, err := newJSON(data) + if err != nil { + return nil, err + } + result := j.Get("result") + res = new(models.MarketKlineResponse) + res.Category = models.Category(result.Get("category").MustString()) + res.Symbol = result.Get("symbol").MustString() + list := result.Get("list").MustArray() + res.List = make([]*models.MarketKlineCandle, len(list)) + for i := range list { + item := result.Get("list").GetIndex(i) + if len(item.MustArray()) < 7 { + return nil, fmt.Errorf("invalid kline response") + } + + res.List[i] = &models.MarketKlineCandle{ + StartTime: item.GetIndex(0).MustString(), + OpenPrice: item.GetIndex(1).MustString(), + HighPrice: item.GetIndex(2).MustString(), + LowPrice: item.GetIndex(3).MustString(), + ClosePrice: item.GetIndex(4).MustString(), + Volume: item.GetIndex(5).MustString(), + Turnover: item.GetIndex(6).MustString(), + } + } + + return res, nil +} + +// MarketMarkPriceKlineService Market mark price kline (GET /v5/market/mark-price-kline) +type MarketMarkPriceKlineService struct { + c *Client + category *models.Category + symbol string + interval string + start *uint64 + end *uint64 + limit *int +} + +// Category set category +func (s *MarketMarkPriceKlineService) Category(category models.Category) *MarketMarkPriceKlineService { + s.category = &category + return s +} + +// Symbol set symbol +func (s *MarketMarkPriceKlineService) Symbol(symbol string) *MarketMarkPriceKlineService { + s.symbol = symbol + return s +} + +// Interval set interval +func (s *MarketMarkPriceKlineService) Interval(interval string) *MarketMarkPriceKlineService { + s.interval = interval + return s +} + +// Limit set limit +func (s *MarketMarkPriceKlineService) Limit(limit int) *MarketMarkPriceKlineService { + s.limit = &limit + return s +} + +// Start set startTime +func (s *MarketMarkPriceKlineService) Start(startTime uint64) *MarketMarkPriceKlineService { + s.start = &startTime + return s +} + +// End set endTime +func (s *MarketMarkPriceKlineService) End(endTime uint64) *MarketMarkPriceKlineService { + s.end = &endTime + return s +} + +func (s *MarketMarkPriceKlineService) Do(ctx context.Context, opts ...RequestOption) (res *models.MarketMarkPriceKlineResponse, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/mark-price-kline", + secType: secTypeNone, + } + if s.category != nil { + r.setParam("category", *s.category) + } + r.setParam("symbol", s.symbol) + r.setParam("interval", s.interval) + if s.start != nil { + r.setParam("start", *s.start) + } + if s.end != nil { + r.setParam("end", *s.end) + } + if s.limit != nil { + r.setParam("limit", *s.limit) + } + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + + j, err := newJSON(data) + if err != nil { + return nil, err + } + result := j.Get("result") + res = new(models.MarketMarkPriceKlineResponse) + res.Category = models.Category(result.Get("category").MustString()) + res.Symbol = result.Get("symbol").MustString() + list := result.Get("list").MustArray() + res.List = make([]*models.MarketMarkPriceKlineCandle, len(list)) + for i := range list { + item := result.Get("list").GetIndex(i) + if len(item.MustArray()) < 5 { + return nil, fmt.Errorf("invalid kline response") + } + + res.List[i] = &models.MarketMarkPriceKlineCandle{ + StartTime: item.GetIndex(0).MustString(), + OpenPrice: item.GetIndex(1).MustString(), + HighPrice: item.GetIndex(2).MustString(), + LowPrice: item.GetIndex(3).MustString(), + ClosePrice: item.GetIndex(4).MustString(), + } + } + + return res, nil +} + +// MarketIndexPriceKlineService Market index price kline (GET /v5/market/index-price-kline) +type MarketIndexPriceKlineService struct { + c *Client + category *models.Category + symbol string + interval string + start *uint64 + end *uint64 + limit *int +} + +// Category set category +func (s *MarketIndexPriceKlineService) Category(category models.Category) *MarketIndexPriceKlineService { + s.category = &category + return s +} + +// Symbol set symbol +func (s *MarketIndexPriceKlineService) Symbol(symbol string) *MarketIndexPriceKlineService { + s.symbol = symbol + return s +} + +// Interval set interval +func (s *MarketIndexPriceKlineService) Interval(interval string) *MarketIndexPriceKlineService { + s.interval = interval + return s +} + +// Limit set limit +func (s *MarketIndexPriceKlineService) Limit(limit int) *MarketIndexPriceKlineService { + s.limit = &limit + return s +} + +// Start set startTime +func (s *MarketIndexPriceKlineService) Start(startTime uint64) *MarketIndexPriceKlineService { + s.start = &startTime + return s +} + +// End set endTime +func (s *MarketIndexPriceKlineService) End(endTime uint64) *MarketIndexPriceKlineService { + s.end = &endTime + return s +} + +func (s *MarketIndexPriceKlineService) Do(ctx context.Context, opts ...RequestOption) (res *models.MarketIndexPriceKlineResponse, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/mark-price-kline", + secType: secTypeNone, + } + if s.category != nil { + r.setParam("category", *s.category) + } + r.setParam("symbol", s.symbol) + r.setParam("interval", s.interval) + if s.start != nil { + r.setParam("start", *s.start) + } + if s.end != nil { + r.setParam("end", *s.end) + } + if s.limit != nil { + r.setParam("limit", *s.limit) + } + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + + j, err := newJSON(data) + if err != nil { + return nil, err + } + result := j.Get("result") + res = new(models.MarketIndexPriceKlineResponse) + res.Category = models.Category(result.Get("category").MustString()) + res.Symbol = result.Get("symbol").MustString() + list := result.Get("list").MustArray() + res.List = make([]*models.MarketIndexPriceKlineCandle, len(list)) + for i := range list { + item := result.Get("list").GetIndex(i) + if len(item.MustArray()) < 5 { + return nil, fmt.Errorf("invalid kline response") + } + + res.List[i] = &models.MarketIndexPriceKlineCandle{ + StartTime: item.GetIndex(0).MustString(), + OpenPrice: item.GetIndex(1).MustString(), + HighPrice: item.GetIndex(2).MustString(), + LowPrice: item.GetIndex(3).MustString(), + ClosePrice: item.GetIndex(4).MustString(), + } + } + + return res, nil +} + +// MarketPremiumIndexPriceKlineService Market premium index price kline (GET /v5/market/premium-index-price-kline) +type MarketPremiumIndexPriceKlineService struct { + c *Client + category *models.Category + symbol string + interval string + start *uint64 + end *uint64 + limit *int +} + +// Category set category +func (s *MarketPremiumIndexPriceKlineService) Category(category models.Category) *MarketPremiumIndexPriceKlineService { + s.category = &category + return s +} + +// Symbol set symbol +func (s *MarketPremiumIndexPriceKlineService) Symbol(symbol string) *MarketPremiumIndexPriceKlineService { + s.symbol = symbol + return s +} + +// Interval set interval +func (s *MarketPremiumIndexPriceKlineService) Interval(interval string) *MarketPremiumIndexPriceKlineService { + s.interval = interval + return s +} + +// Limit set limit +func (s *MarketPremiumIndexPriceKlineService) Limit(limit int) *MarketPremiumIndexPriceKlineService { + s.limit = &limit + return s +} + +// Start set startTime +func (s *MarketPremiumIndexPriceKlineService) Start(startTime uint64) *MarketPremiumIndexPriceKlineService { + s.start = &startTime + return s +} + +// End set endTime +func (s *MarketPremiumIndexPriceKlineService) End(endTime uint64) *MarketPremiumIndexPriceKlineService { + s.end = &endTime + return s +} + +func (s *MarketPremiumIndexPriceKlineService) Do(ctx context.Context, opts ...RequestOption) (res *models.MarketPremiumIndexPriceKlineResponse, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/mark-price-kline", + secType: secTypeNone, + } + if s.category != nil { + r.setParam("category", *s.category) + } + r.setParam("symbol", s.symbol) + r.setParam("interval", s.interval) + if s.start != nil { + r.setParam("start", *s.start) + } + if s.end != nil { + r.setParam("end", *s.end) + } + if s.limit != nil { + r.setParam("limit", *s.limit) + } + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + + j, err := newJSON(data) + if err != nil { + return nil, err + } + result := j.Get("result") + res = new(models.MarketPremiumIndexPriceKlineResponse) + res.Category = models.Category(result.Get("category").MustString()) + res.Symbol = result.Get("symbol").MustString() + list := result.Get("list").MustArray() + res.List = make([]*models.MarketPremiumIndexPriceKlineCandle, len(list)) + for i := range list { + item := result.Get("list").GetIndex(i) + if len(item.MustArray()) < 5 { + return nil, fmt.Errorf("invalid kline response") + } + + res.List[i] = &models.MarketPremiumIndexPriceKlineCandle{ + StartTime: item.GetIndex(0).MustString(), + OpenPrice: item.GetIndex(1).MustString(), + HighPrice: item.GetIndex(2).MustString(), + LowPrice: item.GetIndex(3).MustString(), + ClosePrice: item.GetIndex(4).MustString(), + } + } + + return res, nil +} + +type InstrumentsInfoService struct { + c *Client + category models.Category + symbol *string + statsu *models.SymbolStatus + baseCoin *string + limit *int + cursor *string +} + +// Category set category +func (s *InstrumentsInfoService) Category(category models.Category) *InstrumentsInfoService { + s.category = category + return s +} + +// Symbol set symbol +func (s *InstrumentsInfoService) Symbol(symbol string) *InstrumentsInfoService { + s.symbol = &symbol + return s +} + +// Status set status +func (s *InstrumentsInfoService) Status(status models.SymbolStatus) *InstrumentsInfoService { + s.statsu = &status + return s +} + +// BaseCoin set baseCoin +func (s *InstrumentsInfoService) BaseCoin(baseCoin string) *InstrumentsInfoService { + s.baseCoin = &baseCoin + return s +} + +// Limit set limit +func (s *InstrumentsInfoService) Limit(limit int) *InstrumentsInfoService { + s.limit = &limit + return s +} + +// Cursor set cursor +func (s *InstrumentsInfoService) Cursor(cursor string) *InstrumentsInfoService { + s.cursor = &cursor + return s +} + +func (s *InstrumentsInfoService) Do(ctx context.Context, opts ...RequestOption) (res *models.InstrumentInfoResponse, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/instruments-info", + secType: secTypeNone, + } + + r.setParam("category", s.category) + if s.symbol != nil { + r.setParam("symbol", *s.symbol) + } + if s.statsu != nil { + r.setParam("status", *s.statsu) + } + if s.baseCoin != nil { + r.setParam("baseCoin", *s.baseCoin) + } + if s.limit != nil { + r.setParam("limit", *s.limit) + } + if s.cursor != nil { + r.setParam("cursor", *s.cursor) + } + + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + + type instrumentInfoResponse struct { + InstrumentsInfo *models.InstrumentInfoResponse `json:"result"` + } + resp := new(instrumentInfoResponse) + if err := json.Unmarshal(data, resp); err != nil { + return nil, err + } + + return resp.InstrumentsInfo, nil +} + +type MarketOrderBookService struct { + c *Client + category models.Category + symbol string + limit *int +} + +// Category set category +func (s *MarketOrderBookService) Category(category models.Category) *MarketOrderBookService { + s.category = category + return s +} + +// Symbol set symbol +func (s *MarketOrderBookService) Symbol(symbol string) *MarketOrderBookService { + s.symbol = symbol + return s +} + +func (s *MarketOrderBookService) Limit(limit int) *MarketOrderBookService { + s.limit = &limit + return s +} + +func (s *MarketOrderBookService) Do(ctx context.Context, opts ...RequestOption) (res *models.OrderBookInfo, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/orderbook", + secType: secTypeNone, + } + r.setParam("category", s.category) + r.setParam("symbol", s.symbol) + if s.limit != nil { + r.setParam("limit", *s.limit) + } + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + resp := new(MarketOrderBookResponse) + err = json.Unmarshal(data, resp) + if err != nil { + return nil, err + } + return &resp.Result, nil +} + +type MarketOrderBookResponse struct { + RetCode int `json:"retCode"` + RetMsg string `json:"retMsg"` + Result models.OrderBookInfo `json:"result"` + RetExtInfo struct{} `json:"retExtInfo"` + Time int64 `json:"time"` +} + +type MarketTickersService struct { + c *Client + category models.Category + symbol *string + baseCoin *string + expDate *string +} + +func (s *MarketTickersService) Category(category models.Category) *MarketTickersService { + s.category = category + return s +} + +func (s *MarketTickersService) Symbol(symbol string) *MarketTickersService { + s.symbol = &symbol + return s +} + +func (s *MarketTickersService) BaseCoin(baseCoin string) *MarketTickersService { + s.baseCoin = &baseCoin + return s +} + +func (s *MarketTickersService) ExpDate(expDate string) *MarketTickersService { + s.expDate = &expDate + return s +} + +func (s *MarketTickersService) Do(ctx context.Context, opts ...RequestOption) (res *models.MarketTickers, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/tickers", + secType: secTypeNone, + } + r.setParam("category", s.category) + if s.symbol != nil { + r.setParam("symbol", *s.symbol) + } + if s.baseCoin != nil { + r.setParam("baseCoin", *s.baseCoin) + } + if s.expDate != nil { + r.setParam("expDate", *s.expDate) + } + + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + resp := new(MarketTickersResponse) + err = json.Unmarshal(data, resp) + if err != nil { + return nil, err + } + return &resp.Result, nil +} + +type MarketTickersResponse struct { + RetCode int `json:"retCode"` + RetMsg string `json:"retMsg"` + Result models.MarketTickers `json:"result"` + RetExtInfo struct{} `json:"retExtInfo"` + Time int64 `json:"time"` +} + +type MarketFundingRatesService struct { + c *Client + category models.Category + symbol string + startTime *uint64 + endTime *uint64 + limit *int +} + +func (s *MarketFundingRatesService) Category(category models.Category) *MarketFundingRatesService { + s.category = category + return s +} + +func (s *MarketFundingRatesService) Symbol(symbol string) *MarketFundingRatesService { + s.symbol = symbol + return s +} + +func (s *MarketFundingRatesService) StartTime(startTime uint64) *MarketFundingRatesService { + s.startTime = &startTime + return s +} + +func (s *MarketFundingRatesService) EndTime(endTime uint64) *MarketFundingRatesService { + s.endTime = &endTime + return s +} + +func (s *MarketFundingRatesService) Limit(limit int) *MarketFundingRatesService { + s.limit = &limit + return s +} + +func (s *MarketFundingRatesService) Do(ctx context.Context, opts ...RequestOption) (res *models.FundingRate, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/tickers", + secType: secTypeNone, + } + r.setParam("category", s.category) + r.setParam("symbol", s.symbol) + if s.startTime != nil { + r.setParam("startTime", *s.startTime) + } + if s.endTime != nil { + r.setParam("endTime", *s.endTime) + } + if s.limit != nil { + r.setParam("limit", *s.limit) + } + + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + resp := new(MarketFundingRatesResponse) + err = json.Unmarshal(data, resp) + if err != nil { + return nil, err + } + return &resp.Result, nil +} + +type MarketFundingRatesResponse struct { + RetCode int `json:"retCode"` + RetMsg string `json:"retMsg"` + Result models.FundingRate `json:"result"` + RetExtInfo struct{} `json:"retExtInfo"` + Time int64 `json:"time"` +} + +type GetPublicRecentTradesService struct { + c *Client + category models.Category + symbol *string + baseCoin *string + optionType *string + limit *int +} + +func (s *GetPublicRecentTradesService) Category(category models.Category) *GetPublicRecentTradesService { + s.category = category + return s +} + +func (s *GetPublicRecentTradesService) Symbol(symbol string) *GetPublicRecentTradesService { + s.symbol = &symbol + return s +} + +func (s *GetPublicRecentTradesService) BaseCoin(baseCoin string) *GetPublicRecentTradesService { + s.baseCoin = &baseCoin + return s +} + +func (s *GetPublicRecentTradesService) OptionType(optionType string) *GetPublicRecentTradesService { + s.optionType = &optionType + return s +} + +func (s *GetPublicRecentTradesService) Limit(limit int) *GetPublicRecentTradesService { + s.limit = &limit + return s +} + +func (s *GetPublicRecentTradesService) Do(ctx context.Context, opts ...RequestOption) (res *models.PublicRecentTradeHistory, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/recent-trade", + secType: secTypeNone, + } + r.setParam("category", s.category) + if s.symbol != nil { + r.setParam("symbol", *s.symbol) + } + if s.baseCoin != nil { + r.setParam("baseCoin", *s.baseCoin) + } + if s.optionType != nil { + r.setParam("optionType", *s.optionType) + } + if s.limit != nil { + r.setParam("limit", *s.limit) + } + + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + resp := new(GetPublicRecentTradesResponse) + err = json.Unmarshal(data, resp) + if err != nil { + return nil, err + } + return &resp.Result, nil +} + +type GetPublicRecentTradesResponse struct { + RetCode int `json:"retCode"` + RetMsg string `json:"retMsg"` + Result models.PublicRecentTradeHistory `json:"result"` + RetExtInfo struct{} `json:"retExtInfo"` + Time int64 `json:"time"` +} + +type GetOpenInterestsService struct { + c *Client + category models.Category + symbol string + intervalTime string + startTime *uint64 + endTime *uint64 + limit *int + cursor *string +} + +func (s *GetOpenInterestsService) Category(category models.Category) *GetOpenInterestsService { + s.category = category + return s +} + +func (s *GetOpenInterestsService) Symbol(symbol string) *GetOpenInterestsService { + s.symbol = symbol + return s +} + +func (s *GetOpenInterestsService) IntervalTime(intervalTime string) *GetOpenInterestsService { + s.intervalTime = intervalTime + return s +} + +func (s *GetOpenInterestsService) StartTime(startTime uint64) *GetOpenInterestsService { + s.startTime = &startTime + return s +} + +func (s *GetOpenInterestsService) EndTime(endTime uint64) *GetOpenInterestsService { + s.endTime = &endTime + return s +} + +func (s *GetOpenInterestsService) Limit(limit int) *GetOpenInterestsService { + s.limit = &limit + return s +} + +func (s *GetOpenInterestsService) Cursor(cursor string) *GetOpenInterestsService { + s.cursor = &cursor + return s +} + +func (s *GetOpenInterestsService) Do(ctx context.Context, opts ...RequestOption) (res *models.OpenInterestInfo, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/open-interest", + secType: secTypeNone, + } + r.setParam("category", s.category) + r.setParam("symbol", s.symbol) + r.setParam("intervalTime", s.intervalTime) + if s.startTime != nil { + r.setParam("startTime", *s.startTime) + } + if s.endTime != nil { + r.setParam("endTime", *s.endTime) + } + if s.limit != nil { + r.setParam("limit", *s.limit) + } + if s.cursor != nil { + r.setParam("cursor", *s.cursor) + } + + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + resp := new(GetOpenInterestsResponse) + err = json.Unmarshal(data, resp) + if err != nil { + return nil, err + } + return &resp.Result, nil +} + +type GetOpenInterestsResponse struct { + RetCode int `json:"retCode"` + RetMsg string `json:"retMsg"` + Result models.OpenInterestInfo `json:"result"` + RetExtInfo struct{} `json:"retExtInfo"` + Time int64 `json:"time"` +} + +type GetHistoricalVolatilityService struct { + c *Client + category models.Category + baseCoin *string + period *string + startTime *uint64 + endTime *uint64 +} + +func (s *GetHistoricalVolatilityService) Category(category models.Category) *GetHistoricalVolatilityService { + s.category = category + return s +} + +func (s *GetHistoricalVolatilityService) BaseCoin(baseCoin string) *GetHistoricalVolatilityService { + s.baseCoin = &baseCoin + return s +} + +func (s *GetHistoricalVolatilityService) Period(period string) *GetHistoricalVolatilityService { + s.period = &period + return s +} + +func (s *GetHistoricalVolatilityService) StartTime(startTime uint64) *GetHistoricalVolatilityService { + s.startTime = &startTime + return s +} + +func (s *GetHistoricalVolatilityService) EndTime(endTime uint64) *GetHistoricalVolatilityService { + s.endTime = &endTime + return s +} + +func (s *GetHistoricalVolatilityService) Do(ctx context.Context, opts ...RequestOption) (res *models.HistoricalVolatilityInfo, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/historical-volatility", + secType: secTypeNone, + } + r.setParam("category", s.category) + if s.baseCoin != nil { + r.setParam("baseCoin", *s.baseCoin) + } + if s.period != nil { + r.setParam("period", *s.period) + } + if s.startTime != nil { + r.setParam("startTime", *s.startTime) + } + if s.endTime != nil { + r.setParam("endTime", *s.endTime) + } + + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + res = new(models.HistoricalVolatilityInfo) + err = json.Unmarshal(data, res) + if err != nil { + return nil, err + } + return res, nil +} + +type GetInsuranceInfoService struct { + c *Client + coin *string +} + +func (s *GetInsuranceInfoService) Coin(coin string) *GetInsuranceInfoService { + s.coin = &coin + return s +} + +func (s *GetInsuranceInfoService) Do(ctx context.Context, opts ...RequestOption) (res *models.MarketInsuranceInfo, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/insurance", + secType: secTypeNone, + } + if s.coin != nil { + r.setParam("coin", *s.coin) + } + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + resp := new(GetInsuranceInfoResponse) + err = json.Unmarshal(data, resp) + if err != nil { + return nil, err + } + return &resp.Result, nil +} + +type GetInsuranceInfoResponse struct { + RetCode int `json:"retCode"` + RetMsg string `json:"retMsg"` + Result models.MarketInsuranceInfo `json:"result"` + RetExtInfo struct{} `json:"retExtInfo"` + Time int64 `json:"time"` +} + +type GetRiskLimitService struct { + c *Client + category models.Category + symbol *string +} + +func (s *GetRiskLimitService) Category(category models.Category) *GetRiskLimitService { + s.category = category + return s +} + +func (s *GetRiskLimitService) Symbol(symbol string) *GetRiskLimitService { + s.symbol = &symbol + return s +} + +func (s *GetRiskLimitService) Do(ctx context.Context, opts ...RequestOption) (res *models.MarketRiskLimitInfo, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/risk-limit", + secType: secTypeNone, + } + r.setParam("category", s.category) + if s.symbol != nil { + r.setParam("symbol", *s.symbol) + } + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + resp := new(GetRiskLimitResponse) + err = json.Unmarshal(data, resp) + if err != nil { + return nil, err + } + return &resp.Result, nil +} + +type GetRiskLimitResponse struct { + RetCode int `json:"retCode"` + RetMsg string `json:"retMsg"` + Result models.MarketRiskLimitInfo `json:"result"` + RetExtInfo struct{} `json:"retExtInfo"` + Time int64 `json:"time"` +} + +type GetDeliveryPriceService struct { + c *Client + category models.Category + symbol *string + baseCoin *string + limit *int + cursor *string +} + +func (s *GetDeliveryPriceService) Category(category models.Category) *GetDeliveryPriceService { + s.category = category + return s +} + +func (s *GetDeliveryPriceService) Symbol(symbol string) *GetDeliveryPriceService { + s.symbol = &symbol + return s +} + +func (s *GetDeliveryPriceService) BaseCoin(baseCoin string) *GetDeliveryPriceService { + s.baseCoin = &baseCoin + return s +} + +func (s *GetDeliveryPriceService) Limit(limit int) *GetDeliveryPriceService { + s.limit = &limit + return s +} + +func (s *GetDeliveryPriceService) Cursor(cursor string) *GetDeliveryPriceService { + s.cursor = &cursor + return s +} + +func (s *GetDeliveryPriceService) Do(ctx context.Context, opts ...RequestOption) (res *models.DeliveryPriceInfo, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/delivery-price", + secType: secTypeNone, + } + r.setParam("category", s.category) + if s.symbol != nil { + r.setParam("symbol", *s.symbol) + } + if s.baseCoin != nil { + r.setParam("baseCoin", *s.baseCoin) + } + if s.limit != nil { + r.setParam("limit", *s.limit) + } + if s.cursor != nil { + r.setParam("cursor", *s.cursor) + } + + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + resp := new(GetDeliveryPriceResponse) + err = json.Unmarshal(data, resp) + if err != nil { + return nil, err + } + return &resp.Result, nil +} + +type GetDeliveryPriceResponse struct { + RetCode int `json:"retCode"` + RetMsg string `json:"retMsg"` + Result models.DeliveryPriceInfo `json:"result"` + RetExtInfo struct{} `json:"retExtInfo"` + Time int64 `json:"time"` +} + +type GetMarketLSRatioService struct { + c *Client + category models.Category + baseCoin string + period string + limit *int +} + +func (s *GetMarketLSRatioService) Category(category models.Category) *GetMarketLSRatioService { + s.category = category + return s +} + +func (s *GetMarketLSRatioService) BaseCoin(baseCoin string) *GetMarketLSRatioService { + s.baseCoin = baseCoin + return s +} + +func (s *GetMarketLSRatioService) Period(period string) *GetMarketLSRatioService { + s.period = period + return s +} + +func (s *GetMarketLSRatioService) Limit(limit int) *GetMarketLSRatioService { + s.limit = &limit + return s +} + +func (s *GetMarketLSRatioService) Do(ctx context.Context, opts ...RequestOption) (res *models.MarketLongShortRatioInfo, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/account-ratio", + secType: secTypeNone, + } + r.setParam("category", s.category) + r.setParam("baseCoin", s.baseCoin) + r.setParam("period", s.period) + if s.limit != nil { + r.setParam("limit", *s.limit) + } + + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + resp := new(GetMarketLSRatioResponse) + err = json.Unmarshal(data, resp) + if err != nil { + return nil, err + } + return &resp.Result, nil +} + +type GetMarketLSRatioResponse struct { + RetCode int `json:"retCode"` + RetMsg string `json:"retMsg"` + Result models.MarketLongShortRatioInfo `json:"result"` + RetExtInfo struct{} `json:"retExtInfo"` + Time int64 `json:"time"` +} + +type GetServerTimeService struct { + c *Client +} + +func (s *GetServerTimeService) Do(ctx context.Context, opts ...RequestOption) (res *models.ServerTimeResult, err error) { + r := &request{ + method: http.MethodGet, + endpoint: "/v5/market/time", + secType: secTypeNone, + } + data, err := s.c.callAPI(ctx, r, opts...) + if err != nil { + return nil, err + } + resp := new(GetServerTimeResponse) + err = json.Unmarshal(data, resp) + if err != nil { + return nil, err + } + return &resp.Result, nil +} + +type GetServerTimeResponse struct { + RetCode int `json:"retCode"` + RetMsg string `json:"retMsg"` + Result models.ServerTimeResult `json:"result"` + RetExtInfo struct{} `json:"retExtInfo"` + Time int64 `json:"time"` +} diff --git a/market_service_test.go b/market_service_test.go new file mode 100644 index 0000000..1f0d9c5 --- /dev/null +++ b/market_service_test.go @@ -0,0 +1,1334 @@ +package bybit_connector + +import ( + "net/http" + "testing" + + "github.com/stretchr/testify/suite" + "github.com/wuhewuhe/bybit.go.api/models" +) + +type marketTestSuite struct { + baseTestSuite +} + +func TestMarketKline(t *testing.T) { + suite.Run(t, new(marketTestSuite)) +} + +func (s *marketTestSuite) TestMarketKline() { + data := []byte(`{ + "retCode": 0, + "retMsg": "OK", + "result": { + "symbol": "BTCUSD", + "category": "inverse", + "list": [ + [ + "1670608800000", + "17071", + "17073", + "17027", + "17055.5", + "268611", + "15.74462667" + ], + [ + "1670605200000", + "17071.5", + "17071.5", + "17061", + "17071", + "4177", + "0.24469757" + ], + [ + "1670601600000", + "17086.5", + "17088", + "16978", + "17071.5", + "6356", + "0.37288112" + ] + ] + }, + "retExtInfo": {}, + "time": 1672025956592 +}`) + s.mockDo(data, nil) + defer s.assertDo() + + category := models.CategoryInverse + symbol := "BTCUSD" + interval := "1" + start := uint64(1499040000000) + end := uint64(1499040000001) + limit := 10 + s.assertReq(func(r *request) { + e := newRequest() + e.method = http.MethodGet + e.setParams(params{ + "category": category, + "symbol": symbol, + "interval": interval, + "start": start, + "end": end, + "limit": limit, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewMarketKlineService(). + Category(category).Symbol(symbol).Interval(interval). + Start(start).End(end).Limit(limit). + Do(newContext()) + e1 := &models.MarketKlineResponse{ + Symbol: "BTCUSD", + Category: "inverse", + List: []*models.MarketKlineCandle{ + { + StartTime: "1670608800000", + OpenPrice: "17071", + HighPrice: "17073", + LowPrice: "17027", + ClosePrice: "17055.5", + Volume: "268611", + Turnover: "15.74462667", + }, + { + StartTime: "1670605200000", + OpenPrice: "17071.5", + HighPrice: "17071.5", + LowPrice: "17061", + ClosePrice: "17071", + Volume: "4177", + Turnover: "0.24469757", + }, + { + StartTime: "1670601600000", + OpenPrice: "17086.5", + HighPrice: "17088", + LowPrice: "16978", + ClosePrice: "17071.5", + Volume: "6356", + Turnover: "0.37288112", + }, + }, + } + + s.r().NoError(err) + s.assertMarketKlineEqual(e1, res) +} + +func (s *marketTestSuite) assertMarketKlineEqual(expected, actual *models.MarketKlineResponse) { + r := s.r() + r.Equal(expected.Symbol, actual.Symbol, "Symbol") + r.Equal(expected.Category, actual.Category, "Category") + r.Equal(len(expected.List), len(actual.List), "List") + r.Equal(expected.List, actual.List) +} +func (s *marketTestSuite) TestMarketMarkPriceKline() { + data := []byte(`{ + "retCode": 0, + "retMsg": "OK", + "result": { + "symbol": "BTCUSDT", + "category": "linear", + "list": [ + [ + "1670608800000", + "17164.16", + "17164.16", + "17121.5", + "17131.64" + ] + ] + }, + "retExtInfo": {}, + "time": 1672026361839 +}`) + s.mockDo(data, nil) + defer s.assertDo() + + category := models.CategoryInverse + symbol := "BTCUSDT" + interval := "1" + start := uint64(1499040000000) + end := uint64(1499040000001) + limit := 10 + s.assertReq(func(r *request) { + e := newRequest() + e.method = http.MethodGet + e.setParams(params{ + "category": category, + "symbol": symbol, + "interval": interval, + "start": start, + "end": end, + "limit": limit, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewMarketMarkPriceKlineService(). + Category(category).Symbol(symbol).Interval(interval). + Start(start).End(end).Limit(limit). + Do(newContext()) + e1 := &models.MarketMarkPriceKlineResponse{ + Symbol: "BTCUSDT", + Category: "linear", + List: []*models.MarketMarkPriceKlineCandle{ + { + StartTime: "1670608800000", + OpenPrice: "17164.16", + HighPrice: "17164.16", + LowPrice: "17121.5", + ClosePrice: "17131.64", + }, + }, + } + + s.r().NoError(err) + s.assertMarketMarkPriceKlineEqual(e1, res) +} + +func (s *marketTestSuite) assertMarketMarkPriceKlineEqual(expected, actual *models.MarketMarkPriceKlineResponse) { + r := s.r() + r.Equal(expected.Symbol, actual.Symbol, "Symbol") + r.Equal(expected.Category, actual.Category, "Category") + r.Equal(len(expected.List), len(actual.List), "List") + r.Equal(expected.List, actual.List) +} + +func (s *marketTestSuite) TestMarketIndexPriceKline() { + data := []byte(`{ + "retCode": 0, + "retMsg": "OK", + "result": { + "symbol": "BTCUSDZ22", + "category": "inverse", + "list": [ + [ + "1670608800000", + "17167.00", + "17167.00", + "17161.90", + "17163.07" + ], + [ + "1670608740000", + "17166.54", + "17167.69", + "17165.42", + "17167.00" + ] + ] + }, + "retExtInfo": {}, + "time": 1672026471128 +}`) + s.mockDo(data, nil) + defer s.assertDo() + + category := models.CategoryInverse + symbol := "BTCUSDZ22" + interval := "1" + start := uint64(1499040000000) + end := uint64(1499040000001) + limit := 10 + s.assertReq(func(r *request) { + e := newRequest() + e.method = http.MethodGet + e.setParams(params{ + "category": category, + "symbol": symbol, + "interval": interval, + "start": start, + "end": end, + "limit": limit, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewMarketIndexPriceKlineService(). + Category(category).Symbol(symbol).Interval(interval). + Start(start).End(end).Limit(limit). + Do(newContext()) + e1 := &models.MarketIndexPriceKlineResponse{ + Symbol: "BTCUSDZ22", + Category: models.CategoryInverse, + List: []*models.MarketIndexPriceKlineCandle{ + { + StartTime: "1670608800000", + OpenPrice: "17167.00", + HighPrice: "17167.00", + LowPrice: "17161.90", + ClosePrice: "17163.07", + }, + { + StartTime: "1670608740000", + OpenPrice: "17166.54", + HighPrice: "17167.69", + LowPrice: "17165.42", + ClosePrice: "17167.00", + }, + }, + } + + s.r().NoError(err) + s.assertMarketIndexPriceKlineEqual(e1, res) +} + +func (s *marketTestSuite) assertMarketIndexPriceKlineEqual(expected, actual *models.MarketIndexPriceKlineResponse) { + r := s.r() + r.Equal(expected.Symbol, actual.Symbol, "Symbol") + r.Equal(expected.Category, actual.Category, "Category") + r.Equal(len(expected.List), len(actual.List), "List") + r.Equal(expected.List, actual.List) +} + +func (s *marketTestSuite) TestMarketPremiumIndexPriceKline() { + data := []byte(`{ + "retCode": 0, + "retMsg": "OK", + "result": { + "symbol": "BTCPERP", + "category": "linear", + "list": [ + [ + "1672026540000", + "0.000000", + "0.000000", + "0.000000", + "0.000000" + ], + [ + "1672026480000", + "0.000000", + "0.000000", + "0.000000", + "0.000000" + ] + ] + }, + "retExtInfo": {}, + "time": 1672026605042 +}`) + s.mockDo(data, nil) + defer s.assertDo() + + category := models.CategoryInverse + symbol := "BTCPERP" + interval := "1" + start := uint64(1499040000000) + end := uint64(1499040000001) + limit := 10 + s.assertReq(func(r *request) { + e := newRequest() + e.method = http.MethodGet + e.setParams(params{ + "category": category, + "symbol": symbol, + "interval": interval, + "start": start, + "end": end, + "limit": limit, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewMarketPremiumIndexPriceKlineService(). + Category(category).Symbol(symbol).Interval(interval). + Start(start).End(end).Limit(limit). + Do(newContext()) + e1 := &models.MarketPremiumIndexPriceKlineResponse{ + Symbol: "BTCPERP", + Category: "linear", + List: []*models.MarketPremiumIndexPriceKlineCandle{ + { + StartTime: "1672026540000", + OpenPrice: "0.000000", + HighPrice: "0.000000", + LowPrice: "0.000000", + ClosePrice: "0.000000", + }, + { + StartTime: "1672026480000", + OpenPrice: "0.000000", + HighPrice: "0.000000", + LowPrice: "0.000000", + ClosePrice: "0.000000", + }, + }, + } + + s.r().NoError(err) + s.assertMarketPremiumIndexPriceKlineEqual(e1, res) +} + +func (s *marketTestSuite) assertMarketPremiumIndexPriceKlineEqual(expected, actual *models.MarketPremiumIndexPriceKlineResponse) { + r := s.r() + r.Equal(expected.Symbol, actual.Symbol, "Symbol") + r.Equal(expected.Category, actual.Category, "Category") + r.Equal(len(expected.List), len(actual.List), "List") + r.Equal(expected.List, actual.List) +} + +func (s *marketTestSuite) TestInstrumentsInfo() { + data := []byte(`{ + "retCode": 0, + "retMsg": "OK", + "result": { + "category": "spot", + "list": [ + { + "symbol": "BTCUSDT", + "baseCoin": "BTC", + "quoteCoin": "USDT", + "innovation": "0", + "status": "Trading", + "marginTrading": "both", + "lotSizeFilter": { + "basePrecision": "0.000001", + "quotePrecision": "0.00000001", + "minOrderQty": "0.000048", + "maxOrderQty": "71.73956243", + "minOrderAmt": "1", + "maxOrderAmt": "2000000" + }, + "priceFilter": { + "tickSize": "0.01" + } + } + ] + }, + "retExtInfo": {}, + "time": 1672712468011 +}`) + s.mockDo(data, nil) + defer s.assertDo() + + category := models.CategoryInverse + symbol := "BTCUSD" + status := models.SymbolStatusTrading + baseÇoin := "BTC" + limit := 10 + cursor := "cursor" + s.assertReq(func(r *request) { + e := newRequest() + e.method = http.MethodGet + e.setParams(params{ + "category": category, + "symbol": symbol, + "status": status, + "baseCoin": baseÇoin, + "limit": limit, + "cursor": cursor, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewInstrumentsInfoService(). + Category(category).Symbol(symbol).Status(status). + BaseCoin(baseÇoin).Limit(limit).Cursor(cursor). + Do(newContext()) + + e1 := &models.InstrumentInfoResponse{ + Category: "spot", + NextPageCursor: "", + List: []models.Instrument{ + { + Symbol: "BTCUSDT", + BaseCoin: "BTC", + QuoteCoin: "USDT", + Innovation: "0", + Status: "Trading", + MarginTrading: "both", + LotSizeFilter: models.LotSizeFilter{ + BasePrecision: "0.000001", + QuotePrecision: "0.00000001", + MinOrderQty: "0.000048", + MaxOrderQty: "71.73956243", MinOrderAmt: "1", + MaxOrderAmt: "2000000", + }, + PriceFilter: models.PriceFilter{ + TickSize: "0.01", + }, + }, + }, + } + + s.r().NoError(err) + s.assertInstrumentsInfoEqual(e1, res) +} + +func (s *marketTestSuite) assertInstrumentsInfoEqual(expected, actual *models.InstrumentInfoResponse) { + r := s.r() + r.Equal(expected.Category, actual.Category, "Category") + r.Equal(expected.NextPageCursor, actual.NextPageCursor, "NextPageCursor") + r.Equal(len(expected.List), len(actual.List), "List") + r.Equal(expected.List, actual.List) +} + +func (s *marketTestSuite) TestOrderBookInfo() { + data := []byte(`{ + "retCode": 0, + "retMsg": "SUCCESS", + "result": { + "s": "BTC-30DEC22-18000-C", + "b": [ + [ + "5", + "3.12" + ] + ], + "a": [ + [ + "175", + "4.88" + ] + ], + "u": 1203433656, + "ts": 1672043188375 + }, + "retExtInfo": {}, + "time": 1672043199230 +}`) + s.mockDo(data, nil) + defer s.assertDo() + + category := models.CategoryInverse + symbol := "BTCUSD" + limit := 10 + s.assertReq(func(r *request) { + e := newRequest() + e.method = http.MethodGet + e.setParams(params{ + "category": category, + "symbol": symbol, + "limit": limit, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewOrderBookService(). + Category(category).Symbol(symbol).Limit(limit). + Do(newContext()) + + e1 := &models.OrderBookInfo{ + Symbol: "BTC-30DEC22-18000-C", + Bids: []models.OrderBookEntry{ + { + "5", + "3.12", + }, + }, + Asks: []models.OrderBookEntry{ + { + "175", + "4.88", + }, + }, + Timestamp: 1672043188375, + UpdateID: 1203433656, + } + + s.r().NoError(err) + s.assertOrderbookInfoEqual(e1, res) +} + +func (s *marketTestSuite) assertOrderbookInfoEqual(expected, actual *models.OrderBookInfo) { + r := s.r() + r.Equal(expected.Symbol, actual.Symbol, "Symbol") + r.Equal(expected.Timestamp, actual.Timestamp, "Timestamp") + r.Equal(expected.UpdateID, actual.UpdateID, "UpdateID") + r.Equal(expected.Bids, actual.Bids) + r.Equal(expected.Asks, actual.Asks) +} + +func (s *marketTestSuite) TestInverseTickersInfo() { + data := []byte(`{ + "retCode": 0, + "retMsg": "OK", + "result": { + "category": "inverse", + "list": [ + { + "symbol": "BTCUSD", + "lastPrice": "16597.00", + "indexPrice": "16598.54", + "markPrice": "16596.00", + "prevPrice24h": "16464.50", + "price24hPcnt": "0.008047", + "highPrice24h": "30912.50", + "lowPrice24h": "15700.00", + "prevPrice1h": "16595.50", + "openInterest": "373504107", + "openInterestValue": "22505.67", + "turnover24h": "2352.94950046", + "volume24h": "49337318", + "fundingRate": "-0.001034", + "nextFundingTime": "1672387200000", + "predictedDeliveryPrice": "", + "basisRate": "", + "deliveryFeeRate": "", + "deliveryTime": "0", + "ask1Size": "1", + "bid1Price": "16596.00", + "ask1Price": "16597.50", + "bid1Size": "1", + "basis": "" + } + ] + }, + "retExtInfo": {}, + "time": 1672376496682 +}`) + s.mockDo(data, nil) + defer s.assertDo() + + s.testTickersInfo([]*models.TickerInfo{ + { + Symbol: "BTCUSD", + LastPrice: "16597.00", + IndexPrice: "16598.54", + MarkPrice: "16596.00", + PrevPrice24h: "16464.50", + Price24hPcnt: "0.008047", + HighPrice24h: "30912.50", + LowPrice24h: "15700.00", + PrevPrice1h: "16595.50", + OpenInterest: "373504107", + OpenInterestValue: "22505.67", + Turnover24h: "2352.94950046", + Volume24h: "49337318", + FundingRate: "-0.001034", + NextFundingTime: "1672387200000", + PredictedDeliveryPrice: "", + BasisRate: "", + DeliveryFeeRate: "", + DeliveryTime: "0", + Ask1Size: "1", + Bid1Price: "16596.00", + Ask1Price: "16597.50", + Bid1Size: "1", + Basis: "", + }, + }) +} + +func (s *marketTestSuite) TestOptionTickersInfo() { + data := []byte(`{ + "retCode": 0, + "retMsg": "OK", + "result": { + "category": "option", + "list": [ + { + "symbol": "BTC-30DEC22-18000-C", + "bid1Price": "0", + "bid1Size": "0", + "bid1Iv": "0", + "ask1Price": "435", + "ask1Size": "0.66", + "ask1Iv": "5", + "lastPrice": "435", + "highPrice24h": "435", + "lowPrice24h": "165", + "markPrice": "0.00000009", + "indexPrice": "16600.55", + "markIv": "0.7567", + "underlyingPrice": "16590.42", + "openInterest": "6.3", + "turnover24h": "2482.73", + "volume24h": "0.15", + "totalVolume": "99", + "totalTurnover": "1967653", + "delta": "0.00000001", + "gamma": "0.00000001", + "vega": "0.00000004", + "theta": "-0.00000152", + "predictedDeliveryPrice": "0", + "change24h": "86" + } + ] + }, + "retExtInfo": {}, + "time": 1672376592395 +}`) + s.mockDo(data, nil) + defer s.assertDo() + s.testTickersInfo([]*models.TickerInfo{ + { + Symbol: "BTC-30DEC22-18000-C", + Bid1Price: "0", + Bid1Size: "0", + Bid1Iv: "0", + Ask1Price: "435", + Ask1Size: "0.66", + Ask1Iv: "5", + LastPrice: "435", + HighPrice24h: "435", + LowPrice24h: "165", + MarkPrice: "0.00000009", + IndexPrice: "16600.55", + MarkIv: "0.7567", + UnderlyingPrice: "16590.42", + OpenInterest: "6.3", + Turnover24h: "2482.73", + Volume24h: "0.15", + TotalVolume: "99", + TotalTurnover: "1967653", + // Delta: "0.00000001", + // Gamma: "0.00000001", + // Vega: "0.00000004", + // Theta: "-0.00000152", + PredictedDeliveryPrice: "0", + Change24h: "86", + }, + }) +} + +func (s *marketTestSuite) TestSpotTickersInfo() { + data := []byte(`{ + "retCode": 0, + "retMsg": "OK", + "result": { + "category": "spot", + "list": [ + { + "symbol": "BTCUSDT", + "bid1Price": "20517.96", + "bid1Size": "2", + "ask1Price": "20527.77", + "ask1Size": "1.862172", + "lastPrice": "20533.13", + "prevPrice24h": "20393.48", + "price24hPcnt": "0.0068", + "highPrice24h": "21128.12", + "lowPrice24h": "20318.89", + "turnover24h": "243765620.65899866", + "volume24h": "11801.27771", + "usdIndexPrice": "20784.12009279" + } + ] + }, + "retExtInfo": {}, + "time": 1673859087947 + }`) + s.mockDo(data, nil) + defer s.assertDo() + s.testTickersInfo([]*models.TickerInfo{ + { + Symbol: "BTCUSDT", + Bid1Price: "20517.96", + Bid1Size: "2", + Ask1Price: "20527.77", + Ask1Size: "1.862172", + LastPrice: "20533.13", + PrevPrice24h: "20393.48", + Price24hPcnt: "0.0068", + HighPrice24h: "21128.12", + LowPrice24h: "20318.89", + Turnover24h: "243765620.65899866", + Volume24h: "11801.27771", + UsdIndexPrice: "20784.12009279", + }, + }) +} + +func (s *marketTestSuite) testTickersInfo(e1 []*models.TickerInfo) { + category := models.CategoryInverse + symbol := "BTCUSD" + baseCoin := "BTC" + expDate := "2022-12-30" + s.assertReq(func(r *request) { + e := newRequest() + e.method = http.MethodGet + e.setParams(params{ + "category": category, + "symbol": symbol, + "baseCoin": baseCoin, + "expDate": expDate, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewTickersService(). + Category(category).Symbol(symbol).BaseCoin(baseCoin).ExpDate(expDate). + Do(newContext()) + + s.r().NoError(err) + s.assertTickersEqual(e1, res.List) +} + +func (s *marketTestSuite) assertTickersEqual(expected, actual []*models.TickerInfo) { + r := s.r() + r.Equal(expected, actual) +} + +func (s *marketTestSuite) TestFundingRates() { + data := []byte(`{ + "retCode": 0, + "retMsg": "OK", + "result": { + "category": "linear", + "list": [ + { + "symbol": "ETHPERP", + "fundingRate": "0.0001", + "fundingRateTimestamp": "1672041600000" + } + ] + }, + "retExtInfo": {}, + "time": 1672051897447 +}`) + s.mockDo(data, nil) + defer s.assertDo() + + category := models.CategoryInverse + symbol := "BTCUSD" + startTime := uint64(1499040000000) + endTime := uint64(1499040000001) + limit := 10 + s.assertReq(func(r *request) { + e := newRequest() + e.method = http.MethodGet + e.setParams(params{ + "category": category, + "symbol": symbol, + "startTime": startTime, + "endTime": endTime, + "limit": limit, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewFundingTatesService(). + Category(category).Symbol(symbol). + StartTime(startTime).EndTime(endTime).Limit(limit). + Do(newContext()) + + e1 := &models.FundingRate{ + Category: "linear", + List: []models.FundingRateInfo{ + { + Symbol: "ETHPERP", + FundingRate: "0.0001", + FundingRateTimestamp: "1672041600000", + }, + }, + } + + s.r().NoError(err) + s.assertFundingRatesEqual(e1, res) +} + +func (s *marketTestSuite) assertFundingRatesEqual(expected, actual *models.FundingRate) { + r := s.r() + r.Equal(expected.Category, actual.Category, "Category") + r.Equal(expected.List, actual.List, "List") +} + +func (s *marketTestSuite) TestGetPublicRecentTrades() { + data := []byte(`{ + "retCode": 0, + "retMsg": "OK", + "result": { + "category": "spot", + "list": [ + { + "execId": "2100000000007764263", + "symbol": "BTCUSDT", + "price": "16618.49", + "size": "0.00012", + "side": "Buy", + "time": "1672052955758", + "isBlockTrade": false + } + ] + }, + "retExtInfo": {}, + "time": 1672053054358 +}`) + s.mockDo(data, nil) + defer s.assertDo() + + category := models.CategoryInverse + symbol := "BTCUSD" + baseCoin := "BTC" + optionType := "optionType" + limit := 10 + s.assertReq(func(r *request) { + e := newRequest() + e.method = http.MethodGet + e.setParams(params{ + "category": category, + "symbol": symbol, + "baseCoin": baseCoin, + "optionType": optionType, + "limit": limit, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewGetPublicRecentTradesService(). + Category(category).Symbol(symbol).BaseCoin(baseCoin). + OptionType(optionType).Limit(limit). + Do(newContext()) + + e1 := &models.PublicRecentTradeHistory{ + Category: "spot", + List: []models.TradeInfo{ + { + ExecId: "2100000000007764263", + Symbol: "BTCUSDT", + Price: "16618.49", + Size: "0.00012", + Side: "Buy", + Time: "1672052955758", + IsBlockTrade: false, + }, + }, + } + + s.r().NoError(err) + s.assertPublicRecentTradeHistoryEqual(e1, res) +} + +func (s *marketTestSuite) assertPublicRecentTradeHistoryEqual(expected, actual *models.PublicRecentTradeHistory) { + r := s.r() + r.Equal(expected.Category, actual.Category, "Category") + r.Equal(expected.List, actual.List, "List") +} + +func (s *marketTestSuite) TestGetOpenInterests() { + data := []byte(`{ + "retCode": 0, + "retMsg": "OK", + "result": { + "symbol": "BTCUSD", + "category": "inverse", + "list": [ + { + "openInterest": "461134384.00000000", + "timestamp": "1669571400000" + }, + { + "openInterest": "461134292.00000000", + "timestamp": "1669571100000" + } + ], + "nextPageCursor": "" + }, + "retExtInfo": {}, + "time": 1672053548579 +}`) + s.mockDo(data, nil) + defer s.assertDo() + + category := models.CategoryInverse + symbol := "BTCUSD" + intervalTime := "intervalTime" + startTime := uint64(1499040000000) + endTime := uint64(1499040000001) + limit := 10 + cursor := "cursor" + s.assertReq(func(r *request) { + e := newRequest() + e.method = http.MethodGet + + e.setParams(params{ + "category": category, + "symbol": symbol, + "intervalTime": intervalTime, + "startTime": startTime, + "endTime": endTime, + "limit": limit, + "cursor": cursor, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewGetOpenInterestsService(). + Category(category).Symbol(symbol).IntervalTime(intervalTime). + StartTime(startTime).EndTime(endTime).Limit(limit).Cursor(cursor). + Do(newContext()) + + e1 := &models.OpenInterestInfo{ + Symbol: "BTCUSD", + Category: "inverse", + List: []models.OpenInterest{ + { + OpenInterest: "461134384.00000000", + Timestamp: "1669571400000", + }, + { + OpenInterest: "461134292.00000000", + Timestamp: "1669571100000", + }, + }, + NextPageCursor: "", + } + + s.r().NoError(err) + s.assertOpenInterestInfoEqual(e1, res) +} + +func (s *marketTestSuite) assertOpenInterestInfoEqual(expected, actual *models.OpenInterestInfo) { + r := s.r() + r.Equal(expected.Symbol, actual.Symbol, "Symbol") + r.Equal(expected.Category, actual.Category, "Category") + r.Equal(expected.List, actual.List, "List") + r.Equal(expected.NextPageCursor, actual.NextPageCursor, "NextPageCursor") +} + +func (s *marketTestSuite) TestGetHistoricalVolatility() { + data := []byte(`{ + "retCode": 0, + "retMsg": "SUCCESS", + "category": "option", + "result": [ + { + "period": 7, + "value": "0.27545620", + "time": "1672232400000" + } + ] +}`) + s.mockDo(data, nil) + defer s.assertDo() + + category := models.CategoryInverse + baseCoin := "BTC" + period := "period" + startTime := uint64(1499040000000) + endTime := uint64(1499040000001) + s.assertReq(func(r *request) { + e := newRequest() + e.method = http.MethodGet + + e.setParams(params{ + "category": category, + "baseCoin": baseCoin, + "period": period, + "startTime": startTime, + "endTime": endTime, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewGetHistoricalVolatilityService(). + Category(category).BaseCoin(baseCoin).Period(period). + StartTime(startTime).EndTime(endTime). + Do(newContext()) + + e1 := &models.HistoricalVolatilityInfo{ + Category: "option", + List: []models.VolatilityData{ + { + Period: 7, + Value: "0.27545620", + Time: "1672232400000", + }, + }, + } + + s.r().NoError(err) + s.assertHistoricalVolatilityInfoEqual(e1, res) +} + +func (s *marketTestSuite) assertHistoricalVolatilityInfoEqual(expected, actual *models.HistoricalVolatilityInfo) { + r := s.r() + r.Equal(expected.Category, actual.Category, "Category") + r.Equal(expected.List, actual.List, "List") +} + +func (s *marketTestSuite) TestGetInsuranceInfo() { + data := []byte(`{ + "retCode": 0, + "retMsg": "OK", + "result": { + "updatedTime": "1672012800000", + "list": [ + { + "coin": "ETH", + "balance": "0.00187332", + "value": "0" + } + ] + }, + "retExtInfo": {}, + "time": 1672053931991 +}`) + s.mockDo(data, nil) + defer s.assertDo() + + coin := "BTC" + s.assertReq(func(r *request) { + e := newRequest() + e.method = http.MethodGet + + e.setParams(params{ + "coin": coin, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewGetInsuranceInfoService(). + Coin(coin).Do(newContext()) + + e1 := &models.MarketInsuranceInfo{ + UpdatedTime: "1672012800000", + List: []models.InsuranceData{ + { + Coin: "ETH", + Balance: "0.00187332", + Value: "0", + }, + }, + } + + s.r().NoError(err) + s.assertMarketInsuranceInfoEqual(e1, res) +} + +func (s *marketTestSuite) assertMarketInsuranceInfoEqual(expected, actual *models.MarketInsuranceInfo) { + r := s.r() + r.Equal(expected.UpdatedTime, actual.UpdatedTime, "UpdatedTime") + r.Equal(expected.List, actual.List, "List") +} + +func (s *marketTestSuite) TestGetRiskLimit() { + data := []byte(`{ + "retCode": 0, + "retMsg": "OK", + "result": { + "category": "inverse", + "list": [ + { + "id": 1, + "symbol": "BTCUSD", + "riskLimitValue": "150", + "maintenanceMargin": "0.5", + "initialMargin": "1", + "isLowestRisk": 1, + "maxLeverage": "100.00" + } + ] + }, + "retExtInfo": {}, + "time": 1672054488010 +}`) + s.mockDo(data, nil) + defer s.assertDo() + + category := models.CategoryInverse + symbol := "BTCUSDT" + s.assertReq(func(r *request) { + e := newRequest() + e.method = http.MethodGet + + e.setParams(params{ + "category": category, + "symbol": symbol, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewGetRiskLimitService(). + Category(category).Symbol(symbol). + Do(newContext()) + + e1 := &models.MarketRiskLimitInfo{ + Category: "inverse", + List: []models.RiskLimitData{ + { + Id: 1, + Symbol: "BTCUSD", + RiskLimitValue: "150", + MaintenanceMargin: "0.5", + InitialMargin: "1", + IsLowestRisk: 1, + MaxLeverage: "100.00", + }, + }, + } + + s.r().NoError(err) + s.assertMarketRiskLimitInfoEqual(e1, res) +} + +func (s *marketTestSuite) assertMarketRiskLimitInfoEqual(expected, actual *models.MarketRiskLimitInfo) { + r := s.r() + r.Equal(expected.Category, actual.Category, "Category") + r.Equal(expected.List, actual.List, "List") +} + +func (s *marketTestSuite) TestGetDeliveryPrice() { + data := []byte(`{ + "retCode": 0, + "retMsg": "success", + "result": { + "category": "option", + "nextPageCursor": "", + "list": [ + { + "symbol": "ETH-26DEC22-1400-C", + "deliveryPrice": "1220.728594450", + "deliveryTime": "1672041600000" + } + ] + }, + "retExtInfo": {}, + "time": 1672055336993 +}`) + s.mockDo(data, nil) + defer s.assertDo() + + category := models.CategoryInverse + symbol := "BTCUSDT" + baseCoin := "BTC" + limit := 10 + cursor := "cursor" + s.assertReq(func(r *request) { + e := newRequest() + e.method = http.MethodGet + + e.setParams(params{ + "category": category, + "symbol": symbol, + "baseCoin": baseCoin, + "limit": limit, + "cursor": cursor, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewGetDeliveryPriceService(). + Category(category).Symbol(symbol).BaseCoin(baseCoin). + Limit(limit).Cursor(cursor). + Do(newContext()) + + e1 := &models.DeliveryPriceInfo{ + Category: "option", + NextPageCursor: "", + List: []models.DeliveryPriceData{ + { + Symbol: "ETH-26DEC22-1400-C", + DeliveryPrice: "1220.728594450", + DeliveryTime: "1672041600000", + }, + }, + } + + s.r().NoError(err) + s.assertDeliveryPriceInfoEqual(e1, res) +} + +func (s *marketTestSuite) assertDeliveryPriceInfoEqual(expected, actual *models.DeliveryPriceInfo) { + r := s.r() + r.Equal(expected.Category, actual.Category, "Category") + r.Equal(expected.NextPageCursor, actual.NextPageCursor, "NextPageCursor") + r.Equal(expected.List, actual.List, "List") +} + +func (s *marketTestSuite) TestGetMarketLSRatio() { + data := []byte(`{ + "retCode": 0, + "retMsg": "OK", + "result": { + "list": [ + { + "symbol": "BTCUSDT", + "buyRatio": "0.5777", + "sellRatio": "0.4223", + "timestamp": "1695772800000" + } + ] + }, + "retExtInfo": {}, + "time": 1695785131028 +}`) + s.mockDo(data, nil) + defer s.assertDo() + + category := models.CategoryInverse + baseCoin := "BTC" + period := "period" + limit := 10 + s.assertReq(func(r *request) { + e := newRequest() + e.method = http.MethodGet + + e.setParams(params{ + "category": category, + "baseCoin": baseCoin, + "period": period, + "limit": limit, + }) + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewGetMarketLSRatioService(). + Category(category).BaseCoin(baseCoin). + Period(period).Limit(limit). + Do(newContext()) + + e1 := &models.MarketLongShortRatioInfo{ + List: []models.LongShortRatioData{ + { + Symbol: "BTCUSDT", + BuyRatio: "0.5777", + SellRatio: "0.4223", + Timestamp: "1695772800000", + }, + }, + } + + s.r().NoError(err) + s.assertMarketLongShortRatioInfoEqual(e1, res) +} + +func (s *marketTestSuite) assertMarketLongShortRatioInfoEqual(expected, actual *models.MarketLongShortRatioInfo) { + r := s.r() + r.Equal(expected.List, actual.List, "List") +} +func (s *marketTestSuite) TestGetServerTime() { + data := []byte(`{ + "retCode": 0, + "retMsg": "OK", + "result": { + "timeSecond": "1688639403", + "timeNano": "1688639403423213947" + }, + "retExtInfo": {}, + "time": 1688639403423 +}`) + s.mockDo(data, nil) + defer s.assertDo() + + s.assertReq(func(r *request) { + e := newRequest() + e.method = http.MethodGet + s.assertRequestEqual(e, r) + }) + + res, err := s.client.NewGetServerTimeService().Do(newContext()) + + e1 := &models.ServerTimeResult{ + TimeSecond: "1688639403", + TimeNano: "1688639403423213947", + } + + s.r().NoError(err) + s.assertServerTimeEqual(e1, res) +} + +func (s *marketTestSuite) assertServerTimeEqual(expected, actual *models.ServerTimeResult) { + r := s.r() + r.Equal(expected.TimeSecond, actual.TimeSecond, "TimeSecond") + r.Equal(expected.TimeNano, actual.TimeNano, "TimeNano") +} diff --git a/market_test.go b/market_test.go deleted file mode 100644 index bc3f0f4..0000000 --- a/market_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package bybit_connector - -import ( - "github.com/stretchr/testify/suite" - "github.com/wuhewuhe/bybit.go.api/models" - "testing" -) - -type marketTestSuite struct { - baseTestSuite -} - -func TestMarket(t *testing.T) { - suite.Run(t, new(marketTestSuite)) -} - -func (s *marketTestSuite) TestServerTime() { - data := []byte(`{ - "retCode": 0, - "retMsg": "OK", - "result": { - "timeSecond": "1699287551", - "timeNano": "1699287551919622633" - }, - "retExtInfo": {}, - "time": 1699287551919 - }`) - s.mockDo(data, nil) - defer s.assertDo() - - s.assertReq(func(r *request) { - e := newRequest() - s.assertRequestEqual(e, r) - }) - - serverTime, err := s.client.NewMarketInfoServiceNoParams().GetServerTime(newContext()) - - e1 := &models.ServerTimeResult{ - TimeSecond: "1699287551", - TimeNano: "1699287551919622633", - } - s.r().NoError(err) - s.assertServerTimeEqual(e1, serverTime) -} - -func (s *marketTestSuite) assertServerTimeEqual(expected *models.ServerTimeResult, actual *ServerResponse) { - // Assert that the actual result is not nil and is a map - r := s.r() - actualResult, ok := actual.Result.(map[string]interface{}) - if !ok { - r.FailNow("Actual result is not a map", "Actual Result: %#v", actual.Result) - return - } - - // Cast the map to the expected struct type - var actualStruct models.ServerTimeResult - timeSecond, okSecond := actualResult["timeSecond"].(string) - timeNano, okNano := actualResult["timeNano"].(string) - if !okSecond || !okNano { - r.FailNow("Failed to cast actual result fields to string", "Actual Result: %#v", actual.Result) - return - } - actualStruct.TimeSecond = timeSecond - actualStruct.TimeNano = timeNano - - // Compare the fields - r.Equal(expected.TimeSecond, actualStruct.TimeSecond, "TimeSecond field is not equal") - r.Equal(expected.TimeNano, actualStruct.TimeNano, "TimeNano field is not equal") -} diff --git a/models/marketResponse.go b/models/marketResponse.go index 65e7a0c..c373999 100644 --- a/models/marketResponse.go +++ b/models/marketResponse.go @@ -16,13 +16,55 @@ type MarketKlineCandle struct { } type MarketKlineResponse struct { - Category string `json:"category"` - Symbol string `json:"symbol"` - List []MarketKlineCandle `json:"list"` + Category Category `json:"category"` + Symbol string `json:"symbol"` + List []*MarketKlineCandle `json:"list"` } -type InstrumentInfo struct { - Category string `json:"category"` +type MarketMarkPriceKlineCandle struct { + StartTime string `json:"startTime"` + OpenPrice string `json:"openPrice"` + HighPrice string `json:"highPrice"` + LowPrice string `json:"lowPrice"` + ClosePrice string `json:"closePrice"` +} + +type MarketMarkPriceKlineResponse struct { + Category Category `json:"category"` + Symbol string `json:"symbol"` + List []*MarketMarkPriceKlineCandle `json:"list"` +} + +type MarketIndexPriceKlineCandle struct { + StartTime string `json:"startTime"` + OpenPrice string `json:"openPrice"` + HighPrice string `json:"highPrice"` + LowPrice string `json:"lowPrice"` + ClosePrice string `json:"closePrice"` +} + +type MarketIndexPriceKlineResponse struct { + Category Category `json:"category"` + Symbol string `json:"symbol"` + List []*MarketIndexPriceKlineCandle `json:"list"` +} + +type MarketPremiumIndexPriceKlineCandle struct { + StartTime string `json:"startTime"` + OpenPrice string `json:"openPrice"` + HighPrice string `json:"highPrice"` + LowPrice string `json:"lowPrice"` + ClosePrice string `json:"closePrice"` +} + +type MarketPremiumIndexPriceKlineResponse struct { + Category Category `json:"category"` + Symbol string `json:"symbol"` + List []*MarketPremiumIndexPriceKlineCandle `json:"list"` +} + +type InstrumentInfoResponse struct { + Category Category `json:"category"` NextPageCursor string `json:"nextPageCursor"` List []Instrument `json:"list"` } @@ -30,13 +72,16 @@ type InstrumentInfo struct { type Instrument struct { Symbol string `json:"symbol"` ContractType string `json:"contractType"` - Status string `json:"status"` + OptionType string `json:"optionType"` + Innovation string `json:"innovation"` + Status SymbolStatus `json:"status"` BaseCoin string `json:"baseCoin"` QuoteCoin string `json:"quoteCoin"` LaunchTime string `json:"launchTime"` DeliveryTime string `json:"deliveryTime"` DeliveryFeeRate string `json:"deliveryFeeRate"` PriceScale string `json:"priceScale"` + MarginTrading string `json:"marginTrading"` LeverageFilter LeverageFilter `json:"leverageFilter"` PriceFilter PriceFilter `json:"priceFilter"` LotSizeFilter LotSizeFilter `json:"lotSizeFilter"` @@ -63,12 +108,18 @@ type LotSizeFilter struct { MinOrderQty string `json:"minOrderQty"` QtyStep string `json:"qtyStep"` PostOnlyMaxOrderQty string `json:"postOnlyMaxOrderQty"` + BasePrecision string `json:"basePrecision"` + QuotePrecision string `json:"quotePrecision"` + MaxOrderAmt string `json:"maxOrderAmt"` + MinOrderAmt string `jsoN:"minOrderAmt"` } -type OrderBookEntry struct { - Price string `json:"0"` - Size string `json:"1"` -} +// type OrderBookEntry struct { +// Price string `json:"0"` +// Size string `json:"1"` +// } + +type OrderBookEntry []string type OrderBookInfo struct { Symbol string `json:"s"` @@ -103,11 +154,19 @@ type TickerInfo struct { Bid1Price string `json:"bid1Price"` Ask1Price string `json:"ask1Price"` Bid1Size string `json:"bid1Size"` + Ask1Iv string `json:"ask1Iv"` + Bid1Iv string `json:"bid1Iv"` + MarkIv string `json:"markIv"` + UnderlyingPrice string `json:"underlyingPrice"` + TotalVolume string `json:"totalVolume"` + TotalTurnover string `json:"totalTurnover"` + Change24h string `json:"change24h"` + UsdIndexPrice string `json:"usdIndexPrice"` } type MarketTickers struct { - Category string `json:"category"` - List []TickerInfo `json:"list"` + Category string `json:"category"` + List []*TickerInfo `json:"list"` } type FundingRateInfo struct { @@ -136,6 +195,18 @@ type PublicRecentTradeHistory struct { List []TradeInfo `json:"list"` } +type OpenInterestInfo struct { + Category string `json:"category"` + Symbol string `json:"symbol"` + List []OpenInterest `json:"list"` + NextPageCursor string `json:"nextPageCursor"` +} + +type OpenInterest struct { + OpenInterest string `json:"openInterest"` + Timestamp string `json:"timeStamp"` +} + type VolatilityData struct { Period int `json:"period"` Value string `json:"value"` @@ -144,7 +215,7 @@ type VolatilityData struct { type HistoricalVolatilityInfo struct { Category string `json:"category"` - List []VolatilityData `json:"list"` + List []VolatilityData `json:"result"` } type InsuranceData struct { @@ -159,13 +230,13 @@ type MarketInsuranceInfo struct { } type RiskLimitData struct { - Id int `json:"id"` - Symbol string `json:"symbol"` - RiskLimitValue string `json:"riskLimitValue"` - MaintenanceMargin float64 `json:"maintenanceMargin"` - InitialMargin float64 `json:"initialMargin"` - IsLowestRisk int `json:"isLowestRisk"` - MaxLeverage string `json:"maxLeverage"` + Id int `json:"id"` + Symbol string `json:"symbol"` + RiskLimitValue string `json:"riskLimitValue"` + MaintenanceMargin string `json:"maintenanceMargin"` + InitialMargin string `json:"initialMargin"` + IsLowestRisk int `json:"isLowestRisk"` + MaxLeverage string `json:"maxLeverage"` } type MarketRiskLimitInfo struct {