From e93ee83563e6160108e0a4554fa6c27ef5dc5605 Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Wed, 17 Aug 2022 14:18:53 +1000 Subject: [PATCH] script: implementation of error insertion on return (#986) * exchanges/account: shift credentials to account package and segregate funds to keys * merge: fixes * linter: fix * Update exchanges/account/account.go Co-authored-by: Scott * glorious: nits + protection for string panic * glorious_suggestion: add method for matching keys * linter: fix tests * account: add protected method for credentials minimizing access, display full account details to rpc. * linter: spelling kweeeeeeen * accounts/portfolio: clean/check portfolio code and quickly check balances from change. Add protected method for future matching. * accounts: theres no point in pointerising everything * linter: ok pointerise this then... * exchanges: fix regression add in little notes. * glorious: nits * Update exchanges/account/credentials.go Co-authored-by: Scott * Update exchanges/account/credentials_test.go Co-authored-by: Scott * Update exchanges/account/credentials_test.go Co-authored-by: Scott * glorious: nits * gloriously: fix glorious glorious test gloriously * script: initial implementation of error insertion on return * script: make script context aware(ish) and update error handle in examples * script: add tests * script: add syntax highlighting to readme * Update gctscript/vm/vm.go Co-authored-by: Scott * Update gctscript/vm/vm.go Co-authored-by: Scott * Update gctscript/examples/exchange/account_info.gct Co-authored-by: Scott * Update gctscript/examples/exchange/cancel_order.gct Co-authored-by: Scott * Update gctscript/examples/verbose.gct Co-authored-by: Scott * Update gctscript/modules/gct/gct_test.go Co-authored-by: Scott * glorious: nits * rm: bros * scripts: handle errors in examples when they are going to use the data after fetching * linter: fix rides again * SCOTT_SPELL_CHECK_LINTER: fix * gctscript: fix tests * glorious: niiiiiiiiiiiiits * scriptmodules/gct: standardize runtime errors * Update gctscript/modules/gct/exchange.go Co-authored-by: Scott * Update gctscript/modules/gct/exchange.go Co-authored-by: Scott * Update gctscript/modules/gct/exchange.go Co-authored-by: Scott * Update gctscript/modules/gct/exchange.go Co-authored-by: Scott * Update gctscript/modules/gct/gct.go Co-authored-by: Scott * Update gctscript/modules/gct/gct.go Co-authored-by: Scott * Update gctscript/modules/gct/gct.go Co-authored-by: Scott * Update gctscript/modules/gct/gct.go Co-authored-by: Scott * Update gctscript/modules/gct/gct.go Co-authored-by: Scott * Update gctscript/modules/gct/gct.go Co-authored-by: Scott * Update gctscript/modules/gct/gct.go Co-authored-by: Scott * glorious: nits/reverts * go mod: tidy Co-authored-by: Ryan O'Hara-Reid Co-authored-by: Scott --- dispatch/dispatch.go | 2 +- engine/engine.go | 2 +- engine/engine_test.go | 4 +- exchanges/account/credentials.go | 6 +- gctscript/README.md | 4 + gctscript/examples/account.gct | 31 ++ gctscript/examples/csv.gct | 15 +- gctscript/examples/exchange/account_info.gct | 10 +- gctscript/examples/exchange/cancel_order.gct | 7 +- .../examples/exchange/deposit_address.gct | 3 + gctscript/examples/exchange/ohlcv.gct | 7 +- gctscript/examples/exchange/orderbook.gct | 7 +- gctscript/examples/exchange/pairs.gct | 3 + gctscript/examples/exchange/query_order.gct | 7 +- gctscript/examples/exchange/submit_order.gct | 7 +- gctscript/examples/exchange/ticker.gct | 7 +- .../examples/exchange/withdraw_crypto.gct | 10 +- .../{withdraw.gct => withdraw_fiat.gct} | 9 +- gctscript/examples/exit.gct | 6 +- gctscript/examples/ta/atr.gct | 9 +- gctscript/examples/ta/bbands.gct | 9 +- gctscript/examples/ta/correlation.gct | 16 +- gctscript/examples/ta/ema.gct | 10 +- gctscript/examples/ta/macd.gct | 10 +- gctscript/examples/ta/mfi.gct | 9 +- gctscript/examples/ta/obv.gct | 9 +- gctscript/examples/ta/rsi.gct | 9 +- gctscript/examples/verbose.gct | 27 + gctscript/modules/gct/common.go | 64 ++- gctscript/modules/gct/errors.go | 39 ++ gctscript/modules/gct/errors_test.go | 38 ++ gctscript/modules/gct/exchange.go | 487 ++++++++++-------- gctscript/modules/gct/gct.go | 200 +++++++ gctscript/modules/gct/gct_test.go | 221 +++++++- gctscript/modules/gct/gct_types.go | 10 +- gctscript/vm/gctscript.go | 5 +- gctscript/vm/manager.go | 23 +- gctscript/vm/vm.go | 71 ++- gctscript/vm/vm_types.go | 6 +- gctscript/wrappers/gct/gctwrapper_test.go | 60 ++- go.mod | 1 + go.sum | 2 - 42 files changed, 1110 insertions(+), 372 deletions(-) create mode 100644 gctscript/examples/account.gct rename gctscript/examples/exchange/{withdraw.gct => withdraw_fiat.gct} (54%) create mode 100644 gctscript/examples/verbose.gct create mode 100644 gctscript/modules/gct/errors.go create mode 100644 gctscript/modules/gct/errors_test.go diff --git a/dispatch/dispatch.go b/dispatch/dispatch.go index d3abd7e6..d0323a85 100644 --- a/dispatch/dispatch.go +++ b/dispatch/dispatch.go @@ -67,7 +67,7 @@ func IsRunning() bool { return dispatcher.isRunning() } -// start compares atomic running value, sets defaults, overides with +// start compares atomic running value, sets defaults, overrides with // configuration, then spawns workers func (d *Dispatcher) start(workers, channelCapacity int) error { if d == nil { diff --git a/engine/engine.go b/engine/engine.go index b8720162..5579bf0c 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -150,7 +150,7 @@ func loadConfigWithSettings(settings *Settings, flagSet map[string]bool) (*confi // FlagSet defines set flags from command line args for comparison methods type FlagSet map[string]bool -// WithBool checks the supplied flag. If set it will overide the config boolean +// WithBool checks the supplied flag. If set it will override the config boolean // value as a command line takes precedence. If not set will fall back to config // options. func (f FlagSet) WithBool(key string, flagValue *bool, configValue bool) { diff --git a/engine/engine_test.go b/engine/engine_test.go index 2cad97c1..145468c9 100644 --- a/engine/engine_test.go +++ b/engine/engine_test.go @@ -271,7 +271,7 @@ func TestFlagSetWith(t *testing.T) { flags["IS SET"] = true isRunning = true - // Flag set true which will overide config + // Flag set true which will override config flags.WithBool("IS SET", &isRunning, true) if !isRunning { t.Fatalf("received: '%v' but expected: '%v'", isRunning, true) @@ -283,7 +283,7 @@ func TestFlagSetWith(t *testing.T) { flags["IS SET"] = true isRunning = false - // Flag set false which will overide config + // Flag set false which will override config flags.WithBool("IS SET", &isRunning, true) if isRunning { t.Fatalf("received: '%v' but expected: '%v'", isRunning, false) diff --git a/exchanges/account/credentials.go b/exchanges/account/credentials.go index 5578db96..b68067e6 100644 --- a/exchanges/account/credentials.go +++ b/exchanges/account/credentials.go @@ -190,7 +190,7 @@ func ParseCredentialsMetadata(ctx context.Context, md metadata.MD) (context.Cont } if ctxCreds.IsEmpty() && subAccountHere != "" { // This will override default sub account details if needed. - return deploySubAccountOverrideToContext(ctx, subAccountHere), nil + return DeploySubAccountOverrideToContext(ctx, subAccountHere), nil } // merge sub account to main context credentials ctxCreds.SubAccount = subAccountHere @@ -204,9 +204,9 @@ func DeployCredentialsToContext(ctx context.Context, creds *Credentials) context return context.WithValue(ctx, flag, store) } -// deploySubAccountOverrideToContext sets subaccount as override to credentials +// DeploySubAccountOverrideToContext sets subaccount as override to credentials // as a separate flag. -func deploySubAccountOverrideToContext(ctx context.Context, subAccount string) context.Context { +func DeploySubAccountOverrideToContext(ctx context.Context, subAccount string) context.Context { return context.WithValue(ctx, ContextSubAccountFlag, subAccount) } diff --git a/gctscript/README.md b/gctscript/README.md index 6719767d..1e47fc8d 100644 --- a/gctscript/README.md +++ b/gctscript/README.md @@ -39,6 +39,10 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader To Enable database logging support you must have an active migrated database by following the [database setup guide](../database/README.md) +##### Syntax Highlighting + +To enable syntax highlighting for vscode download extension [graphman65/vscode-tengo](https://github.com/graphman65/vscode-tengo/) then add `".gct"` to vscode-tengo package.json [settings](https://github.com/graphman65/vscode-tengo/blob/master/package.json#L27) to enable highlighting of our files. + ##### Configuration The gctscript configuration struct is currently: diff --git a/gctscript/examples/account.gct b/gctscript/examples/account.gct new file mode 100644 index 00000000..542e6b4a --- /dev/null +++ b/gctscript/examples/account.gct @@ -0,0 +1,31 @@ +global := import("global") +exch := import("exchange") +fmt := import("fmt") + +load := func() { + // 'ctx' is already defined when we construct our bytecode from file. + // It contains script ID and shortname of file as save details to default + // script output directory. + + // Set account func allows the setting of account details via script + // which can then be passed into auth functions to specifically target + // different subaccounts while trading or retrieving fund details. + // Basic required implementation below: + ctx = global.set_account(ctx, "api_key_str", "api_secret_str") + + // Full implementation: + // ctx = global.set_account(ctx, "api_key_str", "api_secret_str", "sub_account_str", "client_Id_str", "PEM_key_str", "OTP_Str") + + // Set sub account func allows the setting of just the individual sub + // account details while utilizing the configured config.json apikeys. + // ctx = global.set_sub_account(ctx, "sub_account_str") + + info := exch.accountinfo(ctx, "ftx", "spot") + if is_error(info) { + // handle error + } + + fmt.println(info) +} + +load() \ No newline at end of file diff --git a/gctscript/examples/csv.gct b/gctscript/examples/csv.gct index dcb488b4..50b8d0a9 100644 --- a/gctscript/examples/csv.gct +++ b/gctscript/examples/csv.gct @@ -1,5 +1,6 @@ exch := import("exchange") t := import("times") +fmt := import("fmt") // Import all the indicators you want atr := import("indicator/atr") sma := import("indicator/sma") @@ -12,7 +13,12 @@ load := func() { end := t.add_date(start, 0, 6 , 0) // This fetches the ohlcv - ohlcvData := exch.ohlcv("binance", "BTC-USDT", "-", "SPOT", start, end, "1d") + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + ohlcvData := exch.ohlcv(ctx, "binance", "BTC-USDT", "-", "SPOT", start, end, "1d") + if is_error(ohlcvData) { + fmt.println(ohlcvData) + return + } // construct ta values avgtr := atr.calculate(ohlcvData.candles, 14) @@ -22,7 +28,14 @@ load := func() { // 'ctx' is already defined when we construct our bytecode from file. // It contains script ID and shortname of file as save details to default // script output directory. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct common.writeascsv(ctx, ohlcvData, avgtr, simma, expma) + + // A custom filename can also be declared when using a string instead of the + // context variable like below. This will continue to save in the output + // folder of the scripts file and will look something like this + // 'super_cool_filename-1658465844999067400.csv'. + // common.writeascsv("super_cool_filename", ohlcvData, avgtr, simma, expma) } load() \ No newline at end of file diff --git a/gctscript/examples/exchange/account_info.gct b/gctscript/examples/exchange/account_info.gct index 47178cfd..b3e979ae 100644 --- a/gctscript/examples/exchange/account_info.gct +++ b/gctscript/examples/exchange/account_info.gct @@ -4,8 +4,14 @@ fmt := import("fmt") exch := import("exchange") load := func() { - // retrieve account information from exchange and store in info variable - info := exch.accountinfo("BTC Markets", "spot") + // Retrieve account information from exchange and store in info variable + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + // for more details. + info := exch.accountinfo(ctx, "BTC Markets", "spot") + if is_error(info) { + // handle error + } // print out info fmt.println(info) } diff --git a/gctscript/examples/exchange/cancel_order.gct b/gctscript/examples/exchange/cancel_order.gct index 18d5d491..47e18980 100644 --- a/gctscript/examples/exchange/cancel_order.gct +++ b/gctscript/examples/exchange/cancel_order.gct @@ -2,7 +2,12 @@ fmt := import("fmt") exch := import("exchange") load := func() { - info := exch.ordercancel("binance","13371337", "btc-usdt", "spot") + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + info := exch.ordercancel(ctx, "binance","13371337", "btc-usdt", "spot") + if is_error(info) { + // handle error + } fmt.println(info) } diff --git a/gctscript/examples/exchange/deposit_address.gct b/gctscript/examples/exchange/deposit_address.gct index d038de6b..618bcdd4 100644 --- a/gctscript/examples/exchange/deposit_address.gct +++ b/gctscript/examples/exchange/deposit_address.gct @@ -3,6 +3,9 @@ exch := import("exchange") load := func() { info := exch.depositaddress("BTC Markets", "BTC", "") + if is_error(info) { + // handle error + } fmt.println(info) } diff --git a/gctscript/examples/exchange/ohlcv.gct b/gctscript/examples/exchange/ohlcv.gct index ff0cabfd..281d5954 100644 --- a/gctscript/examples/exchange/ohlcv.gct +++ b/gctscript/examples/exchange/ohlcv.gct @@ -4,7 +4,12 @@ t := import("times") load := func() { start := t.add(t.now(), -t.hour*24) - ohlcvData := exch.ohlcv("coinbasepro", "BTC-USD", "-", "SPOT", start, t.now(), "1h") + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + ohlcvData := exch.ohlcv(ctx, "coinbasepro", "BTC-USD", "-", "SPOT", start, t.now(), "1h") + if is_error(ohlcvData) { + // handle error + } fmt.println(ohlcvData) } diff --git a/gctscript/examples/exchange/orderbook.gct b/gctscript/examples/exchange/orderbook.gct index 4424c110..a0171e52 100644 --- a/gctscript/examples/exchange/orderbook.gct +++ b/gctscript/examples/exchange/orderbook.gct @@ -5,7 +5,12 @@ name := "run" timer := "5s" load := func() { - tx := exch.orderbook("btc markets", "btc-aud", "-", "spot") + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + tx := exch.orderbook(ctx, "btc markets", "btc-aud", "-", "spot") + if is_error(tx) { + // handle error + } fmt.println(tx) } diff --git a/gctscript/examples/exchange/pairs.gct b/gctscript/examples/exchange/pairs.gct index f5edb576..e483f6e5 100644 --- a/gctscript/examples/exchange/pairs.gct +++ b/gctscript/examples/exchange/pairs.gct @@ -3,6 +3,9 @@ exch := import("exchange") load := func() { info := exch.pairs("BTC Markets", false, "SPOT") + if is_error(info) { + // handle error + } fmt.println(info) } diff --git a/gctscript/examples/exchange/query_order.gct b/gctscript/examples/exchange/query_order.gct index 6cd9532a..1b290159 100644 --- a/gctscript/examples/exchange/query_order.gct +++ b/gctscript/examples/exchange/query_order.gct @@ -2,7 +2,12 @@ fmt := import("fmt") exch := import("exchange") load := func() { - info := exch.orderquery("binance", "4491600698", "BTC-USDT", "spot") + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + info := exch.orderquery(ctx, "binance", "4491600698", "BTC-USDT", "spot") + if is_error(info) { + // handle error + } fmt.println(info) } diff --git a/gctscript/examples/exchange/submit_order.gct b/gctscript/examples/exchange/submit_order.gct index f8665e97..e63e92e8 100644 --- a/gctscript/examples/exchange/submit_order.gct +++ b/gctscript/examples/exchange/submit_order.gct @@ -2,7 +2,12 @@ fmt := import("fmt") exch := import("exchange") load := func() { - info := exch.ordersubmit("BTC Markets","BTC-AUD","-","LIMIT","SELL",1000000, 1,"", "spot") + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + info := exch.ordersubmit(ctx, "BTC Markets","BTC-AUD","-","LIMIT","SELL",1000000, 1,"", "spot") + if is_error(info) { + // handle error + } fmt.println(info) } diff --git a/gctscript/examples/exchange/ticker.gct b/gctscript/examples/exchange/ticker.gct index aeb89f90..18f167da 100644 --- a/gctscript/examples/exchange/ticker.gct +++ b/gctscript/examples/exchange/ticker.gct @@ -5,7 +5,12 @@ name := "run" timer := "5s" load := func() { - tx := exch.ticker("btc markets", "btc-aud", "-", "spot") + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + tx := exch.ticker(ctx, "btc markets", "btc-aud", "-", "spot") + if is_error(tx) { + // handle error + } fmt.println(tx) } diff --git a/gctscript/examples/exchange/withdraw_crypto.gct b/gctscript/examples/exchange/withdraw_crypto.gct index 3f06ffb5..fcb616a9 100644 --- a/gctscript/examples/exchange/withdraw_crypto.gct +++ b/gctscript/examples/exchange/withdraw_crypto.gct @@ -13,10 +13,14 @@ load := func() { // 4: address tag // 5: amount // 6: fee amount - // 7: trade password - // 8: OTP + // 7: description - info := exch.withdrawcrypto("BTC Markets","BTC", "1234562362", "1231", 1.0, 0.0, "","" ) + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + info := exch.withdrawcrypto(ctx, "BTC Markets", "BTC", "1234562362", "1231", 1.0, 0.0, "") + if is_error(info) { + // handle error + } // print out info fmt.println(info) } diff --git a/gctscript/examples/exchange/withdraw.gct b/gctscript/examples/exchange/withdraw_fiat.gct similarity index 54% rename from gctscript/examples/exchange/withdraw.gct rename to gctscript/examples/exchange/withdraw_fiat.gct index 0bef238e..d22c9bb1 100644 --- a/gctscript/examples/exchange/withdraw.gct +++ b/gctscript/examples/exchange/withdraw_fiat.gct @@ -16,8 +16,13 @@ load := func() { // 7: trade password // 8: OTP - // submit request to withdraw funds - info := exch.withdrawfiat("BTC Markets", "AUD", "hello", 1, "-") + // Submit request to withdraw funds + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + info := exch.withdrawfiat(ctx, "BTC Markets", "AUD", "hello", 1, "-") + if is_error(info) { + // handle error + } // print out info fmt.println(info) } diff --git a/gctscript/examples/exit.gct b/gctscript/examples/exit.gct index 11fad5a4..64cb230c 100644 --- a/gctscript/examples/exit.gct +++ b/gctscript/examples/exit.gct @@ -1,14 +1,16 @@ fmt := import("fmt") +// 'timer' is a GCT key word that is captured at compilation and used to execute +// this script task every defined duration. timer := "5s" exit := func() { - timer = 0 + timer = "0s" // This will reset the timer to zero and shutdown the script. } load := func() { for x := 0 ; x < 20; x++ { - fmt.printf("Hello %v", x) + fmt.printf("Hello %v\n", x) } exit() } diff --git a/gctscript/examples/ta/atr.gct b/gctscript/examples/ta/atr.gct index c421a869..cc045195 100644 --- a/gctscript/examples/ta/atr.gct +++ b/gctscript/examples/ta/atr.gct @@ -6,7 +6,14 @@ atr := import("indicator/atr") load := func() { start := t.date(2017, 8 , 17, 0 , 0 , 0, 0) end := t.add_date(start, 0, 6 , 0) - ohlcvData := exch.ohlcv("binance", "BTC-USDT", "-", "SPOT", start, end, "1d") + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + ohlcvData := exch.ohlcv(ctx, "binance", "BTC-USDT", "-", "SPOT", start, end, "1d") + if is_error(ohlcvData) { + // handle error + fmt.println(ohlcvData) + return + } ret := atr.calculate(ohlcvData.candles, 14) fmt.println(ret) diff --git a/gctscript/examples/ta/bbands.gct b/gctscript/examples/ta/bbands.gct index 230521c6..58d109e3 100644 --- a/gctscript/examples/ta/bbands.gct +++ b/gctscript/examples/ta/bbands.gct @@ -6,7 +6,14 @@ bbands := import("indicator/bbands") load := func() { start := t.date(2017, 8 , 17 , 0 , 0 , 0, 0) end := t.add_date(start, 0, 6 , 0) - ohlcvData := exch.ohlcv("binance", "BTC-USDT", "-", "SPOT", start, end, "1d") + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + ohlcvData := exch.ohlcv(ctx, "binance", "BTC-USDT", "-", "SPOT", start, end, "1d") + if is_error(ohlcvData) { + // handle error + fmt.println(ohlcvData) + return + } ret := bbands.calculate("close", ohlcvData.candles, 20, 2.0, 2.0, "sma") fmt.println(ret) diff --git a/gctscript/examples/ta/correlation.gct b/gctscript/examples/ta/correlation.gct index a2dce4f7..68ca04bf 100644 --- a/gctscript/examples/ta/correlation.gct +++ b/gctscript/examples/ta/correlation.gct @@ -6,8 +6,20 @@ cc := import("indicator/correlationcoefficient") load := func() { start := t.date(2017, 8 , 17 , 0 , 0 , 0, 0) end := t.add_date(start, 0, 6 , 0) - ohlcvDataBTC := exch.ohlcv("binance", "BTC-USDT", "-", "SPOT", start, end, "1d") - ohlcvDataETH := exch.ohlcv("binance", "ETH-USDT", "-", "SPOT", start, end, "1d") + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + ohlcvDataBTC := exch.ohlcv(ctx, "binance", "BTC-USDT", "-", "SPOT", start, end, "1d") + if is_error(ohlcvDataBTC) { + // handle error + fmt.println(ohlcvDataBTC) + return + } + ohlcvDataETH := exch.ohlcv(ctx, "binance", "ETH-USDT", "-", "SPOT", start, end, "1d") + if is_error(ohlcvDataETH) { + // handle error + fmt.println(ohlcvDataETH) + return + } ret := cc.calculate(ohlcvDataBTC.candles, ohlcvDataETH.candles, 20) fmt.println(ret) } diff --git a/gctscript/examples/ta/ema.gct b/gctscript/examples/ta/ema.gct index d719e2e7..43e4ed47 100644 --- a/gctscript/examples/ta/ema.gct +++ b/gctscript/examples/ta/ema.gct @@ -7,8 +7,14 @@ ema := import("indicator/ema") load := func() { start := t.date(2017, 8 , 17 , 0 , 0 , 0, 0) end := t.add_date(start, 0, 6 , 0) - ohlcvData := exch.ohlcv("binance", "BTC-USDT", "-", "SPOT", start, end, "1d") - + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + ohlcvData := exch.ohlcv(ctx, "binance", "BTC-USDT", "-", "SPOT", start, end, "1d") + if is_error(ohlcvData) { + // handle error + fmt.println(ohlcvData) + return + } ret := ema.calculate(ohlcvData.candles, 9) fmt.println(ret) diff --git a/gctscript/examples/ta/macd.gct b/gctscript/examples/ta/macd.gct index 41e456c2..5896ab1b 100644 --- a/gctscript/examples/ta/macd.gct +++ b/gctscript/examples/ta/macd.gct @@ -6,8 +6,14 @@ macd := import("indicator/macd") load := func() { start := t.date(2017, 8 , 17 , 0 , 0 , 0, 0) end := t.add_date(start, 0, 6 , 0) - ohlcvData := exch.ohlcv("binance", "BTC-USDT", "-", "SPOT", start, end, "1d") - + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + ohlcvData := exch.ohlcv(ctx, "binance", "BTC-USDT", "-", "SPOT", start, end, "1d") + if is_error(ohlcvData) { + // handle error + fmt.println(ohlcvData) + return + } ret := macd.calculate(ohlcvData.candles, 12, 26, 9) fmt.println(ret) } diff --git a/gctscript/examples/ta/mfi.gct b/gctscript/examples/ta/mfi.gct index 80e364f6..aa92df96 100644 --- a/gctscript/examples/ta/mfi.gct +++ b/gctscript/examples/ta/mfi.gct @@ -6,7 +6,14 @@ mfi := import("indicator/mfi") load := func() { start := t.date(2017, 8 , 17 , 0 , 0 , 0, 0) end := t.add_date(start, 0, 6 , 0) - ohlcvData := exch.ohlcv("binance", "BTC-USDT", "-", "SPOT", start, end, "1d") + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + ohlcvData := exch.ohlcv(ctx, "binance", "BTC-USDT", "-", "SPOT", start, end, "1d") + if is_error(ohlcvData) { + // handle error + fmt.println(ohlcvData) + return + } ret := mfi.calculate(ohlcvData.candles, 14) fmt.println(ret) diff --git a/gctscript/examples/ta/obv.gct b/gctscript/examples/ta/obv.gct index d6bc5989..c01712eb 100644 --- a/gctscript/examples/ta/obv.gct +++ b/gctscript/examples/ta/obv.gct @@ -6,7 +6,14 @@ obv := import("indicator/obv") load := func() { start := t.date(2017, 8 , 17 , 0 , 0 , 0, 0) end := t.add_date(start, 0, 6 , 0) - ohlcvData := exch.ohlcv("binance", "BTC-USDT", "-", "SPOT", start, end, "1d") + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + ohlcvData := exch.ohlcv(ctx, "binance", "BTC-USDT", "-", "SPOT", start, end, "1d") + if is_error(ohlcvData) { + // handle error + fmt.println(ohlcvData) + return + } ret := obv.calculate(ohlcvData.candles) fmt.println(ret) diff --git a/gctscript/examples/ta/rsi.gct b/gctscript/examples/ta/rsi.gct index dad5affe..98def285 100644 --- a/gctscript/examples/ta/rsi.gct +++ b/gctscript/examples/ta/rsi.gct @@ -6,7 +6,14 @@ rsi := import("indicator/rsi") load := func() { start := t.date(2017, 8 , 17 , 0 , 0 , 0, 0) end := t.add_date(start, 0, 6 , 0) - ohlcvData := exch.ohlcv("binance", "BTC-USDT", "-", "SPOT", start, end, "1d") + // 'ctx' is already defined when we construct our bytecode from file. + // To add debugging information to the request, see verbose.gct. To add account credentials, see account.gct + ohlcvData := exch.ohlcv(ctx, "binance", "BTC-USDT", "-", "SPOT", start, end, "1d") + if is_error(ohlcvData) { + // handle error + fmt.println(ohlcvData) + return + } ret := rsi.calculate(ohlcvData.candles, 14) fmt.println(ret) diff --git a/gctscript/examples/verbose.gct b/gctscript/examples/verbose.gct new file mode 100644 index 00000000..ccdd3dbf --- /dev/null +++ b/gctscript/examples/verbose.gct @@ -0,0 +1,27 @@ +global := import("global") +exch := import("exchange") +fmt := import("fmt") + +load := func() { + // 'ctx' is already defined when we construct our bytecode from file. + // It contains script ID and shortname of file as save details to default + // script output directory. + + // Set verbosity func allows the setting of rest request verbosity to + // specifically log out this scripts interaction with any http outbound + // request for debugging purposes. + ctx = global.set_verbose(ctx) + + // Any verbose logs will be output according to logger settings defined in the config + // NOTE: Get account info is cached and updated by another worker thread + // therefore calling this script multiple times, if data has already been + // fetched request verbosity will be limited. + info := exch.accountinfo(ctx, "ftx", "spot") + if is_error(info) { + // handle error + } + + fmt.println(info) +} + +load() \ No newline at end of file diff --git a/gctscript/modules/gct/common.go b/gctscript/modules/gct/common.go index c336b3d4..8a40516e 100644 --- a/gctscript/modules/gct/common.go +++ b/gctscript/modules/gct/common.go @@ -59,6 +59,26 @@ func WriteAsCSV(args ...objects.Object) (objects.Object, error) { case indicators.OHLCV: temp, err = convertOHLCV(args[i]) front = true + case "scriptContext": + if target != "" { + return nil, fmt.Errorf("filename already set, extra string %v cannot be processed", args[i]) + } + scriptCtx, ok := objects.ToInterface(args[i]).(*Context) + if !ok { + return nil, common.GetAssertError("*gct.Context", args[i]) + } + + scriptDetails, ok := scriptCtx.Value["script"] + if !ok { + return nil, errors.New("no script details") + } + + target, ok = objects.ToString(scriptDetails) + if !ok { + return nil, errors.New("failed to convert incoming output to string") + } + + target = processTarget(target) case "string": if target != "" { return nil, fmt.Errorf("filename already set, extra string %v cannot be processed", args[i]) @@ -73,26 +93,7 @@ func WriteAsCSV(args ...objects.Object) (objects.Object, error) { return nil, errors.New("script context details not specified") } - // Removes file transversal - target = filepath.Base(target) - - // checks to see if file is context defined, if not it will allow - // a client defined filename and append a date, forces the use of - // .csv file extension - switch { - case filepath.Ext(target) != ".csv" && strings.Contains(target, common.GctExt): - target += ".csv" - case filepath.Ext(target) == ".csv": - s := strings.Split(target, ".") - if len(s) == 2 { - target = s[0] + "-" + strconv.FormatInt(time.Now().UnixNano(), 10) + ".csv" - } - default: - target += "-" + strconv.FormatInt(time.Now().UnixNano(), 10) + ".csv" - } - - target = filepath.Join(OutputDir, target) - + target = processTarget(target) default: err = fmt.Errorf("%s type is not handled", args[i].TypeName()) } @@ -130,7 +131,7 @@ func WriteAsCSV(args ...objects.Object) (objects.Object, error) { return nil, err } - log.Infof(log.GCTScriptMgr, + log.Debugf(log.GCTScriptMgr, "CSV file successfully saved to: %s", target) return nil, nil @@ -484,3 +485,24 @@ func convertOHLCV(a objects.Object) ([][]string, error) { } return bucket, nil } + +func processTarget(target string) string { + // Removes file transversal + target = filepath.Base(target) + + // checks to see if file is context defined, if not it will allow + // a client defined filename and append a date, forces the use of + // .csv file extension + switch { + case filepath.Ext(target) != ".csv" && strings.Contains(target, common.GctExt): + target += ".csv" + case filepath.Ext(target) == ".csv": + s := strings.Split(target, ".") + if len(s) == 2 { + target = s[0] + "-" + strconv.FormatInt(time.Now().UnixNano(), 10) + ".csv" + } + default: + target += "-" + strconv.FormatInt(time.Now().UnixNano(), 10) + ".csv" + } + return filepath.Join(OutputDir, target) +} diff --git a/gctscript/modules/gct/errors.go b/gctscript/modules/gct/errors.go new file mode 100644 index 00000000..14ef8f3c --- /dev/null +++ b/gctscript/modules/gct/errors.go @@ -0,0 +1,39 @@ +package gct + +import ( + "errors" + "fmt" + + objects "github.com/d5/tengo/v2" + "github.com/thrasher-corp/gocryptotrader/common" +) + +const standardFormatting = "%s" + +var ( + errFormatStringIsEmpty = errors.New("format string is empty") + errNoArguments = errors.New("no arguments for error response") +) + +// errorResponsef is a helper function to apply error details to a return object +// for better script side error handling +func errorResponsef(format string, a ...interface{}) (objects.Object, error) { + if format == "" { + return nil, fmt.Errorf("cannot generate tengo error object %w", errFormatStringIsEmpty) + } + + if len(a) == 0 { + return nil, fmt.Errorf("cannot generate tengo error object %w", errNoArguments) + } + + return &objects.Error{ + Value: &objects.String{Value: fmt.Sprintf(format, a...)}, + }, nil +} + +func constructRuntimeError(argPosition int, funcName, expectedType string, unexpectedData interface{}) error { + return fmt.Errorf("function [%s] argument position [%d] - %w", + funcName, + argPosition, + common.GetAssertError(expectedType, unexpectedData)) +} diff --git a/gctscript/modules/gct/errors_test.go b/gctscript/modules/gct/errors_test.go new file mode 100644 index 00000000..69d698b2 --- /dev/null +++ b/gctscript/modules/gct/errors_test.go @@ -0,0 +1,38 @@ +package gct + +import ( + "errors" + "testing" + + "github.com/thrasher-corp/gocryptotrader/common" +) + +func TestErrorResponse(t *testing.T) { + t.Parallel() + _, err := errorResponsef("") + if !errors.Is(err, errFormatStringIsEmpty) { + t.Fatalf("received: '%v' but expected: '%v'", err, errFormatStringIsEmpty) + } + + _, err = errorResponsef("--") + if !errors.Is(err, errNoArguments) { + t.Fatalf("received: '%v' but expected: '%v'", err, errNoArguments) + } + + errResp, err := errorResponsef("error %s", "hello") + if err != nil { + t.Fatal(err) + } + + if errResp.String() != `error: "error hello"` { + t.Fatalf("received: %v but expected: %v", errResp.String(), `error: "error hello"`) + } +} + +func TestConstructRuntimeError(t *testing.T) { + t.Parallel() + err := constructRuntimeError(0, "", "", nil) + if !errors.Is(err, common.ErrTypeAssertFailure) { + t.Fatalf("receieved: '%v' but expected: '%v'", err, common.ErrTypeAssertFailure) + } +} diff --git a/gctscript/modules/gct/exchange.go b/gctscript/modules/gct/exchange.go index fed4b6d6..e9ecb3fb 100644 --- a/gctscript/modules/gct/exchange.go +++ b/gctscript/modules/gct/exchange.go @@ -1,7 +1,6 @@ package gct import ( - "context" "fmt" "time" @@ -16,74 +15,93 @@ import ( "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) +const ( + orderbookFunc = "orderbook" + tickerFunc = "ticker" + exchangesFunc = "exchanges" + pairsFunc = "pairs" + accountInfoFunc = "accountinfo" + depositAddressFunc = "depositaddress" + orderQueryFunc = "orderquery" + orderCancelFunc = "ordercancel" + orderSubmitFunc = "ordersubmit" + withdrawCryptoFunc = "withdrawcrypto" + withdrawFiatFunc = "withdrawfiat" + ohlcvFunc = "ohlcv" +) + var exchangeModule = map[string]objects.Object{ - "orderbook": &objects.UserFunction{Name: "orderbook", Value: ExchangeOrderbook}, - "ticker": &objects.UserFunction{Name: "ticker", Value: ExchangeTicker}, - "exchanges": &objects.UserFunction{Name: "exchanges", Value: ExchangeExchanges}, - "pairs": &objects.UserFunction{Name: "pairs", Value: ExchangePairs}, - "accountinfo": &objects.UserFunction{Name: "accountinfo", Value: ExchangeAccountInfo}, - "depositaddress": &objects.UserFunction{Name: "depositaddress", Value: ExchangeDepositAddress}, - "orderquery": &objects.UserFunction{Name: "orderquery", Value: ExchangeOrderQuery}, - "ordercancel": &objects.UserFunction{Name: "ordercancel", Value: ExchangeOrderCancel}, - "ordersubmit": &objects.UserFunction{Name: "ordersubmit", Value: ExchangeOrderSubmit}, - "withdrawcrypto": &objects.UserFunction{Name: "withdrawcrypto", Value: ExchangeWithdrawCrypto}, - "withdrawfiat": &objects.UserFunction{Name: "withdrawfiat", Value: ExchangeWithdrawFiat}, - "ohlcv": &objects.UserFunction{Name: "ohlcv", Value: exchangeOHLCV}, + orderbookFunc: &objects.UserFunction{Name: orderbookFunc, Value: ExchangeOrderbook}, + tickerFunc: &objects.UserFunction{Name: tickerFunc, Value: ExchangeTicker}, + exchangesFunc: &objects.UserFunction{Name: exchangesFunc, Value: ExchangeExchanges}, + pairsFunc: &objects.UserFunction{Name: pairsFunc, Value: ExchangePairs}, + accountInfoFunc: &objects.UserFunction{Name: accountInfoFunc, Value: ExchangeAccountInfo}, + depositAddressFunc: &objects.UserFunction{Name: depositAddressFunc, Value: ExchangeDepositAddress}, + orderQueryFunc: &objects.UserFunction{Name: orderQueryFunc, Value: ExchangeOrderQuery}, + orderCancelFunc: &objects.UserFunction{Name: orderCancelFunc, Value: ExchangeOrderCancel}, + orderSubmitFunc: &objects.UserFunction{Name: orderSubmitFunc, Value: ExchangeOrderSubmit}, + withdrawCryptoFunc: &objects.UserFunction{Name: withdrawCryptoFunc, Value: ExchangeWithdrawCrypto}, + withdrawFiatFunc: &objects.UserFunction{Name: withdrawFiatFunc, Value: ExchangeWithdrawFiat}, + ohlcvFunc: &objects.UserFunction{Name: ohlcvFunc, Value: exchangeOHLCV}, } // ExchangeOrderbook returns orderbook for requested exchange & currencypair func ExchangeOrderbook(args ...objects.Object) (objects.Object, error) { - if len(args) != 4 { + if len(args) != 5 { return nil, objects.ErrWrongNumArguments } - exchangeName, ok := objects.ToString(args[0]) + scriptCtx, ok := objects.ToInterface(args[0]).(*Context) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName) + return nil, constructRuntimeError(1, orderbookFunc, "*gct.Context", args[0]) } - currencyPair, ok := objects.ToString(args[1]) + exchangeName, ok := objects.ToString(args[1]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, currencyPair) + return nil, constructRuntimeError(2, orderbookFunc, "string", args[1]) } - delimiter, ok := objects.ToString(args[2]) + currencyPair, ok := objects.ToString(args[2]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, delimiter) + return nil, constructRuntimeError(3, orderbookFunc, "string", args[2]) } - assetTypeParam, ok := objects.ToString(args[3]) + delimiter, ok := objects.ToString(args[3]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, assetTypeParam) + return nil, constructRuntimeError(4, orderbookFunc, "string", args[3]) + } + assetTypeParam, ok := objects.ToString(args[4]) + if !ok { + return nil, constructRuntimeError(5, orderbookFunc, "string", args[4]) } pair, err := currency.NewPairDelimiter(currencyPair, delimiter) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } assetType, err := asset.New(assetTypeParam) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } - ob, err := wrappers.GetWrapper(). - Orderbook(context.TODO(), exchangeName, pair, assetType) + ctx := processScriptContext(scriptCtx) + ob, err := wrappers.GetWrapper().Orderbook(ctx, exchangeName, pair, assetType) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } - var asks, bids objects.Array - + asks := objects.Array{Value: make([]objects.Object, len(ob.Asks))} for x := range ob.Asks { temp := make(map[string]objects.Object, 2) temp["amount"] = &objects.Float{Value: ob.Asks[x].Amount} temp["price"] = &objects.Float{Value: ob.Asks[x].Price} - asks.Value = append(asks.Value, &objects.Map{Value: temp}) + asks.Value[x] = &objects.Map{Value: temp} } + bids := objects.Array{Value: make([]objects.Object, len(ob.Bids))} for x := range ob.Bids { temp := make(map[string]objects.Object, 2) temp["amount"] = &objects.Float{Value: ob.Bids[x].Amount} temp["price"] = &objects.Float{Value: ob.Bids[x].Price} - bids.Value = append(bids.Value, &objects.Map{Value: temp}) + bids.Value[x] = &objects.Map{Value: temp} } data := make(map[string]objects.Object, 5) @@ -93,32 +111,35 @@ func ExchangeOrderbook(args ...objects.Object) (objects.Object, error) { data["bids"] = &bids data["asset"] = &objects.String{Value: ob.Asset.String()} - return &objects.Map{ - Value: data, - }, nil + return &objects.Map{Value: data}, nil } // ExchangeTicker returns ticker data for requested exchange and currency pair func ExchangeTicker(args ...objects.Object) (objects.Object, error) { - if len(args) != 4 { + if len(args) != 5 { return nil, objects.ErrWrongNumArguments } - exchangeName, ok := objects.ToString(args[0]) + scriptCtx, ok := objects.ToInterface(args[0]).(*Context) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName) + return nil, constructRuntimeError(1, tickerFunc, "*gct.Context", args[0]) } - currencyPair, ok := objects.ToString(args[1]) + + exchangeName, ok := objects.ToString(args[1]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, currencyPair) + return nil, constructRuntimeError(2, tickerFunc, "string", args[1]) } - delimiter, ok := objects.ToString(args[2]) + currencyPair, ok := objects.ToString(args[2]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, delimiter) + return nil, constructRuntimeError(3, tickerFunc, "string", args[2]) } - assetTypeParam, ok := objects.ToString(args[3]) + delimiter, ok := objects.ToString(args[3]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, assetTypeParam) + return nil, constructRuntimeError(4, tickerFunc, "string", args[3]) + } + assetTypeParam, ok := objects.ToString(args[4]) + if !ok { + return nil, constructRuntimeError(5, tickerFunc, "string", args[4]) } pair, err := currency.NewPairDelimiter(currencyPair, delimiter) @@ -128,13 +149,13 @@ func ExchangeTicker(args ...objects.Object) (objects.Object, error) { assetType, err := asset.New(assetTypeParam) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } - tx, err := wrappers.GetWrapper(). - Ticker(context.TODO(), exchangeName, pair, assetType) + ctx := processScriptContext(scriptCtx) + tx, err := wrappers.GetWrapper().Ticker(ctx, exchangeName, pair, assetType) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } data := make(map[string]objects.Object, 14) @@ -166,13 +187,15 @@ func ExchangeExchanges(args ...objects.Object) (objects.Object, error) { enabledOnly, ok := objects.ToBool(args[0]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, enabledOnly) + return nil, constructRuntimeError(1, exchangesFunc, "bool", args[0]) } rtnValue := wrappers.GetWrapper().Exchanges(enabledOnly) - r := objects.Array{} + r := objects.Array{ + Value: make([]objects.Object, len(rtnValue)), + } for x := range rtnValue { - r.Value = append(r.Value, &objects.String{Value: rtnValue[x]}) + r.Value[x] = &objects.String{Value: rtnValue[x]} } return &r, nil @@ -186,56 +209,64 @@ func ExchangePairs(args ...objects.Object) (objects.Object, error) { exchangeName, ok := objects.ToString(args[0]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName) + return nil, constructRuntimeError(1, pairsFunc, "string", args[0]) } enabledOnly, ok := objects.ToBool(args[1]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, enabledOnly) + return nil, constructRuntimeError(2, pairsFunc, "bool", args[1]) } assetTypeParam, ok := objects.ToString(args[2]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, assetTypeParam) + return nil, constructRuntimeError(3, pairsFunc, "string", args[2]) } assetType, err := asset.New(assetTypeParam) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } - rtnValue, err := wrappers.GetWrapper().Pairs(exchangeName, enabledOnly, assetType) + pairs, err := wrappers.GetWrapper().Pairs(exchangeName, enabledOnly, assetType) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } - r := objects.Array{} - pairs := *(*[]currency.Pair)(rtnValue) - for x := range pairs { - r.Value = append(r.Value, &objects.String{Value: pairs[x].String()}) + r := objects.Array{ + Value: make([]objects.Object, len(*pairs)), + } + + for x := range *pairs { + r.Value[x] = &objects.String{Value: (*pairs)[x].String()} } return &r, nil } // ExchangeAccountInfo returns account information for requested exchange func ExchangeAccountInfo(args ...objects.Object) (objects.Object, error) { - if len(args) != 2 { + if len(args) != 3 { return nil, objects.ErrWrongNumArguments } - exchangeName, ok := objects.ToString(args[0]) + scriptCtx, ok := objects.ToInterface(args[0]).(*Context) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName) + return nil, constructRuntimeError(1, accountInfoFunc, "*gct.Context", args[0]) } - assetString, ok := objects.ToString(args[1]) + exchangeName, ok := objects.ToString(args[1]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, assetString) + return nil, constructRuntimeError(2, accountInfoFunc, "string", args[1]) + } + assetString, ok := objects.ToString(args[2]) + if !ok { + return nil, constructRuntimeError(3, accountInfoFunc, "string", args[2]) } assetType, err := asset.New(assetString) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } + + ctx := processScriptContext(scriptCtx) rtnValue, err := wrappers.GetWrapper(). - AccountInformation(context.TODO(), exchangeName, assetType) + AccountInformation(ctx, exchangeName, assetType) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } var funds objects.Array @@ -252,62 +283,65 @@ func ExchangeAccountInfo(args ...objects.Object) (objects.Object, error) { data := make(map[string]objects.Object, 2) data["exchange"] = &objects.String{Value: rtnValue.Exchange} data["currencies"] = &funds - - return &objects.Map{ - Value: data, - }, nil + return &objects.Map{Value: data}, nil } // ExchangeOrderQuery query order on exchange func ExchangeOrderQuery(args ...objects.Object) (objects.Object, error) { - if len(args) < 2 { + if len(args) < 3 { return nil, objects.ErrWrongNumArguments } - exchangeName, ok := objects.ToString(args[0]) + scriptCtx, ok := objects.ToInterface(args[0]).(*Context) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName) + return nil, constructRuntimeError(1, orderQueryFunc, "*gct.Context", args[0]) } - orderID, ok := objects.ToString(args[1]) + exchangeName, ok := objects.ToString(args[1]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, orderID) + return nil, constructRuntimeError(2, orderQueryFunc, "string", args[1]) + } + orderID, ok := objects.ToString(args[2]) + if !ok { + return nil, constructRuntimeError(3, orderQueryFunc, "string", args[2]) } var pair currency.Pair assetTypeString := asset.Spot.String() switch len(args) { - case 4: - assetTypeString, ok = objects.ToString(args[3]) + case 5: + assetTypeString, ok = objects.ToString(args[4]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, assetTypeString) + return nil, constructRuntimeError(5, orderQueryFunc, "string", args[4]) } fallthrough - case 3: - currencyPairString, isOk := objects.ToString(args[2]) + case 4: + currencyPairString, isOk := objects.ToString(args[3]) if !isOk { - return nil, fmt.Errorf(ErrParameterConvertFailed, currencyPairString) + return nil, constructRuntimeError(4, orderQueryFunc, "string", args[3]) } var err error pair, err = currency.NewPairFromString(currencyPairString) if err != nil { - return nil, fmt.Errorf(ErrParameterConvertFailed, currencyPairString) + return errorResponsef(standardFormatting, err) } } assetType, err := asset.New(assetTypeString) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } + ctx := processScriptContext(scriptCtx) orderDetails, err := wrappers.GetWrapper(). - QueryOrder(context.TODO(), exchangeName, orderID, pair, assetType) + QueryOrder(ctx, exchangeName, orderID, pair, assetType) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } var tradeHistory objects.Array + tradeHistory.Value = make([]objects.Object, len(orderDetails.Trades)) for x := range orderDetails.Trades { temp := make(map[string]objects.Object, 7) temp["timestamp"] = &objects.Time{Value: orderDetails.Trades[x].Timestamp} @@ -317,7 +351,7 @@ func ExchangeOrderQuery(args ...objects.Object) (objects.Object, error) { temp["type"] = &objects.String{Value: orderDetails.Trades[x].Type.String()} temp["side"] = &objects.String{Value: orderDetails.Trades[x].Side.String()} temp["description"] = &objects.String{Value: orderDetails.Trades[x].Description} - tradeHistory.Value = append(tradeHistory.Value, &objects.Map{Value: temp}) + tradeHistory.Value[x] = &objects.Map{Value: temp} } data := make(map[string]objects.Object, 14) @@ -336,63 +370,65 @@ func ExchangeOrderQuery(args ...objects.Object) (objects.Object, error) { data["status"] = &objects.String{Value: orderDetails.Status.String()} data["trades"] = &tradeHistory - return &objects.Map{ - Value: data, - }, nil + return &objects.Map{Value: data}, nil } // ExchangeOrderCancel cancels order on requested exchange func ExchangeOrderCancel(args ...objects.Object) (objects.Object, error) { - if len(args) < 2 || len(args) > 4 { + if len(args) < 3 || len(args) > 5 { return nil, objects.ErrWrongNumArguments } - exchangeName, ok := objects.ToString(args[0]) + scriptCtx, ok := objects.ToInterface(args[0]).(*Context) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName) + return nil, constructRuntimeError(1, orderCancelFunc, "*gct.Context", args[0]) + } + exchangeName, ok := objects.ToString(args[1]) + if !ok { + return nil, constructRuntimeError(2, orderCancelFunc, "string", args[1]) } if exchangeName == "" { return nil, fmt.Errorf(ErrEmptyParameter, "exchange name") } var orderID string - orderID, ok = objects.ToString(args[1]) + orderID, ok = objects.ToString(args[2]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, orderID) + return nil, constructRuntimeError(3, orderCancelFunc, "string", args[2]) } if orderID == "" { return nil, fmt.Errorf(ErrEmptyParameter, "orderID") } var err error var cp currency.Pair - if len(args) > 2 { + if len(args) > 3 { var currencyPair string - currencyPair, ok = objects.ToString(args[2]) + currencyPair, ok = objects.ToString(args[3]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, currencyPair) + return nil, constructRuntimeError(4, orderCancelFunc, "string", args[3]) } cp, err = currency.NewPairFromString(currencyPair) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } } var a asset.Item - if len(args) > 3 { + if len(args) > 4 { var assetType string - assetType, ok = objects.ToString(args[3]) + assetType, ok = objects.ToString(args[4]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, assetType) + return nil, constructRuntimeError(5, orderCancelFunc, "string", args[4]) } a, err = asset.New(assetType) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } } - var isCancelled bool - isCancelled, err = wrappers.GetWrapper(). - CancelOrder(context.TODO(), exchangeName, orderID, cp, a) + ctx := processScriptContext(scriptCtx) + isCancelled, err := wrappers.GetWrapper(). + CancelOrder(ctx, exchangeName, orderID, cp, a) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } if isCancelled { @@ -403,63 +439,69 @@ func ExchangeOrderCancel(args ...objects.Object) (objects.Object, error) { // ExchangeOrderSubmit submit order on exchange func ExchangeOrderSubmit(args ...objects.Object) (objects.Object, error) { - if len(args) != 9 { + if len(args) != 10 { return nil, objects.ErrWrongNumArguments } - exchangeName, ok := objects.ToString(args[0]) + scriptCtx, ok := objects.ToInterface(args[0]).(*Context) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName) + return nil, constructRuntimeError(1, orderSubmitFunc, "*gct.Context", args[0]) } - currencyPair, ok := objects.ToString(args[1]) + exchangeName, ok := objects.ToString(args[1]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, currencyPair) + return nil, constructRuntimeError(2, orderSubmitFunc, "string", args[1]) } - delimiter, ok := objects.ToString(args[2]) + currencyPair, ok := objects.ToString(args[2]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, delimiter) + return nil, constructRuntimeError(3, orderSubmitFunc, "string", args[2]) } + delimiter, ok := objects.ToString(args[3]) + if !ok { + return nil, constructRuntimeError(4, orderSubmitFunc, "string", args[3]) + } + orderType, ok := objects.ToString(args[4]) + if !ok { + return nil, constructRuntimeError(5, orderSubmitFunc, "string", args[4]) + } + orderSide, ok := objects.ToString(args[5]) + if !ok { + return nil, constructRuntimeError(6, orderSubmitFunc, "string", args[5]) + } + orderPrice, ok := objects.ToFloat64(args[6]) + if !ok { + return nil, constructRuntimeError(7, orderSubmitFunc, "float64", args[6]) + } + orderAmount, ok := objects.ToFloat64(args[7]) + if !ok { + return nil, constructRuntimeError(8, orderSubmitFunc, "float64", args[7]) + } + orderClientID, ok := objects.ToString(args[8]) + if !ok { + return nil, constructRuntimeError(9, orderSubmitFunc, "string", args[8]) + } + assetType, ok := objects.ToString(args[9]) + if !ok { + return nil, constructRuntimeError(10, orderSubmitFunc, "string", args[9]) + } + pair, err := currency.NewPairDelimiter(currencyPair, delimiter) if err != nil { - return nil, err - } - orderType, ok := objects.ToString(args[3]) - if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, orderType) - } - orderSide, ok := objects.ToString(args[4]) - if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, orderSide) - } - orderPrice, ok := objects.ToFloat64(args[5]) - if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, orderPrice) - } - orderAmount, ok := objects.ToFloat64(args[6]) - if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, orderAmount) - } - orderClientID, ok := objects.ToString(args[7]) - if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, orderClientID) - } - assetType, ok := objects.ToString(args[8]) - if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, orderClientID) + return errorResponsef(standardFormatting, err) } + a, err := asset.New(assetType) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } side, err := order.StringToOrderSide(orderSide) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } oType, err := order.StringToOrderType(orderType) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } tempSubmit := &order.Submit{ @@ -473,18 +515,17 @@ func ExchangeOrderSubmit(args ...objects.Object) (objects.Object, error) { Exchange: exchangeName, } - rtn, err := wrappers.GetWrapper().SubmitOrder(context.TODO(), tempSubmit) + ctx := processScriptContext(scriptCtx) + rtn, err := wrappers.GetWrapper().SubmitOrder(ctx, tempSubmit) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } data := make(map[string]objects.Object, 2) data["orderid"] = &objects.String{Value: rtn.OrderID} data["isorderplaced"] = objects.TrueValue - return &objects.Map{ - Value: data, - }, nil + return &objects.Map{Value: data}, nil } // ExchangeDepositAddress returns deposit address (if supported by exchange) @@ -495,65 +536,67 @@ func ExchangeDepositAddress(args ...objects.Object) (objects.Object, error) { exchangeName, ok := objects.ToString(args[0]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName) + return nil, constructRuntimeError(1, depositAddressFunc, "string", args[0]) } currencyCode, ok := objects.ToString(args[1]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, currencyCode) + return nil, constructRuntimeError(2, depositAddressFunc, "string", args[1]) } chain, ok := objects.ToString(args[2]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, chain) + return nil, constructRuntimeError(3, depositAddressFunc, "string", args[2]) } currCode := currency.NewCode(currencyCode) rtn, err := wrappers.GetWrapper().DepositAddress(exchangeName, chain, currCode) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } data := make(map[string]objects.Object, 2) data["address"] = &objects.String{Value: rtn.Address} data["tag"] = &objects.String{Value: rtn.Tag} - return &objects.Map{ - Value: data, - }, nil + return &objects.Map{Value: data}, nil } // ExchangeWithdrawCrypto submit request to withdraw crypto assets func ExchangeWithdrawCrypto(args ...objects.Object) (objects.Object, error) { - if len(args) != 7 { + if len(args) != 8 { return nil, objects.ErrWrongNumArguments } - exchangeName, ok := objects.ToString(args[0]) + scriptCtx, ok := objects.ToInterface(args[0]).(*Context) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName) + return nil, constructRuntimeError(1, withdrawCryptoFunc, "*gct.Context", args[0]) } - cur, ok := objects.ToString(args[1]) + exchangeName, ok := objects.ToString(args[1]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, cur) + return nil, constructRuntimeError(2, withdrawCryptoFunc, "string", args[1]) } - address, ok := objects.ToString(args[2]) + cur, ok := objects.ToString(args[2]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, address) + return nil, constructRuntimeError(3, withdrawCryptoFunc, "string", args[2]) } - addressTag, ok := objects.ToString(args[3]) + address, ok := objects.ToString(args[3]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, addressTag) + return nil, constructRuntimeError(4, withdrawCryptoFunc, "string", args[3]) } - amount, ok := objects.ToFloat64(args[4]) + addressTag, ok := objects.ToString(args[4]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, amount) + return nil, constructRuntimeError(5, withdrawCryptoFunc, "string", args[4]) } - feeAmount, ok := objects.ToFloat64(args[5]) + amount, ok := objects.ToFloat64(args[5]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, feeAmount) + return nil, constructRuntimeError(6, withdrawCryptoFunc, "float64", args[5]) } - description, ok := objects.ToString(args[6]) + feeAmount, ok := objects.ToFloat64(args[6]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, description) + return nil, constructRuntimeError(7, withdrawCryptoFunc, "float64", args[6]) + } + description, ok := objects.ToString(args[7]) + if !ok { + return nil, constructRuntimeError(8, withdrawCryptoFunc, "string", args[7]) } withdrawRequest := &withdraw.Request{ @@ -568,10 +611,10 @@ func ExchangeWithdrawCrypto(args ...objects.Object) (objects.Object, error) { Amount: amount, } - rtn, err := wrappers.GetWrapper(). - WithdrawalCryptoFunds(context.TODO(), withdrawRequest) + ctx := processScriptContext(scriptCtx) + rtn, err := wrappers.GetWrapper().WithdrawalCryptoFunds(ctx, withdrawRequest) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } return &objects.String{Value: rtn}, nil @@ -579,29 +622,33 @@ func ExchangeWithdrawCrypto(args ...objects.Object) (objects.Object, error) { // ExchangeWithdrawFiat submit request to withdraw fiat assets func ExchangeWithdrawFiat(args ...objects.Object) (objects.Object, error) { - if len(args) != 5 { + if len(args) != 6 { return nil, objects.ErrWrongNumArguments } - exchangeName, ok := objects.ToString(args[0]) + scriptCtx, ok := objects.ToInterface(args[0]).(*Context) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName) + return nil, constructRuntimeError(1, withdrawFiatFunc, "*gct.Context", args[0]) } - cur, ok := objects.ToString(args[1]) + exchangeName, ok := objects.ToString(args[1]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, cur) + return nil, constructRuntimeError(2, withdrawFiatFunc, "string", args[1]) } - description, ok := objects.ToString(args[2]) + cur, ok := objects.ToString(args[2]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, description) + return nil, constructRuntimeError(3, withdrawFiatFunc, "string", args[2]) } - amount, ok := objects.ToFloat64(args[3]) + description, ok := objects.ToString(args[3]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, amount) + return nil, constructRuntimeError(4, withdrawFiatFunc, "string", args[3]) } - bankAccountID, ok := objects.ToString(args[4]) + amount, ok := objects.ToFloat64(args[4]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, bankAccountID) + return nil, constructRuntimeError(5, withdrawFiatFunc, "float64", args[4]) + } + bankAccountID, ok := objects.ToString(args[5]) + if !ok { + return nil, constructRuntimeError(6, withdrawFiatFunc, "string", args[5]) } withdrawRequest := &withdraw.Request{ @@ -611,10 +658,11 @@ func ExchangeWithdrawFiat(args ...objects.Object) (objects.Object, error) { Amount: amount, } + ctx := processScriptContext(scriptCtx) rtn, err := wrappers.GetWrapper(). - WithdrawalFiatFunds(context.TODO(), bankAccountID, withdrawRequest) + WithdrawalFiatFunds(ctx, bankAccountID, withdrawRequest) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } return &objects.String{Value: rtn}, nil @@ -631,56 +679,61 @@ func (o *OHLCV) TypeName() string { } func exchangeOHLCV(args ...objects.Object) (objects.Object, error) { - if len(args) != 7 { + if len(args) != 8 { return nil, objects.ErrWrongNumArguments } - exchangeName, ok := objects.ToString(args[0]) + scriptCtx, ok := objects.ToInterface(args[0]).(*Context) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName) + return nil, constructRuntimeError(1, ohlcvFunc, "*gct.Context", args[0]) } - currencyPair, ok := objects.ToString(args[1]) + exchangeName, ok := objects.ToString(args[1]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, currencyPair) + return nil, constructRuntimeError(2, ohlcvFunc, "string", args[1]) } - delimiter, ok := objects.ToString(args[2]) + currencyPair, ok := objects.ToString(args[2]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, delimiter) + return nil, constructRuntimeError(3, ohlcvFunc, "string", args[2]) } - assetTypeParam, ok := objects.ToString(args[3]) + delimiter, ok := objects.ToString(args[3]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, assetTypeParam) + return nil, constructRuntimeError(4, ohlcvFunc, "string", args[3]) + } + assetTypeParam, ok := objects.ToString(args[4]) + if !ok { + return nil, constructRuntimeError(5, ohlcvFunc, "string", args[4]) } - startTime, ok := objects.ToTime(args[4]) + startTime, ok := objects.ToTime(args[5]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, startTime) + return nil, constructRuntimeError(6, ohlcvFunc, "string", args[5]) } - endTime, ok := objects.ToTime(args[5]) + endTime, ok := objects.ToTime(args[6]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, endTime) + return nil, constructRuntimeError(7, ohlcvFunc, "time.Time", args[6]) } - intervalStr, ok := objects.ToString(args[6]) + intervalStr, ok := objects.ToString(args[7]) if !ok { - return nil, fmt.Errorf(ErrParameterConvertFailed, endTime) + return nil, constructRuntimeError(8, ohlcvFunc, "string", args[7]) } interval, err := parseInterval(intervalStr) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } pair, err := currency.NewPairDelimiter(currencyPair, delimiter) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } assetType, err := asset.New(assetTypeParam) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } + ctx := processScriptContext(scriptCtx) ret, err := wrappers.GetWrapper(). - OHLCV(context.TODO(), + OHLCV(ctx, exchangeName, pair, assetType, @@ -688,21 +741,21 @@ func exchangeOHLCV(args ...objects.Object) (objects.Object, error) { endTime, kline.Interval(interval)) if err != nil { - return nil, err + return errorResponsef(standardFormatting, err) } - var candles objects.Array + candles := objects.Array{Value: make([]objects.Object, len(ret.Candles))} for x := range ret.Candles { - candle := &objects.Array{} - candle.Value = append(candle.Value, &objects.Int{Value: ret.Candles[x].Time.Unix()}, - &objects.Float{Value: ret.Candles[x].Open}, - &objects.Float{Value: ret.Candles[x].High}, - &objects.Float{Value: ret.Candles[x].Low}, - &objects.Float{Value: ret.Candles[x].Close}, - &objects.Float{Value: ret.Candles[x].Volume}, - ) - - candles.Value = append(candles.Value, candle) + candles.Value[x] = &objects.Array{ + Value: []objects.Object{ + &objects.Int{Value: ret.Candles[x].Time.Unix()}, + &objects.Float{Value: ret.Candles[x].Open}, + &objects.Float{Value: ret.Candles[x].High}, + &objects.Float{Value: ret.Candles[x].Low}, + &objects.Float{Value: ret.Candles[x].Close}, + &objects.Float{Value: ret.Candles[x].Volume}, + }, + } } retValue := make(map[string]objects.Object, 5) diff --git a/gctscript/modules/gct/gct.go b/gctscript/modules/gct/gct.go index ceabfced..5460a82e 100644 --- a/gctscript/modules/gct/gct.go +++ b/gctscript/modules/gct/gct.go @@ -1,5 +1,25 @@ package gct +import ( + "context" + + objects "github.com/d5/tengo/v2" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" + "github.com/thrasher-corp/gocryptotrader/exchanges/request" +) + +const ( + setVerboseFunc = "set_verbose" + setAccountFunc = "set_account" + setSubAccountFunc = "set_sub_account" +) + +var globalModules = map[string]objects.Object{ + setVerboseFunc: &objects.UserFunction{Name: setVerboseFunc, Value: setVerbose}, + setAccountFunc: &objects.UserFunction{Name: setAccountFunc, Value: setAccount}, + setSubAccountFunc: &objects.UserFunction{Name: setSubAccountFunc, Value: setSubAccount}, +} + // AllModuleNames returns a list of all default module names. func AllModuleNames() []string { names := make([]string, 0, len(Modules)) @@ -8,3 +28,183 @@ func AllModuleNames() []string { } return names } + +// setVerbose specifically sets verbosity for http rest requests for this script +// Params: scriptCTX +func setVerbose(args ...objects.Object) (objects.Object, error) { + if len(args) != 1 { + return nil, objects.ErrWrongNumArguments + } + + ctx, ok := objects.ToInterface(args[0]).(*Context) + if !ok { + return nil, constructRuntimeError(1, setVerboseFunc, "*gct.Context", args[0]) + } + if ctx.Value == nil { + ctx.Value = make(map[string]objects.Object) + } + ctx.Value["verbose"] = objects.TrueValue + return ctx, nil +} + +// setAccount sets account details which overrides default credentials for +// script account management, api key and secret are required. +// Params: scriptCTX, apiKey, apiSecret, subAccount, clientID, PEMKey, OneTimePassword string +func setAccount(args ...objects.Object) (objects.Object, error) { + if len(args) < 3 || len(args) > 7 { + return nil, objects.ErrWrongNumArguments + } + + ctx, ok := objects.ToInterface(args[0]).(*Context) + if !ok { + return nil, constructRuntimeError(1, setAccountFunc, "*gct.Context", args[0]) + } + + apikey, ok := objects.ToInterface(args[1]).(string) + if !ok { + return nil, constructRuntimeError(2, setAccountFunc, "string", args[1]) + } + + if ctx.Value == nil { + ctx.Value = make(map[string]objects.Object) + } + + ctx.Value["apikey"] = &objects.String{Value: apikey} + + apisecret, ok := objects.ToInterface(args[2]).(string) + if !ok { + return nil, constructRuntimeError(3, setAccountFunc, "string", args[2]) + } + + ctx.Value["apisecret"] = &objects.String{Value: apisecret} + + if len(args) > 3 { + var subaccount string + subaccount, ok = objects.ToInterface(args[3]).(string) + if !ok { + return nil, constructRuntimeError(4, setAccountFunc, "string", args[3]) + } + + if subaccount != "" { + ctx.Value["subaccount"] = &objects.String{Value: subaccount} + } + } + + if len(args) > 4 { + var clientID string + clientID, ok = objects.ToInterface(args[4]).(string) + if !ok { + return nil, constructRuntimeError(5, setAccountFunc, "string", args[4]) + } + if clientID != "" { + ctx.Value["clientid"] = &objects.String{Value: clientID} + } + } + + if len(args) > 5 { + var pemKey string + pemKey, ok = objects.ToInterface(args[5]).(string) + if !ok { + return nil, constructRuntimeError(6, setAccountFunc, "string", args[5]) + } + if pemKey != "" { + ctx.Value["pemkey"] = &objects.String{Value: pemKey} + } + } + + if len(args) > 5 { + var oneTimePassword string + oneTimePassword, ok = objects.ToInterface(args[6]).(string) + if !ok { + return nil, constructRuntimeError(7, setAccountFunc, "string", args[6]) + } + if oneTimePassword != "" { + ctx.Value["otp"] = &objects.String{Value: oneTimePassword} + } + } + return ctx, nil +} + +// setSubAccount sets sub account details which overrides default credential +// sub account but uses the same configured api keys. +// Params: scriptCTX, subAccount string +func setSubAccount(args ...objects.Object) (objects.Object, error) { + if len(args) != 2 { + return nil, objects.ErrWrongNumArguments + } + + ctx, ok := objects.ToInterface(args[0]).(*Context) + if !ok { + return nil, constructRuntimeError(1, setSubAccountFunc, "*gct.Context", args[0]) + } + + sub, ok := objects.ToInterface(args[1]).(string) + if !ok { + return nil, constructRuntimeError(2, setSubAccountFunc, "string", args[1]) + } + + if ctx.Value == nil { + ctx.Value = make(map[string]objects.Object) + } + + ctx.Value["subaccount"] = &objects.String{Value: sub} + return ctx, nil +} + +func processScriptContext(scriptCtx *Context) context.Context { + ctx := context.Background() + if scriptCtx == nil || scriptCtx.Value == nil { + return ctx + } + var object objects.Object + if object = scriptCtx.Value["verbose"]; object != nil { + ctx = request.WithVerbose(ctx) + } + + if object = scriptCtx.Value["apikey"]; object != nil { + key, _ := objects.ToString(object) + + var secret string + if object = scriptCtx.Value["apisecret"]; object != nil { + secret, _ = objects.ToString(object) + } + + var subAccount string + if object = scriptCtx.Value["subaccount"]; object != nil { + subAccount, _ = objects.ToString(object) + } + + var clientID string + if object = scriptCtx.Value["clientid"]; object != nil { + clientID, _ = objects.ToString(object) + } + + var pemKey string + if object = scriptCtx.Value["pemkey"]; object != nil { + pemKey, _ = objects.ToString(object) + } + + var otp string + if object = scriptCtx.Value["otp"]; object != nil { + otp, _ = objects.ToString(object) + } + + ctx = account.DeployCredentialsToContext(ctx, &account.Credentials{ + Key: key, + Secret: secret, + SubAccount: subAccount, + ClientID: clientID, + PEMKey: pemKey, + OneTimePassword: otp, + }) + } else if object = scriptCtx.Value["subaccount"]; object != nil { + subAccount, _ := objects.ToString(object) + ctx = account.DeploySubAccountOverrideToContext(ctx, subAccount) + } + return ctx +} + +// TypeName returns the name of the custom type. +func (c *Context) TypeName() string { + return "scriptContext" +} diff --git a/gctscript/modules/gct/gct_test.go b/gctscript/modules/gct/gct_test.go index b8aa03cd..42f51e95 100644 --- a/gctscript/modules/gct/gct_test.go +++ b/gctscript/modules/gct/gct_test.go @@ -8,12 +8,15 @@ import ( "time" objects "github.com/d5/tengo/v2" + "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/gctscript/modules" "github.com/thrasher-corp/gocryptotrader/gctscript/wrappers/validator" ) var ( + ctx = &Context{} exch = &objects.String{ Value: "BTC Markets", } @@ -48,7 +51,7 @@ func TestMain(m *testing.M) { func TestExchangeOrderbook(t *testing.T) { t.Parallel() - _, err := ExchangeOrderbook(exch, currencyPair, delimiter, assetType) + _, err := ExchangeOrderbook(ctx, exch, currencyPair, delimiter, assetType) if err != nil { t.Error(err) } @@ -66,7 +69,7 @@ func TestExchangeOrderbook(t *testing.T) { func TestExchangeTicker(t *testing.T) { t.Parallel() - _, err := ExchangeTicker(exch, currencyPair, delimiter, assetType) + _, err := ExchangeTicker(ctx, exch, currencyPair, delimiter, assetType) if err != nil { t.Error(err) } @@ -133,12 +136,12 @@ func TestAccountInfo(t *testing.T) { t.Error(err) } - _, err = ExchangeAccountInfo(exch, assetType) + _, err = ExchangeAccountInfo(ctx, exch, assetType) if err != nil { t.Error(err) } - _, err = ExchangeAccountInfo(exchError, assetType) + _, err = ExchangeAccountInfo(ctx, exchError, assetType) if err != nil && !errors.Is(err, errTestFailed) { t.Error(err) } @@ -152,12 +155,12 @@ func TestExchangeOrderQuery(t *testing.T) { t.Error(err) } - _, err = ExchangeOrderQuery(exch, orderID) + _, err = ExchangeOrderQuery(ctx, exch, orderID) if err != nil { t.Error(err) } - _, err = ExchangeOrderQuery(exchError, orderID) + _, err = ExchangeOrderQuery(ctx, exchError, orderID) if err != nil && !errors.Is(err, errTestFailed) { t.Error(err) } @@ -180,17 +183,17 @@ func TestExchangeOrderCancel(t *testing.T) { t.Error("expecting error") } - _, err = ExchangeOrderCancel(exch, orderID) + _, err = ExchangeOrderCancel(ctx, exch, orderID) if err != nil { t.Error(err) } - _, err = ExchangeOrderCancel(exch, orderID, currencyPair) + _, err = ExchangeOrderCancel(ctx, exch, orderID, currencyPair) if err != nil { t.Error(err) } - _, err = ExchangeOrderCancel(exch, orderID, currencyPair, assetType) + _, err = ExchangeOrderCancel(ctx, exch, orderID, currencyPair, assetType) if err != nil { t.Error(err) } @@ -209,19 +212,19 @@ func TestExchangeOrderSubmit(t *testing.T) { orderAmount := &objects.Float{Value: 1} orderAsset := &objects.String{Value: asset.Spot.String()} - _, err = ExchangeOrderSubmit(exch, currencyPair, delimiter, + _, err = ExchangeOrderSubmit(ctx, exch, currencyPair, delimiter, orderType, orderSide, orderPrice, orderAmount, orderID, orderAsset) if err != nil && !errors.Is(err, errTestFailed) { t.Error(err) } - _, err = ExchangeOrderSubmit(exch, currencyPair, delimiter, + _, err = ExchangeOrderSubmit(ctx, exch, currencyPair, delimiter, orderType, orderSide, orderPrice, orderAmount, orderID, orderAsset) if err != nil { t.Error(err) } - _, err = ExchangeOrderSubmit(objects.TrueValue, currencyPair, delimiter, + _, err = ExchangeOrderSubmit(ctx, objects.TrueValue, currencyPair, delimiter, orderType, orderSide, orderPrice, orderAmount, orderID, orderAsset) if err != nil { t.Error(err) @@ -269,7 +272,7 @@ func TestExchangeWithdrawCrypto(t *testing.T) { address := &objects.String{Value: "0xTHISISALEGITBTCADDRESSS"} amount := &objects.Float{Value: 1.0} - _, err = ExchangeWithdrawCrypto(exch, currCode, address, address, amount, amount, desc) + _, err = ExchangeWithdrawCrypto(ctx, exch, currCode, address, address, amount, amount, desc) if err != nil { t.Error(err) } @@ -286,7 +289,7 @@ func TestExchangeWithdrawFiat(t *testing.T) { desc := &objects.String{Value: "Hello"} amount := &objects.Float{Value: 1.0} bankID := &objects.String{Value: "test-bank-01"} - _, err = ExchangeWithdrawFiat(exch, currCode, desc, amount, bankID) + _, err = ExchangeWithdrawFiat(ctx, exch, currCode, desc, amount, bankID) if err != nil { t.Error(err) } @@ -333,3 +336,193 @@ func TestParseInterval(t *testing.T) { } } } + +func TestSetVerbose(t *testing.T) { + t.Parallel() + _, err := setVerbose() + if !errors.Is(err, objects.ErrWrongNumArguments) { + t.Fatalf("received: '%v' but expected: '%v'", err, objects.ErrWrongNumArguments) + } + + _, err = setVerbose(objects.TrueValue) + if !errors.Is(err, common.ErrTypeAssertFailure) { + t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrTypeAssertFailure) + } + + resp, err := setVerbose(&Context{}) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + ctx, ok := objects.ToInterface(resp).(*Context) + if !ok { + t.Fatal("should be of type *Context") + } + + val := ctx.Value["verbose"] + if val.String() != objects.TrueValue.String() { + t.Fatal("should contain verbose string in map") + } +} + +var dummyStr = &objects.String{Value: "xxxx"} + +func TestSetAccount(t *testing.T) { + t.Parallel() + _, err := setAccount() + if !errors.Is(err, objects.ErrWrongNumArguments) { + t.Fatalf("received: '%v' but expected: '%v'", err, objects.ErrWrongNumArguments) + } + + _, err = setAccount(objects.TrueValue, objects.TrueValue, objects.TrueValue) + if !errors.Is(err, common.ErrTypeAssertFailure) { + t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrTypeAssertFailure) + } + + _, err = setAccount(&Context{}, objects.TrueValue, objects.TrueValue) + if !errors.Is(err, common.ErrTypeAssertFailure) { + t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrTypeAssertFailure) + } + + _, err = setAccount(&Context{}, dummyStr, objects.TrueValue) + if !errors.Is(err, common.ErrTypeAssertFailure) { + t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrTypeAssertFailure) + } + + _, err = setAccount(&Context{}, dummyStr, dummyStr, objects.TrueValue) + if !errors.Is(err, common.ErrTypeAssertFailure) { + t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrTypeAssertFailure) + } + + _, err = setAccount(&Context{}, dummyStr, dummyStr, dummyStr, objects.TrueValue) + if !errors.Is(err, common.ErrTypeAssertFailure) { + t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrTypeAssertFailure) + } + + _, err = setAccount(&Context{}, dummyStr, dummyStr, dummyStr, dummyStr, objects.TrueValue) + if !errors.Is(err, common.ErrTypeAssertFailure) { + t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrTypeAssertFailure) + } + + _, err = setAccount(&Context{}, dummyStr, dummyStr, dummyStr, dummyStr, dummyStr, objects.TrueValue) + if !errors.Is(err, common.ErrTypeAssertFailure) { + t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrTypeAssertFailure) + } + + resp, err := setAccount(&Context{}, dummyStr, dummyStr, dummyStr, dummyStr, dummyStr, dummyStr) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + ctx, ok := objects.ToInterface(resp).(*Context) + if !ok { + t.Fatal("should be of type *Context") + } + + val := ctx.Value["apikey"] + if val.String() != dummyStr.String() { + t.Fatal("should contain apikey string in map") + } + val = ctx.Value["apisecret"] + if val.String() != dummyStr.String() { + t.Fatal("should contain apisecret string in map") + } + val = ctx.Value["subaccount"] + if val.String() != dummyStr.String() { + t.Fatal("should contain subaccount string in map") + } + val = ctx.Value["clientid"] + if val.String() != dummyStr.String() { + t.Fatal("should contain clientid string in map") + } + val = ctx.Value["pemkey"] + if val.String() != dummyStr.String() { + t.Fatal("should contain pemkey string in map") + } + val = ctx.Value["otp"] + if val.String() != dummyStr.String() { + t.Fatal("should contain otp string in map") + } +} + +func TestSetSubAccount(t *testing.T) { + t.Parallel() + _, err := setSubAccount() + if !errors.Is(err, objects.ErrWrongNumArguments) { + t.Fatalf("received: '%v' but expected: '%v'", err, objects.ErrWrongNumArguments) + } + + _, err = setSubAccount(objects.TrueValue, objects.TrueValue) + if !errors.Is(err, common.ErrTypeAssertFailure) { + t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrTypeAssertFailure) + } + + _, err = setSubAccount(&Context{}, objects.TrueValue) + if !errors.Is(err, common.ErrTypeAssertFailure) { + t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrTypeAssertFailure) + } + + subby, err := setSubAccount(&Context{}, dummyStr) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + ctxWSubAcc, ok := subby.(*Context) + if !ok { + t.Fatal("unexpected type returned") + } + + if ctxWSubAcc.Value["subaccount"].String() != dummyStr.String() { + t.Fatalf("received: '%v' but expected: '%v'", ctxWSubAcc.Value["subaccount"].String(), dummyStr.String()) + } + + // Deploy override to actual context.Context type + ctx := processScriptContext(ctxWSubAcc) + if ctx == nil { + t.Fatal("should not be nil") + } + + subaccount, ok := ctx.Value(account.ContextSubAccountFlag).(string) + if !ok { + t.Fatal("wrong type") + } + + if subaccount != dummyStr.String()[1:5] { + t.Fatalf("received: '%v' but expected: '%v'", subaccount, dummyStr.String()[1:5]) + } +} + +func TestProcessScriptContext(t *testing.T) { + t.Parallel() + ctx := processScriptContext(nil) + if ctx == nil { + t.Fatal("should not be nil") + } + + fromScript, err := setAccount(&Context{}, dummyStr, dummyStr, dummyStr, dummyStr, dummyStr, dummyStr) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + fromScript, err = setVerbose(fromScript) + if !errors.Is(err, nil) { + t.Fatalf("received: '%v' but expected: '%v'", err, nil) + } + + scriptCTX, ok := objects.ToInterface(fromScript).(*Context) + if !ok { + t.Fatal("should assert correctly") + } + + ctx = processScriptContext(scriptCTX) + if ctx == nil { + t.Fatal("should not be nil") + } +} + +func TestScriptCredentialTypeName(t *testing.T) { + t.Parallel() + if name := (&Context{}).TypeName(); name != "scriptContext" { + t.Fatal("unexpected value") + } +} diff --git a/gctscript/modules/gct/gct_types.go b/gctscript/modules/gct/gct_types.go index 53cc02ce..3fba0275 100644 --- a/gctscript/modules/gct/gct_types.go +++ b/gctscript/modules/gct/gct_types.go @@ -3,7 +3,7 @@ package gct import ( "errors" - "github.com/d5/tengo/v2" + objects "github.com/d5/tengo/v2" ) const ( @@ -17,7 +17,13 @@ var errInvalidInterval = errors.New("invalid interval") var supportedDurations = []string{"1m", "3m", "5m", "15m", "30m", "1h", "2h", "4h", "6h", "12h", "24h", "1d", "3d", "1w"} // Modules map of all loadable modules -var Modules = map[string]map[string]tengo.Object{ +var Modules = map[string]map[string]objects.Object{ "exchange": exchangeModule, "common": commonModule, + "global": globalModules, +} + +// Context defines a juncture for script context to go context awareness +type Context struct { + objects.Map } diff --git a/gctscript/vm/gctscript.go b/gctscript/vm/gctscript.go index 2c611d9a..a2bd9ae1 100644 --- a/gctscript/vm/gctscript.go +++ b/gctscript/vm/gctscript.go @@ -52,10 +52,11 @@ func (g *GctScriptManager) ShutdownAll() (err error) { } var shutdownErrors []error - AllVMSync.Range(func(k, v interface{}) bool { + AllVMSync.Range(func(_, v interface{}) bool { vm, ok := v.(*VM) if !ok { shutdownErrors = append(shutdownErrors, errors.New("unable to type assert VM")) + return true } errShutdown := vm.Shutdown() if err != nil { @@ -73,7 +74,7 @@ func (g *GctScriptManager) ShutdownAll() (err error) { // RemoveVM remove VM from list func (g *GctScriptManager) RemoveVM(id uuid.UUID) error { - if _, f := AllVMSync.Load(id); !f { + if _, ok := AllVMSync.Load(id); !ok { return fmt.Errorf(ErrNoVMFound, id.String()) } diff --git a/gctscript/vm/manager.go b/gctscript/vm/manager.go index f29fec27..074dea1a 100644 --- a/gctscript/vm/manager.go +++ b/gctscript/vm/manager.go @@ -33,17 +33,12 @@ func NewManager(config *Config) (*GctScriptManager, error) { if config == nil { return nil, errors.New("config must be provided for script manager") } - return &GctScriptManager{ - config: config, - }, nil + return &GctScriptManager{config: config}, nil } // IsRunning returns if gctscript manager subsystem is started func (g *GctScriptManager) IsRunning() bool { - if g == nil { - return false - } - return atomic.LoadInt32(&g.started) == 1 + return g != nil && atomic.LoadInt32(&g.started) == 1 } // Start starts gctscript subsystem and creates shutdown channel @@ -54,12 +49,6 @@ func (g *GctScriptManager) Start(wg *sync.WaitGroup) (err error) { if !atomic.CompareAndSwapInt32(&g.started, 0, 1) { return fmt.Errorf("%s %s", caseName, ErrScriptFailedValidation) } - defer func() { - if err != nil { - atomic.CompareAndSwapInt32(&g.started, 1, 0) - } - }() - g.shutdown = make(chan struct{}) wg.Add(1) go g.run(wg) @@ -74,9 +63,7 @@ func (g *GctScriptManager) Stop() error { if atomic.LoadInt32(&g.started) == 0 { return fmt.Errorf("%s not running", caseName) } - defer func() { - atomic.CompareAndSwapInt32(&g.started, 1, 0) - }() + defer atomic.CompareAndSwapInt32(&g.started, 1, 0) if err := g.ShutdownAll(); err != nil { return err @@ -90,9 +77,7 @@ func (g *GctScriptManager) run(wg *sync.WaitGroup) { SetDefaultScriptOutput() g.autoLoad() - defer func() { - wg.Done() - }() + defer wg.Done() <-g.shutdown } diff --git a/gctscript/vm/vm.go b/gctscript/vm/vm.go index 2da5f96c..5724771d 100644 --- a/gctscript/vm/vm.go +++ b/gctscript/vm/vm.go @@ -16,6 +16,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" scriptevent "github.com/thrasher-corp/gocryptotrader/database/repository/script" + "github.com/thrasher-corp/gocryptotrader/gctscript/modules/gct" "github.com/thrasher-corp/gocryptotrader/gctscript/modules/loader" "github.com/thrasher-corp/gocryptotrader/gctscript/wrappers/validator" "github.com/thrasher-corp/gocryptotrader/log" @@ -33,10 +34,7 @@ func (g *GctScriptManager) NewVM() *VM { } newUUID, err := uuid.NewV4() if err != nil { - log.Error(log.GCTScriptMgr, Error{ - Action: "New: UUID", - Cause: err, - }) + log.Error(log.GCTScriptMgr, Error{Action: "New: UUID", Cause: err}) return nil } @@ -82,18 +80,19 @@ func (vm *VM) Load(file string) error { code, err := os.ReadFile(file) if err != nil { - return &Error{ - Action: "Load: ReadFile", - Script: file, - Cause: err, - } + return &Error{Action: "Load: ReadFile", Script: file, Cause: err} } vm.File = file vm.Path = filepath.Dir(file) vm.Script = tengo.NewScript(code) - scriptctx := vm.ShortName() + "-" + vm.ID.String() - err = vm.Script.Add("ctx", scriptctx) + + scriptCtx := &gct.Context{} + scriptCtx.Value = map[string]tengo.Object{ + "script": &tengo.String{Value: vm.ShortName() + "-" + vm.ID.String()}, + } + + err = vm.Script.Add("ctx", scriptCtx) if err != nil { return err } @@ -113,9 +112,8 @@ func (vm *VM) Load(file string) error { // Compile compiles to byte code loaded copy of vm script func (vm *VM) Compile() (err error) { - vm.Compiled = new(tengo.Compiled) vm.Compiled, err = vm.Script.Compile() - return + return err } // RunCtx runs compiled byte code with context.Context support. @@ -133,13 +131,10 @@ func (vm *VM) RunCtx() (err error) { err = vm.Compiled.RunContext(ctx) if err != nil { vm.event(StatusFailure, TypeExecute) - return Error{ - Action: "RunCtx", - Cause: err, - } + return Error{Action: "RunCtx", Cause: err} } vm.event(StatusSuccess, TypeExecute) - return + return nil } // CompileAndRun Compile and Run script with support for task running @@ -176,21 +171,18 @@ func (vm *VM) CompileAndRun() { } return } - if vm.T < time.Nanosecond { - log.Error(log.GCTScriptMgr, "Repeat timer cannot be under 1 nano second") - err = vm.Shutdown() - if err != nil { - log.Errorln(log.GCTScriptMgr, err) - } + if vm.T > 0 { + vm.runner() return } - vm.runner() - } else { - err = vm.Shutdown() - if err != nil { - log.Error(log.GCTScriptMgr, err) + + if vm.T < 0 { + log.Error(log.GCTScriptMgr, "Repeat timer cannot be under 1 nano second") } - return + } + err = vm.Shutdown() + if err != nil { + log.Error(log.GCTScriptMgr, err) } } @@ -247,24 +239,23 @@ func (vm *VM) event(status, executionType string) { } func (vm *VM) scriptData() ([]byte, error) { - buf := new(bytes.Buffer) - w := zip.NewWriter(buf) - - f, err := w.Create(vm.ShortName()) - if err != nil { - return []byte{}, err - } contents, err := vm.read() if err != nil { - return []byte{}, err + return nil, err + } + buf := new(bytes.Buffer) + w := zip.NewWriter(buf) + f, err := w.Create(vm.ShortName()) + if err != nil { + return nil, err } _, err = f.Write(contents) if err != nil { - return []byte{}, err + return nil, err } err = w.Close() if err != nil { - return []byte{}, err + return nil, err } return buf.Bytes(), nil } diff --git a/gctscript/vm/vm_types.go b/gctscript/vm/vm_types.go index 34db127d..1f3cf3ec 100644 --- a/gctscript/vm/vm_types.go +++ b/gctscript/vm/vm_types.go @@ -34,11 +34,7 @@ const ( type vmscount int32 var ( - pool = &sync.Pool{ - New: func() interface{} { - return new(tengo.Script) - }, - } + pool = &sync.Pool{New: func() interface{} { return new(tengo.Script) }} // AllVMSync stores all current Virtual Machine instances AllVMSync = &sync.Map{} // VMSCount running total count of Virtual Machines diff --git a/gctscript/wrappers/gct/gctwrapper_test.go b/gctscript/wrappers/gct/gctwrapper_test.go index 31fd9af9..79c23b3c 100644 --- a/gctscript/wrappers/gct/gctwrapper_test.go +++ b/gctscript/wrappers/gct/gctwrapper_test.go @@ -11,7 +11,6 @@ import ( objects "github.com/d5/tengo/v2" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/engine" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/gctscript/modules" "github.com/thrasher-corp/gocryptotrader/gctscript/modules/gct" @@ -102,6 +101,8 @@ var ( Value: "1235", } + ctx = &gct.Context{} + tv = objects.TrueValue fv = objects.FalseValue errTestFailed = errors.New("test failed") @@ -109,12 +110,12 @@ var ( func TestExchangeOrderbook(t *testing.T) { t.Parallel() - _, err := gct.ExchangeOrderbook(exch, currencyPair, delimiter, assetType) + _, err := gct.ExchangeOrderbook(ctx, exch, currencyPair, delimiter, assetType) if err != nil { t.Fatal(err) } - _, err = gct.ExchangeOrderbook(exchError, currencyPair, delimiter, assetType) + _, err = gct.ExchangeOrderbook(ctx, exchError, currencyPair, delimiter, assetType) if err != nil && errors.Is(err, errTestFailed) { t.Fatal(err) } @@ -127,12 +128,12 @@ func TestExchangeOrderbook(t *testing.T) { func TestExchangeTicker(t *testing.T) { t.Parallel() - _, err := gct.ExchangeTicker(exch, currencyPair, delimiter, assetType) + _, err := gct.ExchangeTicker(ctx, exch, currencyPair, delimiter, assetType) if err != nil { t.Fatal(err) } - _, err = gct.ExchangeTicker(exchError, currencyPair, delimiter, assetType) + _, err = gct.ExchangeTicker(ctx, exchError, currencyPair, delimiter, assetType) if err != nil && errors.Is(err, errTestFailed) { t.Fatal(err) } @@ -190,9 +191,14 @@ func TestAccountInfo(t *testing.T) { if !errors.Is(err, objects.ErrWrongNumArguments) { t.Fatal(err) } - _, err = gct.ExchangeAccountInfo(exch, assetType) - if !errors.Is(err, exchange.ErrAuthenticationSupportNotEnabled) { - t.Errorf("received: %v but expected: %v", err, exchange.ErrAuthenticationSupportNotEnabled) + obj, err := gct.ExchangeAccountInfo(ctx, exch, assetType) + if err != nil { + t.Fatalf("received: %v but expected: %v", err, nil) + } + rString, _ := objects.ToString(obj) + if rString != `error: "Bitstamp REST or Websocket authentication support is not enabled"` { + t.Errorf("received: %v but expected: %v", + rString, `error: "Bitstamp REST or Websocket authentication support is not enabled"`) } } @@ -204,7 +210,7 @@ func TestExchangeOrderQuery(t *testing.T) { t.Fatal(err) } - _, err = gct.ExchangeOrderQuery(exch, orderID) + _, err = gct.ExchangeOrderQuery(ctx, exch, orderID) if err != nil && err != common.ErrNotYetImplemented { t.Error(err) } @@ -216,7 +222,7 @@ func TestExchangeOrderCancel(t *testing.T) { if !errors.Is(err, objects.ErrWrongNumArguments) { t.Fatal(err) } - _, err = gct.ExchangeOrderCancel(exch, orderID, currencyPair, assetType) + _, err = gct.ExchangeOrderCancel(ctx, exch, orderID, currencyPair, assetType) if err != nil && err != common.ErrNotYetImplemented { t.Error(err) } @@ -235,10 +241,25 @@ func TestExchangeOrderSubmit(t *testing.T) { 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 !errors.Is(err, exchange.ErrAuthenticationSupportNotEnabled) { - t.Errorf("received: %v but expected: %v", err, exchange.ErrAuthenticationSupportNotEnabled) + obj, err := gct.ExchangeOrderSubmit(ctx, + exch, + currencyPair, + delimiter, + orderType, + orderSide, + orderPrice, + orderAmount, + orderID, + orderAsset) + if err != nil { + t.Fatalf("received: %v but expected: %v", err, nil) + } + + rString, _ := objects.ToString(obj) + if rString != `error: "Bitstamp REST or Websocket authentication support is not enabled"` { + t.Errorf("received: [%v] but expected: %v", + rString, + `error: "Bitstamp REST or Websocket authentication support is not enabled"`) } } @@ -278,7 +299,14 @@ func TestExchangeWithdrawCrypto(t *testing.T) { address := &objects.String{Value: "0xTHISISALEGITBTCADDRESSS"} amount := &objects.Float{Value: 1.0} - _, err = gct.ExchangeWithdrawCrypto(exch, currCode, address, address, amount, amount, desc) + _, err = gct.ExchangeWithdrawCrypto(ctx, + exch, + currCode, + address, + address, + amount, + amount, + desc) if err != nil { t.Error(err) } @@ -295,7 +323,7 @@ func TestExchangeWithdrawFiat(t *testing.T) { amount := &objects.Float{Value: 1.0} desc := &objects.String{Value: "2"} bankID := &objects.String{Value: "3!"} - _, err = gct.ExchangeWithdrawFiat(exch, currCode, desc, amount, bankID) + _, err = gct.ExchangeWithdrawFiat(ctx, exch, currCode, desc, amount, bankID) if err != nil && err.Error() != "exchange Bitstamp bank details not found for TEST" { t.Error(err) } diff --git a/go.mod b/go.mod index dce5ead0..99a1255d 100644 --- a/go.mod +++ b/go.mod @@ -31,6 +31,7 @@ require ( ) require ( + cloud.google.com/go/compute v1.7.0 // indirect github.com/boombuler/barcode v1.0.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/friendsofgo/errors v0.9.2 // indirect diff --git a/go.sum b/go.sum index 205ebe2b..aeff14bd 100644 --- a/go.sum +++ b/go.sum @@ -28,7 +28,6 @@ cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+Y cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.2 h1:t9Iw5QH5v4XtlEQaCtUY7x6sCABps8sW0acw7e2WQ6Y= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go v0.102.0 h1:DAq3r8y4mDgyB/ZPJ9v/5VJNqjgJAxTn6ZYLlUywOu8= cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= @@ -42,7 +41,6 @@ cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTB cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1 h1:2sMmt8prCn7DPaG4Pmh0N3Inmc8cT8ae5k1M6VJ9Wqc= cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.7.0 h1:v/k9Eueb8aAJ0vZuxKMrgm6kPhCLZU9HxFU+AFDs9Uk= cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U=