diff --git a/examples/max-eqmaker/main.go b/examples/max-eqmaker/main.go deleted file mode 100644 index fefc6ccf5..000000000 --- a/examples/max-eqmaker/main.go +++ /dev/null @@ -1,260 +0,0 @@ -package main - -import ( - "context" - "math" - "strings" - "syscall" - "time" - - "github.com/pkg/errors" - log "github.com/sirupsen/logrus" - "github.com/spf13/cobra" - "github.com/spf13/viper" - - "github.com/c9s/bbgo/pkg/cmd/cmdutil" - "github.com/c9s/bbgo/pkg/exchange/max" - maxapi "github.com/c9s/bbgo/pkg/exchange/max/maxapi" - "github.com/c9s/bbgo/pkg/fixedpoint" - "github.com/c9s/bbgo/pkg/types" - "github.com/c9s/bbgo/pkg/util" -) - -func init() { - rootCmd.PersistentFlags().String("max-api-key", "", "max api key") - rootCmd.PersistentFlags().String("max-api-secret", "", "max api secret") - rootCmd.PersistentFlags().String("symbol", "maxusdt", "symbol") - - rootCmd.Flags().String("side", "buy", "side") - rootCmd.Flags().Int("num-orders", 5, "number of orders for one side") - rootCmd.Flags().Float64("behind-volume", 1000.0, "behind volume depth") - rootCmd.Flags().Float64("base-quantity", 100.0, "base quantity") - rootCmd.Flags().Float64("price-tick", 0.02, "price tick") - rootCmd.Flags().Float64("buy-sell-ratio", 1.0, "price tick") -} - -var rootCmd = &cobra.Command{ - Use: "trade", - Short: "start trader", - - // SilenceUsage is an option to silence usage when an error occurs. - SilenceUsage: true, - - RunE: func(cmd *cobra.Command, args []string) error { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - symbol := viper.GetString("symbol") - if len(symbol) == 0 { - return errors.New("empty symbol") - } - - key, secret := viper.GetString("max-api-key"), viper.GetString("max-api-secret") - if len(key) == 0 || len(secret) == 0 { - return errors.New("empty key or secret") - } - - side, err := cmd.Flags().GetString("side") - if err != nil { - return err - } - - iv, err := cmd.Flags().GetInt("num-orders") - if err != nil { - return err - } - var numOrders = iv - - fv, err := cmd.Flags().GetFloat64("base-quantity") - if err != nil { - return err - } - var baseQuantity = fixedpoint.NewFromFloat(fv) - - fv, err = cmd.Flags().GetFloat64("price-tick") - if err != nil { - return err - } - var priceTick = fixedpoint.NewFromFloat(fv) - - fv, err = cmd.Flags().GetFloat64("behind-volume") - if err != nil { - return err - } - - var behindVolume = fixedpoint.NewFromFloat(fv) - - buySellRatio, err := cmd.Flags().GetFloat64("buy-sell-ratio") - if err != nil { - return err - } - - maxRest := maxapi.NewRestClient(maxapi.ProductionAPIURL) - maxRest.Auth(key, secret) - - stream := max.NewStream(key, secret) - stream.Subscribe(types.BookChannel, symbol, types.SubscribeOptions{}) - - stream.OnOrderUpdate(func(order types.Order) { - log.Infof("order: %+v", order) - }) - - stream.OnBalanceSnapshot(func(balances types.BalanceMap) { - log.Infof("balances: %+v", balances) - }) - - streambook := types.NewStreamBook(symbol) - streambook.BindStream(stream) - - cancelSideOrders := func(symbol string, side string) { - if err := maxRest.OrderService.CancelAll(side, symbol); err != nil { - log.WithError(err).Error("cancel all error") - } - - streambook.C.Drain(2*time.Second, 5*time.Second) - } - - updateSideOrders := func(symbol string, side string, baseQuantity fixedpoint.Value) { - book := streambook.Copy() - - var pvs types.PriceVolumeSlice - - switch side { - case "buy": - pvs = book.Bids - case "sell": - pvs = book.Asks - } - - if pvs == nil || len(pvs) == 0 { - log.Warn("empty bids or asks") - return - } - - index := pvs.IndexByVolumeDepth(behindVolume) - if index == -1 { - // do not place orders - log.Warn("depth is not enough") - return - } - - var price = pvs[index].Price - var orders = generateOrders(symbol, side, price, priceTick, baseQuantity, numOrders) - if len(orders) == 0 { - log.Warn("empty orders") - return - } - log.Infof("submitting %d orders", len(orders)) - - retOrders, err := maxRest.OrderService.CreateMulti(symbol, orders) - if err != nil { - log.WithError(err).Error("create multi error") - } - _ = retOrders - - streambook.C.Drain(2*time.Second, 5*time.Second) - } - - update := func() { - switch side { - case "both": - cancelSideOrders(symbol, "buy") - updateSideOrders(symbol, "buy", baseQuantity.MulFloat64(buySellRatio)) - - cancelSideOrders(symbol, "sell") - updateSideOrders(symbol, "sell", baseQuantity.MulFloat64(1.0/buySellRatio)) - - default: - cancelSideOrders(symbol, side) - updateSideOrders(symbol, side, baseQuantity) - } - } - - go func() { - ticker := time.NewTicker(1 * time.Minute) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return - - case <-streambook.C: - streambook.C.Drain(2*time.Second, 5*time.Second) - update() - - case <-ticker.C: - update() - } - } - }() - - log.Info("connecting websocket...") - if err := stream.Connect(ctx); err != nil { - log.Fatal(err) - } - - cmdutil.WaitForSignal(ctx, syscall.SIGINT, syscall.SIGTERM) - return nil - }, -} - -func generateOrders(symbol, side string, price, priceTick, baseVolume fixedpoint.Value, numOrders int) (orders []maxapi.Order) { - var expBase = fixedpoint.NewFromFloat(0.0) - - switch side { - case "buy": - if priceTick > 0 { - priceTick = -priceTick - } - case "sell": - if priceTick < 0 { - priceTick = -priceTick - } - } - - for i := 0; i < numOrders; i++ { - volume := math.Exp(expBase.Float64()) * baseVolume.Float64() - - // skip order less than 10usd - if volume*price.Float64() < 10.0 { - log.Warnf("amount too small (< 10usd). price=%f volume=%f amount=%f", price.Float64(), volume, volume*price.Float64()) - continue - } - - orders = append(orders, maxapi.Order{ - Side: side, - OrderType: maxapi.OrderTypeLimit, - Market: symbol, - Price: util.FormatFloat(price.Float64(), 3), - Volume: util.FormatFloat(volume, 2), - // GroupID: 0, - }) - - log.Infof("%s order: %.2f @ %.3f", side, volume, price.Float64()) - - if len(orders) >= numOrders { - break - } - - price = price + priceTick - declog := math.Log10(math.Abs(priceTick.Float64())) - expBase += fixedpoint.NewFromFloat(math.Pow10(-int(declog)) * math.Abs(priceTick.Float64())) - log.Infof("expBase: %f", expBase.Float64()) - } - - return orders -} - -func main() { - viper.AutomaticEnv() - viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) - - if err := viper.BindPFlags(rootCmd.PersistentFlags()); err != nil { - log.WithError(err).Error("bind pflags error") - } - - if err := rootCmd.ExecuteContext(context.Background()); err != nil { - log.WithError(err).Error("cmd error") - } -} diff --git a/pkg/exchange/max/exchange.go b/pkg/exchange/max/exchange.go index 28392b85b..ddcd0dfc1 100644 --- a/pkg/exchange/max/exchange.go +++ b/pkg/exchange/max/exchange.go @@ -164,7 +164,7 @@ func (e *Exchange) QueryOrder(ctx context.Context, q types.OrderQuery) (*types.O return nil, err } - maxOrder, err := e.client.OrderService.Get(uint64(orderID)) + maxOrder, err := e.client.OrderService.NewGetOrderRequest().Id(uint64(orderID)).Do(ctx) if err != nil { return nil, err } @@ -399,14 +399,14 @@ func (e *Exchange) CancelOrders(ctx context.Context, orders ...types.Order) (err for _, o := range orphanOrders { var req = e.client.OrderService.NewOrderCancelRequest() if o.OrderID > 0 { - req.ID(o.OrderID) + req.Id(o.OrderID) } else if len(o.ClientOrderID) > 0 && o.ClientOrderID != types.NoClientOrderID { req.ClientOrderID(o.ClientOrderID) } else { return fmt.Errorf("order id or client order id is not defined, order=%+v", o) } - if err := req.Do(ctx); err != nil { + if _, err := req.Do(ctx); err != nil { log.WithError(err).Errorf("order cancel error") err2 = err } diff --git a/pkg/exchange/max/maxapi/create_order_request_requestgen.go b/pkg/exchange/max/maxapi/create_order_request_requestgen.go new file mode 100644 index 000000000..300b9fe9f --- /dev/null +++ b/pkg/exchange/max/maxapi/create_order_request_requestgen.go @@ -0,0 +1,247 @@ +// Code generated by "requestgen -method POST -url v2/orders -type CreateOrderRequest -responseType .Order"; DO NOT EDIT. + +package max + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" +) + +func (c *CreateOrderRequest) Market(market string) *CreateOrderRequest { + c.market = market + return c +} + +func (c *CreateOrderRequest) Side(side string) *CreateOrderRequest { + c.side = side + return c +} + +func (c *CreateOrderRequest) Volume(volume string) *CreateOrderRequest { + c.volume = volume + return c +} + +func (c *CreateOrderRequest) OrderType(orderType string) *CreateOrderRequest { + c.orderType = orderType + return c +} + +func (c *CreateOrderRequest) Price(price string) *CreateOrderRequest { + c.price = &price + return c +} + +func (c *CreateOrderRequest) StopPrice(stopPrice string) *CreateOrderRequest { + c.stopPrice = &stopPrice + return c +} + +func (c *CreateOrderRequest) ClientOrderID(clientOrderID string) *CreateOrderRequest { + c.clientOrderID = &clientOrderID + return c +} + +func (c *CreateOrderRequest) GroupID(groupID string) *CreateOrderRequest { + c.groupID = &groupID + return c +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (c *CreateOrderRequest) 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 (c *CreateOrderRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check market field -> json key market + market := c.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 side field -> json key side + side := c.side + + // TEMPLATE check-required + if len(side) == 0 { + return nil, fmt.Errorf("side is required, empty string given") + } + // END TEMPLATE check-required + + // assign parameter of side + params["side"] = side + // check volume field -> json key volume + volume := c.volume + + // TEMPLATE check-required + if len(volume) == 0 { + return nil, fmt.Errorf("volume is required, empty string given") + } + // END TEMPLATE check-required + + // assign parameter of volume + params["volume"] = volume + // check orderType field -> json key ord_type + orderType := c.orderType + + // assign parameter of orderType + params["ord_type"] = orderType + // check price field -> json key price + if c.price != nil { + price := *c.price + + // assign parameter of price + params["price"] = price + } else { + } + // check stopPrice field -> json key stop_price + if c.stopPrice != nil { + stopPrice := *c.stopPrice + + // assign parameter of stopPrice + params["stop_price"] = stopPrice + } else { + } + // check clientOrderID field -> json key client_oid + if c.clientOrderID != nil { + clientOrderID := *c.clientOrderID + + // assign parameter of clientOrderID + params["client_oid"] = clientOrderID + } else { + } + // check groupID field -> json key group_id + if c.groupID != nil { + groupID := *c.groupID + + // assign parameter of groupID + params["group_id"] = groupID + } else { + } + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (c *CreateOrderRequest) GetParametersQuery() (url.Values, error) { + query := url.Values{} + + params, err := c.GetParameters() + if err != nil { + return query, err + } + + for k, v := range params { + if c.isVarSlice(v) { + c.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 (c *CreateOrderRequest) GetParametersJSON() ([]byte, error) { + params, err := c.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 (c *CreateOrderRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (c *CreateOrderRequest) 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 (c *CreateOrderRequest) 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 (c *CreateOrderRequest) isVarSlice(v interface{}) bool { + rt := reflect.TypeOf(v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (c *CreateOrderRequest) GetSlugsMap() (map[string]string, error) { + slugs := map[string]string{} + params, err := c.GetSlugParameters() + if err != nil { + return slugs, nil + } + + for k, v := range params { + slugs[k] = fmt.Sprintf("%v", v) + } + + return slugs, nil +} + +func (c *CreateOrderRequest) Do(ctx context.Context) (*Order, error) { + + params, err := c.GetParameters() + if err != nil { + return nil, err + } + query := url.Values{} + + apiURL := "v2/orders" + + req, err := c.client.NewAuthenticatedRequest(ctx, "POST", apiURL, query, params) + if err != nil { + return nil, err + } + + response, err := c.client.SendRequest(req) + if err != nil { + return nil, err + } + + var apiResponse Order + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } + return &apiResponse, nil +} diff --git a/pkg/exchange/max/maxapi/get_order_history_request_requestgen.go b/pkg/exchange/max/maxapi/get_order_history_request_requestgen.go new file mode 100644 index 000000000..ddaa0873b --- /dev/null +++ b/pkg/exchange/max/maxapi/get_order_history_request_requestgen.go @@ -0,0 +1,174 @@ +// Code generated by "requestgen -method GET -url v2/orders/history -type GetOrderHistoryRequest -responseType []Order"; DO NOT EDIT. + +package max + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" +) + +func (g *GetOrderHistoryRequest) Market(market string) *GetOrderHistoryRequest { + g.market = market + return g +} + +func (g *GetOrderHistoryRequest) FromID(fromID int64) *GetOrderHistoryRequest { + g.fromID = &fromID + return g +} + +func (g *GetOrderHistoryRequest) Limit(limit int) *GetOrderHistoryRequest { + g.limit = &limit + return g +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (g *GetOrderHistoryRequest) 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 *GetOrderHistoryRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check market field -> json key market + market := g.market + + // assign parameter of market + params["market"] = market + // check fromID field -> json key from_id + if g.fromID != nil { + fromID := *g.fromID + + // assign parameter of fromID + params["from_id"] = fromID + } else { + } + // check limit field -> json key limit + if g.limit != nil { + limit := *g.limit + + // assign parameter of limit + params["limit"] = limit + } else { + } + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (g *GetOrderHistoryRequest) 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 *GetOrderHistoryRequest) 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 *GetOrderHistoryRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (g *GetOrderHistoryRequest) 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 *GetOrderHistoryRequest) 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 *GetOrderHistoryRequest) isVarSlice(v interface{}) bool { + rt := reflect.TypeOf(v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (g *GetOrderHistoryRequest) 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 *GetOrderHistoryRequest) Do(ctx context.Context) ([]Order, error) { + + // empty params for GET operation + var params interface{} + query, err := g.GetParametersQuery() + if err != nil { + return nil, err + } + + apiURL := "v2/orders/history" + + 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 []Order + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } + return apiResponse, nil +} diff --git a/pkg/exchange/max/maxapi/get_order_request_requestgen.go b/pkg/exchange/max/maxapi/get_order_request_requestgen.go new file mode 100644 index 000000000..4f495fbe0 --- /dev/null +++ b/pkg/exchange/max/maxapi/get_order_request_requestgen.go @@ -0,0 +1,164 @@ +// Code generated by "requestgen -method GET -url v2/order -type GetOrderRequest -responseType .Order"; DO NOT EDIT. + +package max + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" +) + +func (g *GetOrderRequest) Id(id uint64) *GetOrderRequest { + g.id = &id + return g +} + +func (g *GetOrderRequest) ClientOrderID(clientOrderID string) *GetOrderRequest { + g.clientOrderID = &clientOrderID + return g +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (g *GetOrderRequest) 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 *GetOrderRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check id field -> json key id + if g.id != nil { + id := *g.id + + // assign parameter of id + params["id"] = id + } else { + } + // check clientOrderID field -> json key client_oid + if g.clientOrderID != nil { + clientOrderID := *g.clientOrderID + + // assign parameter of clientOrderID + params["client_oid"] = clientOrderID + } else { + } + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (g *GetOrderRequest) 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 *GetOrderRequest) 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 *GetOrderRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (g *GetOrderRequest) 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 *GetOrderRequest) 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 *GetOrderRequest) isVarSlice(v interface{}) bool { + rt := reflect.TypeOf(v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (g *GetOrderRequest) 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 *GetOrderRequest) Do(ctx context.Context) (*Order, error) { + + // empty params for GET operation + var params interface{} + query, err := g.GetParametersQuery() + if err != nil { + return nil, err + } + + apiURL := "v2/order" + + 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 Order + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } + return &apiResponse, nil +} diff --git a/pkg/exchange/max/maxapi/get_orders_request_requestgen.go b/pkg/exchange/max/maxapi/get_orders_request_requestgen.go new file mode 100644 index 000000000..afbbc8c9a --- /dev/null +++ b/pkg/exchange/max/maxapi/get_orders_request_requestgen.go @@ -0,0 +1,227 @@ +// Code generated by "requestgen -method GET -url v2/orders -type GetOrdersRequest -responseType []Order"; DO NOT EDIT. + +package max + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" +) + +func (g *GetOrdersRequest) Market(market string) *GetOrdersRequest { + g.market = market + return g +} + +func (g *GetOrdersRequest) Side(side string) *GetOrdersRequest { + g.side = &side + return g +} + +func (g *GetOrdersRequest) GroupID(groupID uint32) *GetOrdersRequest { + g.groupID = &groupID + return g +} + +func (g *GetOrdersRequest) Limit(limit int) *GetOrdersRequest { + g.limit = &limit + return g +} + +func (g *GetOrdersRequest) Page(page int) *GetOrdersRequest { + g.page = &page + return g +} + +func (g *GetOrdersRequest) OrderBy(orderBy string) *GetOrdersRequest { + g.orderBy = &orderBy + return g +} + +func (g *GetOrdersRequest) State(state []OrderState) *GetOrdersRequest { + g.state = state + return g +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (g *GetOrdersRequest) 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 *GetOrdersRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check market field -> json key market + market := g.market + + // assign parameter of market + params["market"] = market + // check side field -> json key side + if g.side != nil { + side := *g.side + + // assign parameter of side + params["side"] = side + } else { + } + // check groupID field -> json key groupID + if g.groupID != nil { + groupID := *g.groupID + + // assign parameter of groupID + params["groupID"] = groupID + } else { + } + // check limit field -> json key limit + if g.limit != nil { + limit := *g.limit + + // assign parameter of limit + params["limit"] = limit + } else { + } + // check page field -> json key page + if g.page != nil { + page := *g.page + + // assign parameter of page + params["page"] = page + } else { + } + // check orderBy field -> json key order_by + if g.orderBy != nil { + orderBy := *g.orderBy + + // assign parameter of orderBy + params["order_by"] = orderBy + } else { + orderBy := "desc" + + // assign parameter of orderBy + params["order_by"] = orderBy + } + // check state field -> json key state + state := g.state + + // assign parameter of state + params["state"] = state + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (g *GetOrdersRequest) 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 *GetOrdersRequest) 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 *GetOrdersRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (g *GetOrdersRequest) 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 *GetOrdersRequest) 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 *GetOrdersRequest) isVarSlice(v interface{}) bool { + rt := reflect.TypeOf(v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (g *GetOrdersRequest) 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 *GetOrdersRequest) Do(ctx context.Context) ([]Order, error) { + + // empty params for GET operation + var params interface{} + query, err := g.GetParametersQuery() + if err != nil { + return nil, err + } + + apiURL := "v2/orders" + + 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 []Order + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } + return apiResponse, nil +} diff --git a/pkg/exchange/max/maxapi/order.go b/pkg/exchange/max/maxapi/order.go index 22dee267d..5eca12ba8 100644 --- a/pkg/exchange/max/maxapi/order.go +++ b/pkg/exchange/max/maxapi/order.go @@ -1,11 +1,14 @@ package max +//go:generate -command GetRequest requestgen -method GET +//go:generate -command PostRequest requestgen -method POST + import ( "context" "net/url" - "strconv" "time" + "github.com/c9s/requestgen" "github.com/pkg/errors" "github.com/c9s/bbgo/pkg/types" @@ -172,6 +175,40 @@ func (s *OrderService) Open(market string, options QueryOrderOptions) ([]Order, return orders, nil } +//go:generate GetRequest -url "v2/orders/history" -type GetOrderHistoryRequest -responseType []Order +type GetOrderHistoryRequest struct { + client requestgen.AuthenticatedAPIClient + + market string `param:"market"` + fromID *int64 `param:"from_id"` + limit *int `param:"limit"` +} + +func (s *OrderService) NewGetOrderHistoryRequest() *GetOrderHistoryRequest { + return &GetOrderHistoryRequest{ + client: s.client, + } +} + +//go:generate GetRequest -url "v2/orders" -type GetOrdersRequest -responseType []Order +type GetOrdersRequest struct { + client requestgen.AuthenticatedAPIClient + + market string `param:"market"` + side *string `param:"side"` + groupID *uint32 `param:"groupID"` + limit *int `param:"limit"` + page *int `param:"page"` + orderBy *string `param:"order_by" default:"desc"` + state []OrderState `param:"state"` +} + +func (s *OrderService) NewGetOrdersRequest() *GetOrdersRequest { + return &GetOrdersRequest{ + client: s.client, + } +} + // All returns all orders for the authenticated account. func (s *OrderService) All(market string, limit, page int, states ...OrderState) ([]Order, error) { payload := map[string]interface{}{ @@ -200,52 +237,9 @@ func (s *OrderService) All(market string, limit, page int, states ...OrderState) return orders, nil } -// CancelAll active orders for the authenticated account. -func (s *OrderService) CancelAll(side string, market string) error { - payload := map[string]interface{}{} - if side == "buy" || side == "sell" { - payload["side"] = side - } - if market != "all" { - payload["market"] = market - } - - req, err := s.client.newAuthenticatedRequest(nil, "POST", "v2/orders/clear", nil, payload, relUrlV2OrdersClear) - if err != nil { - return err - } - - _, err = s.client.SendRequest(req) - if err != nil { - return err - } - - return nil -} - // Options carry the option fields for REST API type Options map[string]interface{} -// Create a new order. -func (s *OrderService) Create(market string, side string, volume float64, price float64, orderType string, options Options) (*Order, error) { - options["market"] = market - options["volume"] = strconv.FormatFloat(volume, 'f', -1, 64) - options["price"] = strconv.FormatFloat(price, 'f', -1, 64) - options["side"] = side - options["ord_type"] = orderType - response, err := s.client.sendAuthenticatedRequest("POST", "v2/orders", options) - if err != nil { - return nil, err - } - - var order = Order{} - if err := response.DecodeJSON(&order); err != nil { - return nil, err - } - - return &order, nil -} - // Create multiple order in a single request func (s *OrderService) CreateMulti(market string, orders []Order) (*MultiOrderResponse, error) { req := s.NewCreateMultiOrderRequest() @@ -254,151 +248,41 @@ func (s *OrderService) CreateMulti(market string, orders []Order) (*MultiOrderRe return req.Do(context.Background()) } -// Cancel the order with id `orderID`. -func (s *OrderService) Cancel(orderID uint64, clientOrderID string) error { - req := s.NewOrderCancelRequest() - - if orderID > 0 { - req.ID(orderID) - } else if len(clientOrderID) > 0 { - req.ClientOrderID(clientOrderID) - } - - return req.Do(context.Background()) -} - -type OrderCancelAllRequestParams struct { - *PrivateRequestParams - - Side string `json:"side,omitempty"` - Market string `json:"market,omitempty"` - GroupID int64 `json:"groupID,omitempty"` -} - +//go:generate PostRequest -url "v2/orders/clear" -type OrderCancelAllRequest -responseType []Order type OrderCancelAllRequest struct { - client *RestClient + client requestgen.AuthenticatedAPIClient - params OrderCancelAllRequestParams - - side *string - market *string - groupID *uint32 -} - -func (r *OrderCancelAllRequest) Side(side string) *OrderCancelAllRequest { - r.side = &side - return r -} - -func (r *OrderCancelAllRequest) Market(market string) *OrderCancelAllRequest { - r.market = &market - return r -} - -func (r *OrderCancelAllRequest) GroupID(groupID uint32) *OrderCancelAllRequest { - r.groupID = &groupID - return r -} - -func (r *OrderCancelAllRequest) Do(ctx context.Context) (orders []Order, err error) { - var payload = map[string]interface{}{} - if r.side != nil { - payload["side"] = *r.side - } - if r.market != nil { - payload["market"] = *r.market - } - if r.groupID != nil { - payload["groupID"] = *r.groupID - } - - req, err := r.client.newAuthenticatedRequest(nil, "POST", "v2/orders/clear", nil, payload, nil) - if err != nil { - return - } - - response, err := r.client.SendRequest(req) - if err != nil { - return - } - - err = response.DecodeJSON(&orders) - return + side *string `param:"side"` + market *string `param:"market"` + groupID *uint32 `param:"groupID"` } func (s *OrderService) NewOrderCancelAllRequest() *OrderCancelAllRequest { return &OrderCancelAllRequest{client: s.client} } -type OrderCancelRequestParams struct { - *PrivateRequestParams - - ID uint64 `json:"id,omitempty"` - ClientOrderID string `json:"client_oid,omitempty"` -} - +//go:generate PostRequest -url "v2/order/delete" -type OrderCancelRequest -responseType .Order type OrderCancelRequest struct { - client *RestClient + client requestgen.AuthenticatedAPIClient - params OrderCancelRequestParams -} - -func (r *OrderCancelRequest) ID(id uint64) *OrderCancelRequest { - r.params.ID = id - return r -} - -func (r *OrderCancelRequest) ClientOrderID(id string) *OrderCancelRequest { - r.params.ClientOrderID = id - return r -} - -func (r *OrderCancelRequest) Do(ctx context.Context) error { - req, err := r.client.newAuthenticatedRequest(nil, "POST", "v2/order/delete", nil, &r.params, relUrlV2OrderDelete) - if err != nil { - return err - } - - response, err := r.client.SendRequest(req) - if err != nil { - return err - } - - var order = Order{} - if err := response.DecodeJSON(&order); err != nil { - return err - } - - return err + id *uint64 `param:"id,omitempty"` + clientOrderID *string `param:"client_oid,omitempty"` } func (s *OrderService) NewOrderCancelRequest() *OrderCancelRequest { return &OrderCancelRequest{client: s.client} } -// Status retrieves the given order from the API. -func (s *OrderService) Get(orderID uint64) (*Order, error) { - payload := map[string]interface{}{ - "id": orderID, - } +//go:generate GetRequest -url "v2/order" -type GetOrderRequest -responseType .Order +type GetOrderRequest struct { + client requestgen.AuthenticatedAPIClient - req, err := s.client.newAuthenticatedRequest(nil, "GET", "v2/order", nil, payload, relUrlV2Order) - if err != nil { - return &Order{}, err - } + id *uint64 `param:"id,omitempty"` + clientOrderID *string `param:"client_oid,omitempty"` +} - response, err := s.client.SendRequest(req) - if err != nil { - return nil, err - } - - var order = Order{} - - if err := response.DecodeJSON(&order); err != nil { - return nil, err - } - - return &order, nil +func (s *OrderService) NewGetOrderRequest() *GetOrderRequest { + return &GetOrderRequest{client: s.client} } type MultiOrderRequestParams struct { @@ -482,97 +366,19 @@ func (s *OrderService) NewCreateMultiOrderRequest() *CreateMultiOrderRequest { return &CreateMultiOrderRequest{client: s.client} } +//go:generate PostRequest -url "v2/orders" -type CreateOrderRequest -responseType .Order type CreateOrderRequest struct { - client *RestClient + client requestgen.AuthenticatedAPIClient - market *string - volume *string - price *string - stopPrice *string - side *string - orderType *string - clientOrderID *string - groupID *string -} + market string `param:"market,required"` + side string `param:"side,required"` + volume string `param:"volume,required"` + orderType string `param:"ord_type"` -func (r *CreateOrderRequest) Market(market string) *CreateOrderRequest { - r.market = &market - return r -} - -func (r *CreateOrderRequest) Volume(volume string) *CreateOrderRequest { - r.volume = &volume - return r -} - -func (r *CreateOrderRequest) Price(price string) *CreateOrderRequest { - r.price = &price - return r -} - -func (r *CreateOrderRequest) StopPrice(price string) *CreateOrderRequest { - r.stopPrice = &price - return r -} - -func (r *CreateOrderRequest) Side(side string) *CreateOrderRequest { - r.side = &side - return r -} - -func (r *CreateOrderRequest) OrderType(orderType string) *CreateOrderRequest { - r.orderType = &orderType - return r -} - -func (r *CreateOrderRequest) ClientOrderID(clientOrderID string) *CreateOrderRequest { - r.clientOrderID = &clientOrderID - return r -} - -func (r *CreateOrderRequest) Do(ctx context.Context) (order *Order, err error) { - var payload = map[string]interface{}{ - "market": r.market, - "volume": r.volume, - "side": r.side, - } - - if r.price != nil { - payload["price"] = r.price - } - - if r.stopPrice != nil { - payload["stop_price"] = r.stopPrice - } - - if r.orderType != nil { - payload["ord_type"] = r.orderType - } - - if r.clientOrderID != nil { - payload["client_oid"] = r.clientOrderID - } - - if r.groupID != nil { - payload["group_id"] = r.groupID - } - - req, err := r.client.newAuthenticatedRequest(nil, "POST", "v2/orders", nil, payload, relUrlV2Orders) - if err != nil { - return order, errors.Wrapf(err, "order create error") - } - - response, err := r.client.SendRequest(req) - if err != nil { - return order, err - } - - order = &Order{} - if err := response.DecodeJSON(order); err != nil { - return nil, err - } - - return order, err + price *string `param:"price"` + stopPrice *string `param:"stop_price"` + clientOrderID *string `param:"client_oid"` + groupID *string `param:"group_id"` } func (s *OrderService) NewCreateOrderRequest() *CreateOrderRequest { diff --git a/pkg/exchange/max/maxapi/order_cancel_all_request_requestgen.go b/pkg/exchange/max/maxapi/order_cancel_all_request_requestgen.go new file mode 100644 index 000000000..f1fdac327 --- /dev/null +++ b/pkg/exchange/max/maxapi/order_cancel_all_request_requestgen.go @@ -0,0 +1,176 @@ +// Code generated by "requestgen -method POST -url v2/orders/clear -type OrderCancelAllRequest -responseType []Order"; DO NOT EDIT. + +package max + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" +) + +func (o *OrderCancelAllRequest) Side(side string) *OrderCancelAllRequest { + o.side = &side + return o +} + +func (o *OrderCancelAllRequest) Market(market string) *OrderCancelAllRequest { + o.market = &market + return o +} + +func (o *OrderCancelAllRequest) GroupID(groupID uint32) *OrderCancelAllRequest { + o.groupID = &groupID + return o +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (o *OrderCancelAllRequest) 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 (o *OrderCancelAllRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check side field -> json key side + if o.side != nil { + side := *o.side + + // assign parameter of side + params["side"] = side + } else { + } + // check market field -> json key market + if o.market != nil { + market := *o.market + + // assign parameter of market + params["market"] = market + } else { + } + // check groupID field -> json key groupID + if o.groupID != nil { + groupID := *o.groupID + + // assign parameter of groupID + params["groupID"] = groupID + } else { + } + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (o *OrderCancelAllRequest) GetParametersQuery() (url.Values, error) { + query := url.Values{} + + params, err := o.GetParameters() + if err != nil { + return query, err + } + + for k, v := range params { + if o.isVarSlice(v) { + o.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 (o *OrderCancelAllRequest) GetParametersJSON() ([]byte, error) { + params, err := o.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 (o *OrderCancelAllRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (o *OrderCancelAllRequest) 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 (o *OrderCancelAllRequest) 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 (o *OrderCancelAllRequest) isVarSlice(v interface{}) bool { + rt := reflect.TypeOf(v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (o *OrderCancelAllRequest) GetSlugsMap() (map[string]string, error) { + slugs := map[string]string{} + params, err := o.GetSlugParameters() + if err != nil { + return slugs, nil + } + + for k, v := range params { + slugs[k] = fmt.Sprintf("%v", v) + } + + return slugs, nil +} + +func (o *OrderCancelAllRequest) Do(ctx context.Context) ([]Order, error) { + + params, err := o.GetParameters() + if err != nil { + return nil, err + } + query := url.Values{} + + apiURL := "v2/orders/clear" + + req, err := o.client.NewAuthenticatedRequest(ctx, "POST", apiURL, query, params) + if err != nil { + return nil, err + } + + response, err := o.client.SendRequest(req) + if err != nil { + return nil, err + } + + var apiResponse []Order + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } + return apiResponse, nil +} diff --git a/pkg/exchange/max/maxapi/order_cancel_request_requestgen.go b/pkg/exchange/max/maxapi/order_cancel_request_requestgen.go new file mode 100644 index 000000000..1854837c5 --- /dev/null +++ b/pkg/exchange/max/maxapi/order_cancel_request_requestgen.go @@ -0,0 +1,163 @@ +// Code generated by "requestgen -method POST -url v2/order/delete -type OrderCancelRequest -responseType .Order"; DO NOT EDIT. + +package max + +import ( + "context" + "encoding/json" + "fmt" + "net/url" + "reflect" + "regexp" +) + +func (o *OrderCancelRequest) Id(id uint64) *OrderCancelRequest { + o.id = &id + return o +} + +func (o *OrderCancelRequest) ClientOrderID(clientOrderID string) *OrderCancelRequest { + o.clientOrderID = &clientOrderID + return o +} + +// GetQueryParameters builds and checks the query parameters and returns url.Values +func (o *OrderCancelRequest) 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 (o *OrderCancelRequest) GetParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + // check id field -> json key id + if o.id != nil { + id := *o.id + + // assign parameter of id + params["id"] = id + } else { + } + // check clientOrderID field -> json key client_oid + if o.clientOrderID != nil { + clientOrderID := *o.clientOrderID + + // assign parameter of clientOrderID + params["client_oid"] = clientOrderID + } else { + } + + return params, nil +} + +// GetParametersQuery converts the parameters from GetParameters into the url.Values format +func (o *OrderCancelRequest) GetParametersQuery() (url.Values, error) { + query := url.Values{} + + params, err := o.GetParameters() + if err != nil { + return query, err + } + + for k, v := range params { + if o.isVarSlice(v) { + o.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 (o *OrderCancelRequest) GetParametersJSON() ([]byte, error) { + params, err := o.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 (o *OrderCancelRequest) GetSlugParameters() (map[string]interface{}, error) { + var params = map[string]interface{}{} + + return params, nil +} + +func (o *OrderCancelRequest) 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 (o *OrderCancelRequest) 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 (o *OrderCancelRequest) isVarSlice(v interface{}) bool { + rt := reflect.TypeOf(v) + switch rt.Kind() { + case reflect.Slice: + return true + } + return false +} + +func (o *OrderCancelRequest) GetSlugsMap() (map[string]string, error) { + slugs := map[string]string{} + params, err := o.GetSlugParameters() + if err != nil { + return slugs, nil + } + + for k, v := range params { + slugs[k] = fmt.Sprintf("%v", v) + } + + return slugs, nil +} + +func (o *OrderCancelRequest) Do(ctx context.Context) (*Order, error) { + + params, err := o.GetParameters() + if err != nil { + return nil, err + } + query := url.Values{} + + apiURL := "v2/order/delete" + + req, err := o.client.NewAuthenticatedRequest(ctx, "POST", apiURL, query, params) + if err != nil { + return nil, err + } + + response, err := o.client.SendRequest(req) + if err != nil { + return nil, err + } + + var apiResponse Order + if err := response.DecodeJSON(&apiResponse); err != nil { + return nil, err + } + return &apiResponse, nil +} diff --git a/pkg/exchange/max/maxapi/order_test.go b/pkg/exchange/max/maxapi/order_test.go new file mode 100644 index 000000000..a945bb20d --- /dev/null +++ b/pkg/exchange/max/maxapi/order_test.go @@ -0,0 +1,114 @@ +package max + +import ( + "context" + "os" + "regexp" + "testing" + + "github.com/stretchr/testify/assert" +) + +func maskSecret(s string) string { + re := regexp.MustCompile(`\b(\w{4})\w+\b`) + s = re.ReplaceAllString(s, "$1******") + return s +} + +func integrationTestConfigured(t *testing.T, prefix string) (key, secret string, ok bool) { + var hasKey, hasSecret bool + key, hasKey = os.LookupEnv(prefix + "_API_KEY") + secret, hasSecret = os.LookupEnv(prefix + "_API_SECRET") + ok = hasKey && hasSecret && os.Getenv("TEST_"+prefix) == "1" + if ok { + t.Logf(prefix+" api integration test enabled, key = %s, secret = %s", maskSecret(key), maskSecret(secret)) + } + return key, secret, ok +} + +func TestOrderService_GetOrdersRequest(t *testing.T) { + key, secret, ok := integrationTestConfigured(t, "MAX") + if !ok { + t.SkipNow() + } + + ctx := context.Background() + + client := NewRestClient(ProductionAPIURL) + client.Auth(key, secret) + + req3 := client.OrderService.NewGetOrdersRequest() + req3.State([]OrderState{OrderStateDone, OrderStateFinalizing}) + // req3.State([]OrderState{OrderStateDone}) + req3.Market("btcusdt") + orders, err := req3.Do(ctx) + assert.NoError(t, err) + assert.NotNil(t, orders) +} + +func TestOrderService_GetOrdersRequest_SingleState(t *testing.T) { + key, secret, ok := integrationTestConfigured(t, "MAX") + if !ok { + t.SkipNow() + } + + ctx := context.Background() + + client := NewRestClient(ProductionAPIURL) + client.Auth(key, secret) + + req3 := client.OrderService.NewGetOrdersRequest() + req3.State([]OrderState{OrderStateDone}) + req3.Market("btcusdt") + orders, err := req3.Do(ctx) + assert.NoError(t, err) + assert.NotNil(t, orders) +} + +func TestOrderService_GetOrderHistoryRequest(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.OrderService.NewGetOrderHistoryRequest() + req.Market("btcusdt") + req.FromID(1) + orders, err := req.Do(ctx) + assert.NoError(t, err) + assert.NotNil(t, orders) +} + + +func TestOrderService(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.OrderService.NewCreateOrderRequest() + order, err := req.Market("btcusdt"). + Price("10000"). + Volume("0.001"). + OrderType("limit"). + Side("buy").Do(ctx) + + if assert.NoError(t, err) { + assert.NotNil(t, order) + req2 := client.OrderService.NewOrderCancelRequest() + req2.Id(order.ID) + cancelResp, err := req2.Do(ctx) + assert.NoError(t, err) + t.Logf("cancelResponse: %+v", cancelResp) + } + +} diff --git a/pkg/exchange/max/maxapi/public.go b/pkg/exchange/max/maxapi/public.go index 856b49b02..6b228a8bc 100644 --- a/pkg/exchange/max/maxapi/public.go +++ b/pkg/exchange/max/maxapi/public.go @@ -1,6 +1,7 @@ package max import ( + "context" "fmt" "io/ioutil" "net/url" @@ -11,7 +12,7 @@ import ( "github.com/pkg/errors" "github.com/valyala/fastjson" - "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/types" ) @@ -46,7 +47,7 @@ type Ticker struct { func (s *PublicService) Timestamp() (serverTimestamp int64, err error) { // sync timestamp with server - req, err := s.client.NewRequest(nil, "GET", "v2/timestamp", nil, nil) + req, err := s.client.NewRequest(context.Background(), "GET", "v2/timestamp", nil, nil) if err != nil { return 0, err } @@ -65,7 +66,7 @@ func (s *PublicService) Timestamp() (serverTimestamp int64, err error) { } func (s *PublicService) Markets() ([]Market, error) { - req, err := s.client.NewRequest(nil, "GET", "v2/markets", url.Values{}, nil) + req, err := s.client.NewRequest(context.Background(), "GET", "v2/markets", url.Values{}, nil) if err != nil { return nil, err } @@ -85,7 +86,7 @@ func (s *PublicService) Markets() ([]Market, error) { func (s *PublicService) Tickers() (map[string]Ticker, error) { var endPoint = "v2/tickers" - req, err := s.client.NewRequest(nil, "GET", endPoint, url.Values{}, nil) + req, err := s.client.NewRequest(context.Background(), "GET", endPoint, url.Values{}, nil) if err != nil { return nil, err } @@ -116,7 +117,7 @@ func (s *PublicService) Tickers() (map[string]Ticker, error) { func (s *PublicService) Ticker(market string) (*Ticker, error) { var endPoint = "v2/tickers/" + market - req, err := s.client.NewRequest(nil, "GET", endPoint, url.Values{}, nil) + req, err := s.client.NewRequest(context.Background(), "GET", endPoint, url.Values{}, nil) if err != nil { return nil, err } @@ -250,7 +251,7 @@ func (s *PublicService) KLines(symbol string, resolution string, start time.Time queries.Set("limit", strconv.Itoa(limit)) // default to 30, max limit = 10,000 } - req, err := s.client.NewRequest(nil, "GET", fmt.Sprintf("%s/k", s.client.BaseURL), queries, nil) + req, err := s.client.NewRequest(context.Background(), "GET", fmt.Sprintf("%s/k", s.client.BaseURL), queries, nil) if err != nil { return nil, fmt.Errorf("request build error: %s", err.Error()) } diff --git a/pkg/exchange/max/maxapi/restapi.go b/pkg/exchange/max/maxapi/restapi.go index 7c847a516..800ee2b08 100644 --- a/pkg/exchange/max/maxapi/restapi.go +++ b/pkg/exchange/max/maxapi/restapi.go @@ -15,9 +15,9 @@ import ( "net/http" "net/http/httputil" "net/url" - "reflect" "regexp" "strconv" + "strings" "sync/atomic" "time" @@ -224,18 +224,18 @@ func (c *RestClient) newAuthenticatedRequest(ctx context.Context, m string, refU } var p []byte + var payload map[string]interface{} switch d := data.(type) { case nil: - payload := map[string]interface{}{ + payload = map[string]interface{}{ "nonce": c.getNonce(), "path": c.BaseURL.ResolveReference(rel).Path, } - p, err = json.Marshal(payload) case map[string]interface{}: - payload := map[string]interface{}{ + payload = map[string]interface{}{ "nonce": c.getNonce(), "path": c.BaseURL.ResolveReference(rel).Path, } @@ -243,19 +243,22 @@ func (c *RestClient) newAuthenticatedRequest(ctx context.Context, m string, refU for k, v := range d { payload[k] = v } + } - p, err = json.Marshal(payload) - - default: - params, err := getPrivateRequestParamsObject(data) - if err != nil { - return nil, errors.Wrapf(err, "unsupported payload type: %T", d) + if m == "GET" { + for k, vs := range params { + k = strings.TrimSuffix(k, "[]") + if len(vs) == 1 { + payload[k] = vs[0] + } else { + payload[k] = vs + } } + } - params.Nonce = c.getNonce() - params.Path = c.BaseURL.ResolveReference(rel).Path - - p, err = json.Marshal(d) + p, err = castPayload(payload) + if err != nil { + return nil, err } if debugMaxRequestPayload { @@ -282,8 +285,6 @@ func (c *RestClient) newAuthenticatedRequest(ctx context.Context, m string, refU encoded := base64.StdEncoding.EncodeToString(p) req.Header.Add("Content-Type", "application/json") - // accept is not necessary - // req.Header.Add("Accept", "application/json") req.Header.Add("X-MAX-ACCESSKEY", c.APIKey) req.Header.Add("X-MAX-PAYLOAD", encoded) req.Header.Add("X-MAX-SIGNATURE", signPayload(encoded, c.APISecret)) @@ -300,38 +301,6 @@ func (c *RestClient) newAuthenticatedRequest(ctx context.Context, m string, refU return req, nil } -func getPrivateRequestParamsObject(v interface{}) (*PrivateRequestParams, error) { - vt := reflect.ValueOf(v) - - if vt.Kind() == reflect.Ptr { - vt = vt.Elem() - } - - if vt.Kind() != reflect.Struct { - return nil, errors.New("reflect error: given object is not a struct" + vt.Kind().String()) - } - - if !vt.CanSet() { - return nil, errors.New("reflect error: can not set object") - } - - field := vt.FieldByName("PrivateRequestParams") - if !field.IsValid() { - return nil, errors.New("reflect error: field PrivateRequestParams not found") - } - - if field.IsNil() { - field.Set(reflect.ValueOf(&PrivateRequestParams{})) - } - - params, ok := field.Interface().(*PrivateRequestParams) - if !ok { - return nil, errors.New("reflect error: failed to cast value to *PrivateRequestParams") - } - - return params, nil -} - func signPayload(payload string, secret string) string { var sig = hmac.New(sha256.New, []byte(secret)) _, err := sig.Write([]byte(payload)) @@ -460,25 +429,27 @@ func ToErrorResponse(response *requestgen.Response) (errorResponse *ErrorRespons // convert 5xx error from the HTML page to the ErrorResponse errorResponse.Err.Message = htmlTagPattern.ReplaceAllLiteralString(string(response.Body), "") return errorResponse, nil + case "text/plain": + errorResponse.Err.Message = string(response.Body) + return errorResponse, nil } return errorResponse, fmt.Errorf("unexpected response content type %s", contentType) } func castPayload(payload interface{}) ([]byte, error) { - if payload != nil { - switch v := payload.(type) { - case string: - return []byte(v), nil - - case []byte: - return v, nil - - default: - body, err := json.Marshal(v) - return body, err - } + if payload == nil { + return nil, nil } - return nil, nil + switch v := payload.(type) { + case string: + return []byte(v), nil + + case []byte: + return v, nil + } + + body, err := json.Marshal(payload) + return body, err }