Engine: Scripting support (#383)

* WIP

* updated appveyor and increased deadline 5 seconds due to increased linters being added

* revert files to upstream/engine

* WIP

* WIP

* mod file changes

* added script manager

* Added manager/and cli interfaces to scripting

* Added script task handler

* WIP - Added timer/repeat support and fleshed out wrapper further

* autoload support added + WIP

* WIP commit

* added account balance info

* btc markets temp work around

* WIP - merged with upstream for new order package BTC Markets responses broken

* Cancel order wrapper WIP

*  order wrapper update

* Added test coverage for VM

* moved to map for VM List shutdown of all VM now handled added gctcli commands for list and stop of running scripts

* added override to load/execute for path

* fixed incorrect channel shutdown added further test coverage and restructured gctcli commands into sub commands

* increased test coverage for packages

* Added docs cleaned up tests and example scripts

* Test coverage increased for module/gct/exchange package

* windows fixes

* merged upstream/engine

* WIP

* logger fixes - removed pointer to bool check removed duplicate test check for logger

* remove unused mutex

* added inital upload support

* fix linter issues for go-fmt

* added zip support for uploading and added base for fund withdrawing

* changed error return types and also log errors, fix zip path issue

* improved error outputs and code flow

* pairs response fix added protobuf defs for stop all and list all

* added stop all running scripts general clean up and moved across to OrderManager

* linter fixes (gofmt)

* added list all command

* rewrote zip handler to be cleaner also fixed file overwrite on upload

* added query command reworked tests

* added further error checking to compileandrun corrected use of pointers for accountinfo

* bumped tengo version

* Removed named returns reworded log messages removed unused falseptr

* WIP

* Added virutal machine limit improved config options

* added model for script event added upload validation

* script_event table has been completed, tests for wrapper functions implemented

* README updates

* reverted changes opened new PR to move withdraw struct outs

* intial work on adding withdraw support after merger of withdraw package

* started work on examples

* Added crypto withdraw support

* fix switch case assignment and gofmt project

* Reworking Fiat withdraw request pending #402

* removed double pointer call

* added withdraw support for fiat  currencies

* added tests for withdraw methods increased readme

* removed local tengo require and also fix linter issues

* Added default log size const added basic test for invalid script execution

* First pass at moving wrapper to validator package to allow proper validation of uploaded scripts

* Added script details to README added config test added test for no file extension

* moved tests to const and fixed incorrect pathing

* added test coverage to withdraw package

* corrected file close handling

* point to included configtest.json

* extended validator support when a script is uploaded

* Bug fix on bool logic

* Added mutex

* Don't create autit events on test execution

* reverted common to master

* moved file rename to unix timestamp format

* converted logger enabled back to pointer as i need nilness check also moved scriptid to text over blob

* started work on autoload add/remove support

* First round of PR fixes (mostly commented exports)

* Moved GCTScript load to last, removed unneeded error from cleanup()

* Comment clairty for AuitEventID

* added autoload add/remove command to cli

* added tests for autoload

* Test updates for Exchanges

* linter fixes (gofmt)

* Removed double check of engine pointer

* remove possible nil pointer on GetSpecificTicker

* Fixed not closing file handler on write that causes archive removal to fail

* file handler Close clean ups

* corrected spelling on error return and return invalid name n autoload

* moved strings to cosnt moved bool pointer creation to convert package

* new zip extractor added

* Validation has been added to archive uploads

* removed shadow var on err

* added ok check to conversion

* converted condition check

* basic test for zip extract added

* new zip handler

* reverted back to old atomic loading system

* removed shadow err

* lets add a new line

* added space to error return

* command line toggle for script now works properly

* readme updated

* set configLoaded to true

* check for configLoaded condition

* added mutex to allow for multiple access on virtual machine increased test coverage disable script manager if scripting is disabled

* linked up to enable/disablesubsystem commands

* added start/stop example to readme

* reworked logic on test as check should be done on Load()

* updated to tengo v2

* linters

* lower time on ntp client to stop slippage

* remove all fails if any fail validtion from an archive

* remove vm from list if timer is invalid

* removed shadow on err

* remove config creation from NTPCheck test

* WIP testing DB changes

* add unique constraint

* WIP: created has many model

* linters run

* basic sqlite3 support added for new database format

* linters run

* Added test coverage for script repo

* removed unused print

* updated env vars for CI instances

* updated env vars for CI instances

* Updated test packages

* Test updates for postgresql

* removed invalid tests from postgres

* remove duplication of struct and improved code flow

* general cleanup

* wording changes on log output

* use databasemgr logger and add support for autoload without file extension

* corrected test naming

* return correct error

* return correct error again version 82

* store scriptdata on creation

* Hello

* Errorln -> Errorf

* Removed unused vars

* Read me updates

* testing without parallel

* comment on exported type

* added nil check against VM for test

* add debugging information

* gofmt

* remove verbose and data sent to channel

* Added debug information

* linter fixes (gofmt)

* remove unused CompileAndRun() call

* test sleep to see if issue is timing related

* semi-concurrent map fixes

* one day i will run gofmt or setup precommit hooks

* new line :D

* increased test coverage

* added correct sleep time

* Moved over to sync map

* linter fixes (gofmt)

* goimports

* moved VM related methods to vm.go

* new line at end of file

* trying increased timeout on golangci-lint for appveyor

* add debugging information

* removed timeout

* reworked timeout logic

* linter fixes (gofmt)

* increased test coverage

* increased test coverage

* one day i will run gofmt or setup precommit hooks

* removed unused exchange test

* increased golangci-lint timeout

* Added nil check on shutdown and test coverage for it lowered timeout back to 1:30

* reworked ID system

* removed script hash as it was unused

* added comments on exported methods and read me update

* reorder code

* removed to atomic.value for test execution flag

* increased test coverage

* move add further up execution

* point to correct script file
This commit is contained in:
Andrew
2020-01-23 13:54:04 +11:00
committed by Adrian Gallagher
parent 4c33a0738a
commit f6fd94ea69
102 changed files with 15181 additions and 370 deletions

View File

@@ -0,0 +1,498 @@
package gct
import (
"fmt"
"strings"
objects "github.com/d5/tengo/v2"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/withdraw"
"github.com/thrasher-corp/gocryptotrader/gctscript/wrappers"
)
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},
}
// ExchangeOrderbook returns orderbook for requested exchange & currencypair
func ExchangeOrderbook(args ...objects.Object) (objects.Object, error) {
if len(args) != 4 {
return nil, objects.ErrWrongNumArguments
}
exchangeName, ok := objects.ToString(args[0])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName)
}
currencyPair, ok := objects.ToString(args[1])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, currencyPair)
}
delimiter, ok := objects.ToString(args[2])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, delimiter)
}
assetTypeParam, ok := objects.ToString(args[3])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, assetTypeParam)
}
pairs := currency.NewPairDelimiter(currencyPair, delimiter)
assetType := asset.Item(assetTypeParam)
ob, err := wrappers.GetWrapper().Orderbook(exchangeName, pairs, assetType)
if err != nil {
return nil, err
}
var asks, bids objects.Array
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})
}
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})
}
data := make(map[string]objects.Object, 5)
data["exchange"] = &objects.String{Value: ob.ExchangeName}
data["pair"] = &objects.String{Value: ob.Pair.String()}
data["asks"] = &asks
data["bids"] = &bids
data["asset"] = &objects.String{Value: ob.AssetType.String()}
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 {
return nil, objects.ErrWrongNumArguments
}
exchangeName, ok := objects.ToString(args[0])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName)
}
currencyPair, ok := objects.ToString(args[1])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, currencyPair)
}
delimiter, ok := objects.ToString(args[2])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, delimiter)
}
assetTypeParam, ok := objects.ToString(args[3])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, assetTypeParam)
}
pairs := currency.NewPairDelimiter(currencyPair, delimiter)
assetType := asset.Item(assetTypeParam)
tx, err := wrappers.GetWrapper().Ticker(exchangeName, pairs, assetType)
if err != nil {
return nil, err
}
data := make(map[string]objects.Object, 14)
data["exchange"] = &objects.String{Value: tx.ExchangeName}
data["last"] = &objects.Float{Value: tx.Last}
data["High"] = &objects.Float{Value: tx.High}
data["Low"] = &objects.Float{Value: tx.Low}
data["bid"] = &objects.Float{Value: tx.Bid}
data["ask"] = &objects.Float{Value: tx.Ask}
data["volume"] = &objects.Float{Value: tx.Volume}
data["quotevolume"] = &objects.Float{Value: tx.QuoteVolume}
data["priceath"] = &objects.Float{Value: tx.PriceATH}
data["open"] = &objects.Float{Value: tx.Open}
data["close"] = &objects.Float{Value: tx.Close}
data["pair"] = &objects.String{Value: tx.Pair.String()}
data["asset"] = &objects.String{Value: tx.AssetType.String()}
data["updated"] = &objects.Time{Value: tx.LastUpdated}
return &objects.Map{
Value: data,
}, nil
}
// ExchangeExchanges returns list of exchanges either enabled or all
func ExchangeExchanges(args ...objects.Object) (objects.Object, error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
enabledOnly, ok := objects.ToBool(args[0])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, enabledOnly)
}
rtnValue := wrappers.GetWrapper().Exchanges(enabledOnly)
r := objects.Array{}
for x := range rtnValue {
r.Value = append(r.Value, &objects.String{Value: rtnValue[x]})
}
return &r, nil
}
// ExchangePairs returns currency pairs for requested exchange
func ExchangePairs(args ...objects.Object) (objects.Object, error) {
if len(args) != 3 {
return nil, objects.ErrWrongNumArguments
}
exchangeName, ok := objects.ToString(args[0])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName)
}
enabledOnly, ok := objects.ToBool(args[1])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, enabledOnly)
}
assetTypeParam, ok := objects.ToString(args[2])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, assetTypeParam)
}
assetType := asset.Item(strings.ToLower(assetTypeParam))
rtnValue, err := wrappers.GetWrapper().Pairs(exchangeName, enabledOnly, assetType)
if err != nil {
return nil, err
}
r := objects.Array{}
for x := range rtnValue.Slice() {
r.Value = append(r.Value, &objects.String{Value: rtnValue.Slice()[x].String()})
}
return &r, nil
}
// ExchangeAccountInfo returns account information for requested exchange
func ExchangeAccountInfo(args ...objects.Object) (objects.Object, error) {
if len(args) != 1 {
return nil, objects.ErrWrongNumArguments
}
exchangeName, ok := objects.ToString(args[0])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName)
}
rtnValue, err := wrappers.GetWrapper().AccountInformation(exchangeName)
if err != nil {
return nil, err
}
var funds objects.Array
for x := range rtnValue.Accounts {
for y := range rtnValue.Accounts[x].Currencies {
temp := make(map[string]objects.Object, 3)
temp["name"] = &objects.String{Value: rtnValue.Accounts[x].Currencies[y].CurrencyName.String()}
temp["total"] = &objects.Float{Value: rtnValue.Accounts[x].Currencies[y].TotalValue}
temp["hold"] = &objects.Float{Value: rtnValue.Accounts[x].Currencies[y].Hold}
funds.Value = append(funds.Value, &objects.Map{Value: temp})
}
}
data := make(map[string]objects.Object, 2)
data["exchange"] = &objects.String{Value: rtnValue.Exchange}
data["currencies"] = &funds
return &objects.Map{
Value: data,
}, nil
}
// ExchangeOrderQuery query order on exchange
func ExchangeOrderQuery(args ...objects.Object) (objects.Object, error) {
if len(args) != 2 {
return nil, objects.ErrWrongNumArguments
}
exchangeName, ok := objects.ToString(args[0])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName)
}
orderID, ok := objects.ToString(args[1])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, orderID)
}
orderDetails, err := wrappers.GetWrapper().QueryOrder(exchangeName, orderID)
if err != nil {
return nil, err
}
var tradeHistory objects.Array
for x := range orderDetails.Trades {
temp := make(map[string]objects.Object, 7)
temp["timestamp"] = &objects.Time{Value: orderDetails.Trades[x].Timestamp}
temp["price"] = &objects.Float{Value: orderDetails.Trades[x].Price}
temp["fee"] = &objects.Float{Value: orderDetails.Trades[x].Fee}
temp["amount"] = &objects.Float{Value: orderDetails.Trades[x].Amount}
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})
}
data := make(map[string]objects.Object, 14)
data["exchange"] = &objects.String{Value: orderDetails.Exchange}
data["id"] = &objects.String{Value: orderDetails.ID}
data["accountid"] = &objects.String{Value: orderDetails.AccountID}
data["currencypair"] = &objects.String{Value: orderDetails.CurrencyPair.String()}
data["price"] = &objects.Float{Value: orderDetails.Price}
data["amount"] = &objects.Float{Value: orderDetails.Amount}
data["amountexecuted"] = &objects.Float{Value: orderDetails.ExecutedAmount}
data["amountremaining"] = &objects.Float{Value: orderDetails.RemainingAmount}
data["fee"] = &objects.Float{Value: orderDetails.Fee}
data["side"] = &objects.String{Value: orderDetails.OrderSide.String()}
data["type"] = &objects.String{Value: orderDetails.OrderType.String()}
data["date"] = &objects.String{Value: orderDetails.OrderDate.String()}
data["status"] = &objects.String{Value: orderDetails.Status.String()}
data["trades"] = &tradeHistory
return &objects.Map{
Value: data,
}, nil
}
// ExchangeOrderCancel cancels order on requested exchange
func ExchangeOrderCancel(args ...objects.Object) (objects.Object, error) {
if len(args) != 2 {
return nil, objects.ErrWrongNumArguments
}
exchangeName, ok := objects.ToString(args[0])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName)
}
orderID, ok := objects.ToString(args[1])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, orderID)
}
rtn, err := wrappers.GetWrapper().CancelOrder(exchangeName, orderID)
if err != nil {
return nil, err
}
if rtn {
return objects.TrueValue, nil
}
return objects.FalseValue, nil
}
// ExchangeOrderSubmit submit order on exchange
func ExchangeOrderSubmit(args ...objects.Object) (objects.Object, error) {
if len(args) != 8 {
return nil, objects.ErrWrongNumArguments
}
exchangeName, ok := objects.ToString(args[0])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName)
}
currencyPair, ok := objects.ToString(args[1])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, currencyPair)
}
delimiter, ok := objects.ToString(args[2])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, delimiter)
}
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)
}
pair := currency.NewPairDelimiter(currencyPair, delimiter)
tempSubmit := &order.Submit{
Pair: pair,
OrderType: order.Type(orderType),
OrderSide: order.Side(orderSide),
Price: orderPrice,
Amount: orderAmount,
ClientID: orderClientID,
}
err := tempSubmit.Validate()
if err != nil {
return nil, err
}
rtn, err := wrappers.GetWrapper().SubmitOrder(exchangeName, tempSubmit)
if err != nil {
return nil, err
}
data := make(map[string]objects.Object, 2)
data["orderid"] = &objects.String{Value: rtn.OrderID}
if rtn.IsOrderPlaced {
data["isorderplaced"] = objects.TrueValue
} else {
data["isorderplaced"] = objects.FalseValue
}
return &objects.Map{
Value: data,
}, nil
}
// ExchangeDepositAddress returns deposit address (if supported by exchange)
func ExchangeDepositAddress(args ...objects.Object) (objects.Object, error) {
if len(args) != 2 {
return nil, objects.ErrWrongNumArguments
}
exchangeName, ok := objects.ToString(args[0])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName)
}
currencyCode, ok := objects.ToString(args[1])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, currencyCode)
}
currCode := currency.NewCode(currencyCode)
rtn, err := wrappers.GetWrapper().DepositAddress(exchangeName, currCode)
if err != nil {
return nil, err
}
return &objects.String{Value: rtn}, nil
}
// ExchangeWithdrawCrypto submit request to withdraw crypto assets
func ExchangeWithdrawCrypto(args ...objects.Object) (objects.Object, error) {
if len(args) != 7 {
return nil, objects.ErrWrongNumArguments
}
exchangeName, ok := objects.ToString(args[0])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName)
}
cur, ok := objects.ToString(args[1])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, cur)
}
address, ok := objects.ToString(args[2])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, address)
}
addressTag, ok := objects.ToString(args[3])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, addressTag)
}
amount, ok := objects.ToFloat64(args[4])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, amount)
}
feeAmount, ok := objects.ToFloat64(args[5])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, feeAmount)
}
description, ok := objects.ToString(args[6])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, description)
}
withdrawRequest := &withdraw.CryptoRequest{
GenericInfo: withdraw.GenericInfo{
Currency: currency.NewCode(cur),
Description: description,
Amount: amount,
},
Address: address,
AddressTag: addressTag,
FeeAmount: feeAmount,
}
rtn, err := wrappers.GetWrapper().WithdrawalCryptoFunds(exchangeName, withdrawRequest)
if err != nil {
return nil, err
}
return &objects.String{Value: rtn}, nil
}
// ExchangeWithdrawFiat submit request to withdraw fiat assets
func ExchangeWithdrawFiat(args ...objects.Object) (objects.Object, error) {
if len(args) != 5 {
return nil, objects.ErrWrongNumArguments
}
exchangeName, ok := objects.ToString(args[0])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, exchangeName)
}
cur, ok := objects.ToString(args[1])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, cur)
}
description, ok := objects.ToString(args[2])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, description)
}
amount, ok := objects.ToFloat64(args[3])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, amount)
}
bankAccountID, ok := objects.ToString(args[4])
if !ok {
return nil, fmt.Errorf(ErrParameterConvertFailed, bankAccountID)
}
withdrawRequest := &withdraw.FiatRequest{
GenericInfo: withdraw.GenericInfo{
Currency: currency.NewCode(cur),
Description: description,
Amount: amount,
},
}
rtn, err := wrappers.GetWrapper().WithdrawalFiatFunds(exchangeName, bankAccountID, withdrawRequest)
if err != nil {
return nil, err
}
return &objects.String{Value: rtn}, nil
}

