diff --git a/common/common.go b/common/common.go index 580dbb8a..67ee52a9 100644 --- a/common/common.go +++ b/common/common.go @@ -1,7 +1,6 @@ package common import ( - "encoding/csv" "encoding/json" "errors" "fmt" @@ -262,23 +261,6 @@ func ExtractPort(host string) int { return port } -// OutputCSV dumps data into a file as comma-separated values -func OutputCSV(filePath string, data [][]string) error { - file, err := os.Create(filePath) - if err != nil { - return err - } - - writer := csv.NewWriter(file) - if err = writer.WriteAll(data); err != nil { - file.Close() - return err - } - - file.Close() - return nil -} - // GetURIPath returns the path of a URL given a URI func GetURIPath(uri string) string { urip, err := url.Parse(uri) diff --git a/common/common_test.go b/common/common_test.go index 2a5ad76f..31006441 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -314,23 +314,6 @@ func TestExtractPort(t *testing.T) { } } -func TestOutputCSV(t *testing.T) { - path := "../testdata/dump" - var data [][]string - rowOne := []string{"Appended", "to", "two", "dimensional", "array"} - rowTwo := []string{"Appended", "to", "two", "dimensional", "array", "two"} - data = append(data, rowOne, rowTwo) - - err := OutputCSV(path, data) - if err != nil { - t.Errorf("common OutputCSV error: %s", err) - } - err = OutputCSV("/:::notapath:::", data) - if err == nil { - t.Error("common OutputCSV, tried writing to invalid path") - } -} - func TestGetURIPath(t *testing.T) { t.Parallel() // mapping of input vs expected result diff --git a/common/file/file.go b/common/file/file.go index 8d550062..9567e067 100644 --- a/common/file/file.go +++ b/common/file/file.go @@ -1,6 +1,9 @@ package file import ( + "bytes" + "encoding/csv" + "errors" "fmt" "io" "io/ioutil" @@ -58,3 +61,33 @@ func Exists(name string) bool { _, err := os.Stat(name) return !os.IsNotExist(err) } + +// WriteAsCSV takes a table of records and writes it as CSV +func WriteAsCSV(filename string, records [][]string) error { + if len(records) == 0 { + return errors.New("no records in matrix") + } + + buf := bytes.Buffer{} + w := csv.NewWriter(&buf) + + alignment := len(records[0]) + for i := range records { + if len(records[i]) != alignment { + return errors.New("incorrect alignment") + } + + err := w.Write(records[i]) + if err != nil { + return err + } + } + + w.Flush() + + err := w.Error() + if err != nil { + return err + } + return Write(filename, buf.Bytes()) +} diff --git a/common/file/file_test.go b/common/file/file_test.go index cb969ab0..7fd1fc1e 100644 --- a/common/file/file_test.go +++ b/common/file/file_test.go @@ -125,3 +125,63 @@ func TestExists(t *testing.T) { t.Errorf("unable to remove %s, manual deletion is required", tmpFile) } } + +func TestWriteAsCSV(t *testing.T) { + tester := func(in string, data [][]string) error { + err := WriteAsCSV(in, data) + if err != nil { + return err + } + return os.Remove(in) + } + + type testTable struct { + InFile string + Payload [][]string + ErrExpected bool + } + + records := [][]string{ + {"title", "first_name", "last_name"}, + {"King", "Robert", "Baratheon"}, + {"Lord Regent of the Seven Kingdoms", "Eddard", "Stark"}, + {"Lord of Baelish Castle", "Petyr", "Baelish"}, + } + + missAligned := [][]string{ + {"first_name", "last_name", "username"}, + {"Sup", "bra"}, + } + + testFile, err := ioutil.TempFile(os.TempDir(), "gct-csv-test.*.csv") + if err != nil { + t.Fatal(err) + } + testFile.Close() + defer os.Remove(testFile.Name()) + + tests := []testTable{ + {InFile: testFile.Name(), Payload: nil, ErrExpected: true}, + {InFile: testFile.Name(), Payload: records, ErrExpected: false}, + {InFile: testFile.Name(), Payload: missAligned, ErrExpected: true}, + } + switch runtime.GOOS { + case "windows": + tests = append(tests, + testTable{InFile: "*", Payload: [][]string{}, ErrExpected: true}, + testTable{InFile: "*", Payload: nil, ErrExpected: true}, + ) + default: + tests = append(tests, + testTable{InFile: "", Payload: [][]string{}, ErrExpected: true}, + testTable{InFile: "", Payload: nil, ErrExpected: true}, + ) + } + + for x := range tests { + err := tester(tests[x].InFile, tests[x].Payload) + if err != nil && !tests[x].ErrExpected { + t.Errorf("Test %d failed, unexpected err %s\n", x, err) + } + } +} diff --git a/config/config.go b/config/config.go index 08b0bdcf..4a92c3c7 100644 --- a/config/config.go +++ b/config/config.go @@ -1212,6 +1212,12 @@ func (c *Config) checkGCTScriptConfig() error { return err } + outputPath := filepath.Join(scriptPath, "output") + err = common.CreateDir(outputPath) + if err != nil { + return err + } + gctscript.ScriptPath = scriptPath gctscript.GCTScriptConfig = &c.GCTScript diff --git a/engine/gctscript.go b/engine/gctscript.go index 5fdb673d..80454224 100644 --- a/engine/gctscript.go +++ b/engine/gctscript.go @@ -64,6 +64,7 @@ func (g *gctScriptManager) run() { log.Debugln(log.Global, gctscriptManagerName, MsgSubSystemStarted) Bot.ServicesWG.Add(1) + vm.SetDefaultScriptOutput() g.autoLoad() defer func() { atomic.CompareAndSwapInt32(&g.stopped, 1, 0) diff --git a/gctscript/examples/csv.gct b/gctscript/examples/csv.gct new file mode 100644 index 00000000..dcb488b4 --- /dev/null +++ b/gctscript/examples/csv.gct @@ -0,0 +1,28 @@ +exch := import("exchange") +t := import("times") +// Import all the indicators you want +atr := import("indicator/atr") +sma := import("indicator/sma") +ema := import("indicator/ema") +common := import("common") + +load := func() { + // define your start and end within reason. + start := t.date(2017, 8 , 17, 0 , 0 , 0, 0) + end := t.add_date(start, 0, 6 , 0) + + // This fetches the ohlcv + ohlcvData := exch.ohlcv("binance", "BTC-USDT", "-", "SPOT", start, end, "1d") + + // construct ta values + avgtr := atr.calculate(ohlcvData.candles, 14) + simma := sma.calculate(ohlcvData.candles, 9) + expma := ema.calculate(ohlcvData.candles, 9) + + // '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. + common.writeascsv(ctx, ohlcvData, avgtr, simma, expma) +} + +load() \ No newline at end of file diff --git a/gctscript/examples/exchange/account_info.gct b/gctscript/examples/exchange/account_info.gct index 20d7f209..82c3721d 100644 --- a/gctscript/examples/exchange/account_info.gct +++ b/gctscript/examples/exchange/account_info.gct @@ -7,7 +7,7 @@ load := func() { // retrieve account information from exchange and store in info variable info := exch.accountinfo("BTC Markets") // print out info - fmt.print(info) + fmt.println(info) } load() diff --git a/gctscript/examples/exchange/pairs.gct b/gctscript/examples/exchange/pairs.gct index befd810e..f5edb576 100644 --- a/gctscript/examples/exchange/pairs.gct +++ b/gctscript/examples/exchange/pairs.gct @@ -3,7 +3,7 @@ exch := import("exchange") load := func() { info := exch.pairs("BTC Markets", false, "SPOT") - fmt.print(info) + fmt.println(info) } load() diff --git a/gctscript/examples/exchange/query_order.gct b/gctscript/examples/exchange/query_order.gct index 725a834a..cbe1968c 100644 --- a/gctscript/examples/exchange/query_order.gct +++ b/gctscript/examples/exchange/query_order.gct @@ -3,7 +3,7 @@ exch := import("exchange") load := func() { info := exch.orderquery("BTC Markets", "4491600698") - fmt.print(info) + fmt.println(info) } load() diff --git a/gctscript/examples/exchange/submit_order.gct b/gctscript/examples/exchange/submit_order.gct index b2868ed5..927b5dab 100644 --- a/gctscript/examples/exchange/submit_order.gct +++ b/gctscript/examples/exchange/submit_order.gct @@ -3,7 +3,7 @@ exch := import("exchange") load := func() { info := exch.ordersubmit("BTC Markets","BTC-AUD","-","LIMIT","SELL",1000000, 1,"") - fmt.print(info) + fmt.println(info) } load() diff --git a/gctscript/examples/exchange/withdraw.gct b/gctscript/examples/exchange/withdraw.gct index 04a01fa0..0bef238e 100644 --- a/gctscript/examples/exchange/withdraw.gct +++ b/gctscript/examples/exchange/withdraw.gct @@ -19,7 +19,7 @@ load := func() { // submit request to withdraw funds info := exch.withdrawfiat("BTC Markets", "AUD", "hello", 1, "-") // print out info - fmt.print(info) + fmt.println(info) } load() diff --git a/gctscript/examples/exchange/withdraw_crypto.gct b/gctscript/examples/exchange/withdraw_crypto.gct index e4b32b38..3f06ffb5 100644 --- a/gctscript/examples/exchange/withdraw_crypto.gct +++ b/gctscript/examples/exchange/withdraw_crypto.gct @@ -18,7 +18,7 @@ load := func() { info := exch.withdrawcrypto("BTC Markets","BTC", "1234562362", "1231", 1.0, 0.0, "","" ) // print out info - fmt.print(info) + fmt.println(info) } load() diff --git a/gctscript/modules/gct/common.go b/gctscript/modules/gct/common.go new file mode 100644 index 00000000..7857f2c1 --- /dev/null +++ b/gctscript/modules/gct/common.go @@ -0,0 +1,457 @@ +package gct + +import ( + "errors" + "fmt" + "path/filepath" + "strconv" + "strings" + "time" + + objects "github.com/d5/tengo/v2" + "github.com/thrasher-corp/gocryptotrader/common/file" + "github.com/thrasher-corp/gocryptotrader/gctscript/modules/ta/indicators" + "github.com/thrasher-corp/gocryptotrader/log" +) + +var commonModule = map[string]objects.Object{ + "writeascsv": &objects.UserFunction{Name: "writeascsv", Value: WriteAsCSV}, +} + +// OutputDir is the default script output directory +var OutputDir string + +// WriteAsCSV takes in a slice matrix to save to file +func WriteAsCSV(args ...objects.Object) (objects.Object, error) { + if len(args) == 0 { + return nil, errors.New("cannot write to file, no data present") + } + + var bucket [][]string + var err error + var target string + for i := range args { + if args[i] == nil { + return nil, errors.New("data is nil") + } + var front bool + var temp [][]string + switch args[i].TypeName() { + case indicators.AverageTrueRange: + temp, err = convertATR(args[i]) + case indicators.BollingerBands: + temp, err = convertBollingerBands(args[i]) + case indicators.ExponentialMovingAverage: + temp, err = convertEMA(args[i]) + case indicators.MovingAverageConvergenceDivergence: + temp, err = convertMACD(args[i]) + case indicators.MoneyFlowIndex: + temp, err = convertMFI(args[i]) + case indicators.OnBalanceVolume: + temp, err = convertOBV(args[i]) + case indicators.RelativeStrengthIndex: + temp, err = convertRSI(args[i]) + case indicators.SimpleMovingAverage: + temp, err = convertSMA(args[i]) + case indicators.OHLCV: + temp, err = convertOHLCV(args[i]) + front = true + case "string": + if target != "" { + return nil, fmt.Errorf("filename already set, extra string %v cannot be processed", args[i]) + } + var ok bool + target, ok = objects.ToString(args[i]) + if !ok { + return nil, errors.New("failed to convert incoming output to string") + } + + if target == "" { + 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, ".gct"): + 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) + + default: + err = fmt.Errorf("%s type is not handled", args[i].TypeName()) + } + if err != nil { + return nil, err + } + + if front { + var newBucket [][]string + newBucket = append(newBucket, temp...) + for x := range bucket { + newBucket[x] = append(newBucket[x], bucket[x]...) + } + + bucket = newBucket + front = false + continue + } + + if len(bucket) == 0 { + bucket = temp + } else { + for i := range temp { + bucket[i] = append(bucket[i], temp[i]...) + } + } + } + + if target == "" { + return nil, errors.New("filename unset please set in writeascsv as ctx or client defined filename") + } + + err = file.WriteAsCSV(target, bucket) + if err != nil { + return nil, err + } + + log.Infof(log.GCTScriptMgr, + "CSV file successfully saved to: %s", + target) + return nil, nil +} + +func convertATR(a objects.Object) ([][]string, error) { + obj, ok := objects.ToInterface(a).(*indicators.ATR) + if !ok { + return nil, errors.New("casting failure") + } + + var bucket = [][]string{ + { + indicators.AverageTrueRange, + }, + { + fmt.Sprintf("Period:%d", obj.Period), + }, + } + + var val string + for i := range obj.Value { + val, ok = objects.ToString(obj.Value[i]) + if !ok { + return nil, errors.New("cannot convert object to string") + } + + bucket = append(bucket, []string{val}) + } + return bucket, nil +} + +func convertBollingerBands(a objects.Object) ([][]string, error) { + obj, ok := objects.ToInterface(a).(*indicators.BBands) + if !ok { + return nil, errors.New("casting failure") + } + + upperS := fmt.Sprintf("Upper_Band (NBDevUp:%f)", obj.STDDevUp) + lowerS := fmt.Sprintf("Lower_band(NBDevDown:%f)", obj.STDDevDown) + middleS := fmt.Sprintf("Middle_Band (Period:%d)", obj.Period) + MAType := "MA_TYPE:SMA" + if obj.MAType != 0 { + MAType = "MA_TYPE:EMA" + } + + var bucket = [][]string{ + { + indicators.BollingerBands, "", MAType, + }, + { + upperS, middleS, lowerS, + }, + } + + for x := range obj.Value { + element := obj.Value[x].Iterate() + var upper, middle, lower string + for i := 0; element.Next(); i++ { + switch i { + case 0: + upper, ok = objects.ToString(element.Value()) + if !ok { + return nil, errors.New("cannot convert object to string") + } + case 1: + middle, ok = objects.ToString(element.Value()) + if !ok { + return nil, errors.New("cannot convert object to string") + } + case 2: + lower, ok = objects.ToString(element.Value()) + if !ok { + return nil, errors.New("cannot convert object to string") + } + } + } + bucket = append(bucket, []string{upper, middle, lower}) + } + return bucket, nil +} + +func convertEMA(a objects.Object) ([][]string, error) { + obj, ok := objects.ToInterface(a).(*indicators.EMA) + if !ok { + return nil, errors.New("casting failure") + } + + var bucket = [][]string{ + { + indicators.ExponentialMovingAverage, + }, + { + fmt.Sprintf("Period:%d", obj.Period), + }, + } + + var val string + for i := range obj.Value { + val, ok = objects.ToString(obj.Value[i]) + if !ok { + return nil, errors.New("cannot convert object to string") + } + bucket = append(bucket, []string{val}) + } + return bucket, nil +} + +func convertMACD(a objects.Object) ([][]string, error) { + obj, ok := objects.ToInterface(a).(*indicators.MACD) + if !ok { + return nil, errors.New("casting failure") + } + + var bucket = [][]string{ + { + indicators.MovingAverageConvergenceDivergence, + fmt.Sprintf("Period:%d Fast:%d Slow:%d", + obj.Period, + obj.PeriodFast, + obj.PeriodSlow), + "", + }, + { + "MACD", "Signal", "Histogram", + }, + } + + for x := range obj.Value { + element := obj.Value[x].Iterate() + var macd, signal, hist string + for i := 0; element.Next(); i++ { + switch i { + case 0: + macd, ok = objects.ToString(element.Value()) + if !ok { + return nil, errors.New("cannot convert object to string") + } + case 1: + signal, ok = objects.ToString(element.Value()) + if !ok { + return nil, errors.New("cannot convert object to string") + } + case 2: + hist, ok = objects.ToString(element.Value()) + if !ok { + return nil, errors.New("cannot convert object to string") + } + } + } + bucket = append(bucket, []string{macd, signal, hist}) + } + return bucket, nil +} + +func convertMFI(a objects.Object) ([][]string, error) { + obj, ok := objects.ToInterface(a).(*indicators.MFI) + if !ok { + return nil, errors.New("casting failure") + } + + var bucket = [][]string{ + { + indicators.MoneyFlowIndex, + }, + { + fmt.Sprintf("Period:%d", obj.Period), + }, + } + + var val string + for i := range obj.Value { + val, ok = objects.ToString(obj.Value[i]) + if !ok { + return nil, errors.New("cannot convert object to string") + } + bucket = append(bucket, []string{val}) + } + return bucket, nil +} + +func convertOBV(a objects.Object) ([][]string, error) { + var bucket = [][]string{ + { + indicators.OnBalanceVolume, + }, + { + "", + }, + } + + obj, ok := objects.ToInterface(a).(*indicators.OBV) + if !ok { + return nil, errors.New("casting failure") + } + + var val string + for i := range obj.Value { + val, ok = objects.ToString(obj.Value[i]) + if !ok { + return nil, errors.New("cannot convert object to string") + } + bucket = append(bucket, []string{val}) + } + return bucket, nil +} + +func convertRSI(a objects.Object) ([][]string, error) { + obj, ok := objects.ToInterface(a).(*indicators.RSI) + if !ok { + return nil, errors.New("casting failure") + } + + var bucket = [][]string{ + { + indicators.RelativeStrengthIndex, + }, + { + fmt.Sprintf("Period:%d", obj.Period), + }, + } + + var val string + for i := range obj.Value { + val, ok = objects.ToString(obj.Value[i]) + if !ok { + return nil, errors.New("cannot convert object to string") + } + bucket = append(bucket, []string{val}) + } + return bucket, nil +} + +func convertSMA(a objects.Object) ([][]string, error) { + obj, ok := objects.ToInterface(a).(*indicators.SMA) + if !ok { + return nil, errors.New("casting failure") + } + + var bucket = [][]string{ + { + indicators.SimpleMovingAverage, + }, + { + fmt.Sprintf("Period:%d", obj.Period), + }, + } + + var val string + for i := range obj.Value { + val, ok = objects.ToString(obj.Value[i]) + if !ok { + return nil, errors.New("cannot convert object to string") + } + bucket = append(bucket, []string{val}) + } + return bucket, nil +} + +func convertOHLCV(a objects.Object) ([][]string, error) { + obj, ok := objects.ToInterface(a).(*OHLCV) + if !ok { + return nil, errors.New("casting failure") + } + + exchange, ok := objects.ToString(obj.Value["exchange"]) + if !ok { + return nil, errors.New("cannot convert object to string") + } + + pair, ok := objects.ToString(obj.Value["pair"]) + if !ok { + return nil, errors.New("cannot convert object to string") + } + + asset, ok := objects.ToString(obj.Value["asset"]) + if !ok { + return nil, errors.New("cannot convert object to string") + } + + interval, ok := objects.ToString(obj.Value["intervals"]) + if !ok { + return nil, errors.New("cannot convert object to string") + } + + var bucket = [][]string{ + { + indicators.OHLCV, "Exchange:" + exchange, pair, asset, interval, "", + }, + { + "Date", "Volume", "Open", "High", "Low", "Close", + }, + } + + candles, ok := obj.Value["candles"] + if !ok { + return nil, errors.New("candles not found in object map") + } + + data := candles.Iterate() + + for data.Next() { + var date, open, high, low, closed, volume string + candle := data.Value().Iterate() + for i := 0; candle.Next(); i++ { + switch i { + case 0: + date, ok = objects.ToString(candle.Value()) + case 1: + open, ok = objects.ToString(candle.Value()) + case 2: + high, ok = objects.ToString(candle.Value()) + case 3: + low, ok = objects.ToString(candle.Value()) + case 4: + closed, ok = objects.ToString(candle.Value()) + case 5: + volume, ok = objects.ToString(candle.Value()) + } + if !ok { + return nil, errors.New("failed to convert") + } + } + bucket = append(bucket, []string{date, volume, open, high, low, closed}) + } + return bucket, nil +} diff --git a/gctscript/modules/gct/common_test.go b/gctscript/modules/gct/common_test.go new file mode 100644 index 00000000..bf3f114c --- /dev/null +++ b/gctscript/modules/gct/common_test.go @@ -0,0 +1,175 @@ +package gct + +import ( + "os" + "path/filepath" + "testing" + "time" + + objects "github.com/d5/tengo/v2" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/gctscript/modules/ta/indicators" +) + +var ( + atrPayload = &indicators.ATR{Array: oneElement} + bbandsPayload = &indicators.BBands{Array: threeElement} + emaPayload = &indicators.EMA{Array: oneElement} + macdPayload = &indicators.MACD{Array: threeElement} + mfiPayload = &indicators.MFI{Array: oneElement} + obvPayload = &indicators.OBV{Array: oneElement} + rsiPayload = &indicators.RSI{Array: oneElement} + smaPayload = &indicators.SMA{Array: oneElement} + ohlcPayload = &OHLCV{Map: ohlcdata} + unhandled = &objects.Array{} + + oneElement = objects.Array{ + Value: []objects.Object{ + &objects.Float{Value: 1}, + &objects.Float{Value: 2}, + &objects.Float{Value: 3}, + &objects.Float{Value: 4}, + &objects.Float{Value: 5}, + }, + } + + threeElement = objects.Array{ + Value: []objects.Object{ + &objects.Array{ + Value: []objects.Object{ + &objects.Float{Value: 11}, + &objects.Float{Value: 12}, + &objects.Float{Value: 13}, + }, + }, + &objects.Array{ + Value: []objects.Object{ + &objects.Float{Value: 21}, + &objects.Float{Value: 22}, + &objects.Float{Value: 23}, + }, + }, + &objects.Array{ + Value: []objects.Object{ + &objects.Float{Value: 31}, + &objects.Float{Value: 32}, + &objects.Float{Value: 33}, + }, + }, + &objects.Array{ + Value: []objects.Object{ + &objects.Float{Value: 41}, + &objects.Float{Value: 42}, + &objects.Float{Value: 43}, + }, + }, + &objects.Array{ + Value: []objects.Object{ + &objects.Float{Value: 51}, + &objects.Float{Value: 52}, + &objects.Float{Value: 53}, + }, + }, + }, + } + + ohlcv = []objects.Object{ + &objects.Time{Value: time.Now()}, + &objects.Float{Value: 100}, + &objects.Float{Value: 100}, + &objects.Float{Value: 100}, + &objects.Float{Value: 100}, + &objects.Float{Value: 1}, + } + + ohlcdata = objects.Map{ + Value: map[string]objects.Object{ + "exchange": &objects.String{Value: "exchange"}, + "pair": &objects.String{Value: "BTC-USD"}, + "asset": &objects.String{Value: asset.Spot.String()}, + "intervals": &objects.String{Value: time.Minute.String()}, + "candles": &objects.Array{ + Value: []objects.Object{ + &objects.Array{ + Value: ohlcv, + }, + &objects.Array{ + Value: ohlcv, + }, + &objects.Array{ + Value: ohlcv, + }, + &objects.Array{ + Value: ohlcv, + }, + &objects.Array{ + Value: ohlcv, + }, + }, + }, + }, + } +) + +func TestCommonWriteToCSV(t *testing.T) { + t.Parallel() + + OutputDir = filepath.Join(os.TempDir(), "script-temp") + defer func() { + err := os.RemoveAll(OutputDir) + if err != nil { + t.Fatal(err) + } + }() + + _, err := WriteAsCSV() + if err == nil { + t.Fatal("error cannot be nil") + } + + _, err = WriteAsCSV(nil) + if err == nil { + t.Fatal("error cannot be nil") + } + + _, err = WriteAsCSV(&objects.String{Value: "something.txt"}) + if err == nil { + t.Fatal(err) + } + + _, err = WriteAsCSV(&objects.String{Value: "something.txt"}, + &objects.String{Value: "extra string"}) + if err == nil { + t.Fatal(err) + } + + _, err = WriteAsCSV(&objects.String{Value: "script-temp.csv"}, unhandled) + if err == nil { + t.Fatal("error cannot be nil") + } + + _, err = WriteAsCSV(&objects.String{Value: "script-temp.csv"}, + atrPayload, + bbandsPayload, + emaPayload, + macdPayload, + mfiPayload, + obvPayload, + rsiPayload, + smaPayload, + ohlcPayload) + if err != nil { + t.Fatal(err) + } + + _, err = WriteAsCSV(atrPayload) + if err == nil { + t.Fatal(err) + } + + _, err = WriteAsCSV(&objects.String{Value: "test.gct-script-temp2"}, + atrPayload) + if err != nil { + t.Fatal(err) + } +} diff --git a/gctscript/modules/gct/exchange.go b/gctscript/modules/gct/exchange.go index de1a341a..31f4af9f 100644 --- a/gctscript/modules/gct/exchange.go +++ b/gctscript/modules/gct/exchange.go @@ -10,6 +10,7 @@ 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/gctscript/modules/ta/indicators" "github.com/thrasher-corp/gocryptotrader/gctscript/wrappers" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -498,6 +499,16 @@ func ExchangeWithdrawFiat(args ...objects.Object) (objects.Object, error) { return &objects.String{Value: rtn}, nil } +// OHLCV defines a custom Open High Low Close Volume tengo object +type OHLCV struct { + objects.Map +} + +// TypeName returns the name of the custom type. +func (o *OHLCV) TypeName() string { + return indicators.OHLCV +} + func exchangeOHLCV(args ...objects.Object) (objects.Object, error) { if len(args) != 7 { return nil, objects.ErrWrongNumArguments @@ -567,9 +578,9 @@ func exchangeOHLCV(args ...objects.Object) (objects.Object, error) { retValue["intervals"] = &objects.String{Value: ret.Interval.String()} retValue["candles"] = &candles - return &objects.Map{ - Value: retValue, - }, nil + c := new(OHLCV) + c.Value = retValue + return c, nil } // parseInterval will parse the interval param of indictors that have them and convert to time.Duration diff --git a/gctscript/modules/gct/gct_types.go b/gctscript/modules/gct/gct_types.go index 0f78acb4..73cdff93 100644 --- a/gctscript/modules/gct/gct_types.go +++ b/gctscript/modules/gct/gct_types.go @@ -17,4 +17,5 @@ var supportedDurations = []string{"1m", "3m", "5m", "15m", "30m", "1h", "2h", "4 // Modules map of all loadable modules var Modules = map[string]map[string]tengo.Object{ "exchange": exchangeModule, + "common": commonModule, } diff --git a/gctscript/modules/loader/loader.go b/gctscript/modules/loader/loader.go index 06eb3201..cbdafd8d 100644 --- a/gctscript/modules/loader/loader.go +++ b/gctscript/modules/loader/loader.go @@ -37,3 +37,8 @@ func GetModuleMap() *tengo.ModuleMap { } return modules } + +// SetDefaultScriptOutput sets the output folder +func SetDefaultScriptOutput(path string) { + gct.OutputDir = path +} diff --git a/gctscript/modules/ta/indicators/atr.go b/gctscript/modules/ta/indicators/atr.go index 53a01c6f..e811953f 100644 --- a/gctscript/modules/ta/indicators/atr.go +++ b/gctscript/modules/ta/indicators/atr.go @@ -17,12 +17,25 @@ var AtrModule = map[string]objects.Object{ "calculate": &objects.UserFunction{Name: "calculate", Value: atr}, } +// AverageTrueRange is the string constant +const AverageTrueRange = "Average True Range" + +// ATR defines a custom Average True Range indicator tengo object +type ATR struct { + objects.Array + Period int +} + +// TypeName returns the name of the custom type. +func (o *ATR) TypeName() string { + return AverageTrueRange +} + func atr(args ...objects.Object) (objects.Object, error) { if len(args) != 2 { return nil, objects.ErrWrongNumArguments } - - r := &objects.Array{} + r := new(ATR) if validator.IsTestExecution.Load() == true { return r, nil } @@ -71,6 +84,7 @@ func atr(args ...objects.Object) (objects.Object, error) { return nil, errors.New(strings.Join(allErrors, ", ")) } + r.Period = inTimePeriod ret := indicators.ATR(ohlcvData[2], ohlcvData[3], ohlcvData[4], inTimePeriod) for x := range ret { r.Value = append(r.Value, &objects.Float{Value: math.Round(ret[x]*100) / 100}) diff --git a/gctscript/modules/ta/indicators/bbands.go b/gctscript/modules/ta/indicators/bbands.go index f8d90ea5..e9c4fc9d 100644 --- a/gctscript/modules/ta/indicators/bbands.go +++ b/gctscript/modules/ta/indicators/bbands.go @@ -17,14 +17,30 @@ var BBandsModule = map[string]objects.Object{ "calculate": &objects.UserFunction{Name: "calculate", Value: bbands}, } +// BollingerBands is the string constant +const BollingerBands = "Bollinger Bands" + +// BBands defines a custom Bollinger Bands indicator tengo object +type BBands struct { + objects.Array + Period int + STDDevUp, STDDevDown float64 + MAType indicators.MaType +} + +// TypeName returns the name of the custom type. +func (o *BBands) TypeName() string { + return BollingerBands +} + func bbands(args ...objects.Object) (objects.Object, error) { if len(args) != 6 { return nil, objects.ErrWrongNumArguments } - var ret objects.Array + r := new(BBands) if validator.IsTestExecution.Load() == true { - return &ret, nil + return r, nil } ohlcIndicatorType, ok := objects.ToString(args[0]) @@ -101,6 +117,11 @@ func bbands(args ...objects.Object) (objects.Object, error) { return nil, err } + r.Period = inTimePeriod + r.STDDevDown = inNbDevDn + r.STDDevUp = inNbDevUp + r.MAType = MAType + retUpper, retMiddle, retLower := indicators.BBANDS(ohlcvData[selector], inTimePeriod, inNbDevDn, inNbDevDn, MAType) for x := range retMiddle { temp := &objects.Array{} @@ -111,8 +132,8 @@ func bbands(args ...objects.Object) (objects.Object, error) { if retLower != nil { temp.Value = append(temp.Value, &objects.Float{Value: math.Round(retLower[x]*100) / 100}) } - ret.Value = append(ret.Value, temp) + r.Value = append(r.Value, temp) } - return &ret, nil + return r, nil } diff --git a/gctscript/modules/ta/indicators/ema.go b/gctscript/modules/ta/indicators/ema.go index f903b40c..60f52ecc 100644 --- a/gctscript/modules/ta/indicators/ema.go +++ b/gctscript/modules/ta/indicators/ema.go @@ -17,12 +17,26 @@ var EMAModule = map[string]objects.Object{ "calculate": &objects.UserFunction{Name: "calculate", Value: ema}, } +// ExponentialMovingAverage is the string constant +const ExponentialMovingAverage = "Exponential Moving Average" + +// EMA defines a custom Exponential Moving Average indicator tengo object +type EMA struct { + objects.Array + Period int +} + +// TypeName returns the name of the custom type. +func (o *EMA) TypeName() string { + return ExponentialMovingAverage +} + func ema(args ...objects.Object) (objects.Object, error) { if len(args) != 2 { return nil, objects.ErrWrongNumArguments } - r := &objects.Array{} + r := new(EMA) if validator.IsTestExecution.Load() == true { return r, nil } @@ -54,6 +68,8 @@ func ema(args ...objects.Object) (objects.Object, error) { return nil, errors.New(strings.Join(allErrors, ", ")) } + r.Period = inTimePeriod + ret := indicators.EMA(ohlcvClose, inTimePeriod) for x := range ret { r.Value = append(r.Value, &objects.Float{Value: math.Round(ret[x]*100) / 100}) diff --git a/gctscript/modules/ta/indicators/macd.go b/gctscript/modules/ta/indicators/macd.go index 6eafda85..096c5853 100644 --- a/gctscript/modules/ta/indicators/macd.go +++ b/gctscript/modules/ta/indicators/macd.go @@ -17,12 +17,27 @@ var MACDModule = map[string]objects.Object{ "calculate": &objects.UserFunction{Name: "calculate", Value: macd}, } +// MovingAverageConvergenceDivergence is the string constant +const MovingAverageConvergenceDivergence = "Moving Average Convergence Divergence" + +// MACD defines a custom Moving Average Convergence Divergence tengo indicator +// object type +type MACD struct { + objects.Array + Period, PeriodSlow, PeriodFast int +} + +// TypeName returns the name of the custom type. +func (o *MACD) TypeName() string { + return MovingAverageConvergenceDivergence +} + func macd(args ...objects.Object) (objects.Object, error) { if len(args) != 4 { return nil, objects.ErrWrongNumArguments } - r := &objects.Array{} + r := new(MACD) if validator.IsTestExecution.Load() == true { return r, nil } @@ -63,6 +78,10 @@ func macd(args ...objects.Object) (objects.Object, error) { return nil, errors.New(strings.Join(allErrors, ", ")) } + r.Period = inTimePeriod + r.PeriodFast = inFastPeriod + r.PeriodSlow = inSlowPeriod + macd, macdSignal, macdHist := indicators.MACD(ohlcvClose, inFastPeriod, inSlowPeriod, inTimePeriod) for x := range macdHist { tempMACD := &objects.Array{} diff --git a/gctscript/modules/ta/indicators/mfi.go b/gctscript/modules/ta/indicators/mfi.go index 1203d5d8..6f0a1dd8 100644 --- a/gctscript/modules/ta/indicators/mfi.go +++ b/gctscript/modules/ta/indicators/mfi.go @@ -17,12 +17,26 @@ var MfiModule = map[string]objects.Object{ "calculate": &objects.UserFunction{Name: "calculate", Value: mfi}, } +// MoneyFlowIndex is the string constant +const MoneyFlowIndex = "Money Flow Index" + +// MFI defines a custom Money Flow Index tengo indicator object type +type MFI struct { + objects.Array + Period int +} + +// TypeName returns the name of the custom type. +func (o *MFI) TypeName() string { + return MoneyFlowIndex +} + func mfi(args ...objects.Object) (objects.Object, error) { if len(args) != 2 { return nil, objects.ErrWrongNumArguments } - r := &objects.Array{} + r := new(MFI) if validator.IsTestExecution.Load() == true { return r, nil } @@ -69,6 +83,8 @@ func mfi(args ...objects.Object) (objects.Object, error) { return nil, fmt.Errorf(modules.ErrParameterConvertFailed, inTimePeriod) } + r.Period = inTimePeriod + ret := indicators.MFI(ohlcvData[2], ohlcvData[3], ohlcvData[4], ohlcvData[5], inTimePeriod) for x := range ret { r.Value = append(r.Value, &objects.Float{Value: math.Round(ret[x]*100) / 100}) diff --git a/gctscript/modules/ta/indicators/obv.go b/gctscript/modules/ta/indicators/obv.go index bd54babe..39622ed3 100644 --- a/gctscript/modules/ta/indicators/obv.go +++ b/gctscript/modules/ta/indicators/obv.go @@ -17,12 +17,25 @@ var ObvModule = map[string]objects.Object{ "calculate": &objects.UserFunction{Name: "calculate", Value: obv}, } +// OnBalanceVolume is the string constant +const OnBalanceVolume = "On Balance Volume" + +// OBV defines a custom On Balance Volume tengo indicator object type +type OBV struct { + objects.Array +} + +// TypeName returns the name of the custom type. +func (o *OBV) TypeName() string { + return OnBalanceVolume +} + func obv(args ...objects.Object) (objects.Object, error) { if len(args) != 1 { return nil, objects.ErrWrongNumArguments } - r := &objects.Array{} + r := new(OBV) if validator.IsTestExecution.Load() == true { return r, nil } diff --git a/gctscript/modules/ta/indicators/rsi.go b/gctscript/modules/ta/indicators/rsi.go index 06554d41..c8f18dd6 100644 --- a/gctscript/modules/ta/indicators/rsi.go +++ b/gctscript/modules/ta/indicators/rsi.go @@ -17,12 +17,26 @@ var RsiModule = map[string]objects.Object{ "calculate": &objects.UserFunction{Name: "calculate", Value: rsi}, } +// RelativeStrengthIndex is the string constant +const RelativeStrengthIndex = "Relative Strength Index" + +// RSI defines a custom Relative Strength Index indicator tengo object type +type RSI struct { + objects.Array + Period int +} + +// TypeName returns the name of the custom type. +func (o *RSI) TypeName() string { + return RelativeStrengthIndex +} + func rsi(args ...objects.Object) (objects.Object, error) { if len(args) != 2 { return nil, objects.ErrWrongNumArguments } - r := &objects.Array{} + r := new(RSI) if validator.IsTestExecution.Load() == true { return r, nil } @@ -54,6 +68,7 @@ func rsi(args ...objects.Object) (objects.Object, error) { return nil, errors.New(strings.Join(allErrors, ", ")) } + r.Period = inTimePeriod ret := indicators.RSI(ohlcvClose, inTimePeriod) for x := range ret { r.Value = append(r.Value, &objects.Float{Value: math.Round(ret[x]*100) / 100}) diff --git a/gctscript/modules/ta/indicators/sma.go b/gctscript/modules/ta/indicators/sma.go index 471fc6d0..1d0ad83a 100644 --- a/gctscript/modules/ta/indicators/sma.go +++ b/gctscript/modules/ta/indicators/sma.go @@ -17,12 +17,26 @@ var SMAModule = map[string]objects.Object{ "calculate": &objects.UserFunction{Name: "calculate", Value: sma}, } +// SimpleMovingAverage is the string constant +const SimpleMovingAverage = "Simple Moving Average" + +// SMA defines a custom Simple Moving Average indicator tengo object type +type SMA struct { + objects.Array + Period int +} + +// TypeName returns the name of the custom type. +func (o *SMA) TypeName() string { + return SimpleMovingAverage +} + func sma(args ...objects.Object) (objects.Object, error) { if len(args) != 2 { return nil, objects.ErrWrongNumArguments } - r := &objects.Array{} + r := new(SMA) if validator.IsTestExecution.Load() == true { return r, nil } @@ -52,6 +66,7 @@ func sma(args ...objects.Object) (objects.Object, error) { if len(allErrors) > 0 { return nil, errors.New(strings.Join(allErrors, ", ")) } + r.Period = inTimePeriod ret := indicators.SMA(ohlcvClose, inTimePeriod) for x := range ret { r.Value = append(r.Value, &objects.Float{Value: math.Round(ret[x]*100) / 100}) diff --git a/gctscript/vm/vm.go b/gctscript/vm/vm.go index 7c1ccdc7..44489488 100644 --- a/gctscript/vm/vm.go +++ b/gctscript/vm/vm.go @@ -42,6 +42,11 @@ func NewVM() (vm *VM) { return } +// SetDefaultScriptOutput sets default output file for scripts +func SetDefaultScriptOutput() { + loader.SetDefaultScriptOutput(filepath.Join(ScriptPath, "output")) +} + // Load parses and creates a new instance of tengo script vm func (vm *VM) Load(file string) error { if vm == nil { @@ -75,6 +80,12 @@ func (vm *VM) Load(file string) error { 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) + if err != nil { + return err + } + vm.Script.SetImports(loader.GetModuleMap()) vm.Hash = vm.getHash() diff --git a/go.mod b/go.mod index f5778399..e5d3bf2c 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ require ( golang.org/x/net v0.0.0-20191002035440-2ec189313ef0 golang.org/x/sys v0.0.0-20191003212358-c178f38b412c // indirect golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 - google.golang.org/genproto v0.0.0-20191002211648-c459b9ce5143 + google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a google.golang.org/grpc v1.29.1 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect gopkg.in/yaml.v2 v2.2.7 // indirect diff --git a/go.sum b/go.sum index b0764acd..4c578e15 100644 --- a/go.sum +++ b/go.sum @@ -5,6 +5,7 @@ cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSR cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= @@ -51,10 +52,6 @@ github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/d5/tengo/v2 v2.1.2 h1:JR5O6qJW2GW9lpv/MfEqK16a/Wpp2y8I0JZZ5fqNOL0= -github.com/d5/tengo/v2 v2.1.2/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8= -github.com/d5/tengo/v2 v2.2.0 h1:S9R1Dx4GNedqp6FYjuuplxvXzdbHpz12MZOvpKLf3iw= -github.com/d5/tengo/v2 v2.2.0/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8= github.com/d5/tengo/v2 v2.3.0 h1:KD4BKNeN+GvZcA/Y5m5K7LdozraQm4CX4u6D6GNKGw0= github.com/d5/tengo/v2 v2.3.0/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= @@ -119,7 +116,6 @@ github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= @@ -140,8 +136,6 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 h1:0IKlLyQ3Hs9nDaiK5cSHAGmcQ github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.14.4 h1:IOPK2xMPP3aV6/NPt4jt//ELFo3Vv8sDVD8j3+tleDU= -github.com/grpc-ecosystem/grpc-gateway v1.14.4/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0= github.com/grpc-ecosystem/grpc-gateway v1.14.5 h1:aiLxiiVzAXb7wb3lAmubA69IokWOoUNe+E7TdGKh8yw= github.com/grpc-ecosystem/grpc-gateway v1.14.5/go.mod h1:UJ0EZAp832vCd54Wev9N1BMKEyvcZ5+IM0AwDrnlkEc= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= @@ -169,7 +163,6 @@ github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NH github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -189,10 +182,6 @@ github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.4.0 h1:TmtCFbH+Aw0AixwyttznSMQDgbR5Yed/Gg6S8Funrhc= -github.com/lib/pq v1.4.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.5.1 h1:Jn6HYxiYrtQ92CopqJLvfPCJUrrruw1+1cn0jM9dKrI= -github.com/lib/pq v1.5.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.5.2 h1:yTSXVswvWUOQ3k1sd7vJfDrbSl8lKuscqFJRqjC0ifw= github.com/lib/pq v1.5.2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -213,9 +202,7 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= @@ -280,8 +267,6 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.6.3 h1:pDDu1OyEDTKzpJwdq4TiuLyMsUgRa/BT5cn5O62NoHs= -github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw= github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -369,6 +354,7 @@ golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -433,6 +419,7 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -443,8 +430,7 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191002211648-c459b9ce5143 h1:tikhlQEJeezbnu0Zcblj7g5vm/L7xt6g1vnfq8mRCS4= -google.golang.org/genproto v0.0.0-20191002211648-c459b9ce5143/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= @@ -461,6 +447,7 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0 h1:cJv5/xdbk1NnMPR1VP9+HU6gupuG9MLBoH1r6RHZ2MY= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=