mirror of
https://github.com/c9s/bbgo.git
synced 2024-11-21 22:43:52 +00:00
pkg/exchange: support QueryTickers API on bybit
This commit is contained in:
parent
3c32acc3ed
commit
ef8d1c7046
|
@ -157,9 +157,10 @@ sample:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type APIResponse struct {
|
type APIResponse struct {
|
||||||
RetCode uint `json:"retCode"`
|
RetCode uint `json:"retCode"`
|
||||||
RetMsg string `json:"retMsg"`
|
RetMsg string `json:"retMsg"`
|
||||||
Result json.RawMessage `json:"result"`
|
Result json.RawMessage `json:"result"`
|
||||||
RetExtInfo json.RawMessage `json:"retExtInfo"`
|
RetExtInfo json.RawMessage `json:"retExtInfo"`
|
||||||
Time types.MillisecondTimestamp `json:"time"`
|
// Time is current timestamp (ms)
|
||||||
|
Time types.MillisecondTimestamp `json:"time"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,4 +45,16 @@ func TestClient(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
t.Logf("instrumentsInfo: %+v", instrumentsInfo)
|
t.Logf("instrumentsInfo: %+v", instrumentsInfo)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("GetTicker", func(t *testing.T) {
|
||||||
|
req := client.NewGetTickersRequest()
|
||||||
|
apiResp, err := req.Symbol("BTCUSDT").Do(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
t.Logf("apiResp: %+v", apiResp)
|
||||||
|
|
||||||
|
req = client.NewGetTickersRequest()
|
||||||
|
tickers, err := req.Symbol("BTCUSDT").DoWithResponseTime(ctx)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
t.Logf("tickers: %+v", tickers)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
72
pkg/exchange/bybit/bybitapi/get_tickers_request.go
Normal file
72
pkg/exchange/bybit/bybitapi/get_tickers_request.go
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
package bybitapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/c9s/bbgo/pkg/fixedpoint"
|
||||||
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
|
"github.com/c9s/requestgen"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:generate -command GetRequest requestgen -method GET -responseType .APIResponse -responseDataField Result
|
||||||
|
//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Result
|
||||||
|
|
||||||
|
type Tickers struct {
|
||||||
|
Category Category `json:"category"`
|
||||||
|
List []Ticker `json:"list"`
|
||||||
|
|
||||||
|
// ClosedTime is current timestamp (ms). This value is obtained from outside APIResponse.
|
||||||
|
ClosedTime types.MillisecondTimestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ticker struct {
|
||||||
|
Symbol string `json:"symbol"`
|
||||||
|
Bid1Price fixedpoint.Value `json:"bid1Price"`
|
||||||
|
Bid1Size fixedpoint.Value `json:"bid1Size"`
|
||||||
|
Ask1Price fixedpoint.Value `json:"ask1Price"`
|
||||||
|
Ask1Size fixedpoint.Value `json:"ask1Size"`
|
||||||
|
LastPrice fixedpoint.Value `json:"lastPrice"`
|
||||||
|
PrevPrice24H fixedpoint.Value `json:"prevPrice24h"`
|
||||||
|
Price24HPcnt fixedpoint.Value `json:"price24hPcnt"`
|
||||||
|
HighPrice24H fixedpoint.Value `json:"highPrice24h"`
|
||||||
|
LowPrice24H fixedpoint.Value `json:"lowPrice24h"`
|
||||||
|
Turnover24H fixedpoint.Value `json:"turnover24h"`
|
||||||
|
Volume24H fixedpoint.Value `json:"volume24h"`
|
||||||
|
UsdIndexPrice fixedpoint.Value `json:"usdIndexPrice"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTickersRequest without **-responseDataType .InstrumentsInfo** in generation command, because the caller
|
||||||
|
// needs the APIResponse.Time. We implemented the DoWithResponseTime to handle this.
|
||||||
|
//
|
||||||
|
//go:generate GetRequest -url "/v5/market/tickers" -type GetTickersRequest
|
||||||
|
type GetTickersRequest struct {
|
||||||
|
client requestgen.APIClient
|
||||||
|
|
||||||
|
category Category `param:"category,query" validValues:"spot"`
|
||||||
|
symbol *string `param:"symbol,query"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RestClient) NewGetTickersRequest() *GetTickersRequest {
|
||||||
|
return &GetTickersRequest{
|
||||||
|
client: c,
|
||||||
|
category: CategorySpot,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetTickersRequest) DoWithResponseTime(ctx context.Context) (*Tickers, error) {
|
||||||
|
resp, err := g.Do(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var data Tickers
|
||||||
|
if err := json.Unmarshal(resp.Result, &data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Our types.Ticker requires the closed time, but this API does not provide it. This API returns the Tickers of the
|
||||||
|
// past 24 hours, so in terms of closed time, it is the current time, so fill it in Tickers.ClosedTime.
|
||||||
|
data.ClosedTime = resp.Time
|
||||||
|
return &data, nil
|
||||||
|
}
|
172
pkg/exchange/bybit/bybitapi/get_tickers_request_requestgen.go
Normal file
172
pkg/exchange/bybit/bybitapi/get_tickers_request_requestgen.go
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Result -url /v5/market/tickers -type GetTickersRequest"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package bybitapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (g *GetTickersRequest) Category(category Category) *GetTickersRequest {
|
||||||
|
g.category = category
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetTickersRequest) Symbol(symbol string) *GetTickersRequest {
|
||||||
|
g.symbol = &symbol
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetQueryParameters builds and checks the query parameters and returns url.Values
|
||||||
|
func (g *GetTickersRequest) GetQueryParameters() (url.Values, error) {
|
||||||
|
var params = map[string]interface{}{}
|
||||||
|
// check category field -> json key category
|
||||||
|
category := g.category
|
||||||
|
|
||||||
|
// TEMPLATE check-valid-values
|
||||||
|
switch category {
|
||||||
|
case "spot":
|
||||||
|
params["category"] = category
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("category value %v is invalid", category)
|
||||||
|
|
||||||
|
}
|
||||||
|
// END TEMPLATE check-valid-values
|
||||||
|
|
||||||
|
// assign parameter of category
|
||||||
|
params["category"] = category
|
||||||
|
// check symbol field -> json key symbol
|
||||||
|
if g.symbol != nil {
|
||||||
|
symbol := *g.symbol
|
||||||
|
|
||||||
|
// assign parameter of symbol
|
||||||
|
params["symbol"] = symbol
|
||||||
|
} else {
|
||||||
|
}
|
||||||
|
|
||||||
|
query := url.Values{}
|
||||||
|
for _k, _v := range params {
|
||||||
|
query.Add(_k, fmt.Sprintf("%v", _v))
|
||||||
|
}
|
||||||
|
|
||||||
|
return query, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetParameters builds and checks the parameters and return the result in a map object
|
||||||
|
func (g *GetTickersRequest) GetParameters() (map[string]interface{}, error) {
|
||||||
|
var params = map[string]interface{}{}
|
||||||
|
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
|
||||||
|
func (g *GetTickersRequest) GetParametersQuery() (url.Values, error) {
|
||||||
|
query := url.Values{}
|
||||||
|
|
||||||
|
params, err := g.GetParameters()
|
||||||
|
if err != nil {
|
||||||
|
return query, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _k, _v := range params {
|
||||||
|
if g.isVarSlice(_v) {
|
||||||
|
g.iterateSlice(_v, func(it interface{}) {
|
||||||
|
query.Add(_k+"[]", fmt.Sprintf("%v", it))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
query.Add(_k, fmt.Sprintf("%v", _v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return query, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetParametersJSON converts the parameters from GetParameters into the JSON format
|
||||||
|
func (g *GetTickersRequest) GetParametersJSON() ([]byte, error) {
|
||||||
|
params, err := g.GetParameters()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Marshal(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSlugParameters builds and checks the slug parameters and return the result in a map object
|
||||||
|
func (g *GetTickersRequest) GetSlugParameters() (map[string]interface{}, error) {
|
||||||
|
var params = map[string]interface{}{}
|
||||||
|
|
||||||
|
return params, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetTickersRequest) applySlugsToUrl(url string, slugs map[string]string) string {
|
||||||
|
for _k, _v := range slugs {
|
||||||
|
needleRE := regexp.MustCompile(":" + _k + "\\b")
|
||||||
|
url = needleRE.ReplaceAllString(url, _v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetTickersRequest) iterateSlice(slice interface{}, _f func(it interface{})) {
|
||||||
|
sliceValue := reflect.ValueOf(slice)
|
||||||
|
for _i := 0; _i < sliceValue.Len(); _i++ {
|
||||||
|
it := sliceValue.Index(_i).Interface()
|
||||||
|
_f(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetTickersRequest) isVarSlice(_v interface{}) bool {
|
||||||
|
rt := reflect.TypeOf(_v)
|
||||||
|
switch rt.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetTickersRequest) GetSlugsMap() (map[string]string, error) {
|
||||||
|
slugs := map[string]string{}
|
||||||
|
params, err := g.GetSlugParameters()
|
||||||
|
if err != nil {
|
||||||
|
return slugs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _k, _v := range params {
|
||||||
|
slugs[_k] = fmt.Sprintf("%v", _v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return slugs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetTickersRequest) Do(ctx context.Context) (*APIResponse, error) {
|
||||||
|
|
||||||
|
// no body params
|
||||||
|
var params interface{}
|
||||||
|
query, err := g.GetQueryParameters()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
apiURL := "/v5/market/tickers"
|
||||||
|
|
||||||
|
req, err := g.client.NewRequest(ctx, "GET", apiURL, query, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := g.client.SendRequest(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiResponse APIResponse
|
||||||
|
if err := response.DecodeJSON(&apiResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &apiResponse, nil
|
||||||
|
}
|
|
@ -2,34 +2,13 @@ package bybit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/exchange/bybit/bybitapi"
|
"github.com/c9s/bbgo/pkg/exchange/bybit/bybitapi"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func toGlobalMarket(m bybitapi.Instrument) types.Market {
|
func toGlobalMarket(m bybitapi.Instrument) types.Market {
|
||||||
// sample:
|
|
||||||
//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
|
|
||||||
//}
|
|
||||||
return types.Market{
|
return types.Market{
|
||||||
Symbol: m.Symbol,
|
Symbol: m.Symbol,
|
||||||
LocalSymbol: m.Symbol,
|
LocalSymbol: m.Symbol,
|
||||||
|
@ -51,3 +30,16 @@ func toGlobalMarket(m bybitapi.Instrument) types.Market {
|
||||||
TickSize: m.PriceFilter.TickSize,
|
TickSize: m.PriceFilter.TickSize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func toGlobalTicker(stats bybitapi.Ticker, time time.Time) types.Ticker {
|
||||||
|
return types.Ticker{
|
||||||
|
Volume: stats.Volume24H,
|
||||||
|
Last: stats.LastPrice,
|
||||||
|
Open: stats.PrevPrice24H, // Market price 24 hours ago
|
||||||
|
High: stats.HighPrice24H,
|
||||||
|
Low: stats.LowPrice24H,
|
||||||
|
Buy: stats.Bid1Price,
|
||||||
|
Sell: stats.Ask1Price,
|
||||||
|
Time: time,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package bybit
|
||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
@ -12,6 +13,26 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestToGlobalMarket(t *testing.T) {
|
func TestToGlobalMarket(t *testing.T) {
|
||||||
|
// sample:
|
||||||
|
//{
|
||||||
|
// "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
|
||||||
|
// }
|
||||||
|
//}
|
||||||
inst := bybitapi.Instrument{
|
inst := bybitapi.Instrument{
|
||||||
Symbol: "BTCUSDT",
|
Symbol: "BTCUSDT",
|
||||||
BaseCoin: "BTC",
|
BaseCoin: "BTC",
|
||||||
|
@ -60,3 +81,52 @@ func TestToGlobalMarket(t *testing.T) {
|
||||||
|
|
||||||
assert.Equal(t, toGlobalMarket(inst), exp)
|
assert.Equal(t, toGlobalMarket(inst), exp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToGlobalTicker(t *testing.T) {
|
||||||
|
// sample
|
||||||
|
//{
|
||||||
|
// "symbol": "BTCUSDT",
|
||||||
|
// "bid1Price": "28995.98",
|
||||||
|
// "bid1Size": "4.741552",
|
||||||
|
// "ask1Price": "28995.99",
|
||||||
|
// "ask1Size": "0.16075",
|
||||||
|
// "lastPrice": "28994",
|
||||||
|
// "prevPrice24h": "29900",
|
||||||
|
// "price24hPcnt": "-0.0303",
|
||||||
|
// "highPrice24h": "30344.78",
|
||||||
|
// "lowPrice24h": "28948.87",
|
||||||
|
// "turnover24h": "184705500.13172874",
|
||||||
|
// "volume24h": "6240.807096",
|
||||||
|
// "usdIndexPrice": "28977.82001643"
|
||||||
|
//}
|
||||||
|
ticker := bybitapi.Ticker{
|
||||||
|
Symbol: "BTCUSDT",
|
||||||
|
Bid1Price: fixedpoint.NewFromFloat(28995.98),
|
||||||
|
Bid1Size: fixedpoint.NewFromFloat(4.741552),
|
||||||
|
Ask1Price: fixedpoint.NewFromFloat(28995.99),
|
||||||
|
Ask1Size: fixedpoint.NewFromFloat(0.16075),
|
||||||
|
LastPrice: fixedpoint.NewFromFloat(28994),
|
||||||
|
PrevPrice24H: fixedpoint.NewFromFloat(29900),
|
||||||
|
Price24HPcnt: fixedpoint.NewFromFloat(-0.0303),
|
||||||
|
HighPrice24H: fixedpoint.NewFromFloat(30344.78),
|
||||||
|
LowPrice24H: fixedpoint.NewFromFloat(28948.87),
|
||||||
|
Turnover24H: fixedpoint.NewFromFloat(184705500.13172874),
|
||||||
|
Volume24H: fixedpoint.NewFromFloat(6240.807096),
|
||||||
|
UsdIndexPrice: fixedpoint.NewFromFloat(28977.82001643),
|
||||||
|
}
|
||||||
|
|
||||||
|
timeNow := time.Now()
|
||||||
|
|
||||||
|
exp := types.Ticker{
|
||||||
|
Time: timeNow,
|
||||||
|
Volume: ticker.Volume24H,
|
||||||
|
Last: ticker.LastPrice,
|
||||||
|
Open: ticker.PrevPrice24H,
|
||||||
|
High: ticker.HighPrice24H,
|
||||||
|
Low: ticker.LowPrice24H,
|
||||||
|
Buy: ticker.Bid1Price,
|
||||||
|
Sell: ticker.Ask1Price,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, toGlobalTicker(ticker, timeNow), exp)
|
||||||
|
}
|
||||||
|
|
|
@ -2,13 +2,23 @@ package bybit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
|
|
||||||
"github.com/c9s/bbgo/pkg/exchange/bybit/bybitapi"
|
"github.com/c9s/bbgo/pkg/exchange/bybit/bybitapi"
|
||||||
"github.com/c9s/bbgo/pkg/types"
|
"github.com/c9s/bbgo/pkg/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// https://bybit-exchange.github.io/docs/zh-TW/v5/rate-limit
|
||||||
|
// sharedRateLimiter indicates that the API belongs to the public API.
|
||||||
|
//
|
||||||
|
// The default order limiter apply 2 requests per second and a 2 initial bucket
|
||||||
|
// this includes QueryMarkets, QueryTicker
|
||||||
|
var sharedRateLimiter = rate.NewLimiter(rate.Every(time.Second/2), 2)
|
||||||
|
|
||||||
var log = logrus.WithFields(logrus.Fields{
|
var log = logrus.WithFields(logrus.Fields{
|
||||||
"exchange": "bybit",
|
"exchange": "bybit",
|
||||||
})
|
})
|
||||||
|
@ -47,8 +57,14 @@ func (e *Exchange) PlatformFeeCurrency() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
|
func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
|
||||||
|
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
||||||
|
log.WithError(err).Errorf("markets rate limiter wait error")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
instruments, err := e.client.NewGetInstrumentsInfoRequest().Do(ctx)
|
instruments, err := e.client.NewGetInstrumentsInfoRequest().Do(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Warnf("failed to query instruments, err: %v", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,3 +75,56 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) {
|
||||||
|
|
||||||
return marketMap, nil
|
return marketMap, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *Exchange) QueryTicker(ctx context.Context, symbol string) (*types.Ticker, error) {
|
||||||
|
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
||||||
|
log.WithError(err).Errorf("ticker rate limiter wait error")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := e.client.NewGetTickersRequest().Symbol(symbol).DoWithResponseTime(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed to get tickers, symbol: %s, err: %v", symbol, err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(s.List) != 1 {
|
||||||
|
log.Warnf("unexpected ticker length, exp: 1, got: %d", len(s.List))
|
||||||
|
return nil, fmt.Errorf("unexpected ticker lenght, exp:1, got:%d", len(s.List))
|
||||||
|
}
|
||||||
|
|
||||||
|
ticker := toGlobalTicker(s.List[0], s.ClosedTime.Time())
|
||||||
|
return &ticker, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Exchange) QueryTickers(ctx context.Context, symbols ...string) (map[string]types.Ticker, error) {
|
||||||
|
tickers := map[string]types.Ticker{}
|
||||||
|
if len(symbols) > 0 {
|
||||||
|
for _, s := range symbols {
|
||||||
|
t, err := e.QueryTicker(ctx, s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tickers[s] = *t
|
||||||
|
}
|
||||||
|
|
||||||
|
return tickers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sharedRateLimiter.Wait(ctx); err != nil {
|
||||||
|
log.WithError(err).Errorf("ticker rate limiter wait error")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
allTickers, err := e.client.NewGetTickersRequest().DoWithResponseTime(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("failed to get tickers, err: %v", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range allTickers.List {
|
||||||
|
tickers[s.Symbol] = toGlobalTicker(s, allTickers.ClosedTime.Time())
|
||||||
|
}
|
||||||
|
|
||||||
|
return tickers, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user