View File

@@ -0,0 +1,10 @@
package gct
// AllModuleNames returns a list of all default module names.
func AllModuleNames() []string {
var names []string
for name := range Modules {
names = append(names, name)
}
return names
}

View File

@@ -0,0 +1,270 @@
package gct
import (
"errors"
"os"
"reflect"
"testing"
objects "github.com/d5/tengo/v2"
"github.com/thrasher-corp/gocryptotrader/gctscript/modules"
"github.com/thrasher-corp/gocryptotrader/gctscript/wrappers/validator"
)
var (
exch = &objects.String{
Value: "BTC Markets",
}
exchError = &objects.String{
Value: "error",
}
currencyPair = &objects.String{
Value: "BTC-AUD",
}
delimiter = &objects.String{
Value: "-",
}
assetType = &objects.String{
Value: "SPOT",
}
orderID = &objects.String{
Value: "1235",
}
tv = objects.TrueValue
fv = objects.FalseValue
errTestFailed = errors.New("test failed")
)
func TestMain(m *testing.M) {
modules.SetModuleWrapper(validator.Wrapper{})
os.Exit(m.Run())
}
func TestExchangeOrderbook(t *testing.T) {
t.Parallel()
_, err := ExchangeOrderbook(exch, currencyPair, delimiter, assetType)
if err != nil {
t.Fatal(err)
}
_, err = ExchangeOrderbook(exchError, currencyPair, delimiter, assetType)
if err != nil && errors.Is(err, errTestFailed) {
t.Fatal(err)
}
_, err = ExchangeOrderbook()
if !errors.Is(err, objects.ErrWrongNumArguments) {
t.Fatal(err)
}
}
func TestExchangeTicker(t *testing.T) {
t.Parallel()
_, err := ExchangeTicker(exch, currencyPair, delimiter, assetType)
if err != nil {
t.Fatal(err)
}
_, err = ExchangeTicker(exchError, currencyPair, delimiter, assetType)
if err != nil && errors.Is(err, errTestFailed) {
t.Fatal(err)
}
_, err = ExchangeTicker()
if !errors.Is(err, objects.ErrWrongNumArguments) {
t.Fatal(err)
}
}
func TestExchangeExchanges(t *testing.T) {
t.Parallel()
_, err := ExchangeExchanges(tv)
if err != nil {
t.Fatal(err)
}
_, err = ExchangeExchanges(exch)
if err != nil {
t.Fatal(err)
}
_, err = ExchangeExchanges(fv)
if err != nil {
t.Fatal(err)
}
_, err = ExchangeExchanges()
if !errors.Is(err, objects.ErrWrongNumArguments) {
t.Fatal(err)
}
}
func TestExchangePairs(t *testing.T) {
t.Parallel()
_, err := ExchangePairs(exch, tv, assetType)
if err != nil {
t.Fatal(err)
}
_, err = ExchangePairs(exchError, tv, assetType)
if err != nil && errors.Is(err, errTestFailed) {
t.Fatal(err)
}
_, err = ExchangePairs()
if !errors.Is(err, objects.ErrWrongNumArguments) {
t.Fatal(err)
}
}
func TestAccountInfo(t *testing.T) {
t.Parallel()
_, err := ExchangeAccountInfo()
if !errors.Is(err, objects.ErrWrongNumArguments) {
t.Fatal(err)
}
_, err = ExchangeAccountInfo(exch)
if err != nil {
t.Fatal(err)
}
_, err = ExchangeAccountInfo(exchError)
if err != nil && !errors.Is(err, errTestFailed) {
t.Fatal(err)
}
}
func TestExchangeOrderQuery(t *testing.T) {
t.Parallel()
_, err := ExchangeOrderQuery()
if !errors.Is(err, objects.ErrWrongNumArguments) {
t.Fatal(err)
}
_, err = ExchangeOrderQuery(exch, orderID)
if err != nil {
t.Fatal(err)
}
_, err = ExchangeOrderQuery(exchError, orderID)
if err != nil && !errors.Is(err, errTestFailed) {
t.Fatal(err)
}
}
func TestExchangeOrderCancel(t *testing.T) {
_, err := ExchangeOrderCancel()
if !errors.Is(err, objects.ErrWrongNumArguments) {
t.Fatal(err)
}
_, err = ExchangeOrderCancel(exch, orderID)
if err != nil {
t.Fatal(err)
}
_, err = ExchangeOrderCancel(exch, objects.FalseValue)
if err != nil {
t.Fatal(err)
}
_, err = ExchangeOrderCancel(exchError, orderID)
if err != nil && !errors.Is(err, errTestFailed) {
t.Fatal(err)
}
}
func TestExchangeOrderSubmit(t *testing.T) {
_, err := ExchangeOrderSubmit()
if !errors.Is(err, objects.ErrWrongNumArguments) {
t.Fatal(err)
}
orderSide := &objects.String{Value: "ASK"}
orderType := &objects.String{Value: "LIMIT"}
orderPrice := &objects.Float{Value: 1}
orderAmount := &objects.Float{Value: 1}
_, err = ExchangeOrderSubmit(exch, currencyPair, delimiter,
orderType, orderSide, orderPrice, orderAmount, orderID)
if err != nil && !errors.Is(err, errTestFailed) {
t.Fatal(err)
}
_, err = ExchangeOrderSubmit(exch, currencyPair, delimiter,
orderType, orderSide, orderPrice, orderAmount, orderID)
if err != nil {
t.Fatal(err)
}
_, err = ExchangeOrderSubmit(objects.TrueValue, currencyPair, delimiter,
orderType, orderSide, orderPrice, orderAmount, orderID)
if err != nil {
t.Fatal(err)
}
}
func TestAllModuleNames(t *testing.T) {
x := AllModuleNames()
xType := reflect.TypeOf(x).Kind()
if xType != reflect.Slice {
t.Fatalf("AllModuleNames() should return slice instead received: %v", x)
}
}
func TestExchangeDepositAddress(t *testing.T) {
_, err := ExchangeDepositAddress()
if !errors.Is(err, objects.ErrWrongNumArguments) {
t.Fatal(err)
}
currCode := &objects.String{Value: "BTC"}
_, err = ExchangeDepositAddress(exch, currCode)
if err != nil {
t.Fatal(err)
}
_, err = ExchangeDepositAddress(exchError, currCode)
if err != nil && !errors.Is(err, errTestFailed) {
t.Fatal(err)
}
}
func TestExchangeWithdrawCrypto(t *testing.T) {
_, err := ExchangeWithdrawCrypto()
if !errors.Is(err, objects.ErrWrongNumArguments) {
t.Fatal(err)
}
currCode := &objects.String{Value: "BTC"}
desc := &objects.String{Value: "HELLO"}
address := &objects.String{Value: "0xTHISISALEGITBTCADDRESSS"}
amount := &objects.Float{Value: 1.0}
_, err = ExchangeWithdrawCrypto(exch, currCode, address, address, amount, amount, desc)
if err != nil {
t.Fatal(err)
}
}
func TestExchangeWithdrawFiat(t *testing.T) {
_, err := ExchangeWithdrawFiat()
if !errors.Is(err, objects.ErrWrongNumArguments) {
t.Fatal(err)
}
currCode := &objects.String{Value: "AUD"}
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)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -0,0 +1,15 @@
package gct
import (
"github.com/d5/tengo/v2"
)
const (
// ErrParameterConvertFailed error to return when type conversion fails
ErrParameterConvertFailed = "%v failed conversion"
)
// Modules map of all loadable modules
var Modules = map[string]map[string]tengo.Object{
"exchange": exchangeModule,
}

