Merge pull request #1415 from c9s/edwin/bitget/use-v2-tickers

FEATURE: [bitget] use v2 tickers
This commit is contained in:
bailantaotao 2023-11-14 20:48:52 +08:00 committed by GitHub
commit 43c50b46a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 277 additions and 52 deletions

View File

@ -93,4 +93,10 @@ func TestClient(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
t.Logf("resp: %+v", resp) 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)
})
} }

View File

@ -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}
}

View File

@ -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
}

View File

@ -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{ return types.Ticker{
Time: ticker.Ts.Time(), Time: ticker.Ts.Time(),
Volume: ticker.BaseVol, Volume: ticker.BaseVolume,
Last: ticker.Close, Last: ticker.LastPr,
Open: ticker.OpenUtc0, Open: ticker.Open,
High: ticker.High24H, High: ticker.High24H,
Low: ticker.Low24H, Low: ticker.Low24H,
Buy: ticker.BuyOne, Buy: ticker.BidPr,
Sell: ticker.SellOne, Sell: ticker.AskPr,
} }
} }

View File

@ -101,38 +101,40 @@ func Test_toGlobalMarket(t *testing.T) {
func Test_toGlobalTicker(t *testing.T) { func Test_toGlobalTicker(t *testing.T) {
// sample: // sample:
//{ //{
// "open":"36465.96",
// "symbol":"BTCUSDT", // "symbol":"BTCUSDT",
// "high24h": "24175.65", // "high24h":"37040.25",
// "low24h": "23677.75", // "low24h":"36202.65",
// "close": "24014.11", // "lastPr":"36684.42",
// "quoteVol": "177689342.3025", // "quoteVolume":"311893591.2805",
// "baseVol": "7421.5009", // "baseVolume":"8507.3684",
// "usdtVol": "177689342.302407", // "usdtVolume":"311893591.280427",
// "ts": "1660704288118", // "ts":"1699947106122",
// "buyOne": "24013.94", // "bidPr":"36684.49",
// "sellOne": "24014.06", // "askPr":"36684.51",
// "bidSz": "0.0663", // "bidSz":"0.3812",
// "askSz": "0.0119", // "askSz":"0.0133",
// "openUtc0": "23856.72", // "openUtc":"36465.96",
// "changeUtc":"0.00301", // "changeUtc24h":"0.00599",
// "change":"0.00069" // "change24h":"-0.00426"
//} //}
ticker := bitgetapi.Ticker{ ticker := v2.Ticker{
Symbol: "BTCUSDT", Symbol: "BTCUSDT",
High24H: fixedpoint.NewFromFloat(24175.65), High24H: fixedpoint.NewFromFloat(24175.65),
Low24H: fixedpoint.NewFromFloat(23677.75), Low24H: fixedpoint.NewFromFloat(23677.75),
Close: fixedpoint.NewFromFloat(24014.11), LastPr: fixedpoint.NewFromFloat(24014.11),
QuoteVol: fixedpoint.NewFromFloat(177689342.3025), QuoteVolume: fixedpoint.NewFromFloat(177689342.3025),
BaseVol: fixedpoint.NewFromFloat(7421.5009), BaseVolume: fixedpoint.NewFromFloat(7421.5009),
UsdtVol: fixedpoint.NewFromFloat(177689342.302407), UsdtVolume: fixedpoint.NewFromFloat(177689342.302407),
Ts: types.NewMillisecondTimestampFromInt(1660704288118), Ts: types.NewMillisecondTimestampFromInt(1660704288118),
BuyOne: fixedpoint.NewFromFloat(24013.94), BidPr: fixedpoint.NewFromFloat(24013.94),
SellOne: fixedpoint.NewFromFloat(24014.06), AskPr: fixedpoint.NewFromFloat(24014.06),
BidSz: fixedpoint.NewFromFloat(0.0663), BidSz: fixedpoint.NewFromFloat(0.0663),
AskSz: fixedpoint.NewFromFloat(0.0119), AskSz: fixedpoint.NewFromFloat(0.0119),
OpenUtc0: fixedpoint.NewFromFloat(23856.72), OpenUtc: fixedpoint.NewFromFloat(23856.72),
ChangeUtc: fixedpoint.NewFromFloat(0.00301), ChangeUtc24H: fixedpoint.NewFromFloat(0.00301),
Change: fixedpoint.NewFromFloat(0.00069), Change24H: fixedpoint.NewFromFloat(0.00069),
Open: fixedpoint.NewFromFloat(23856.72),
} }
assert.Equal(t, types.Ticker{ assert.Equal(t, types.Ticker{

View File

@ -58,7 +58,7 @@ type Exchange struct {
key, secret, passphrase string key, secret, passphrase string
client *bitgetapi.RestClient client *bitgetapi.RestClient
v2Client *v2.Client v2client *v2.Client
} }
func New(key, secret, passphrase string) *Exchange { func New(key, secret, passphrase string) *Exchange {
@ -73,7 +73,7 @@ func New(key, secret, passphrase string) *Exchange {
secret: secret, secret: secret,
passphrase: passphrase, passphrase: passphrase,
client: client, 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) return nil, fmt.Errorf("markets rate limiter wait error: %w", err)
} }
req := e.v2Client.NewGetSymbolsRequest() req := e.v2client.NewGetSymbolsRequest()
symbols, err := req.Do(ctx) symbols, err := req.Do(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
@ -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) return nil, fmt.Errorf("ticker rate limiter wait error: %w", err)
} }
req := e.client.NewGetTickerRequest() req := e.v2client.NewGetTickersRequest()
req.Symbol(symbol) req.Symbol(symbol)
resp, err := req.Do(ctx) resp, err := req.Do(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to query ticker: %w", err) 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 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) 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 { if err != nil {
return nil, fmt.Errorf("failed to query tickers: %w", err) return nil, fmt.Errorf("failed to query tickers: %w", err)
} }
@ -164,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, // 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. // 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) { 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] intervalStr, found := toLocalGranularity[interval]
if !found { if !found {
return nil, fmt.Errorf("%s not supported, supported granlarity: %+v", intervalStr, toLocalGranularity) return nil, fmt.Errorf("%s not supported, supported granlarity: %+v", intervalStr, toLocalGranularity)
@ -252,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) return nil, fmt.Errorf("order.Market.Symbol is required: %+v", order)
} }
req := e.v2Client.NewPlaceOrderRequest() req := e.v2client.NewPlaceOrderRequest()
req.Symbol(order.Market.Symbol) req.Symbol(order.Market.Symbol)
// set order type // set order type
@ -320,7 +323,7 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (cr
} }
orderId := res.OrderId orderId := res.OrderId
ordersResp, err := e.v2Client.NewGetUnfilledOrdersRequest().OrderId(orderId).Do(ctx) ordersResp, err := e.v2client.NewGetUnfilledOrdersRequest().OrderId(orderId).Do(ctx)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to query open order by order id: %s, err: %w", orderId, err) return nil, fmt.Errorf("failed to query open order by order id: %s, err: %w", orderId, err)
} }
@ -329,7 +332,7 @@ func (e *Exchange) SubmitOrder(ctx context.Context, order types.SubmitOrder) (cr
case 0: case 0:
// The market order will be executed immediately, so we cannot retrieve it through the NewGetUnfilledOrdersRequest API. // 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. // 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 { if err != nil {
return nil, fmt.Errorf("failed to query history order by order id: %s, err: %w", orderId, err) return nil, fmt.Errorf("failed to query history order by order id: %s, err: %w", orderId, err)
} }
@ -355,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) return nil, fmt.Errorf("open order rate limiter wait error: %w", err)
} }
req := e.v2Client.NewGetUnfilledOrdersRequest(). req := e.v2client.NewGetUnfilledOrdersRequest().
Symbol(symbol). Symbol(symbol).
Limit(strconv.FormatInt(queryLimit, 10)) Limit(strconv.FormatInt(queryLimit, 10))
if nextCursor != 0 { if nextCursor != 0 {
@ -414,7 +417,7 @@ func (e *Exchange) QueryClosedOrders(ctx context.Context, symbol string, since,
if err := closedQueryOrdersRateLimiter.Wait(ctx); err != nil { if err := closedQueryOrdersRateLimiter.Wait(ctx); err != nil {
return nil, fmt.Errorf("query closed order rate limiter wait error: %w", err) 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). Symbol(symbol).
Limit(strconv.Itoa(queryLimit)). Limit(strconv.Itoa(queryLimit)).
StartTime(since.UnixMilli()). StartTime(since.UnixMilli()).
@ -502,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.") 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) req.Symbol(symbol)
if options.StartTime != nil { if options.StartTime != nil {