diff --git a/pkg/exchange/max/convert.go b/pkg/exchange/max/convert.go index ca36a41f8..a497ceef9 100644 --- a/pkg/exchange/max/convert.go +++ b/pkg/exchange/max/convert.go @@ -371,3 +371,20 @@ func convertWithdrawStatusV2(state max.WithdrawState) types.WithdrawStatus { return types.WithdrawStatus(state) } } + +func convertDepth(symbol string, depth *v3.Depth) (snapshot types.SliceOrderBook, finalUpdateID int64, err error) { + snapshot.Symbol = symbol + snapshot.Time = time.Unix(depth.Timestamp, 0) + snapshot.LastUpdateId = depth.LastUpdateId + + finalUpdateID = depth.LastUpdateId + for _, entry := range depth.Bids { + snapshot.Bids = append(snapshot.Bids, types.PriceVolume{Price: entry[0], Volume: entry[1]}) + } + + for _, entry := range depth.Asks { + snapshot.Asks = append(snapshot.Asks, types.PriceVolume{Price: entry[0], Volume: entry[1]}) + } + + return snapshot, finalUpdateID, err +} diff --git a/pkg/exchange/max/exchange.go b/pkg/exchange/max/exchange.go index a7bd7e79a..cee0b151e 100644 --- a/pkg/exchange/max/exchange.go +++ b/pkg/exchange/max/exchange.go @@ -1134,6 +1134,19 @@ func (e *Exchange) QueryKLines( return kLines, nil } +func (e *Exchange) QueryDepth(ctx context.Context, symbol string, limit int) (snapshot types.SliceOrderBook, finalUpdateID int64, err error) { + req := e.v3client.NewGetDepthRequest() + req.Market(symbol) + req.Limit(limit) + + depth, err := req.Do(ctx) + if err != nil { + return snapshot, finalUpdateID, err + } + + return convertDepth(symbol, depth) +} + var Two = fixedpoint.NewFromInt(2) func (e *Exchange) QueryAveragePrice(ctx context.Context, symbol string) (fixedpoint.Value, error) { diff --git a/pkg/exchange/max/maxapi/v3/get_depth_request.go b/pkg/exchange/max/maxapi/v3/get_depth_request.go new file mode 100644 index 000000000..fb7c6e3e0 --- /dev/null +++ b/pkg/exchange/max/maxapi/v3/get_depth_request.go @@ -0,0 +1,29 @@ +package v3 + +//go:generate -command GetRequest requestgen -method GET + +import ( + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/requestgen" +) + +type Depth struct { + Timestamp int64 `json:"timestamp"` + LastUpdateVersion int64 `json:"last_update_version"` + LastUpdateId int64 `json:"last_update_id"` + Bids [][]fixedpoint.Value `json:"bids"` + Asks [][]fixedpoint.Value `json:"asks"` +} + +//go:generate GetRequest -url "/api/v3/depth" -type GetDepthRequest -responseType Depth +type GetDepthRequest struct { + client requestgen.APIClient + + market string `param:"market,required"` + limit *int `param:"limit"` + sortByPrice *bool `param:"sort_by_price"` +} + +func (s *Client) NewGetDepthRequest() *GetDepthRequest { + return &GetDepthRequest{client: s.Client} +} diff --git a/pkg/exchange/max/maxapi/v3/get_depth_request_requestgen.go b/pkg/exchange/max/maxapi/v3/get_depth_request_requestgen.go new file mode 100644 index 000000000..243f80fe6 --- /dev/null +++ b/pkg/exchange/max/maxapi/v3/get_depth_request_requestgen.go @@ -0,0 +1,212 @@ +// Code generated by "requestgen -method GET -url /api/v3/depth -type GetDepthRequest -responseType Depth"; DO NOT EDIT. + +package v3 + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" +) + +func (g *GetDepthRequest) Market(market string) *GetDepthRequest { + g.market = market + return g +} + +func (g *GetDepthRequest) Limit(limit int) *GetDepthRequest { + g.limit = &limit + return g +} + +func (g *GetDepthRequest) SortByPrice(sortByPrice bool) *GetDepthRequest { + g.sortByPrice = &sortByPrice + return g +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (g *GetDepthRequest) GetQueryParameters() (url.Values, error) { + var params = map[string]interface{}{} + + 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 *GetDepthRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check market field -> json key market + market := g.market + + // TEMPLATE check-required + if len(market) == 0 { + return nil, fmt.Errorf("market is required, empty string given") + } + // END TEMPLATE check-required + + // assign parameter of market + params["market"] = market + // check limit field -> json key limit + if g.limit != nil { + limit := *g.limit + + // assign parameter of limit + params["limit"] = limit + } else { + } + // check sortByPrice field -> json key sort_by_price + if g.sortByPrice != nil { + sortByPrice := *g.sortByPrice + + // assign parameter of sortByPrice + params["sort_by_price"] = sortByPrice + } else { + } + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (g *GetDepthRequest) 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 *GetDepthRequest) 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 *GetDepthRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (g *GetDepthRequest) 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 *GetDepthRequest) 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 *GetDepthRequest) isVarSlice(_v interface{}) bool { + rt := reflect.TypeOf(_v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (g *GetDepthRequest) 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 *GetDepthRequest) GetPath() string { + return "/api/v3/depth" +} + +// Do generates the request object and send the request object to the API endpoint +func (g *GetDepthRequest) Do(ctx context.Context) (*Depth, error) { + + // empty params for GET operation + var params interface{} + query, err := g.GetParametersQuery() + 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 Depth + + type responseUnmarshaler interface { + Unmarshal(data []byte) error + } + + if unmarshaler, ok := interface{}(&apiResponse).(responseUnmarshaler); ok { + if err := unmarshaler.Unmarshal(response.Body); err != nil { + return nil, err + } + } else { + // The line below checks the content type, however, some API server might not send the correct content type header, + // Hence, this is commented for backward compatibility + // response.IsJSON() + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } + } + + type responseValidator interface { + Validate() error + } + + if validator, ok := interface{}(&apiResponse).(responseValidator); ok { + if err := validator.Validate(); err != nil { + return nil, err + } + } + return &apiResponse, nil +}