From 53bce6d5c1f721eca0ca33c37aa867ff8abae5f2 Mon Sep 17 00:00:00 2001 From: Edwin Date: Tue, 14 Nov 2023 14:57:07 +0800 Subject: [PATCH 1/2] pkg/exchange: use v2 query ticker --- .../bitget/bitgetapi/v2/client_test.go | 6 + .../bitgetapi/v2/get_tickers_request.go | 40 ++++ .../v2/get_tickers_request_requestgen.go | 174 ++++++++++++++++++ pkg/exchange/bitget/convert.go | 12 +- pkg/exchange/bitget/convert_test.go | 68 +++---- pkg/exchange/bitget/exchange.go | 9 +- 6 files changed, 267 insertions(+), 42 deletions(-) create mode 100644 pkg/exchange/bitget/bitgetapi/v2/get_tickers_request.go create mode 100644 pkg/exchange/bitget/bitgetapi/v2/get_tickers_request_requestgen.go diff --git a/pkg/exchange/bitget/bitgetapi/v2/client_test.go b/pkg/exchange/bitget/bitgetapi/v2/client_test.go index d9feba7b1..afefe7c9e 100644 --- a/pkg/exchange/bitget/bitgetapi/v2/client_test.go +++ b/pkg/exchange/bitget/bitgetapi/v2/client_test.go @@ -93,4 +93,10 @@ func TestClient(t *testing.T) { assert.NoError(t, err) t.Logf("resp: %+v", resp) }) + + t.Run("GetTickersRequest", func(t *testing.T) { + resp, err := client.NewGetTickersRequest().Do(ctx) + assert.NoError(t, err) + t.Logf("resp: %+v", resp) + }) } diff --git a/pkg/exchange/bitget/bitgetapi/v2/get_tickers_request.go b/pkg/exchange/bitget/bitgetapi/v2/get_tickers_request.go new file mode 100644 index 000000000..02e5d07b5 --- /dev/null +++ b/pkg/exchange/bitget/bitgetapi/v2/get_tickers_request.go @@ -0,0 +1,40 @@ +package bitgetapi + +import ( + "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 Data +//go:generate -command PostRequest requestgen -method POST -responseType .APIResponse -responseDataField Data + +type Ticker struct { + Symbol string `json:"symbol"` + High24H fixedpoint.Value `json:"high24h"` + Open fixedpoint.Value `json:"open"` + Low24H fixedpoint.Value `json:"low24h"` + LastPr fixedpoint.Value `json:"lastPr"` + QuoteVolume fixedpoint.Value `json:"quoteVolume"` + BaseVolume fixedpoint.Value `json:"baseVolume"` + UsdtVolume fixedpoint.Value `json:"usdtVolume"` + BidPr fixedpoint.Value `json:"bidPr"` + AskPr fixedpoint.Value `json:"askPr"` + BidSz fixedpoint.Value `json:"bidSz"` + AskSz fixedpoint.Value `json:"askSz"` + OpenUtc fixedpoint.Value `json:"openUtc"` + Ts types.MillisecondTimestamp `json:"ts"` + ChangeUtc24H fixedpoint.Value `json:"changeUtc24h"` + Change24H fixedpoint.Value `json:"change24h"` +} + +//go:generate GetRequest -url "/api/v2/spot/market/tickers" -type GetTickersRequest -responseDataType []Ticker +type GetTickersRequest struct { + client requestgen.APIClient + + symbol *string `param:"symbol,query"` +} + +func (s *Client) NewGetTickersRequest() *GetTickersRequest { + return &GetTickersRequest{client: s.Client} +} diff --git a/pkg/exchange/bitget/bitgetapi/v2/get_tickers_request_requestgen.go b/pkg/exchange/bitget/bitgetapi/v2/get_tickers_request_requestgen.go new file mode 100644 index 000000000..6e60c2106 --- /dev/null +++ b/pkg/exchange/bitget/bitgetapi/v2/get_tickers_request_requestgen.go @@ -0,0 +1,174 @@ +// Code generated by "requestgen -method GET -responseType .APIResponse -responseDataField Data -url /api/v2/spot/market/tickers -type GetTickersRequest -responseDataType []Ticker"; DO NOT EDIT. + +package bitgetapi + +import ( + "context" + "encoding/json" + "fmt" + "github.com/c9s/bbgo/pkg/exchange/bitget/bitgetapi" + "net/url" + "reflect" + "regexp" +) + +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 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 +} + +// GetPath returns the request path of the API +func (g *GetTickersRequest) GetPath() string { + return "/api/v2/spot/market/tickers" +} + +// Do generates the request object and send the request object to the API endpoint +func (g *GetTickersRequest) Do(ctx context.Context) ([]Ticker, error) { + + // no body params + var params interface{} + query, err := g.GetQueryParameters() + if err != nil { + return nil, err + } + + var apiURL string + + apiURL = g.GetPath() + + 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 bitgetapi.APIResponse + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } + + type responseValidator interface { + Validate() error + } + validator, ok := interface{}(apiResponse).(responseValidator) + if ok { + if err := validator.Validate(); err != nil { + return nil, err + } + } + var data []Ticker + if err := json.Unmarshal(apiResponse.Data, &data); err != nil { + return nil, err + } + return data, nil +} diff --git a/pkg/exchange/bitget/convert.go b/pkg/exchange/bitget/convert.go index afe228c5c..3a8a875dc 100644 --- a/pkg/exchange/bitget/convert.go +++ b/pkg/exchange/bitget/convert.go @@ -47,16 +47,16 @@ func toGlobalMarket(s v2.Symbol) types.Market { } } -func toGlobalTicker(ticker bitgetapi.Ticker) types.Ticker { +func toGlobalTicker(ticker v2.Ticker) types.Ticker { return types.Ticker{ Time: ticker.Ts.Time(), - Volume: ticker.BaseVol, - Last: ticker.Close, - Open: ticker.OpenUtc0, + Volume: ticker.BaseVolume, + Last: ticker.LastPr, + Open: ticker.Open, High: ticker.High24H, Low: ticker.Low24H, - Buy: ticker.BuyOne, - Sell: ticker.SellOne, + Buy: ticker.BidPr, + Sell: ticker.AskPr, } } diff --git a/pkg/exchange/bitget/convert_test.go b/pkg/exchange/bitget/convert_test.go index a6feab7e2..dff77b436 100644 --- a/pkg/exchange/bitget/convert_test.go +++ b/pkg/exchange/bitget/convert_test.go @@ -100,39 +100,41 @@ func Test_toGlobalMarket(t *testing.T) { func Test_toGlobalTicker(t *testing.T) { // sample: - // { - // "symbol": "BTCUSDT", - // "high24h": "24175.65", - // "low24h": "23677.75", - // "close": "24014.11", - // "quoteVol": "177689342.3025", - // "baseVol": "7421.5009", - // "usdtVol": "177689342.302407", - // "ts": "1660704288118", - // "buyOne": "24013.94", - // "sellOne": "24014.06", - // "bidSz": "0.0663", - // "askSz": "0.0119", - // "openUtc0": "23856.72", - // "changeUtc":"0.00301", - // "change":"0.00069" - // } - ticker := bitgetapi.Ticker{ - Symbol: "BTCUSDT", - High24H: fixedpoint.NewFromFloat(24175.65), - Low24H: fixedpoint.NewFromFloat(23677.75), - Close: fixedpoint.NewFromFloat(24014.11), - QuoteVol: fixedpoint.NewFromFloat(177689342.3025), - BaseVol: fixedpoint.NewFromFloat(7421.5009), - UsdtVol: fixedpoint.NewFromFloat(177689342.302407), - Ts: types.NewMillisecondTimestampFromInt(1660704288118), - BuyOne: fixedpoint.NewFromFloat(24013.94), - SellOne: fixedpoint.NewFromFloat(24014.06), - BidSz: fixedpoint.NewFromFloat(0.0663), - AskSz: fixedpoint.NewFromFloat(0.0119), - OpenUtc0: fixedpoint.NewFromFloat(23856.72), - ChangeUtc: fixedpoint.NewFromFloat(0.00301), - Change: fixedpoint.NewFromFloat(0.00069), + //{ + // "open":"36465.96", + // "symbol":"BTCUSDT", + // "high24h":"37040.25", + // "low24h":"36202.65", + // "lastPr":"36684.42", + // "quoteVolume":"311893591.2805", + // "baseVolume":"8507.3684", + // "usdtVolume":"311893591.280427", + // "ts":"1699947106122", + // "bidPr":"36684.49", + // "askPr":"36684.51", + // "bidSz":"0.3812", + // "askSz":"0.0133", + // "openUtc":"36465.96", + // "changeUtc24h":"0.00599", + // "change24h":"-0.00426" + //} + ticker := v2.Ticker{ + Symbol: "BTCUSDT", + High24H: fixedpoint.NewFromFloat(24175.65), + Low24H: fixedpoint.NewFromFloat(23677.75), + LastPr: fixedpoint.NewFromFloat(24014.11), + QuoteVolume: fixedpoint.NewFromFloat(177689342.3025), + BaseVolume: fixedpoint.NewFromFloat(7421.5009), + UsdtVolume: fixedpoint.NewFromFloat(177689342.302407), + Ts: types.NewMillisecondTimestampFromInt(1660704288118), + BidPr: fixedpoint.NewFromFloat(24013.94), + AskPr: fixedpoint.NewFromFloat(24014.06), + BidSz: fixedpoint.NewFromFloat(0.0663), + AskSz: fixedpoint.NewFromFloat(0.0119), + OpenUtc: fixedpoint.NewFromFloat(23856.72), + ChangeUtc24H: fixedpoint.NewFromFloat(0.00301), + Change24H: fixedpoint.NewFromFloat(0.00069), + Open: fixedpoint.NewFromFloat(23856.72), } assert.Equal(t, types.Ticker{ diff --git a/pkg/exchange/bitget/exchange.go b/pkg/exchange/bitget/exchange.go index 7750062f9..86e891c31 100644 --- a/pkg/exchange/bitget/exchange.go +++ b/pkg/exchange/bitget/exchange.go @@ -113,14 +113,17 @@ func (e *Exchange) QueryTicker(ctx context.Context, symbol string) (*types.Ticke return nil, fmt.Errorf("ticker rate limiter wait error: %w", err) } - req := e.client.NewGetTickerRequest() + req := e.v2Client.NewGetTickersRequest() req.Symbol(symbol) resp, err := req.Do(ctx) if err != nil { return nil, fmt.Errorf("failed to query ticker: %w", err) } + if len(resp) != 1 { + return nil, fmt.Errorf("unexpected length of query single symbol: %+v", resp) + } - ticker := toGlobalTicker(*resp) + ticker := toGlobalTicker(resp[1]) return &ticker, nil } @@ -143,7 +146,7 @@ func (e *Exchange) QueryTickers(ctx context.Context, symbols ...string) (map[str return nil, fmt.Errorf("tickers rate limiter wait error: %w", err) } - resp, err := e.client.NewGetAllTickersRequest().Do(ctx) + resp, err := e.v2Client.NewGetTickersRequest().Do(ctx) if err != nil { return nil, fmt.Errorf("failed to query tickers: %w", err) } From 562f85af75ca4b537afcbb384ab5e2876124bc91 Mon Sep 17 00:00:00 2001 From: Edwin Date: Tue, 14 Nov 2023 20:42:11 +0800 Subject: [PATCH 2/2] pkg/exchange: rename v2Client -> v2client --- pkg/exchange/bitget/exchange.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/exchange/bitget/exchange.go b/pkg/exchange/bitget/exchange.go index 86e891c31..064a303e0 100644 --- a/pkg/exchange/bitget/exchange.go +++ b/pkg/exchange/bitget/exchange.go @@ -58,7 +58,7 @@ type Exchange struct { key, secret, passphrase string client *bitgetapi.RestClient - v2Client *v2.Client + v2client *v2.Client } func New(key, secret, passphrase string) *Exchange { @@ -73,7 +73,7 @@ func New(key, secret, passphrase string) *Exchange { secret: secret, passphrase: passphrase, client: client, - v2Client: v2.NewClient(client), + v2client: v2.NewClient(client), } } @@ -94,7 +94,7 @@ func (e *Exchange) QueryMarkets(ctx context.Context) (types.MarketMap, error) { return nil, fmt.Errorf("markets rate limiter wait error: %w", err) } - req := e.v2Client.NewGetSymbolsRequest() + req := e.v2client.NewGetSymbolsRequest() symbols, err := req.Do(ctx) if err != nil { return nil, err @@ -113,7 +113,7 @@ func (e *Exchange) QueryTicker(ctx context.Context, symbol string) (*types.Ticke return nil, fmt.Errorf("ticker rate limiter wait error: %w", err) } - req := e.v2Client.NewGetTickersRequest() + req := e.v2client.NewGetTickersRequest() req.Symbol(symbol) resp, err := req.Do(ctx) if err != nil { @@ -146,7 +146,7 @@ func (e *Exchange) QueryTickers(ctx context.Context, symbols ...string) (map[str return nil, fmt.Errorf("tickers rate limiter wait error: %w", err) } - resp, err := e.v2Client.NewGetTickersRequest().Do(ctx) + resp, err := e.v2client.NewGetTickersRequest().Do(ctx) if err != nil { return nil, fmt.Errorf("failed to query tickers: %w", err) } @@ -167,7 +167,7 @@ func (e *Exchange) QueryTickers(ctx context.Context, symbols ...string) (map[str // The end time has different limits. 1m, 5m can query for one month,15m can query for 52 days,30m can query for 62 days, // 1H can query for 83 days,4H can query for 240 days,6H can query for 360 days. func (e *Exchange) QueryKLines(ctx context.Context, symbol string, interval types.Interval, options types.KLineQueryOptions) ([]types.KLine, error) { - req := e.v2Client.NewGetKLineRequest().Symbol(symbol) + req := e.v2client.NewGetKLineRequest().Symbol(symbol) intervalStr, found := toLocalGranularity[interval] if !found { return nil, fmt.Errorf("%s not supported, supported granlarity: %+v", intervalStr, toLocalGranularity) @@ -255,7 +255,7 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (cr return nil, fmt.Errorf("order.Market.Symbol is required: %+v", order) } - req := e.v2Client.NewPlaceOrderRequest() + req := e.v2client.NewPlaceOrderRequest() req.Symbol(order.Market.Symbol) // set order type @@ -323,7 +323,7 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (cr } orderId := res.OrderId - ordersResp, err := e.v2Client.NewGetUnfilledOrdersRequest().OrderId(orderId).Do(ctx) + ordersResp, err := e.v2client.NewGetUnfilledOrdersRequest().OrderId(orderId).Do(ctx) if err != nil { return nil, fmt.Errorf("failed to query open order by order id: %s, err: %w", orderId, err) } @@ -332,7 +332,7 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (cr case 0: // The market order will be executed immediately, so we cannot retrieve it through the NewGetUnfilledOrdersRequest API. // Try to get the order from the NewGetHistoryOrdersRequest API. - ordersResp, err := e.v2Client.NewGetHistoryOrdersRequest().OrderId(orderId).Do(ctx) + ordersResp, err := e.v2client.NewGetHistoryOrdersRequest().OrderId(orderId).Do(ctx) if err != nil { return nil, fmt.Errorf("failed to query history order by order id: %s, err: %w", orderId, err) } @@ -358,7 +358,7 @@ func (e *Exchange) QueryOpenOrders(ctx context.Context, symbol string) (orders [ return nil, fmt.Errorf("open order rate limiter wait error: %w", err) } - req := e.v2Client.NewGetUnfilledOrdersRequest(). + req := e.v2client.NewGetUnfilledOrdersRequest(). Symbol(symbol). Limit(strconv.FormatInt(queryLimit, 10)) if nextCursor != 0 { @@ -417,7 +417,7 @@ func (e *Exchange) QueryClosedOrders(ctx context.Context, symbol string, since, if err := closedQueryOrdersRateLimiter.Wait(ctx); err != nil { return nil, fmt.Errorf("query closed order rate limiter wait error: %w", err) } - res, err := e.v2Client.NewGetHistoryOrdersRequest(). + res, err := e.v2client.NewGetHistoryOrdersRequest(). Symbol(symbol). Limit(strconv.Itoa(queryLimit)). StartTime(since.UnixMilli()). @@ -505,7 +505,7 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type log.Warn("!!!BITGET EXCHANGE API NOTICE!!! The trade of response is in descending order, so the last trade id not supported.") } - req := e.v2Client.NewGetTradeFillsRequest() + req := e.v2client.NewGetTradeFillsRequest() req.Symbol(symbol) if options.StartTime != nil {