View File

@@ -0,0 +1,32 @@
package loader
import (
"github.com/d5/tengo/v2"
"github.com/d5/tengo/v2/stdlib"
"github.com/thrasher-corp/gocryptotrader/gctscript/modules/gct"
)
// GetModuleMap returns the module map that includes all modules
// for the given module names.
func GetModuleMap() *tengo.ModuleMap {
modules := tengo.NewModuleMap()
gctModuleList := gct.AllModuleNames()
for _, name := range gctModuleList {
if mod := gct.Modules[name]; mod != nil {
modules.AddBuiltinModule(name, mod)
}
}
stdLib := stdlib.AllModuleNames()
for _, name := range stdLib {
if mod := stdlib.BuiltinModules[name]; mod != nil {
modules.AddBuiltinModule(name, mod)
}
if mod := stdlib.SourceModules[name]; mod != "" {
modules.AddSourceModule(name, []byte(mod))
}
}
return modules
}

View File

@@ -0,0 +1,18 @@
package loader
import (
"reflect"
"testing"
)
func TestGetModuleMap(t *testing.T) {
x := GetModuleMap()
xType := reflect.TypeOf(x).String()
if xType != "*tengo.ModuleMap" {
t.Fatalf("GetModuleMap() should return pointer to ModuleMap instead received: %v", x)
}
if x.Len() == 0 {
t.Fatal("expected GetModuleMap() to contain module results instead received 0 value")
}
}

