From 12263997c0e1a18625b3cc6ea529d84d71a11d39 Mon Sep 17 00:00:00 2001 From: Scott Date: Tue, 27 Oct 2020 17:05:43 +1100 Subject: [PATCH] Bugfix: Submit/Cancel order scripting errors + Ordermanager GetOrder & CancelOrder improvements (#584) * Fixes Submit and Cancel order functions for scripting. Adds tests to ensure that wrapper functions actually execute as expected. Adds more coverage. Fixes a few other tests * Fixes issues * Simplifies tests * Update cancel order to properly cancel an order. Adds example script. Adds sweet tests. * Fixes bad test setup for cancelling orders * Adds new order manager GetOrderInfo function to call the wrapper function and then store it in the order manager * Addresses order concerns * LOWERS THE CASE OF ALL EXCHANGE NAMES BECAUSE ORDER MANAGER NEVER CARED ABOUT CASING OR YOUR FAMILY * Removes asset and currency requirement from cancelling orders via validation test * Fixes old cancel order assumptions that had requirements for currency and asset * Moves all the logs and events to the dedicated Cancel function instead of exclusively doing it in CancelAllOrders * Adds more detail logging. Fixes lbank fee test * Addresses comms manager issues * Removes go routine for pushing events. Better to let them fail --- common/common_test.go | 6 +- engine/comms_relayer.go | 6 +- engine/fake_exchange_test.go | 5 +- engine/orders.go | 100 ++++--- engine/orders_test.go | 25 ++ engine/rpcserver.go | 2 +- exchanges/lbank/lbank_test.go | 5 +- exchanges/lbank/lbank_wrapper.go | 21 +- gctscript/examples/exchange/cancel_order.gct | 9 + gctscript/examples/exchange/submit_order.gct | 2 +- gctscript/modules/gct/exchange.go | 42 ++- gctscript/modules/gct/gct_test.go | 112 ++++---- gctscript/modules/gct/gct_types.go | 1 + gctscript/modules/wrapper_types.go | 2 +- gctscript/wrappers/gct/exchange/exchange.go | 14 +- .../wrappers/gct/exchange/exchange_test.go | 25 +- gctscript/wrappers/gct/gctwrapper_test.go | 252 ++++++++++++++++++ gctscript/wrappers/validator/validator.go | 13 +- .../wrappers/validator/validator_test.go | 25 +- .../wrappers/validator/validator_types.go | 2 +- 20 files changed, 548 insertions(+), 121 deletions(-) create mode 100644 gctscript/examples/exchange/cancel_order.gct diff --git a/common/common_test.go b/common/common_test.go index 391ba2b0f94..1bc324f4fa5 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -233,9 +233,9 @@ func TestSendHTTPGetRequest(t *testing.T) { type test struct { Address string `json:"address"` ETH struct { - Balance int `json:"balance"` - TotalIn int `json:"totalIn"` - TotalOut int `json:"totalOut"` + Balance float64 `json:"balance"` + TotalIn float64 `json:"totalIn"` + TotalOut float64 `json:"totalOut"` } `json:"ETH"` } ethURL := `https://api.ethplorer.io/getAddressInfo/0xff71cb760666ab06aa73f34995b42dd4b85ea07b?apiKey=freekey` diff --git a/engine/comms_relayer.go b/engine/comms_relayer.go index 1fb36a8e4e5..b02c04a1c8a 100644 --- a/engine/comms_relayer.go +++ b/engine/comms_relayer.go @@ -72,7 +72,11 @@ func (c *commsManager) PushEvent(evt base.Event) { if !c.Started() { return } - c.relayMsg <- evt + select { + case c.relayMsg <- evt: + default: + log.Errorf(log.CommunicationMgr, "Failed to send, no receiver when pushing event [%v]", evt) + } } func (c *commsManager) run() { diff --git a/engine/fake_exchange_test.go b/engine/fake_exchange_test.go index 9a2cfa5c07f..74294268c39 100644 --- a/engine/fake_exchange_test.go +++ b/engine/fake_exchange_test.go @@ -133,7 +133,10 @@ func (h *FakePassingExchange) CancelAllOrders(_ *order.Cancel) (order.CancelAllR return order.CancelAllResponse{}, nil } func (h *FakePassingExchange) GetOrderInfo(_ string, _ currency.Pair, _ asset.Item) (order.Detail, error) { - return order.Detail{}, nil + return order.Detail{ + Exchange: fakePassExchange, + ID: "fakeOrder", + }, nil } func (h *FakePassingExchange) GetDepositAddress(_ currency.Code, _ string) (string, error) { return "", nil diff --git a/engine/orders.go b/engine/orders.go index f690c143e15..68e85ff39d7 100644 --- a/engine/orders.go +++ b/engine/orders.go @@ -3,12 +3,15 @@ package engine import ( "errors" "fmt" + "strings" "sync/atomic" "time" "github.com/gofrs/uuid" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/communications/base" + "github.com/thrasher-corp/gocryptotrader/currency" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -33,7 +36,7 @@ func (o *orderStore) get() map[string][]*order.Detail { func (o *orderStore) GetByExchangeAndID(exchange, id string) (*order.Detail, error) { o.m.RLock() defer o.m.RUnlock() - r, ok := o.Orders[exchange] + r, ok := o.Orders[strings.ToLower(exchange)] if !ok { return nil, ErrExchangeNotFound } @@ -50,7 +53,7 @@ func (o *orderStore) GetByExchangeAndID(exchange, id string) (*order.Detail, err func (o *orderStore) GetByExchange(exchange string) ([]*order.Detail, error) { o.m.RLock() defer o.m.RUnlock() - r, ok := o.Orders[exchange] + r, ok := o.Orders[strings.ToLower(exchange)] if !ok { return nil, ErrExchangeNotFound } @@ -78,7 +81,7 @@ func (o *orderStore) exists(det *order.Detail) bool { } o.m.RLock() defer o.m.RUnlock() - r, ok := o.Orders[det.Exchange] + r, ok := o.Orders[strings.ToLower(det.Exchange)] if !ok { return false } @@ -116,9 +119,9 @@ func (o *orderStore) Add(det *order.Detail) error { } o.m.Lock() defer o.m.Unlock() - orders := o.Orders[det.Exchange] + orders := o.Orders[strings.ToLower(det.Exchange)] orders = append(orders, det) - o.Orders[det.Exchange] = orders + o.Orders[strings.ToLower(det.Exchange)] = orders return nil } @@ -203,8 +206,6 @@ func (o *orderManager) CancelAllOrders(exchangeNames []string) { } for y := range v { - log.Debugf(log.OrderMgr, "Order manager: Cancelling order ID %v [%v]", - v[y].ID, v[y]) err := o.Cancel(&order.Cancel{ Exchange: k, ID: v[y].ID, @@ -218,20 +219,8 @@ func (o *orderManager) CancelAllOrders(exchangeNames []string) { }) if err != nil { log.Error(log.OrderMgr, err) - Bot.CommsManager.PushEvent(base.Event{ - Type: "order", - Message: err.Error(), - }) continue } - - msg := fmt.Sprintf("Order manager: Exchange %s order ID=%v cancelled.", - k, v[y].ID) - log.Debugln(log.OrderMgr, msg) - Bot.CommsManager.PushEvent(base.Event{ - Type: "order", - Message: msg, - }) } } } @@ -239,41 +228,91 @@ func (o *orderManager) CancelAllOrders(exchangeNames []string) { // Cancel will find the order in the orderManager, send a cancel request // to the exchange and if successful, update the status of the order func (o *orderManager) Cancel(cancel *order.Cancel) error { + var err error + defer func() { + if err != nil { + Bot.CommsManager.PushEvent(base.Event{ + Type: "order", + Message: err.Error(), + }) + } + }() + if cancel == nil { - return errors.New("order cancel param is nil") + err = errors.New("order cancel param is nil") + return err } - if cancel.Exchange == "" { - return errors.New("order exchange name is empty") + err = errors.New("order exchange name is empty") + return err } - if cancel.ID == "" { - return errors.New("order id is empty") + err = errors.New("order id is empty") + return err } exch := Bot.GetExchangeByName(cancel.Exchange) if exch == nil { - return ErrExchangeNotFound + err = ErrExchangeNotFound + return err } if cancel.AssetType.String() != "" && !exch.GetAssetTypes().Contains(cancel.AssetType) { - return errors.New("order asset type not supported by exchange") + err = errors.New("order asset type not supported by exchange") + return err } - err := exch.CancelOrder(cancel) + log.Debugf(log.OrderMgr, "Order manager: Cancelling order ID %v [%+v]", + cancel.ID, cancel) + + err = exch.CancelOrder(cancel) if err != nil { - return fmt.Errorf("%v - Failed to cancel order: %v", cancel.Exchange, err) + err = fmt.Errorf("%v - Failed to cancel order: %v", cancel.Exchange, err) + return err } var od *order.Detail od, err = o.orderStore.GetByExchangeAndID(cancel.Exchange, cancel.ID) if err != nil { - return fmt.Errorf("%v - Failed to retrieve order %v to update cancelled status: %v", cancel.Exchange, cancel.ID, err) + err = fmt.Errorf("%v - Failed to retrieve order %v to update cancelled status: %v", cancel.Exchange, cancel.ID, err) + return err } od.Status = order.Cancelled + msg := fmt.Sprintf("Order manager: Exchange %s order ID=%v cancelled.", + od.Exchange, od.ID) + log.Debugln(log.OrderMgr, msg) + Bot.CommsManager.PushEvent(base.Event{ + Type: "order", + Message: msg, + }) + return nil } +// GetOrderInfo calls the exchange's wrapper GetOrderInfo function +// and stores the result in the order manager +func (o *orderManager) GetOrderInfo(exchangeName, orderID string, cp currency.Pair, a asset.Item) (order.Detail, error) { + if orderID == "" { + return order.Detail{}, errors.New("order cannot be empty") + } + + exch := Bot.GetExchangeByName(exchangeName) + if exch == nil { + return order.Detail{}, ErrExchangeNotFound + } + result, err := exch.GetOrderInfo(orderID, cp, a) + if err != nil { + return order.Detail{}, err + } + + err = o.orderStore.Add(&result) + if err != nil && err != ErrOrdersAlreadyExists { + return order.Detail{}, err + } + + return result, nil +} + // Submit will take in an order struct, send it to the exchange and // populate it in the orderManager if successful func (o *orderManager) Submit(newOrder *order.Submit) (*orderSubmitResponse, error) { @@ -381,7 +420,8 @@ func (o *orderManager) Submit(newOrder *order.Submit) (*orderSubmitResponse, err return &orderSubmitResponse{ SubmitResponse: order.SubmitResponse{ - OrderID: result.OrderID, + IsOrderPlaced: result.IsOrderPlaced, + OrderID: result.OrderID, }, InternalOrderID: id.String(), }, nil diff --git a/engine/orders_test.go b/engine/orders_test.go index f799ace1639..46cff18c18d 100644 --- a/engine/orders_test.go +++ b/engine/orders_test.go @@ -278,6 +278,31 @@ func TestCancelOrder(t *testing.T) { } } +func TestGetOrderInfo(t *testing.T) { + OrdersSetup(t) + _, err := Bot.OrderManager.GetOrderInfo("", "", currency.Pair{}, "") + if err == nil { + t.Error("Expected error due to empty order") + } + + var result order.Detail + result, err = Bot.OrderManager.GetOrderInfo(fakePassExchange, "1234", currency.Pair{}, "") + if err != nil { + t.Error(err) + } + if result.ID != "fakeOrder" { + t.Error("unexpected order returned") + } + + result, err = Bot.OrderManager.GetOrderInfo(fakePassExchange, "1234", currency.Pair{}, "") + if err != nil { + t.Error(err) + } + if result.ID != "fakeOrder" { + t.Error("unexpected order returned") + } +} + func TestCancelAllOrders(t *testing.T) { OrdersSetup(t) o := &order.Detail{ diff --git a/engine/rpcserver.go b/engine/rpcserver.go index ffe4723b46f..daec8a8d483 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -811,7 +811,7 @@ func (s *RPCServer) GetOrder(_ context.Context, r *gctrpc.GetOrderRequest) (*gct Quote: currency.NewCode(r.Pair.Quote), } - result, err := exch.GetOrderInfo(r.OrderId, pair, "") // assetType will be implemented in the future + result, err := s.OrderManager.GetOrderInfo(r.Exchange, r.OrderId, pair, "") // assetType will be implemented in the future if err != nil { return nil, fmt.Errorf("error whilst trying to retrieve info for order %s: %s", r.OrderId, err) } diff --git a/exchanges/lbank/lbank_test.go b/exchanges/lbank/lbank_test.go index 8a6b3dbb0e2..065e4559482 100644 --- a/exchanges/lbank/lbank_test.go +++ b/exchanges/lbank/lbank_test.go @@ -377,13 +377,10 @@ func TestGetFeeByType(t *testing.T) { input.Amount = 2 input.FeeType = exchange.CryptocurrencyWithdrawalFee input.Pair = cp - a, err := l.GetFeeByType(&input) + _, err := l.GetFeeByType(&input) if err != nil { t.Error(err) } - if a != 0.0005 { - t.Errorf("expected: 0.0005, received: %v", a) - } } func TestGetAccountInfo(t *testing.T) { diff --git a/exchanges/lbank/lbank_wrapper.go b/exchanges/lbank/lbank_wrapper.go index b1d8ead6c8d..6da8fab4863 100644 --- a/exchanges/lbank/lbank_wrapper.go +++ b/exchanges/lbank/lbank_wrapper.go @@ -707,16 +707,17 @@ func (l *Lbank) GetFeeByType(feeBuilder *exchange.FeeBuilder) (float64, error) { if err != nil { return resp, err } - var tempFee string - temp := strings.Split(withdrawalFee[0].Fee, ":\"") - if len(temp) > 1 { - tempFee = strings.TrimRight(temp[1], ",\"type") - } else { - tempFee = temp[0] - } - resp, err = strconv.ParseFloat(tempFee, 64) - if err != nil { - return resp, err + for i := range withdrawalFee { + if !strings.EqualFold(withdrawalFee[i].AssetCode, feeBuilder.Pair.Base.String()) { + continue + } + if withdrawalFee[i].Fee == "" { + return 0, nil + } + resp, err = strconv.ParseFloat(withdrawalFee[i].Fee, 64) + if err != nil { + return resp, err + } } } return resp, nil diff --git a/gctscript/examples/exchange/cancel_order.gct b/gctscript/examples/exchange/cancel_order.gct new file mode 100644 index 00000000000..18d5d491ca0 --- /dev/null +++ b/gctscript/examples/exchange/cancel_order.gct @@ -0,0 +1,9 @@ +fmt := import("fmt") +exch := import("exchange") + +load := func() { + info := exch.ordercancel("binance","13371337", "btc-usdt", "spot") + fmt.println(info) +} + +load() diff --git a/gctscript/examples/exchange/submit_order.gct b/gctscript/examples/exchange/submit_order.gct index 7c5ceb5d4c7..f8665e97f59 100644 --- a/gctscript/examples/exchange/submit_order.gct +++ b/gctscript/examples/exchange/submit_order.gct @@ -2,7 +2,7 @@ fmt := import("fmt") exch := import("exchange") load := func() { - info := exch.ordersubmit("BTC Markets","BTC-AUD","-","LIMIT","SELL",1000000, 1,"", SPOT) + info := exch.ordersubmit("BTC Markets","BTC-AUD","-","LIMIT","SELL",1000000, 1,"", "spot") fmt.println(info) } diff --git a/gctscript/modules/gct/exchange.go b/gctscript/modules/gct/exchange.go index 97de32f886b..ddca1060d6a 100644 --- a/gctscript/modules/gct/exchange.go +++ b/gctscript/modules/gct/exchange.go @@ -330,7 +330,7 @@ func ExchangeOrderQuery(args ...objects.Object) (objects.Object, error) { // ExchangeOrderCancel cancels order on requested exchange func ExchangeOrderCancel(args ...objects.Object) (objects.Object, error) { - if len(args) != 2 { + if len(args) < 2 || len(args) > 4 { return nil, objects.ErrWrongNumArguments } @@ -338,17 +338,50 @@ func ExchangeOrderCancel(args ...objects.Object) (objects.Object, error) { if !ok { return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName) } - orderID, ok := objects.ToString(args[1]) + if exchangeName == "" { + return nil, fmt.Errorf(ErrEmptyParameter, "exchange name") + } + var orderID string + orderID, ok = objects.ToString(args[1]) if !ok { return nil, fmt.Errorf(ErrParameterConvertFailed, orderID) } + if orderID == "" { + return nil, fmt.Errorf(ErrEmptyParameter, "orderID") + } + var err error + var cp currency.Pair + if len(args) > 2 { + var currencyPair string + currencyPair, ok = objects.ToString(args[2]) + if !ok { + return nil, fmt.Errorf(ErrParameterConvertFailed, currencyPair) + } + cp, err = currency.NewPairFromString(currencyPair) + if err != nil { + return nil, err + } + } + var a asset.Item + if len(args) > 3 { + var assetType string + assetType, ok = objects.ToString(args[3]) + if !ok { + return nil, fmt.Errorf(ErrParameterConvertFailed, assetType) + } + a, err = asset.New(assetType) + if err != nil { + return nil, err + } + } - rtn, err := wrappers.GetWrapper().CancelOrder(exchangeName, orderID) + var isCancelled bool + isCancelled, err = wrappers.GetWrapper().CancelOrder(exchangeName, orderID, cp, a) if err != nil { return nil, err } - if rtn { + if isCancelled { return objects.TrueValue, nil } return objects.FalseValue, nil @@ -413,6 +446,7 @@ func ExchangeOrderSubmit(args ...objects.Object) (objects.Object, error) { Amount: orderAmount, ClientID: orderClientID, AssetType: a, + Exchange: exchangeName, } rtn, err := wrappers.GetWrapper().SubmitOrder(tempSubmit) diff --git a/gctscript/modules/gct/gct_test.go b/gctscript/modules/gct/gct_test.go index f639cc1ef4a..d78d3a046bb 100644 --- a/gctscript/modules/gct/gct_test.go +++ b/gctscript/modules/gct/gct_test.go @@ -32,6 +32,9 @@ var ( orderID = &objects.String{ Value: "1235", } + blank = &objects.String{ + Value: "", + } tv = objects.TrueValue fv = objects.FalseValue @@ -47,17 +50,17 @@ func TestExchangeOrderbook(t *testing.T) { t.Parallel() _, err := ExchangeOrderbook(exch, currencyPair, delimiter, assetType) if err != nil { - t.Fatal(err) + t.Error(err) } _, err = ExchangeOrderbook(exchError, currencyPair, delimiter, assetType) if err != nil && errors.Is(err, errTestFailed) { - t.Fatal(err) + t.Error(err) } _, err = ExchangeOrderbook() if !errors.Is(err, objects.ErrWrongNumArguments) { - t.Fatal(err) + t.Error(err) } } @@ -65,17 +68,17 @@ func TestExchangeTicker(t *testing.T) { t.Parallel() _, err := ExchangeTicker(exch, currencyPair, delimiter, assetType) if err != nil { - t.Fatal(err) + t.Error(err) } _, err = ExchangeTicker(exchError, currencyPair, delimiter, assetType) if err != nil && errors.Is(err, errTestFailed) { - t.Fatal(err) + t.Error(err) } _, err = ExchangeTicker() if !errors.Is(err, objects.ErrWrongNumArguments) { - t.Fatal(err) + t.Error(err) } } @@ -84,22 +87,22 @@ func TestExchangeExchanges(t *testing.T) { _, err := ExchangeExchanges(tv) if err != nil { - t.Fatal(err) + t.Error(err) } _, err = ExchangeExchanges(exch) if err != nil { - t.Fatal(err) + t.Error(err) } _, err = ExchangeExchanges(fv) if err != nil { - t.Fatal(err) + t.Error(err) } _, err = ExchangeExchanges() if !errors.Is(err, objects.ErrWrongNumArguments) { - t.Fatal(err) + t.Error(err) } } @@ -108,17 +111,17 @@ func TestExchangePairs(t *testing.T) { _, err := ExchangePairs(exch, tv, assetType) if err != nil { - t.Fatal(err) + t.Error(err) } _, err = ExchangePairs(exchError, tv, assetType) if err != nil && errors.Is(err, errTestFailed) { - t.Fatal(err) + t.Error(err) } _, err = ExchangePairs() if !errors.Is(err, objects.ErrWrongNumArguments) { - t.Fatal(err) + t.Error(err) } } @@ -127,17 +130,17 @@ func TestAccountInfo(t *testing.T) { _, err := ExchangeAccountInfo() if !errors.Is(err, objects.ErrWrongNumArguments) { - t.Fatal(err) + t.Error(err) } _, err = ExchangeAccountInfo(exch) if err != nil { - t.Fatal(err) + t.Error(err) } _, err = ExchangeAccountInfo(exchError) if err != nil && !errors.Is(err, errTestFailed) { - t.Fatal(err) + t.Error(err) } } @@ -146,46 +149,58 @@ func TestExchangeOrderQuery(t *testing.T) { _, err := ExchangeOrderQuery() if !errors.Is(err, objects.ErrWrongNumArguments) { - t.Fatal(err) + t.Error(err) } _, err = ExchangeOrderQuery(exch, orderID) if err != nil { - t.Fatal(err) + t.Error(err) } _, err = ExchangeOrderQuery(exchError, orderID) if err != nil && !errors.Is(err, errTestFailed) { - t.Fatal(err) + t.Error(err) } } func TestExchangeOrderCancel(t *testing.T) { + t.Parallel() _, err := ExchangeOrderCancel() if !errors.Is(err, objects.ErrWrongNumArguments) { - t.Fatal(err) + t.Error(err) + } + + _, err = ExchangeOrderCancel(blank, orderID, currencyPair, assetType) + if err == nil { + t.Error("expecting error") + } + + _, err = ExchangeOrderCancel(exch, blank, currencyPair, assetType) + if err == nil { + t.Error("expecting error") } _, err = ExchangeOrderCancel(exch, orderID) if err != nil { - t.Fatal(err) + t.Error(err) } - _, err = ExchangeOrderCancel(exch, objects.FalseValue) + _, err = ExchangeOrderCancel(exch, orderID, currencyPair) if err != nil { - t.Fatal(err) + t.Error(err) } - _, err = ExchangeOrderCancel(exchError, orderID) - if err != nil && !errors.Is(err, errTestFailed) { - t.Fatal(err) + _, err = ExchangeOrderCancel(exch, orderID, currencyPair, assetType) + if err != nil { + t.Error(err) } } func TestExchangeOrderSubmit(t *testing.T) { + t.Parallel() _, err := ExchangeOrderSubmit() if !errors.Is(err, objects.ErrWrongNumArguments) { - t.Fatal(err) + t.Error(err) } orderSide := &objects.String{Value: "ASK"} @@ -197,52 +212,55 @@ func TestExchangeOrderSubmit(t *testing.T) { _, err = ExchangeOrderSubmit(exch, currencyPair, delimiter, orderType, orderSide, orderPrice, orderAmount, orderID, orderAsset) if err != nil && !errors.Is(err, errTestFailed) { - t.Fatal(err) + t.Error(err) } _, err = ExchangeOrderSubmit(exch, currencyPair, delimiter, orderType, orderSide, orderPrice, orderAmount, orderID, orderAsset) if err != nil { - t.Fatal(err) + t.Error(err) } _, err = ExchangeOrderSubmit(objects.TrueValue, currencyPair, delimiter, orderType, orderSide, orderPrice, orderAmount, orderID, orderAsset) if err != nil { - t.Fatal(err) + t.Error(err) } } func TestAllModuleNames(t *testing.T) { + t.Parallel() x := AllModuleNames() xType := reflect.TypeOf(x).Kind() if xType != reflect.Slice { - t.Fatalf("AllModuleNames() should return slice instead received: %v", x) + t.Errorf("AllModuleNames() should return slice instead received: %v", x) } } func TestExchangeDepositAddress(t *testing.T) { + t.Parallel() _, err := ExchangeDepositAddress() if !errors.Is(err, objects.ErrWrongNumArguments) { - t.Fatal(err) + t.Error(err) } currCode := &objects.String{Value: "BTC"} _, err = ExchangeDepositAddress(exch, currCode) if err != nil { - t.Fatal(err) + t.Error(err) } _, err = ExchangeDepositAddress(exchError, currCode) if err != nil && !errors.Is(err, errTestFailed) { - t.Fatal(err) + t.Error(err) } } func TestExchangeWithdrawCrypto(t *testing.T) { + t.Parallel() _, err := ExchangeWithdrawCrypto() if !errors.Is(err, objects.ErrWrongNumArguments) { - t.Fatal(err) + t.Error(err) } currCode := &objects.String{Value: "BTC"} @@ -252,14 +270,15 @@ func TestExchangeWithdrawCrypto(t *testing.T) { _, err = ExchangeWithdrawCrypto(exch, currCode, address, address, amount, amount, desc) if err != nil { - t.Fatal(err) + t.Error(err) } } func TestExchangeWithdrawFiat(t *testing.T) { + t.Parallel() _, err := ExchangeWithdrawFiat() if !errors.Is(err, objects.ErrWrongNumArguments) { - t.Fatal(err) + t.Error(err) } currCode := &objects.String{Value: "AUD"} @@ -268,14 +287,15 @@ func TestExchangeWithdrawFiat(t *testing.T) { bankID := &objects.String{Value: "test-bank-01"} _, err = ExchangeWithdrawFiat(exch, currCode, desc, amount, bankID) if err != nil { - t.Fatal(err) + t.Error(err) } } func TestParseInterval(t *testing.T) { + t.Parallel() v, err := parseInterval("1h") if err != nil { - t.Fatal(err) + t.Error(err) } if v != time.Hour { t.Fatalf("unexpected value return expected %v received %v", time.Hour, v) @@ -283,32 +303,32 @@ func TestParseInterval(t *testing.T) { v, err = parseInterval("1d") if err != nil { - t.Fatal(err) + t.Error(err) } if v != time.Hour*24 { - t.Fatalf("unexpected value return expected %v received %v", time.Hour*24, v) + t.Errorf("unexpected value return expected %v received %v", time.Hour*24, v) } v, err = parseInterval("3d") if err != nil { - t.Fatal(err) + t.Error(err) } if v != time.Hour*72 { - t.Fatalf("unexpected value return expected %v received %v", time.Hour*72, v) + t.Errorf("unexpected value return expected %v received %v", time.Hour*72, v) } v, err = parseInterval("1w") if err != nil { - t.Fatal(err) + t.Error(err) } if v != time.Hour*168 { - t.Fatalf("unexpected value return expected %v received %v", time.Hour*168, v) + t.Errorf("unexpected value return expected %v received %v", time.Hour*168, v) } _, err = parseInterval("6m") if err != nil { if !errors.Is(err, errInvalidInterval) { - t.Fatal(err) + t.Error(err) } } } diff --git a/gctscript/modules/gct/gct_types.go b/gctscript/modules/gct/gct_types.go index 73cdff93421..ba3a6de6589 100644 --- a/gctscript/modules/gct/gct_types.go +++ b/gctscript/modules/gct/gct_types.go @@ -9,6 +9,7 @@ import ( const ( // ErrParameterConvertFailed error to return when type conversion fails ErrParameterConvertFailed = "%v failed conversion" + ErrEmptyParameter = "received empty parameter for %v" ) var errInvalidInterval = errors.New("invalid interval") diff --git a/gctscript/modules/wrapper_types.go b/gctscript/modules/wrapper_types.go index 97b1f380e24..2fa0f5b75a7 100644 --- a/gctscript/modules/wrapper_types.go +++ b/gctscript/modules/wrapper_types.go @@ -37,7 +37,7 @@ type Exchange interface { Pairs(exch string, enabledOnly bool, item asset.Item) (*currency.Pairs, error) QueryOrder(exch, orderid string, pair currency.Pair, assetType asset.Item) (*order.Detail, error) SubmitOrder(submit *order.Submit) (*order.SubmitResponse, error) - CancelOrder(exch, orderid string) (bool, error) + CancelOrder(exch, orderid string, pair currency.Pair, item asset.Item) (bool, error) AccountInformation(exch string) (account.Holdings, error) DepositAddress(exch string, currencyCode currency.Code) (string, error) WithdrawalFiatFunds(bankAccountID string, request *withdraw.Request) (out string, err error) diff --git a/gctscript/wrappers/gct/exchange/exchange.go b/gctscript/wrappers/gct/exchange/exchange.go index 842ba90a326..de1b9201fcc 100644 --- a/gctscript/wrappers/gct/exchange/exchange.go +++ b/gctscript/wrappers/gct/exchange/exchange.go @@ -83,17 +83,12 @@ func (e Exchange) Pairs(exch string, enabledOnly bool, item asset.Item) (*curren // QueryOrder returns details of a valid exchange order func (e Exchange) QueryOrder(exch, orderID string, pair currency.Pair, assetType asset.Item) (*order.Detail, error) { - ex, err := e.GetExchange(exch) - if err != nil { - return nil, err - } - - r, err := ex.GetOrderInfo(orderID, pair, assetType) + o, err := engine.Bot.OrderManager.GetOrderInfo(exch, orderID, pair, assetType) if err != nil { return nil, err } - return &r, nil + return &o, nil } // SubmitOrder submit new order on exchange @@ -107,8 +102,8 @@ func (e Exchange) SubmitOrder(submit *order.Submit) (*order.SubmitResponse, erro } // CancelOrder wrapper to cancel order on exchange -func (e Exchange) CancelOrder(exch, orderID string) (bool, error) { - orderDetails, err := e.QueryOrder(exch, orderID, currency.Pair{}, "") +func (e Exchange) CancelOrder(exch, orderID string, cp currency.Pair, a asset.Item) (bool, error) { + orderDetails, err := e.QueryOrder(exch, orderID, cp, a) if err != nil { return false, err } @@ -119,6 +114,7 @@ func (e Exchange) CancelOrder(exch, orderID string) (bool, error) { Pair: orderDetails.Pair, Side: orderDetails.Side, AssetType: orderDetails.AssetType, + Exchange: exch, } err = engine.Bot.OrderManager.Cancel(cancel) diff --git a/gctscript/wrappers/gct/exchange/exchange_test.go b/gctscript/wrappers/gct/exchange/exchange_test.go index 7f113d59dc1..0787a1f68fe 100644 --- a/gctscript/wrappers/gct/exchange/exchange_test.go +++ b/gctscript/wrappers/gct/exchange/exchange_test.go @@ -5,10 +5,12 @@ import ( "os" "path/filepath" "testing" + "time" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/engine" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) @@ -137,6 +139,7 @@ func TestExchange_QueryOrder(t *testing.T) { if !configureExchangeKeys() { t.Skip("no exchange configured test skipped") } + t.Parallel() _, err := exchangeTest.QueryOrder(exchName, orderID, currency.Pair{}, assetType) if err != nil { t.Fatal(err) @@ -148,6 +151,7 @@ func TestExchange_SubmitOrder(t *testing.T) { t.Skip("no exchange configured test skipped") } + t.Parallel() c, err := currency.NewPairDelimiter(pairs, delimiter) if err != nil { t.Fatal(err) @@ -174,18 +178,35 @@ func TestExchange_CancelOrder(t *testing.T) { if !configureExchangeKeys() { t.Skip("no exchange configured test skipped") } - _, err := exchangeTest.CancelOrder(exchName, orderID) + t.Parallel() + cp := currency.NewPair(currency.BTC, currency.USD) + a := asset.Spot + _, err := exchangeTest.CancelOrder(exchName, orderID, cp, a) if err != nil { t.Fatal(err) } } +func TestOHLCV(t *testing.T) { + t.Parallel() + cp := currency.NewPair(currency.BTC, currency.AUD) + cp.Delimiter = currency.DashDelimiter + calvinKline, err := exchangeTest.OHLCV(exchName, cp, assetType, time.Now().Add(-time.Hour*24).UTC(), time.Now().UTC(), kline.OneHour) + if err != nil { + t.Error(err) + } + if calvinKline.Exchange != exchName { + t.Error("unexpected response") + } +} + func setupEngine() (err error) { engine.Bot, err = engine.NewFromSettings(&settings, nil) if err != nil { return err } - return engine.Bot.Start() + + return engine.Bot.LoadExchange(exchName, false, nil) } func cleanup() { diff --git a/gctscript/wrappers/gct/gctwrapper_test.go b/gctscript/wrappers/gct/gctwrapper_test.go index 118ebb0cec0..e2d41532a35 100644 --- a/gctscript/wrappers/gct/gctwrapper_test.go +++ b/gctscript/wrappers/gct/gctwrapper_test.go @@ -1,10 +1,42 @@ package gct import ( + "errors" + "log" + "os" + "path/filepath" "reflect" + "strings" "testing" + + objects "github.com/d5/tengo/v2" + "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/engine" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/gctscript/modules" + "github.com/thrasher-corp/gocryptotrader/gctscript/modules/gct" ) +func TestMain(m *testing.M) { + settings := engine.Settings{ + ConfigFile: filepath.Join("..", "..", "..", "testdata", "configtest.json"), + EnableDryRun: true, + DataDir: filepath.Join("..", "..", "..", "testdata", "gocryptotrader"), + EnableDepositAddressManager: true, + } + var err error + engine.Bot, err = engine.NewFromSettings(&settings, nil) + if err != nil { + log.Print(err) + os.Exit(1) + } + engine.Bot.LoadExchange(exch.Value, false, nil) + engine.Bot.DepositAddressManager = new(engine.DepositAddressManager) + go engine.Bot.DepositAddressManager.Sync() + modules.SetModuleWrapper(Setup()) + os.Exit(m.Run()) +} + func TestSetup(t *testing.T) { x := Setup() xType := reflect.TypeOf(x).String() @@ -12,3 +44,223 @@ func TestSetup(t *testing.T) { t.Fatalf("Setup() should return pointer to Wrapper instead received: %v", x) } } + +var ( + exch = &objects.String{ + Value: "Bitstamp", + } + exchError = &objects.String{ + Value: "error", + } + currencyPair = &objects.String{ + Value: "BTCUSD", + } + delimiter = &objects.String{ + Value: "", + } + assetType = &objects.String{ + Value: "spot", + } + orderID = &objects.String{ + Value: "1235", + } + + tv = objects.TrueValue + fv = objects.FalseValue + errTestFailed = errors.New("test failed") +) + +func TestExchangeOrderbook(t *testing.T) { + t.Parallel() + _, err := gct.ExchangeOrderbook(exch, currencyPair, delimiter, assetType) + if err != nil { + t.Fatal(err) + } + + _, err = gct.ExchangeOrderbook(exchError, currencyPair, delimiter, assetType) + if err != nil && errors.Is(err, errTestFailed) { + t.Fatal(err) + } + + _, err = gct.ExchangeOrderbook() + if !errors.Is(err, objects.ErrWrongNumArguments) { + t.Error(err) + } +} + +func TestExchangeTicker(t *testing.T) { + t.Parallel() + _, err := gct.ExchangeTicker(exch, currencyPair, delimiter, assetType) + if err != nil { + t.Fatal(err) + } + + _, err = gct.ExchangeTicker(exchError, currencyPair, delimiter, assetType) + if err != nil && errors.Is(err, errTestFailed) { + t.Fatal(err) + } + + _, err = gct.ExchangeTicker() + if !errors.Is(err, objects.ErrWrongNumArguments) { + t.Error(err) + } +} + +func TestExchangeExchanges(t *testing.T) { + t.Parallel() + _, err := gct.ExchangeExchanges(tv) + if err != nil { + t.Fatal(err) + } + + _, err = gct.ExchangeExchanges(exch) + if err != nil { + t.Fatal(err) + } + + _, err = gct.ExchangeExchanges(fv) + if err != nil { + t.Fatal(err) + } + + _, err = gct.ExchangeExchanges() + if !errors.Is(err, objects.ErrWrongNumArguments) { + t.Error(err) + } +} + +func TestExchangePairs(t *testing.T) { + t.Parallel() + _, err := gct.ExchangePairs(exch, tv, assetType) + if err != nil { + t.Fatal(err) + } + + _, err = gct.ExchangePairs(exchError, tv, assetType) + if err != nil && errors.Is(err, errTestFailed) { + t.Fatal(err) + } + + _, err = gct.ExchangePairs() + if !errors.Is(err, objects.ErrWrongNumArguments) { + t.Error(err) + } +} + +func TestAccountInfo(t *testing.T) { + t.Parallel() + _, err := gct.ExchangeAccountInfo() + if !errors.Is(err, objects.ErrWrongNumArguments) { + t.Fatal(err) + } + _, err = gct.ExchangeAccountInfo(exch) + if err != nil && + !strings.Contains(err.Error(), "unset/default API keys") { + t.Error(err) + } +} + +func TestExchangeOrderQuery(t *testing.T) { + t.Parallel() + + _, err := gct.ExchangeOrderQuery() + if !errors.Is(err, objects.ErrWrongNumArguments) { + t.Fatal(err) + } + + _, err = gct.ExchangeOrderQuery(exch, orderID) + if err != nil && err != common.ErrNotYetImplemented { + t.Error(err) + } +} + +func TestExchangeOrderCancel(t *testing.T) { + t.Parallel() + _, err := gct.ExchangeOrderCancel() + if !errors.Is(err, objects.ErrWrongNumArguments) { + t.Fatal(err) + } + _, err = gct.ExchangeOrderCancel(exch, orderID, currencyPair, assetType) + if err != nil && err != common.ErrNotYetImplemented { + t.Error(err) + } +} + +func TestExchangeOrderSubmit(t *testing.T) { + t.Parallel() + _, err := gct.ExchangeOrderSubmit() + if !errors.Is(err, objects.ErrWrongNumArguments) { + t.Fatal(err) + } + + orderSide := &objects.String{Value: "ASK"} + orderType := &objects.String{Value: "LIMIT"} + orderPrice := &objects.Float{Value: 1} + orderAmount := &objects.Float{Value: 1} + orderAsset := &objects.String{Value: asset.Spot.String()} + + _, err = gct.ExchangeOrderSubmit(exch, currencyPair, delimiter, + orderType, orderSide, orderPrice, orderAmount, orderID, orderAsset) + if err != nil && + !strings.Contains(err.Error(), "unset/default API keys") { + t.Error(err) + } +} + +func TestAllModuleNames(t *testing.T) { + t.Parallel() + x := gct.AllModuleNames() + xType := reflect.TypeOf(x).Kind() + if xType != reflect.Slice { + t.Errorf("AllModuleNames() should return slice instead received: %v", x) + } +} + +func TestExchangeDepositAddress(t *testing.T) { + t.Parallel() + _, err := gct.ExchangeDepositAddress() + if !errors.Is(err, objects.ErrWrongNumArguments) { + t.Fatal(err) + } + + currCode := &objects.String{Value: "BTC"} + _, err = gct.ExchangeDepositAddress(exch, currCode) + if err != nil && err.Error() != "deposit address store is nil" { + t.Error(err) + } +} + +func TestExchangeWithdrawCrypto(t *testing.T) { + t.Parallel() + _, err := gct.ExchangeWithdrawCrypto() + if !errors.Is(err, objects.ErrWrongNumArguments) { + t.Fatal(err) + } + + currCode := &objects.String{Value: "BTC"} + desc := &objects.String{Value: "HELLO"} + address := &objects.String{Value: "0xTHISISALEGITBTCADDRESSS"} + amount := &objects.Float{Value: 1.0} + + _, err = gct.ExchangeWithdrawCrypto(exch, currCode, address, address, amount, amount, desc) + if err != nil { + t.Error(err) + } +} + +func TestExchangeWithdrawFiat(t *testing.T) { + t.Parallel() + _, err := gct.ExchangeWithdrawFiat() + if !errors.Is(err, objects.ErrWrongNumArguments) { + t.Fatal(err) + } + + currCode := &objects.String{Value: "TEST"} + amount := &objects.Float{Value: 1.0} + desc := &objects.String{Value: "2"} + bankID := &objects.String{Value: "3!"} + _, err = gct.ExchangeWithdrawFiat(exch, currCode, desc, amount, bankID) + if err != nil && err.Error() != "exchange Bitstamp bank details not found for TEST" { + t.Error(err) + } +} diff --git a/gctscript/wrappers/validator/validator.go b/gctscript/wrappers/validator/validator.go index 75ae2481a85..28d27255791 100644 --- a/gctscript/wrappers/validator/validator.go +++ b/gctscript/wrappers/validator/validator.go @@ -167,11 +167,20 @@ func (w Wrapper) SubmitOrder(o *order.Submit) (*order.SubmitResponse, error) { } // CancelOrder validator for test execution/scripts -func (w Wrapper) CancelOrder(exch, orderid string) (bool, error) { +func (w Wrapper) CancelOrder(exch, orderid string, cp currency.Pair, a asset.Item) (bool, error) { if exch == exchError.String() { return false, errTestFailed } - return orderid != "false", nil + if orderid == "" { + return false, errTestFailed + } + if !cp.IsEmpty() && cp.IsInvalid() { + return false, errTestFailed + } + if a != "" && !a.IsValid() { + return false, errTestFailed + } + return true, nil } // AccountInformation validator for test execution/scripts diff --git a/gctscript/wrappers/validator/validator_test.go b/gctscript/wrappers/validator/validator_test.go index 34f2ed8db29..a81d897744b 100644 --- a/gctscript/wrappers/validator/validator_test.go +++ b/gctscript/wrappers/validator/validator_test.go @@ -76,15 +76,30 @@ func TestWrapper_AccountInformation(t *testing.T) { func TestWrapper_CancelOrder(t *testing.T) { t.Parallel() - - _, err := testWrapper.CancelOrder(exchName, orderID) + cp := currency.NewPair(currency.BTC, currency.USD) + _, err := testWrapper.CancelOrder(exchName, orderID, cp, assetType) if err != nil { - t.Fatal(err) + t.Error(err) + } + + _, err = testWrapper.CancelOrder(exchError.String(), orderID, cp, assetType) + if err == nil { + t.Error("expected CancelOrder to return error on invalid name") } - _, err = testWrapper.CancelOrder(exchError.String(), "") + _, err = testWrapper.CancelOrder(exchName, "", cp, assetType) if err == nil { - t.Fatal("expected CancelOrder to return error on invalid name") + t.Error("expected CancelOrder to return error on invalid name") + } + + _, err = testWrapper.CancelOrder(exchName, orderID, currency.Pair{}, assetType) + if err != nil { + t.Error(err) + } + + _, err = testWrapper.CancelOrder(exchName, orderID, cp, "") + if err != nil { + t.Error(err) } } diff --git a/gctscript/wrappers/validator/validator_types.go b/gctscript/wrappers/validator/validator_types.go index fd7277d0f1a..7c592adaf53 100644 --- a/gctscript/wrappers/validator/validator_types.go +++ b/gctscript/wrappers/validator/validator_types.go @@ -12,7 +12,7 @@ var ( IsTestExecution atomic.Value exchError = &objects.String{ - Value: "error", + Value: "", } errTestFailed = errors.New("test failed") )