diff --git a/examples/max-rewards/main.go b/examples/max-rewards/main.go index 3f75a7476..59a432670 100644 --- a/examples/max-rewards/main.go +++ b/examples/max-rewards/main.go @@ -18,7 +18,7 @@ func main() { ctx := context.Background() - var req *maxapi.RewardsRequest + var req *maxapi.GetRewardsRequest if len(os.Args) > 1 { pathType := os.Args[1] @@ -27,9 +27,9 @@ func main() { log.Fatal(err) } - req = api.RewardService.NewRewardsByTypeRequest(rewardType) + req = api.RewardService.NewGetRewardsOfTypeRequest(rewardType) } else { - req = api.RewardService.NewRewardsRequest() + req = api.RewardService.NewGetRewardsRequest() } // req.From(1613931192) diff --git a/pkg/exchange/max/exchange.go b/pkg/exchange/max/exchange.go index 45a5779be..c05c1fdd7 100644 --- a/pkg/exchange/max/exchange.go +++ b/pkg/exchange/max/exchange.go @@ -900,7 +900,7 @@ func (e *Exchange) QueryRewards(ctx context.Context, startTime time.Time) ([]typ // an user might get most 14 commission records by currency per day // limit 1000 / 14 = 71 days to := from.Add(time.Hour * 24 * 30) - req := e.client.RewardService.NewRewardsRequest() + req := e.client.RewardService.NewGetRewardsRequest() req.From(from.Unix()) req.To(to.Unix()) req.Limit(1000) diff --git a/pkg/exchange/max/maxapi/datatype.go b/pkg/exchange/max/maxapi/datatype.go deleted file mode 100644 index bef497e24..000000000 --- a/pkg/exchange/max/maxapi/datatype.go +++ /dev/null @@ -1,22 +0,0 @@ -package max - -import ( - "encoding/json" - "time" -) - -type Timestamp time.Time - -func (t Timestamp) String() string { - return time.Time(t).String() -} - -func (t *Timestamp) UnmarshalJSON(o []byte) error { - var timestamp int64 - if err := json.Unmarshal(o, ×tamp); err != nil { - return err - } - - *t = Timestamp(time.Unix(timestamp, 0)) - return nil -} diff --git a/pkg/exchange/max/maxapi/get_rewards_of_type_request_requestgen.go b/pkg/exchange/max/maxapi/get_rewards_of_type_request_requestgen.go new file mode 100644 index 000000000..225d48aa4 --- /dev/null +++ b/pkg/exchange/max/maxapi/get_rewards_of_type_request_requestgen.go @@ -0,0 +1,222 @@ +// Code generated by "requestgen -method GET -url v2/rewards/:path_type -type GetRewardsOfTypeRequest -responseType []Reward"; DO NOT EDIT. + +package max + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" +) + +func (g *GetRewardsOfTypeRequest) From(from int64) *GetRewardsOfTypeRequest { + g.from = &from + return g +} + +func (g *GetRewardsOfTypeRequest) To(to int64) *GetRewardsOfTypeRequest { + g.to = &to + return g +} + +func (g *GetRewardsOfTypeRequest) Page(page int64) *GetRewardsOfTypeRequest { + g.page = &page + return g +} + +func (g *GetRewardsOfTypeRequest) Limit(limit int64) *GetRewardsOfTypeRequest { + g.limit = &limit + return g +} + +func (g *GetRewardsOfTypeRequest) Offset(offset int64) *GetRewardsOfTypeRequest { + g.offset = &offset + return g +} + +func (g *GetRewardsOfTypeRequest) PathType(pathType RewardType) *GetRewardsOfTypeRequest { + g.pathType = &pathType + return g +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (g *GetRewardsOfTypeRequest) 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 *GetRewardsOfTypeRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check from field -> json key from + if g.from != nil { + from := *g.from + + // assign parameter of from + params["from"] = from + } else { + } + // check to field -> json key to + if g.to != nil { + to := *g.to + + // assign parameter of to + params["to"] = to + } else { + } + // check page field -> json key page + if g.page != nil { + page := *g.page + + // assign parameter of page + params["page"] = page + } else { + } + // check limit field -> json key limit + if g.limit != nil { + limit := *g.limit + + // assign parameter of limit + params["limit"] = limit + } else { + } + // check offset field -> json key offset + if g.offset != nil { + offset := *g.offset + + // assign parameter of offset + params["offset"] = offset + } else { + } + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (g *GetRewardsOfTypeRequest) 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 *GetRewardsOfTypeRequest) 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 *GetRewardsOfTypeRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check pathType field -> json key path_type + if g.pathType != nil { + pathType := *g.pathType + + // assign parameter of pathType + params["path_type"] = pathType + + } + + return params, nil +} + +func (g *GetRewardsOfTypeRequest) 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 *GetRewardsOfTypeRequest) 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 *GetRewardsOfTypeRequest) isVarSlice(v interface{}) bool { + rt := reflect.TypeOf(v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (g *GetRewardsOfTypeRequest) 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 *GetRewardsOfTypeRequest) Do(ctx context.Context) ([]Reward, error) { + + // empty params for GET operation + var params interface{} + query, err := g.GetParametersQuery() + if err != nil { + return nil, err + } + + apiURL := "v2/rewards/:path_type" + slugs, err := g.GetSlugsMap() + if err != nil { + return nil, err + } + + apiURL = g.applySlugsToUrl(apiURL, slugs) + + req, err := g.client.NewAuthenticatedRequest(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 []Reward + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } + return apiResponse, nil +} diff --git a/pkg/exchange/max/maxapi/get_rewards_request_requestgen.go b/pkg/exchange/max/maxapi/get_rewards_request_requestgen.go new file mode 100644 index 000000000..e21f1b529 --- /dev/null +++ b/pkg/exchange/max/maxapi/get_rewards_request_requestgen.go @@ -0,0 +1,216 @@ +// Code generated by "requestgen -method GET -url v2/rewards -type GetRewardsRequest -responseType []Reward"; DO NOT EDIT. + +package max + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" +) + +func (g *GetRewardsRequest) Currency(currency string) *GetRewardsRequest { + g.currency = ¤cy + return g +} + +func (g *GetRewardsRequest) From(from int64) *GetRewardsRequest { + g.from = &from + return g +} + +func (g *GetRewardsRequest) To(to int64) *GetRewardsRequest { + g.to = &to + return g +} + +func (g *GetRewardsRequest) Page(page int64) *GetRewardsRequest { + g.page = &page + return g +} + +func (g *GetRewardsRequest) Limit(limit int64) *GetRewardsRequest { + g.limit = &limit + return g +} + +func (g *GetRewardsRequest) Offset(offset int64) *GetRewardsRequest { + g.offset = &offset + return g +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (g *GetRewardsRequest) 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 *GetRewardsRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check currency field -> json key currency + if g.currency != nil { + currency := *g.currency + + // assign parameter of currency + params["currency"] = currency + } else { + } + // check from field -> json key from + if g.from != nil { + from := *g.from + + // assign parameter of from + params["from"] = from + } else { + } + // check to field -> json key to + if g.to != nil { + to := *g.to + + // assign parameter of to + params["to"] = to + } else { + } + // check page field -> json key page + if g.page != nil { + page := *g.page + + // assign parameter of page + params["page"] = page + } else { + } + // check limit field -> json key limit + if g.limit != nil { + limit := *g.limit + + // assign parameter of limit + params["limit"] = limit + } else { + } + // check offset field -> json key offset + if g.offset != nil { + offset := *g.offset + + // assign parameter of offset + params["offset"] = offset + } else { + } + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (g *GetRewardsRequest) 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 *GetRewardsRequest) 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 *GetRewardsRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (g *GetRewardsRequest) 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 *GetRewardsRequest) 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 *GetRewardsRequest) isVarSlice(v interface{}) bool { + rt := reflect.TypeOf(v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (g *GetRewardsRequest) 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 *GetRewardsRequest) Do(ctx context.Context) ([]Reward, error) { + + // empty params for GET operation + var params interface{} + query, err := g.GetParametersQuery() + if err != nil { + return nil, err + } + + apiURL := "v2/rewards" + + req, err := g.client.NewAuthenticatedRequest(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 []Reward + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } + return apiResponse, nil +} diff --git a/pkg/exchange/max/maxapi/reward.go b/pkg/exchange/max/maxapi/reward.go index 85ee914d7..685580e05 100644 --- a/pkg/exchange/max/maxapi/reward.go +++ b/pkg/exchange/max/maxapi/reward.go @@ -1,11 +1,15 @@ package max +//go:generate -command GetRequest requestgen -method GET +//go:generate -command PostRequest requestgen -method POST + import ( - "context" "encoding/json" "fmt" "strings" + "github.com/c9s/requestgen" + "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/types" ) @@ -18,6 +22,7 @@ const ( RewardHolding = RewardType("holding_reward") RewardMining = RewardType("mining_reward") RewardTrading = RewardType("trading_reward") + RewardRedemption = RewardType("redemption_reward") RewardVipRebate = RewardType("vip_rebate") ) @@ -35,6 +40,8 @@ func ParseRewardType(s string) (RewardType, error) { return RewardTrading, nil case "vip_rebate": return RewardVipRebate, nil + case "redemption_reward": + return RewardRedemption, nil } @@ -93,7 +100,7 @@ type Reward struct { Note string `json:"note"` // Unix timestamp in seconds - CreatedAt Timestamp `json:"created_at"` + CreatedAt types.Timestamp `json:"created_at"` } func (reward Reward) Reward() (*types.Reward, error) { @@ -119,88 +126,44 @@ type RewardService struct { client *RestClient } -func (s *RewardService) NewRewardsRequest() *RewardsRequest { - return &RewardsRequest{client: s.client} +func (s *RewardService) NewGetRewardsRequest() *GetRewardsRequest { + return &GetRewardsRequest{client: s.client} } -func (s *RewardService) NewRewardsByTypeRequest(pathType RewardType) *RewardsRequest { - return &RewardsRequest{client: s.client, pathType: &pathType} +func (s *RewardService) NewGetRewardsOfTypeRequest(pathType RewardType) *GetRewardsOfTypeRequest { + return &GetRewardsOfTypeRequest{client: s.client, pathType: &pathType} } -type RewardsRequest struct { - client *RestClient +//go:generate GetRequest -url "v2/rewards/:path_type" -type GetRewardsOfTypeRequest -responseType []Reward +type GetRewardsOfTypeRequest struct { + client requestgen.AuthenticatedAPIClient - pathType *RewardType - - currency *string + pathType *RewardType `param:"path_type,slug"` // From Unix-timestamp - from *int64 + from *int64 `param:"from"` // To Unix-timestamp - to *int64 + to *int64 `param:"to"` - limit *int + page *int64 `param:"page"` + limit *int64 `param:"limit"` + offset *int64 `param:"offset"` } -func (r *RewardsRequest) Currency(currency string) *RewardsRequest { - r.currency = ¤cy - return r -} - -func (r *RewardsRequest) From(from int64) *RewardsRequest { - r.from = &from - return r -} - -func (r *RewardsRequest) Limit(limit int) *RewardsRequest { - r.limit = &limit - return r -} - -func (r *RewardsRequest) To(to int64) *RewardsRequest { - r.to = &to - return r -} - -func (r *RewardsRequest) Do(ctx context.Context) (rewards []Reward, err error) { - payload := map[string]interface{}{} - - if r.currency != nil { - payload["currency"] = r.currency - } - - if r.to != nil { - payload["to"] = r.to - } - - if r.from != nil { - payload["from"] = r.from - } - - if r.limit != nil { - payload["limit"] = r.limit - } - - refURL := "v2/rewards" - - if r.pathType != nil { - refURL += "/" + string(*r.pathType) - } - - req, err := r.client.newAuthenticatedRequest(context.Background(), "GET", refURL, nil, payload, nil) - if err != nil { - return rewards, err - } - - response, err := r.client.SendRequest(req) - if err != nil { - return rewards, err - } - - if err := response.DecodeJSON(&rewards); err != nil { - return rewards, err - } - - return rewards, err +//go:generate GetRequest -url "v2/rewards" -type GetRewardsRequest -responseType []Reward +type GetRewardsRequest struct { + client requestgen.AuthenticatedAPIClient + + currency *string `param:"currency"` + + // From Unix-timestamp + from *int64 `param:"from"` + + // To Unix-timestamp + to *int64 `param:"to"` + + page *int64 `param:"page"` + limit *int64 `param:"limit"` + offset *int64 `param:"offset"` } diff --git a/pkg/exchange/max/maxapi/reward_test.go b/pkg/exchange/max/maxapi/reward_test.go new file mode 100644 index 000000000..cb89066a3 --- /dev/null +++ b/pkg/exchange/max/maxapi/reward_test.go @@ -0,0 +1,52 @@ +package max + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestRewardService_GetRewardsRequest(t *testing.T) { + key, secret, ok := integrationTestConfigured(t, "MAX") + if !ok { + t.SkipNow() + } + + ctx := context.Background() + + client := NewRestClient(ProductionAPIURL) + client.Auth(key, secret) + + req := client.RewardService.NewGetRewardsRequest() + rewards, err := req.Do(ctx) + assert.NoError(t, err) + assert.NotNil(t, rewards) + assert.NotEmpty(t, rewards) + + t.Logf("rewards: %+v", rewards) +} + +func TestRewardService_GetRewardsOfTypeRequest(t *testing.T) { + key, secret, ok := integrationTestConfigured(t, "MAX") + if !ok { + t.SkipNow() + } + + ctx := context.Background() + + client := NewRestClient(ProductionAPIURL) + client.Auth(key, secret) + + req := client.RewardService.NewGetRewardsOfTypeRequest(RewardCommission) + rewards, err := req.Do(ctx) + assert.NoError(t, err) + assert.NotNil(t, rewards) + assert.NotEmpty(t, rewards) + + t.Logf("rewards: %+v", rewards) + + for _, reward := range rewards { + assert.Equal(t, RewardCommission, reward.Type) + } +} diff --git a/pkg/types/time.go b/pkg/types/time.go index 6355abdcc..8502b605c 100644 --- a/pkg/types/time.go +++ b/pkg/types/time.go @@ -269,3 +269,33 @@ func (t *LooseFormatTime) UnmarshalJSON(data []byte) error { func (t LooseFormatTime) Time() time.Time { return time.Time(t) } + +// Timestamp is used for parsing unix timestamp (seconds) +type Timestamp time.Time + +func (t Timestamp) Format(layout string) string { + return time.Time(t).Format(layout) +} + +func (t Timestamp) Time() time.Time { + return time.Time(t) +} + +func (t Timestamp) String() string { + return time.Time(t).String() +} + +func (t Timestamp) MarshalJSON() ([]byte, error) { + ts := time.Time(t).Unix() + return json.Marshal(ts) +} + +func (t *Timestamp) UnmarshalJSON(o []byte) error { + var timestamp int64 + if err := json.Unmarshal(o, ×tamp); err != nil { + return err + } + + *t = Timestamp(time.Unix(timestamp, 0)) + return nil +}