View File

@@ -0,0 +1,59 @@
package modules
import (
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
"github.com/thrasher-corp/gocryptotrader/exchanges/withdraw"
)
// Wrapper instance of GCT to use for modules
var Wrapper GCT
// GCT interface requirements
type GCT interface {
Exchange
}
// Exchange interface requirements
type Exchange interface {
Exchanges(enabledOnly bool) []string
IsEnabled(exch string) bool
Orderbook(exch string, pair currency.Pair, item asset.Item) (*orderbook.Base, error)
Ticker(exch string, pair currency.Pair, item asset.Item) (*ticker.Price, error)
Pairs(exch string, enabledOnly bool, item asset.Item) (*currency.Pairs, error)
QueryOrder(exch, orderid string) (*order.Detail, error)
SubmitOrder(exch string, submit *order.Submit) (*order.SubmitResponse, error)
CancelOrder(exch, orderid string) (bool, error)
AccountInformation(exch string) (*AccountInfo, error)
DepositAddress(exch string, currencyCode currency.Code) (string, error)
WithdrawalFiatFunds(exch, bankaccountid string, request *withdraw.FiatRequest) (out string, err error)
WithdrawalCryptoFunds(exch string, request *withdraw.CryptoRequest) (out string, err error)
}
// SetModuleWrapper link the wrapper and interface to use for modules
func SetModuleWrapper(wrapper GCT) {
Wrapper = wrapper
}
// AccountInfo is a Generic type to hold each exchange's holdings in
// all enabled currencies
type AccountInfo struct {
Exchange string
Accounts []Account
}
// Account defines a singular account type with associated currencies
type Account struct {
ID string
Currencies []AccountCurrencyInfo
}
// AccountCurrencyInfo is a sub type to store currency name and value
type AccountCurrencyInfo struct {
CurrencyName currency.Code
TotalValue float64
Hold float64
}