Merge pull request #553 from c9s/feature/max-order-history-api

refactor: rewrite max private trade query request with requestgen
This commit is contained in:
Yo-An Lin 2022-04-22 13:12:20 +08:00 committed by GitHub
commit 6f810bf081
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 321 additions and 117 deletions

View File

@ -204,7 +204,7 @@ func toGlobalTrade(t max.Trade) (*types.Trade, error) {
var side = toGlobalSideType(t.Side) var side = toGlobalSideType(t.Side)
// trade time // trade time
mts := time.Unix(0, t.CreatedAtMilliSeconds*int64(time.Millisecond)) mts := t.CreatedAtMilliSeconds
price, err := fixedpoint.NewFromString(t.Price) price, err := fixedpoint.NewFromString(t.Price)
if err != nil { if err != nil {

View File

@ -876,7 +876,7 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
return nil, err return nil, err
} }
req := e.client.TradeService.NewPrivateTradeRequest() req := e.client.TradeService.NewGetPrivateTradeRequest()
req.Market(toLocalSymbol(symbol)) req.Market(toLocalSymbol(symbol))
if options.Limit > 0 { if options.Limit > 0 {
@ -899,11 +899,6 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
return nil, err return nil, err
} }
// ensure everything is sorted ascending
sort.Slice(maxTrades, func(i, j int) bool {
return maxTrades[i].CreatedAtMilliSeconds < maxTrades[j].CreatedAtMilliSeconds
})
for _, t := range maxTrades { for _, t := range maxTrades {
localTrade, err := toGlobalTrade(t) localTrade, err := toGlobalTrade(t)
if err != nil { if err != nil {
@ -914,6 +909,9 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type
trades = append(trades, *localTrade) trades = append(trades, *localTrade)
} }
// ensure everything is sorted ascending
trades = types.SortTradesAscending(trades)
return trades, nil return trades, nil
} }

View File

@ -0,0 +1,242 @@
// Code generated by "requestgen -method GET -url v2/trades/my -type GetPrivateTradesRequest -responseType []Trade"; DO NOT EDIT.
package max
import (
"context"
"encoding/json"
"fmt"
"net/url"
"reflect"
"regexp"
"strconv"
"time"
)
func (p *GetPrivateTradesRequest) Market(market string) *GetPrivateTradesRequest {
p.market = market
return p
}
func (p *GetPrivateTradesRequest) Timestamp(timestamp time.Time) *GetPrivateTradesRequest {
p.timestamp = &timestamp
return p
}
func (p *GetPrivateTradesRequest) From(from int64) *GetPrivateTradesRequest {
p.from = &from
return p
}
func (p *GetPrivateTradesRequest) To(to int64) *GetPrivateTradesRequest {
p.to = &to
return p
}
func (p *GetPrivateTradesRequest) OrderBy(orderBy string) *GetPrivateTradesRequest {
p.orderBy = &orderBy
return p
}
func (p *GetPrivateTradesRequest) Pagination(pagination bool) *GetPrivateTradesRequest {
p.pagination = &pagination
return p
}
func (p *GetPrivateTradesRequest) Limit(limit int64) *GetPrivateTradesRequest {
p.limit = &limit
return p
}
func (p *GetPrivateTradesRequest) Offset(offset int64) *GetPrivateTradesRequest {
p.offset = &offset
return p
}
// GetQueryParameters builds and checks the query parameters and returns url.Values
func (p *GetPrivateTradesRequest) 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 (p *GetPrivateTradesRequest) GetParameters() (map[string]interface{}, error) {
var params = map[string]interface{}{}
// check market field -> json key market
market := p.market
// assign parameter of market
params["market"] = market
// check timestamp field -> json key timestamp
if p.timestamp != nil {
timestamp := *p.timestamp
// assign parameter of timestamp
// convert time.Time to seconds time stamp
params["timestamp"] = strconv.FormatInt(timestamp.Unix(), 10)
} else {
}
// check from field -> json key from
if p.from != nil {
from := *p.from
// assign parameter of from
params["from"] = from
} else {
}
// check to field -> json key to
if p.to != nil {
to := *p.to
// assign parameter of to
params["to"] = to
} else {
}
// check orderBy field -> json key order_by
if p.orderBy != nil {
orderBy := *p.orderBy
// assign parameter of orderBy
params["order_by"] = orderBy
} else {
}
// check pagination field -> json key pagination
if p.pagination != nil {
pagination := *p.pagination
// assign parameter of pagination
params["pagination"] = pagination
} else {
}
// check limit field -> json key limit
if p.limit != nil {
limit := *p.limit
// assign parameter of limit
params["limit"] = limit
} else {
}
// check offset field -> json key offset
if p.offset != nil {
offset := *p.offset
// assign parameter of offset
params["offset"] = offset
} else {
}
return params, nil
}
// GetParametersQuery converts the parameters from GetParameters into the url.Values format
func (p *GetPrivateTradesRequest) GetParametersQuery() (url.Values, error) {
query := url.Values{}
params, err := p.GetParameters()
if err != nil {
return query, err
}
for k, v := range params {
if p.isVarSlice(v) {
p.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 (p *GetPrivateTradesRequest) GetParametersJSON() ([]byte, error) {
params, err := p.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 (p *GetPrivateTradesRequest) GetSlugParameters() (map[string]interface{}, error) {
var params = map[string]interface{}{}
return params, nil
}
func (p *GetPrivateTradesRequest) 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 (p *GetPrivateTradesRequest) 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 (p *GetPrivateTradesRequest) isVarSlice(v interface{}) bool {
rt := reflect.TypeOf(v)
switch rt.Kind() {
case reflect.Slice:
return true
}
return false
}
func (p *GetPrivateTradesRequest) GetSlugsMap() (map[string]string, error) {
slugs := map[string]string{}
params, err := p.GetSlugParameters()
if err != nil {
return slugs, nil
}
for k, v := range params {
slugs[k] = fmt.Sprintf("%v", v)
}
return slugs, nil
}
func (p *GetPrivateTradesRequest) Do(ctx context.Context) ([]Trade, error) {
// empty params for GET operation
var params interface{}
query, err := p.GetParametersQuery()
if err != nil {
return nil, err
}
apiURL := "v2/trades/my"
req, err := p.client.NewAuthenticatedRequest(ctx, "GET", apiURL, query, params)
if err != nil {
return nil, err
}
response, err := p.client.SendRequest(req)
if err != nil {
return nil, err
}
var apiResponse []Trade
if err := response.DecodeJSON(&apiResponse); err != nil {
return nil, err
}
return apiResponse, nil
}

View File

@ -1,11 +1,16 @@
package max package max
//go:generate -command GetRequest requestgen -method GET
//go:generate -command PostRequest requestgen -method POST
import ( import (
"context"
"net/url" "net/url"
"strconv" "strconv"
"time"
"github.com/pkg/errors" "github.com/c9s/requestgen"
"github.com/c9s/bbgo/pkg/types"
) )
type MarkerInfo struct { type MarkerInfo struct {
@ -30,7 +35,7 @@ type Trade struct {
Market string `json:"market" db:"market"` Market string `json:"market" db:"market"`
MarketName string `json:"market_name"` MarketName string `json:"market_name"`
CreatedAt int64 `json:"created_at"` CreatedAt int64 `json:"created_at"`
CreatedAtMilliSeconds int64 `json:"created_at_in_ms"` CreatedAtMilliSeconds types.MillisecondTimestamp `json:"created_at_in_ms"`
Side string `json:"side" db:"side"` Side string `json:"side" db:"side"`
OrderID uint64 `json:"order_id"` OrderID uint64 `json:"order_id"`
Fee string `json:"fee" db:"fee"` // float number as string Fee string `json:"fee" db:"fee"` // float number as string
@ -111,8 +116,8 @@ func (options *QueryTradeOptions) Params() url.Values {
return params return params
} }
func (s *TradeService) NewPrivateTradeRequest() *PrivateTradeRequest { func (s *TradeService) NewGetPrivateTradeRequest() *GetPrivateTradesRequest {
return &PrivateTradeRequest{client: s.client} return &GetPrivateTradesRequest{client: s.client}
} }
type PrivateRequestParams struct { type PrivateRequestParams struct {
@ -120,120 +125,27 @@ type PrivateRequestParams struct {
Path string `json:"path"` Path string `json:"path"`
} }
type PrivateTradeRequest struct { //go:generate GetRequest -url "v2/trades/my" -type GetPrivateTradesRequest -responseType []Trade
client *RestClient type GetPrivateTradesRequest struct {
client requestgen.AuthenticatedAPIClient
market *string market string `param:"market"`
// Timestamp is the seconds elapsed since Unix epoch, set to return trades executed before the time only // timestamp is the seconds elapsed since Unix epoch, set to return trades executed before the time only
timestamp *int64 timestamp *time.Time `param:"timestamp,seconds"`
// From field is a trade id, set ot return trades created after the trade // From field is a trade id, set ot return trades created after the trade
from *int64 from *int64 `param:"from"`
// To field trade id, set to return trades created before the trade // To field trade id, set to return trades created before the trade
to *int64 to *int64 `param:"to"`
orderBy *string orderBy *string `param:"order_by"`
pagination *bool pagination *bool `param:"pagination"`
limit *int64 limit *int64 `param:"limit"`
offset *int64 offset *int64 `param:"offset"`
}
func (r *PrivateTradeRequest) Market(market string) *PrivateTradeRequest {
r.market = &market
return r
}
func (r *PrivateTradeRequest) From(from int64) *PrivateTradeRequest {
r.from = &from
return r
}
func (r *PrivateTradeRequest) Timestamp(t int64) *PrivateTradeRequest {
r.timestamp = &t
return r
}
func (r *PrivateTradeRequest) To(to int64) *PrivateTradeRequest {
r.to = &to
return r
}
func (r *PrivateTradeRequest) Limit(limit int64) *PrivateTradeRequest {
r.limit = &limit
return r
}
func (r *PrivateTradeRequest) Offset(offset int64) *PrivateTradeRequest {
r.offset = &offset
return r
}
func (r *PrivateTradeRequest) Pagination(p bool) *PrivateTradeRequest {
r.pagination = &p
return r
}
func (r *PrivateTradeRequest) OrderBy(orderBy string) *PrivateTradeRequest {
r.orderBy = &orderBy
return r
}
func (r *PrivateTradeRequest) Do(ctx context.Context) (trades []Trade, err error) {
if r.market == nil {
return nil, errors.New("parameter market is mandatory")
}
payload := map[string]interface{}{
"market": r.market,
}
if r.timestamp != nil {
payload["timestamp"] = r.timestamp
}
if r.from != nil {
payload["from"] = r.from
}
if r.to != nil {
payload["to"] = r.to
}
if r.orderBy != nil {
payload["order_by"] = r.orderBy
}
if r.pagination != nil {
payload["pagination"] = r.pagination
}
if r.limit != nil {
payload["limit"] = r.limit
}
if r.offset != nil {
payload["offset"] = r.offset
}
req, err := r.client.newAuthenticatedRequest(context.Background(), "GET", "v2/trades/my", nil, payload, nil)
if err != nil {
return trades, err
}
response, err := r.client.SendRequest(req)
if err != nil {
return trades, err
}
if err := response.DecodeJSON(&trades); err != nil {
return trades, err
}
return trades, err
} }

View File

@ -0,0 +1,52 @@
package max
import (
"context"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestTradeService(t *testing.T) {
key, secret, ok := integrationTestConfigured(t, "MAX")
if !ok {
t.SkipNow()
}
ctx := context.Background()
client := NewRestClient(ProductionAPIURL)
client.Auth(key, secret)
t.Run("default timestamp", func(t *testing.T) {
req := client.TradeService.NewGetPrivateTradeRequest()
until := time.Now().AddDate(0, -6, 0)
trades, err := req.Market("btcusdt").
Timestamp(until).
Do(ctx)
if assert.NoError(t, err) {
assert.NotEmptyf(t, trades, "got %d trades", len(trades))
for _, td := range trades {
t.Logf("trade: %+v", td)
assert.True(t, td.CreatedAtMilliSeconds.Time().Before(until))
}
}
})
t.Run("desc and pagination = false", func(t *testing.T) {
req := client.TradeService.NewGetPrivateTradeRequest()
trades, err := req.Market("btcusdt").
Pagination(false).
OrderBy("asc").
Do(ctx)
if assert.NoError(t, err) {
assert.NotEmptyf(t, trades, "got %d trades", len(trades))
for _, td := range trades {
t.Logf("trade: %+v", td)
}
}
})
}