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 <gloriousCode@users.noreply.github.com>

* 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 <gloriousCode@users.noreply.github.com>

* Update exchanges/account/credentials_test.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update exchanges/account/credentials_test.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* 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 <gloriousCode@users.noreply.github.com>

* Update gctscript/vm/vm.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update gctscript/examples/exchange/account_info.gct

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update gctscript/examples/exchange/cancel_order.gct

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update gctscript/examples/verbose.gct

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update gctscript/modules/gct/gct_test.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* 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 <gloriousCode@users.noreply.github.com>

* Update gctscript/modules/gct/exchange.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update gctscript/modules/gct/exchange.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update gctscript/modules/gct/exchange.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update gctscript/modules/gct/gct.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update gctscript/modules/gct/gct.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update gctscript/modules/gct/gct.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update gctscript/modules/gct/gct.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update gctscript/modules/gct/gct.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update gctscript/modules/gct/gct.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* Update gctscript/modules/gct/gct.go

Co-authored-by: Scott <gloriousCode@users.noreply.github.com>

* glorious: nits/reverts

* go mod: tidy

Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io>
Co-authored-by: Scott <gloriousCode@users.noreply.github.com>
This commit is contained in:
Ryan O'Hara-Reid
2022-08-17 14:18:53 +10:00
committed by GitHub
parent 68588560e3
commit e93ee83563
42 changed files with 1110 additions and 372 deletions

View File

@@ -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:

View File

@@ -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()

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -3,6 +3,9 @@ exch := import("exchange")
load := func() {
info := exch.depositaddress("BTC Markets", "BTC", "")
if is_error(info) {
// handle error
}
fmt.println(info)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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()
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -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))
}

View File

@@ -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)
}
}

View File

@@ -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)

View File

@@ -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"
}

View File

@@ -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")
}
}

View File

@@ -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
}

View File

@@ -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())
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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)
}