From f65a582cd6b5a1ce12989860ae696d5595067855 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 14 Nov 2023 15:07:35 +1100 Subject: [PATCH] kucoin: fix spot order unmarshal bug (#1380) * kucoin: fix unmarshal bug * kucoin: Add time in force handling * kucoin: fix test * thrasher: nits * kucoin_test: shift skip check down for coverage, rm market testing, change buy price to reduce chance of instant match * kucoin: fix nits and force usage of uuid to rm conflicts --------- Co-authored-by: shazbert --- exchanges/kucoin/kucoin.go | 11 ++++++---- exchanges/kucoin/kucoin_test.go | 33 ++++++++++++++++-------------- exchanges/kucoin/kucoin_types.go | 2 +- exchanges/kucoin/kucoin_wrapper.go | 30 ++++++++++++++++++++------- 4 files changed, 49 insertions(+), 27 deletions(-) diff --git a/exchanges/kucoin/kucoin.go b/exchanges/kucoin/kucoin.go index 52cdc3db..8bd3494f 100644 --- a/exchanges/kucoin/kucoin.go +++ b/exchanges/kucoin/kucoin.go @@ -815,6 +815,7 @@ func (ku *Kucoin) GetServiceStatus(ctx context.Context) (*ServiceStatus, error) // Note: use this only for SPOT trades func (ku *Kucoin) PostOrder(ctx context.Context, arg *SpotOrderParam) (string, error) { if arg.ClientOrderID == "" { + // NOTE: 128 bit max length character string. UUID recommended. return "", errInvalidClientOrderID } if arg.Side == "" { @@ -841,11 +842,13 @@ func (ku *Kucoin) PostOrder(ctx context.Context, arg *SpotOrderParam) (string, e default: return "", fmt.Errorf("%w %s", order.ErrTypeIsInvalid, arg.OrderType) } - resp := struct { - OrderID string `json:"orderId"` + var resp struct { + Data struct { + OrderID string `json:"orderId"` + } `json:"data"` Error - }{} - return resp.OrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, placeOrderEPL, http.MethodPost, kucoinPostOrder, &arg, &resp) + } + return resp.Data.OrderID, ku.SendAuthHTTPRequest(ctx, exchange.RestSpot, placeOrderEPL, http.MethodPost, kucoinPostOrder, &arg, &resp) } // PostMarginOrder used to place two types of margin orders: limit and market diff --git a/exchanges/kucoin/kucoin_test.go b/exchanges/kucoin/kucoin_test.go index 625e755c..0b96beda 100644 --- a/exchanges/kucoin/kucoin_test.go +++ b/exchanges/kucoin/kucoin_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" @@ -563,7 +564,6 @@ func TestGetServiceStatus(t *testing.T) { func TestPostOrder(t *testing.T) { t.Parallel() - sharedtestvalues.SkipTestIfCredentialsUnset(t, ku, canManipulateRealOrders) // default order type is limit _, err := ku.PostOrder(context.Background(), &SpotOrderParam{ @@ -571,43 +571,46 @@ func TestPostOrder(t *testing.T) { if !errors.Is(err, errInvalidClientOrderID) { t.Errorf("PostOrder() expected %v, but found %v", errInvalidClientOrderID, err) } + + customID, err := uuid.NewV4() + if err != nil { + t.Fatal(err) + } + _, err = ku.PostOrder(context.Background(), &SpotOrderParam{ - ClientOrderID: "5bd6e9286d99522a52e458de", Symbol: spotTradablePair, + ClientOrderID: customID.String(), Symbol: spotTradablePair, OrderType: ""}) if !errors.Is(err, order.ErrSideIsInvalid) { t.Errorf("PostOrder() expected %v, but found %v", order.ErrSideIsInvalid, err) } _, err = ku.PostOrder(context.Background(), &SpotOrderParam{ - ClientOrderID: "5bd6e9286d99522a52e458de", Symbol: currency.EMPTYPAIR, + ClientOrderID: customID.String(), Symbol: currency.EMPTYPAIR, Size: 0.1, Side: "buy", Price: 234565}) if !errors.Is(err, currency.ErrCurrencyPairEmpty) { t.Errorf("PostOrder() expected %v, but found %v", currency.ErrCurrencyPairEmpty, err) } _, err = ku.PostOrder(context.Background(), &SpotOrderParam{ - ClientOrderID: "5bd6e9286d99522a52e458de", Side: "buy", + ClientOrderID: customID.String(), Side: "buy", Symbol: spotTradablePair, OrderType: "limit", Size: 0.1}) if !errors.Is(err, errInvalidPrice) { t.Errorf("PostOrder() expected %v, but found %v", errInvalidPrice, err) } _, err = ku.PostOrder(context.Background(), &SpotOrderParam{ - ClientOrderID: "5bd6e9286d99522a52e458de", Symbol: spotTradablePair, Side: "buy", + ClientOrderID: customID.String(), Symbol: spotTradablePair, Side: "buy", OrderType: "limit", Price: 234565}) if !errors.Is(err, errInvalidSize) { t.Errorf("PostOrder() expected %v, but found %v", errInvalidSize, err) } - _, err = ku.PostOrder(context.Background(), &SpotOrderParam{ - ClientOrderID: "5bd6e9286d99522a52e458de", Side: "buy", - Symbol: spotTradablePair, OrderType: "limit", Size: 0.1, Price: 234565}) - if err != nil { - t.Error("PostOrder() error", err) - } - // market order + sharedtestvalues.SkipTestIfCredentialsUnset(t, ku, canManipulateRealOrders) _, err = ku.PostOrder(context.Background(), &SpotOrderParam{ - ClientOrderID: "5bd6e9286d99522a52e458de", Side: "buy", - Symbol: spotTradablePair, - OrderType: "market", Remark: "remark", Size: 0.1}) + ClientOrderID: customID.String(), + Side: "buy", + Symbol: spotTradablePair, + OrderType: "limit", + Size: 0.005, + Price: 1000}) if err != nil { t.Error("PostOrder() error", err) } diff --git a/exchanges/kucoin/kucoin_types.go b/exchanges/kucoin/kucoin_types.go index bed7d7af..af5a1fae 100644 --- a/exchanges/kucoin/kucoin_types.go +++ b/exchanges/kucoin/kucoin_types.go @@ -32,7 +32,7 @@ var ( errMissingOrderbookSequence = errors.New("missing orderbook sequence") errSizeOrFundIsRequired = errors.New("at least one required among size and funds") errInvalidLeverage = errors.New("invalid leverage value") - errInvalidClientOrderID = errors.New("invalid client order ID") + errInvalidClientOrderID = errors.New("no client order ID supplied, this endpoint requires a UUID or similar string") subAccountRegExp = regexp.MustCompile("^[a-zA-Z0-9]{7-32}$") subAccountPassphraseRegExp = regexp.MustCompile("^[a-zA-Z0-9]{7-24}$") diff --git a/exchanges/kucoin/kucoin_wrapper.go b/exchanges/kucoin/kucoin_wrapper.go index 6df942bb..a5a1975a 100644 --- a/exchanges/kucoin/kucoin_wrapper.go +++ b/exchanges/kucoin/kucoin_wrapper.go @@ -761,13 +761,29 @@ func (ku *Kucoin) SubmitOrder(ctx context.Context, s *order.Submit) (*order.Subm } return s.DeriveSubmitResponse(o) case asset.Spot: - if s.ClientID != "" && s.ClientOrderID == "" { - s.ClientOrderID = s.ClientID + timeInForce := "" + if s.Type == order.Limit { + switch { + case s.FillOrKill: + timeInForce = "FOK" + case s.ImmediateOrCancel: + timeInForce = "IOC" + case s.PostOnly: + default: + timeInForce = "GTC" + } } o, err := ku.PostOrder(ctx, &SpotOrderParam{ - ClientOrderID: s.ClientOrderID, Side: sideString, - Symbol: s.Pair, OrderType: s.Type.Lower(), Size: s.Amount, - Price: s.Price, PostOnly: s.PostOnly, Hidden: s.Hidden}) + ClientOrderID: s.ClientOrderID, + Side: sideString, + Symbol: s.Pair, + OrderType: s.Type.Lower(), + Size: s.Amount, + Price: s.Price, + PostOnly: s.PostOnly, + Hidden: s.Hidden, + TimeInForce: timeInForce, + }) if err != nil { return nil, err } @@ -1936,7 +1952,7 @@ func (ku *Kucoin) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) continue } pair, enabled, err := ku.MatchSymbolCheckEnabled(symbols[x].Symbol, a, true) - if err != nil { + if err != nil && !errors.Is(err, currency.ErrPairNotFound) { return err } if !enabled { @@ -1962,7 +1978,7 @@ func (ku *Kucoin) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) limits = make([]order.MinMaxLevel, 0, len(contract)) for x := range contract { pair, enabled, err := ku.MatchSymbolCheckEnabled(contract[x].Symbol, a, false) - if err != nil { + if err != nil && !errors.Is(err, currency.ErrPairNotFound) { return err } if !enabled {