From 8afee0b4b258f2fca5f633d5b3eb8482b069763b Mon Sep 17 00:00:00 2001 From: Adrian Gallagher Date: Wed, 3 Jun 2020 12:48:36 +1000 Subject: [PATCH] Miscellaneous bug fixes (#513) * Various bug fixes * Deadlink, cleanup plus bug fixes * Various Kraken fixes * Add convert func for decimal unix timestamps * Convert all test times to UTC * Kraken: Make assets a pointer to prevent excessive copying * Docker slash fix * Address nits plus bump ITBit last checked pairs timestamp * Set pairs to enabled pairs when getting active orders * Use asset translator for UpdateAccountInfo and more checks for the exchange template tool * Address MadCozBadd's nits * Make exchange var 2 chars * Make program more user friendly * Project wide comment checks and exchName check * Fix Huobi indexing bug and use correct pair formatting * Address nits + readme change --- CONTRIBUTORS | 7 +- Dockerfile | 2 +- README.md | 20 +- cmd/documentation/documentation.go | 5 + .../root_templates/root_readme.tmpl | 5 +- cmd/exchange_template/exchange_template.go | 282 +++++++++--------- .../exchange_template_test.go | 74 +++++ cmd/exchange_template/readme_file.tmpl | 2 +- cmd/exchange_template/test_file.tmpl | 9 + cmd/exchange_template/wrapper_file.tmpl | 45 ++- common/convert/convert.go | 36 +-- common/convert/convert_test.go | 72 +---- config/config.go | 13 + config/config_test.go | 23 ++ config_example.json | 2 +- docs/EXCHANGE_API.md | 2 +- engine/orders.go | 48 +-- exchanges/binance/binance_websocket.go | 1 + exchanges/coinbasepro/coinbasepro.go | 33 -- exchanges/coinbasepro/coinbasepro_test.go | 19 +- exchanges/coinbasepro/coinbasepro_types.go | 182 +++++------ .../coinbasepro/coinbasepro_websocket.go | 7 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 32 +- exchanges/exmo/exmo_wrapper.go | 59 ++-- exchanges/gateio/gateio_test.go | 19 ++ exchanges/gateio/gateio_websocket.go | 13 +- exchanges/gateio/gateio_wrapper.go | 7 +- exchanges/huobi/huobi_wrapper.go | 47 +-- exchanges/kraken/kraken.go | 95 +++++- exchanges/kraken/kraken_test.go | 114 +++++-- exchanges/kraken/kraken_types.go | 7 + exchanges/kraken/kraken_websocket.go | 41 +-- exchanges/kraken/kraken_wrapper.go | 87 ++++-- exchanges/order/order_types.go | 1 + exchanges/poloniex/poloniex.go | 27 +- exchanges/poloniex/poloniex_wrapper.go | 22 +- exchanges/yobit/yobit_wrapper.go | 9 +- gctscript/modules/wrapper_types.go | 3 +- go.sum | 49 +-- testdata/configtest.json | 34 ++- testdata/preengine_config.json | 2 +- 41 files changed, 879 insertions(+), 678 deletions(-) create mode 100644 cmd/exchange_template/exchange_template_test.go diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 50918086..33822b9b 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -3,13 +3,14 @@ Thanks to the following contributors: thrasher- | https://github.com/thrasher- shazbert | https://github.com/shazbert gloriousCode | https://github.com/gloriousCode -xtda | https://github.com/xtda dependabot-preview[bot] | https://github.com/apps/dependabot-preview +xtda | https://github.com/xtda ermalguni | https://github.com/ermalguni vadimzhukck | https://github.com/vadimzhukck +MadCozBadd | https://github.com/MadCozBadd 140am | https://github.com/140am marcofranssen | https://github.com/marcofranssen -MadCozBadd | https://github.com/MadCozBadd +dackroyd | https://github.com/dackroyd cranktakular | https://github.com/cranktakular woshidama323 | https://github.com/woshidama323 vazha | https://github.com/vazha @@ -29,7 +30,6 @@ CodeLingoBot | https://github.com/CodeLingoBot CodeLingoTeam | https://github.com/CodeLingoTeam Daanikus | https://github.com/Daanikus daniel-cohen | https://github.com/daniel-cohen -merkeld | https://github.com/merkeld DirectX | https://github.com/DirectX frankzougc | https://github.com/frankzougc idoall | https://github.com/idoall @@ -41,3 +41,4 @@ zeldrinn | https://github.com/zeldrinn starit | https://github.com/starit Jimexist | https://github.com/Jimexist lookfirst | https://github.com/lookfirst +merkeld | https://github.com/merkeld diff --git a/Dockerfile b/Dockerfile index e7f7a259..db5b1977 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,6 +13,6 @@ VOLUME /root/.gocryptotrader RUN apk update && apk add --no-cache ca-certificates bash COPY --from=build /go/bin/gocryptotrader /app/ COPY --from=build /go/bin/gctcli /app/ -COPY --from=build /go/src/github.com/thrasher-corp/gocryptotrader/config.json /app/ +COPY --from=build /go/src/github.com/thrasher-corp/gocryptotrader/config.json /root/.gocryptotrader/ EXPOSE 9050-9053 ENTRYPOINT [ "/app/gocryptotrader" ] diff --git a/README.md b/README.md index 248993a5..88a68ce7 100644 --- a/README.md +++ b/README.md @@ -47,9 +47,10 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader | Yobit | Yes | NA | NA | | ZB.COM | Yes | Yes | NA | -We are aiming to support the top 20 highest volume exchanges based off the [CoinMarketCap exchange data](https://coinmarketcap.com/exchanges/volume/24-hour/). +We are aiming to support the top 30 exchanges sorted by average liquidity as [ranked by CoinMarketCap](https://coinmarketcap.com/rankings/exchanges/). +However, we welcome pull requests for any exchange which does not match this criterion. If you need help with this, please join us on [Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk). -** NA means not applicable as the Exchange does not support the feature. +** NA means not applicable as the exchange does not support the feature. ## Current Features @@ -138,16 +139,17 @@ Binaries will be published once the codebase reaches a stable condition. |User|Contribution Amount| |--|--| -| [thrasher-](https://github.com/thrasher-) | 637 | -| [shazbert](https://github.com/shazbert) | 188 | -| [gloriousCode](https://github.com/gloriousCode) | 167 | -| [xtda](https://github.com/xtda) | 42 | -| [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 23 | +| [thrasher-](https://github.com/thrasher-) | 639 | +| [shazbert](https://github.com/shazbert) | 191 | +| [gloriousCode](https://github.com/gloriousCode) | 169 | +| [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 46 | +| [xtda](https://github.com/xtda) | 43 | | [ermalguni](https://github.com/ermalguni) | 14 | | [vadimzhukck](https://github.com/vadimzhukck) | 10 | +| [MadCozBadd](https://github.com/MadCozBadd) | 8 | | [140am](https://github.com/140am) | 8 | | [marcofranssen](https://github.com/marcofranssen) | 8 | -| [MadCozBadd](https://github.com/MadCozBadd) | 7 | +| [dackroyd](https://github.com/dackroyd) | 5 | | [cranktakular](https://github.com/cranktakular) | 5 | | [woshidama323](https://github.com/woshidama323) | 3 | | [vazha](https://github.com/vazha) | 3 | @@ -167,7 +169,6 @@ Binaries will be published once the codebase reaches a stable condition. | [CodeLingoTeam](https://github.com/CodeLingoTeam) | 1 | | [Daanikus](https://github.com/Daanikus) | 1 | | [daniel-cohen](https://github.com/daniel-cohen) | 1 | -| [merkeld](https://github.com/merkeld) | 1 | | [DirectX](https://github.com/DirectX) | 1 | | [frankzougc](https://github.com/frankzougc) | 1 | | [idoall](https://github.com/idoall) | 1 | @@ -179,3 +180,4 @@ Binaries will be published once the codebase reaches a stable condition. | [starit](https://github.com/starit) | 1 | | [Jimexist](https://github.com/Jimexist) | 1 | | [lookfirst](https://github.com/lookfirst) | 1 | +| [merkeld](https://github.com/merkeld) | 1 | diff --git a/cmd/documentation/documentation.go b/cmd/documentation/documentation.go index af98e060..09fccc7a 100644 --- a/cmd/documentation/documentation.go +++ b/cmd/documentation/documentation.go @@ -226,6 +226,11 @@ func main() { URL: "https://github.com/lookfirst", Contributions: 1, }, + { + Login: "merkeld", + URL: "https://github.com/merkeld", + Contributions: 1, + }, }...) if verbose { diff --git a/cmd/documentation/root_templates/root_readme.tmpl b/cmd/documentation/root_templates/root_readme.tmpl index a6be5e00..5a953d7c 100644 --- a/cmd/documentation/root_templates/root_readme.tmpl +++ b/cmd/documentation/root_templates/root_readme.tmpl @@ -48,9 +48,10 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader | Yobit | Yes | NA | NA | | ZB.COM | Yes | Yes | NA | -We are aiming to support the top 20 highest volume exchanges based off the [CoinMarketCap exchange data](https://coinmarketcap.com/exchanges/volume/24-hour/). +We are aiming to support the top 30 exchanges sorted by average liquidity as [ranked by CoinMarketCap](https://coinmarketcap.com/rankings/exchanges/). +However, we welcome pull requests for any exchange which does not match this criterion. If you need help with this, please join us on [Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk). -** NA means not applicable as the Exchange does not support the feature. +** NA means not applicable as the exchange does not support the feature. ## Current Features diff --git a/cmd/exchange_template/exchange_template.go b/cmd/exchange_template/exchange_template.go index ef976e09..770167f0 100644 --- a/cmd/exchange_template/exchange_template.go +++ b/cmd/exchange_template/exchange_template.go @@ -1,6 +1,7 @@ package main import ( + "errors" "flag" "fmt" "html/template" @@ -12,28 +13,14 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" + "github.com/thrasher-corp/gocryptotrader/core" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" ) const ( - packageTests = "%s_test.go" - packageTypes = "%s_types.go" - packageWrapper = "%s_wrapper.go" - packageMain = "%s.go" - packageReadme = "README.md" - - exchangePackageLocation = "../../exchanges" - exchangeConfigPath = "../../testdata/configtest.json" -) - -var ( - exchangeDirectory string - exchangeTest string - exchangeTypes string - exchangeWrapper string - exchangeMain string - exchangeReadme string + exchangeConfigPath = "../../testdata/configtest.json" + targetPath = "../../exchanges" ) type exchange struct { @@ -45,25 +32,40 @@ type exchange struct { FIX bool } +var ( + errInvalidExchangeName = errors.New("invalid exchange name") +) + func main() { var newExchangeName string var websocketSupport, restSupport, fixSupport bool - flag.StringVar(&newExchangeName, "name", "", "-name [string] adds a new exchange") - flag.BoolVar(&websocketSupport, "ws", false, "-websocket adds websocket support") - flag.BoolVar(&restSupport, "rest", false, "-rest adds REST support") - flag.BoolVar(&fixSupport, "fix", false, "-fix adds FIX support?") + flag.StringVar(&newExchangeName, "name", "", "the exchange name") + flag.BoolVar(&websocketSupport, "ws", false, "whether the exchange supports websocket") + flag.BoolVar(&restSupport, "rest", false, "whether the exchange supports REST") + flag.BoolVar(&fixSupport, "fix", false, "whether the exchange supports FIX") flag.Parse() fmt.Println("GoCryptoTrader: Exchange templating tool.") + fmt.Println(core.Copyright) + fmt.Println() - if newExchangeName == "" || newExchangeName == " " { - log.Fatal(`GoCryptoTrader: Exchange templating tool exchange name not set e.g. "exchange_template -name [newExchangeNameString]"`) + if len(os.Args) == 1 { + log.Println("Invalid arguments supplied, please see application usage below:") + flag.Usage() + return } + if err := checkExchangeName(newExchangeName); err != nil { + log.Fatal(err) + } + newExchangeName = strings.ToLower(newExchangeName) + if !websocketSupport && !restSupport && !fixSupport { - log.Fatal(`GoCryptoTrader: Exchange templating tool support not set e.g. "exchange_template -name [newExchangeNameString] [-fix -ws -rest]"`) + log.Println("At least one protocol must be specified (rest/ws or fix)") + flag.Usage() + return } fmt.Println("Exchange Name: ", newExchangeName) @@ -83,37 +85,64 @@ func main() { log.Fatal("GoCryptoTrader: Exchange templating tool stopped...") } - newExchangeName = strings.ToLower(newExchangeName) - v := newExchangeName[:1] - capName := strings.ToUpper(v) + newExchangeName[1:] - exch := exchange{ - Name: newExchangeName, - CapitalName: capName, - Variable: v, - REST: restSupport, - WS: websocketSupport, - FIX: fixSupport, + Name: newExchangeName, + REST: restSupport, + WS: websocketSupport, + FIX: fixSupport, } + if err = makeExchange(&exch); err != nil { + log.Fatal(err) + } + + fmt.Println("GoCryptoTrader: Exchange templating tool service complete") + fmt.Println("When the exchange code implementation has been completed (REST/Websocket/wrappers and tests), please add the exchange to engine/exchange.go") + fmt.Println("Add the exchange config settings to config_example.json (it will automatically be added to testdata/configtest.json)") + fmt.Println("Increment the available exchanges counter in config/config_test.go") + fmt.Println("Add the exchange name to exchanges/support.go") + fmt.Println("Ensure go test ./... -race passes") + fmt.Println("Open a pull request") + fmt.Println("If help is needed, please post a message in Slack.") +} + +func checkExchangeName(exchName string) error { + if strings.Contains(exchName, " ") || + len(exchName) <= 2 { + return errInvalidExchangeName + } + return nil +} + +func makeExchange(exch *exchange) error { configTestFile := config.GetConfig() - err = configTestFile.LoadConfig(exchangeConfigPath, true) + err := configTestFile.LoadConfig(exchangeConfigPath, true) if err != nil { - log.Fatal("GoCryptoTrader: Exchange templating configuration retrieval error ", err) + return err } // NOTE need to nullify encrypt configuration - var configTestExchanges []string - for x := range configTestFile.Exchanges { - configTestExchanges = append(configTestExchanges, configTestFile.Exchanges[x].Name) + _, err = configTestFile.GetExchangeConfig(exch.Name) + if err == nil { + return errors.New("exchange already exists") } - if common.StringDataContainsInsensitive(configTestExchanges, capName) { - log.Fatal("GoCryptoTrader: Exchange templating configuration error - exchange already exists") + exchangeDirectory := filepath.Join(targetPath, exch.Name) + _, err = os.Stat(exchangeDirectory) + if !os.IsNotExist(err) { + return errors.New("directory already exists") + } + err = os.MkdirAll(exchangeDirectory, 0770) + if err != nil { + return err } + fmt.Printf("Output directory: %s\n", exchangeDirectory) + + exch.CapitalName = strings.Title(exch.Name) + exch.Variable = exch.Name[0:2] newExchConfig := config.ExchangeConfig{} - newExchConfig.Name = capName + newExchConfig.Name = exch.CapitalName newExchConfig.Enabled = true newExchConfig.API.Credentials.Key = "Key" newExchConfig.API.Credentials.Secret = "Secret" @@ -130,112 +159,99 @@ func main() { }, } - configTestFile.Exchanges = append(configTestFile.Exchanges, newExchConfig) + outputFiles := []struct { + Name string + Filename string + FilePostfix string + TemplateFile string + }{ + { + Name: "readme", + Filename: "README.md", + TemplateFile: "readme_file.tmpl", + }, + { + Name: "main", + Filename: "main_file.tmpl", + FilePostfix: ".go", + TemplateFile: "main_file.tmpl", + }, + { + Name: "test", + Filename: "test_file.tmpl", + FilePostfix: "_test.go", + TemplateFile: "test_file.tmpl", + }, + { + Name: "type", + Filename: "type_file.tmpl", + FilePostfix: "_types.go", + TemplateFile: "type_file.tmpl", + }, + { + Name: "wrapper", + Filename: "wrapper_file.tmpl", + FilePostfix: "_wrapper.go", + TemplateFile: "wrapper_file.tmpl", + }, + } + for x := range outputFiles { + var tmpl *template.Template + tmpl, err = template.New(outputFiles[x].Name).ParseFiles(outputFiles[x].TemplateFile) + if err != nil { + return fmt.Errorf("%s template error: %s", outputFiles[x].Name, err) + } + + filename := outputFiles[x].Filename + if outputFiles[x].FilePostfix != "" { + filename = exch.Name + outputFiles[x].FilePostfix + } + + outputFile := filepath.Join(exchangeDirectory, filename) + newFile(outputFile) + var f *os.File + f, err = os.OpenFile(outputFile, os.O_WRONLY, 0770) + if err != nil { + return err + } + + if err = tmpl.Execute(f, exch); err != nil { + f.Close() + return err + } + f.Close() + } + + cmd := exec.Command("go", "fmt") + cmd.Dir = exchangeDirectory + out, err := cmd.Output() + if err != nil { + return fmt.Errorf("unable to go fmt. output: %s err: %s", out, err) + } + + configTestFile.Exchanges = append(configTestFile.Exchanges, newExchConfig) err = configTestFile.SaveConfig(exchangeConfigPath, false) if err != nil { - log.Fatal("GoCryptoTrader: Exchange templating configuration error - cannot save") + return err } - exchangeDirectory = filepath.Join(exchangePackageLocation, newExchangeName) - exchangeTest = filepath.Join(exchangeDirectory, fmt.Sprintf(packageTests, newExchangeName)) - exchangeTypes = filepath.Join(exchangeDirectory, fmt.Sprintf(packageTypes, newExchangeName)) - exchangeWrapper = filepath.Join(exchangeDirectory, fmt.Sprintf(packageWrapper, newExchangeName)) - exchangeMain = filepath.Join(exchangeDirectory, fmt.Sprintf(packageMain, newExchangeName)) - exchangeReadme = filepath.Join(exchangeDirectory, packageReadme) - - err = os.Mkdir(exchangeDirectory, 0700) + cmd = exec.Command("go", "test") + cmd.Dir = exchangeDirectory + out, err = cmd.Output() if err != nil { - log.Fatal("GoCryptoTrader: Exchange templating tool cannot make directory ", err) + return fmt.Errorf("unable to go test. output: %s err: %s", out, err) } - tReadme, err := template.New("readme").ParseFiles("readme_file.tmpl") - if err != nil { - log.Fatal("GoCryptoTrader: Exchange templating tool error ", err) - } - newFile(exchangeReadme) - r1, err := os.OpenFile(exchangeReadme, os.O_WRONLY, 0700) - if err != nil { - log.Fatal("GoCryptoTrader: Exchange templating tool cannot open file ", err) - } - tReadme.Execute(r1, exch) - r1.Close() - - tMain, err := template.New("main").ParseFiles("main_file.tmpl") - if err != nil { - log.Fatal("GoCryptoTrader: Exchange templating tool error ", err) - } - newFile(exchangeMain) - m1, err := os.OpenFile(exchangeMain, os.O_WRONLY, 0700) - if err != nil { - log.Fatal("GoCryptoTrader: Exchange templating tool cannot open file ", err) - } - tMain.Execute(m1, exch) - m1.Close() - - tTest, err := template.New("test").ParseFiles("test_file.tmpl") - if err != nil { - log.Fatal("GoCryptoTrader: Exchange templating tool error ", err) - } - newFile(exchangeTest) - t1, err := os.OpenFile(exchangeTest, os.O_WRONLY, 0700) - if err != nil { - log.Fatal("GoCryptoTrader: Exchange templating tool cannot open file ", err) - } - tTest.Execute(t1, exch) - t1.Close() - - tType, err := template.New("type").ParseFiles("type_file.tmpl") - if err != nil { - log.Fatal("GoCryptoTrader: Exchange templating tool error ", err) - } - newFile(exchangeTypes) - ty1, err := os.OpenFile(exchangeTypes, os.O_WRONLY, 0700) - if err != nil { - log.Fatal("GoCryptoTrader: Exchange templating tool cannot open file ", err) - } - tType.Execute(ty1, exch) - ty1.Close() - - tWrapper, err := template.New("wrapper").ParseFiles("wrapper_file.tmpl") - if err != nil { - log.Fatal("GoCryptoTrader: Exchange templating tool error ", err) - } - newFile(exchangeWrapper) - w1, err := os.OpenFile(exchangeWrapper, os.O_WRONLY, 0700) - if err != nil { - log.Fatal("GoCryptoTrader: Exchange templating tool cannot open file ", err) - } - tWrapper.Execute(w1, exch) - w1.Close() - - err = exec.Command("go", "fmt", exchangeDirectory).Run() - if err != nil { - log.Fatal("GoCryptoTrader: Exchange templating tool go fmt error ", err) - } - - err = exec.Command("go", "test", exchangeDirectory).Run() - if err != nil { - log.Fatal("GoCryptoTrader: Exchange templating tool testing failed ", err) - } - - fmt.Println("GoCryptoTrader: Exchange templating tool service complete") - fmt.Println("When the exchange code implementation has been completed (REST/Websocket/wrappers and tests), please add the exchange to engine/exchange.go") - fmt.Println("Add the exchange config settings to config_example.json (it will automatically be added to testdata/configtest.json)") - fmt.Println("Increment the available exchanges counter in config/config_test.go") - fmt.Println("Add the exchange name to exchanges/support.go") - fmt.Println("Ensure go test ./... -race passes") - fmt.Println("Open a pull request") - fmt.Println("If help is needed, please post a message in Slack.") + return nil } func newFile(path string) { _, err := os.Stat(path) - if os.IsNotExist(err) { var file, err = os.Create(path) if err != nil { - log.Fatal("GoCryptoTrader: Exchange templating tool file creation error ", err) + log.Fatal(err) } file.Close() } diff --git a/cmd/exchange_template/exchange_template_test.go b/cmd/exchange_template/exchange_template_test.go new file mode 100644 index 00000000..1ce84a75 --- /dev/null +++ b/cmd/exchange_template/exchange_template_test.go @@ -0,0 +1,74 @@ +package main + +import ( + "os" + "path/filepath" + "testing" + + "github.com/thrasher-corp/gocryptotrader/config" +) + +func TestCheckExchangeName(t *testing.T) { + tester := []struct { + Name string + ErrExpected error + }{ + { + Name: "test exch", + ErrExpected: errInvalidExchangeName, + }, + { + ErrExpected: errInvalidExchangeName, + }, + { + Name: " ", + ErrExpected: errInvalidExchangeName, + }, + { + Name: "m", + ErrExpected: errInvalidExchangeName, + }, + { + Name: "mu", + ErrExpected: errInvalidExchangeName, + }, + { + Name: "testexch", + }, + } + + for x := range tester { + if r := checkExchangeName(tester[x].Name); r != tester[x].ErrExpected { + t.Errorf("test: %d unexpected result", x) + } + } +} + +func TestNewExchange(t *testing.T) { + testExchangeName := "testexch" + testExchangeDir := filepath.Join(targetPath, testExchangeName) + + if err := makeExchange(&exchange{ + Name: testExchangeName, + REST: true, + WS: true, + }); err != nil { + t.Error(err) + } + + if err := os.RemoveAll(testExchangeDir); err != nil { + t.Errorf("unable to remove dir: %s, manual removal required", err) + } + + cfg := config.GetConfig() + if err := cfg.LoadConfig(exchangeConfigPath, true); err != nil { + t.Fatal(err) + } + if success := cfg.RemoveExchange(testExchangeName); !success { + t.Fatalf("unable to remove exchange config for %s, manual removal required\n", + testExchangeName) + } + if err := cfg.SaveConfig(exchangeConfigPath, false); err != nil { + t.Fatal(err) + } +} diff --git a/cmd/exchange_template/readme_file.tmpl b/cmd/exchange_template/readme_file.tmpl index b7bee611..8a096a02 100644 --- a/cmd/exchange_template/readme_file.tmpl +++ b/cmd/exchange_template/readme_file.tmpl @@ -7,7 +7,7 @@ An exchange interface wrapper for the GoCryptoTrader application. ## This is still in active development - You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). +You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). ## Current {{.CapitalName}} Exchange Features diff --git a/cmd/exchange_template/test_file.tmpl b/cmd/exchange_template/test_file.tmpl index 989238e8..c1c672c9 100644 --- a/cmd/exchange_template/test_file.tmpl +++ b/cmd/exchange_template/test_file.tmpl @@ -7,6 +7,7 @@ import ( "testing" "github.com/thrasher-corp/gocryptotrader/config" + exchange "github.com/thrasher-corp/gocryptotrader/exchanges" ) // Please supply your own keys here to do authenticated endpoint testing @@ -44,6 +45,14 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } +// Ensures that this exchange package is compatible with IBotExchange +func TestInterface(t *testing.T) { + var e exchange.IBotExchange + if e = new({{.CapitalName}}); e == nil { + t.Fatal("unable to allocate exchange") + } +} + func areTestAPIKeysSet() bool { return {{.Variable}}.ValidateAPICredentials() } diff --git a/cmd/exchange_template/wrapper_file.tmpl b/cmd/exchange_template/wrapper_file.tmpl index c04451be..99d314df 100644 --- a/cmd/exchange_template/wrapper_file.tmpl +++ b/cmd/exchange_template/wrapper_file.tmpl @@ -9,15 +9,17 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/protocol" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/websocket/wshandler" - "github.com/thrasher-corp/gocryptotrader/withdraw" "github.com/thrasher-corp/gocryptotrader/log" + "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) // GetDefaultConfig returns a default exchange config @@ -83,9 +85,10 @@ func ({{.Variable}} *{{.CapitalName}}) SetDefaults() { AutoPairUpdates: true, }, } + // NOTE: SET THE EXCHANGES RATE LIMIT HERE {{.Variable}}.Requester = request.New({{.Variable}}.Name, - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(request.NewRateLimit(time.Second, 0))) + common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) + {{.Variable}}.API.Endpoints.URLDefault = {{.Name}}APIURL {{.Variable}}.API.Endpoints.URL = {{.Variable}}.API.Endpoints.URLDefault {{.Variable}}.Websocket = wshandler.New() @@ -279,10 +282,14 @@ func ({{.Variable}} *{{.CapitalName}}) UpdateOrderbook(p currency.Pair, assetTyp return orderbook.Get({{.Variable}}.Name, p, assetType) } -// GetAccountInfo retrieves balances for all enabled currencies for the -// {{.CapitalName}} exchange -func ({{.Variable}} *{{.CapitalName}}) GetAccountInfo() (exchange.AccountInfo, error) { - return exchange.AccountInfo{}, common.ErrNotYetImplemented +// UpdateAccountInfo retrieves balances for all enabled currencies +func ({{.Variable}} *{{.CapitalName}}) UpdateAccountInfo() (account.Holdings, error) { + return account.Holdings{}, common.ErrNotYetImplemented +} + +// FetchAccountInfo retrieves balances for all enabled currencies +func ({{.Variable}} *{{.CapitalName}}) FetchAccountInfo() (account.Holdings, error) { + return account.Holdings{}, common.ErrNotYetImplemented } // GetFundingHistory returns funding history, deposits and @@ -334,19 +341,19 @@ func ({{.Variable}} *{{.CapitalName}}) GetDepositAddress(cryptocurrency currency // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is // submitted func ({{.Variable}} *{{.CapitalName}}) WithdrawCryptocurrencyFunds(withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) { - return "", common.ErrNotYetImplemented + return nil, common.ErrNotYetImplemented } // WithdrawFiatFunds returns a withdrawal ID when a withdrawal is // submitted func ({{.Variable}} *{{.CapitalName}}) WithdrawFiatFunds(withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) { - return "", common.ErrNotYetImplemented + return nil, common.ErrNotYetImplemented } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a withdrawal is // submitted -func ({{.Variable}} *{{.CapitalName}}) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw.Request) (string, error) { - return "", common.ErrNotYetImplemented +func ({{.Variable}} *{{.CapitalName}}) WithdrawFiatFundsToInternationalBank(withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) { + return nil, common.ErrNotYetImplemented } // GetWebsocket returns a pointer to the exchange websocket @@ -385,13 +392,25 @@ func ({{.Variable}} *{{.CapitalName}}) UnsubscribeToWebsocketChannels(channels [ } // GetSubscriptions returns a copied list of subscriptions -func ({{.Variable}} *{{.CapitalName}}) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { +func ({{.Variable}} *{{.CapitalName}}) GetSubscriptions() ([]wshandler.WebsocketChannelSubscription, error) { return nil, common.ErrNotYetImplemented } // AuthenticateWebsocket sends an authentication message to the websocket -func ({{.Variable}} *{{.CapitalName}}) AuthenticateWebsocket() error { +func ({{.Variable}} *{{.CapitalName}}) AuthenticateWebsocket() error { return common.ErrNotYetImplemented } +// ValidateCredentials validates current credentials used for wrapper +// functionality +func ({{.Variable}} *{{.CapitalName}}) ValidateCredentials() error { + _, err := {{.Variable}}.UpdateAccountInfo() + return {{.Variable}}.CheckTransientError(err) +} + +// GetHistoricCandles returns candles between a time period for a set time interval +func ({{.Variable}} *{{.CapitalName}}) GetHistoricCandles(pair currency.Pair, a asset.Item, start, end time.Time, interval time.Duration) (kline.Item, error) { + return kline.Item{}, common.ErrNotYetImplemented +} + {{end}} diff --git a/common/convert/convert.go b/common/convert/convert.go index 8507d1a9..61d1c42d 100644 --- a/common/convert/convert.go +++ b/common/convert/convert.go @@ -1,11 +1,9 @@ package convert import ( - "errors" "fmt" "math" "strconv" - "strings" "time" ) @@ -57,6 +55,13 @@ func TimeFromUnixTimestampFloat(raw interface{}) (time.Time, error) { return time.Unix(0, int64(ts)*int64(time.Millisecond)), nil } +// TimeFromUnixTimestampDecimal converts a unix timestamp in decimal form to +// a time.Time +func TimeFromUnixTimestampDecimal(input float64) time.Time { + i, f := math.Modf(input) + return time.Unix(int64(i), int64(f*(1e9))) +} + // UnixTimestampToTime returns time.time func UnixTimestampToTime(timeint64 int64) time.Time { return time.Unix(timeint64, 0) @@ -81,33 +86,6 @@ func RecvWindow(d time.Duration) int64 { return int64(d) / int64(time.Millisecond) } -// SplitFloatDecimals takes in a float64 and splits -// the decimals into their own integers -// Warning. Passing in numbers with many decimals -// can lead to a loss of accuracy -func SplitFloatDecimals(input float64) (baseNum, decimalNum int64, err error) { - if input > float64(math.MaxInt64) { - return 0, 0, errors.New("number too large to split into integers") - } - if input == float64(int64(input)) { - return int64(input), 0, nil - } - decStr := strconv.FormatFloat(input, 'f', -1, 64) - splitNum := strings.Split(decStr, ".") - baseNum, err = strconv.ParseInt(splitNum[0], 10, 64) - if err != nil { - return 0, 0, err - } - decimalNum, err = strconv.ParseInt(splitNum[1], 10, 64) - if err != nil { - return 0, 0, err - } - if baseNum < 0 { - decimalNum *= -1 - } - return baseNum, decimalNum, nil -} - // BoolPtr takes in boolen condition and returns pointer version of it func BoolPtr(condition bool) *bool { b := condition diff --git a/common/convert/convert_test.go b/common/convert/convert_test.go index b52172d1..456a5796 100644 --- a/common/convert/convert_test.go +++ b/common/convert/convert_test.go @@ -1,7 +1,6 @@ package convert import ( - "math" "testing" "time" ) @@ -96,6 +95,22 @@ func TestTimeFromUnixTimestampFloat(t *testing.T) { } } +func TestTimeFromUnixTimestampDecimal(t *testing.T) { + r := TimeFromUnixTimestampDecimal(1590633982.5714) + if r.Year() != 2020 || + r.Month().String() != "May" || + r.Day() != 28 { + t.Error("unexpected result") + } + + r = TimeFromUnixTimestampDecimal(1560516023.070651) + if r.Year() != 2019 || + r.Month().String() != "June" || + r.Day() != 14 { + t.Error("unexpected result") + } +} + func TestUnixTimestampToTime(t *testing.T) { t.Parallel() testTime := int64(1489439831) @@ -151,61 +166,6 @@ func TestRecvWindow(t *testing.T) { } } -// TestSplitFloatDecimals ensures SplitFloatDecimals -// accurately splits decimals into integers -func TestSplitFloatDecimals(t *testing.T) { - x, y, err := SplitFloatDecimals(1.2) - if err != nil { - t.Error(err) - } - if x != 1 && y != 2 { - t.Error("Conversion error") - } - x, y, err = SplitFloatDecimals(123456.654321) - if err != nil { - t.Error(err) - } - if x != 123456 && y != 654321 { - t.Error("Conversion error") - } - x, y, err = SplitFloatDecimals(123.111000) - if err != nil { - t.Error(err) - } - if x != 123 && y != 111 { - t.Error("Conversion error") - } - x, y, err = SplitFloatDecimals(0123.111001) - if err != nil { - t.Error(err) - } - if x != 123 && y != 111001 { - t.Error("Conversion error") - } - x, y, err = SplitFloatDecimals(1) - if err != nil { - t.Error(err) - } - if x != 1 && y != 0 { - t.Error("Conversion error") - } - _, _, err = SplitFloatDecimals(float64(math.MaxInt64) + 1) - if err == nil { - t.Error("Expected conversion error") - } - _, _, err = SplitFloatDecimals(1797693134862315700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111) - if err == nil { - t.Error("Expected conversion error") - } - x, y, err = SplitFloatDecimals(-1.2) - if err != nil { - t.Error(err) - } - if x != -1 && y != -2 { - t.Error("Conversion error") - } -} - func TestBoolPtr(t *testing.T) { y := BoolPtr(true) if !*y { diff --git a/config/config.go b/config/config.go index 4a92c3c7..5467373e 100644 --- a/config/config.go +++ b/config/config.go @@ -1690,3 +1690,16 @@ func (c *Config) UpdateConfig(configPath string, newCfg *Config, dryrun bool) er func GetConfig() *Config { return &Cfg } + +// RemoveExchange removes an exchange config +func (c *Config) RemoveExchange(exchName string) bool { + m.Lock() + defer m.Unlock() + for x := range c.Exchanges { + if strings.EqualFold(c.Exchanges[x].Name, exchName) { + c.Exchanges = append(c.Exchanges[:x], c.Exchanges[x+1:]...) + return true + } + } + return false +} diff --git a/config/config_test.go b/config/config_test.go index 64ca3d20..716513a1 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1844,3 +1844,26 @@ func TestPreengineConfigUpgrade(t *testing.T) { t.Fatal(err) } } + +func TestRemoveExchange(t *testing.T) { + t.Parallel() + var c Config + const testExchangeName = "0xBAAAAAAD" + c.Exchanges = append(c.Exchanges, ExchangeConfig{ + Name: testExchangeName, + }) + _, err := c.GetExchangeConfig(testExchangeName) + if err != nil { + t.Fatal(err) + } + if success := c.RemoveExchange(testExchangeName); !success { + t.Fatal("exchange should of been removed") + } + _, err = c.GetExchangeConfig(testExchangeName) + if err == nil { + t.Fatal("non-existent exchange should throw an error") + } + if success := c.RemoveExchange("1D10TH0RS3"); success { + t.Fatal("exchange shouldn't exist") + } +} diff --git a/config_example.json b/config_example.json index 12979852..9a6218ef 100644 --- a/config_example.json +++ b/config_example.json @@ -1607,7 +1607,7 @@ "uppercase": true }, "useGlobalFormat": true, - "lastUpdated": 1566798411, + "lastUpdated": 1591062026, "assetTypes": [ "spot" ], diff --git a/docs/EXCHANGE_API.md b/docs/EXCHANGE_API.md index 2af91388..46da5798 100644 --- a/docs/EXCHANGE_API.md +++ b/docs/EXCHANGE_API.md @@ -22,7 +22,7 @@ GoCryptoTrader supports a unified API for dealing with exchanges. Each exchange has its own wrapper file which maps the exchanges own RESTful endpoints into a standardised way for bot and standalone application usage. -A full breakdown of all the supported wrapper funcs can be found [here.](https://github.com/thrasher-corp/gocryptotrader/blob/engine/exchanges/interfaces.go#L16) +A full breakdown of all the supported wrapper funcs can be found [here.](https://github.com/thrasher-corp/gocryptotrader/blob/master/exchanges/interfaces.go#L21) Please note that these change on a regular basis as GoCryptoTrader is undergoing rapid development. diff --git a/engine/orders.go b/engine/orders.go index 83e65cd9..ca91e898 100644 --- a/engine/orders.go +++ b/engine/orders.go @@ -389,31 +389,35 @@ func (o *orderManager) Submit(newOrder *order.Submit) (*orderSubmitResponse, err func (o *orderManager) processOrders() { authExchanges := GetAuthAPISupportedExchanges() for x := range authExchanges { - log.Debugf(log.OrderMgr, "Order manager: Procesing orders for exchange %v.", authExchanges[x]) + log.Debugf(log.OrderMgr, "Order manager: Processing orders for exchange %v.", authExchanges[x]) exch := GetExchangeByName(authExchanges[x]) - req := order.GetOrdersRequest{ - Side: order.AnySide, - Type: order.AnyType, - } - result, err := exch.GetActiveOrders(&req) - if err != nil { - log.Warnf(log.OrderMgr, "Order manager: Unable to get active orders: %s", err) - continue - } - - for x := range result { - ord := &result[x] - result := o.orderStore.Add(ord) - if result != ErrOrdersAlreadyExists { - msg := fmt.Sprintf("Order manager: Exchange %s added order ID=%v pair=%v price=%v amount=%v side=%v type=%v.", - ord.Exchange, ord.ID, ord.Pair, ord.Price, ord.Amount, ord.Side, ord.Type) - log.Debugf(log.OrderMgr, "%v", msg) - Bot.CommsManager.PushEvent(base.Event{ - Type: "order", - Message: msg, - }) + supportedAssets := exch.GetAssetTypes() + for y := range supportedAssets { + req := order.GetOrdersRequest{ + Side: order.AnySide, + Type: order.AnyType, + Pairs: exch.GetEnabledPairs(supportedAssets[y]), + } + result, err := exch.GetActiveOrders(&req) + if err != nil { + log.Warnf(log.OrderMgr, "Order manager: Unable to get active orders: %s", err) continue } + + for z := range result { + ord := &result[z] + result := o.orderStore.Add(ord) + if result != ErrOrdersAlreadyExists { + msg := fmt.Sprintf("Order manager: Exchange %s added order ID=%v pair=%v price=%v amount=%v side=%v type=%v.", + ord.Exchange, ord.ID, ord.Pair, ord.Price, ord.Amount, ord.Side, ord.Type) + log.Debugf(log.OrderMgr, "%v", msg) + Bot.CommsManager.PushEvent(base.Event{ + Type: "order", + Message: msg, + }) + continue + } + } } } } diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index 6a97da42..260af14d 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -415,6 +415,7 @@ func (b *Binance) SeedLocalCache(p currency.Pair) error { return b.SeedLocalCacheWithBook(p, &ob) } +// SeedLocalCacheWithBook seeds the local orderbook cache func (b *Binance) SeedLocalCacheWithBook(p currency.Pair, orderbookNew *OrderBook) error { var newOrderBook orderbook.Base for i := range orderbookNew.Bids { diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 599e8676..63d08cdf 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -42,8 +42,6 @@ const ( coinbaseproReports = "reports" coinbaseproTime = "time" coinbaseproMarginTransfer = "profiles/margin-transfer" - coinbaseproFunding = "funding" - coinbaseproFundingRepay = "funding/repay" coinbaseproPosition = "position" coinbaseproPositionClose = "position/close" coinbaseproPaymentMethod = "payment-methods" @@ -490,37 +488,6 @@ func (c *CoinbasePro) GetFills(orderID, currencyPair string) ([]FillResponse, er c.SendAuthenticatedHTTPRequest(http.MethodGet, uri[1:], nil, &resp) } -// GetFundingRecords every order placed with a margin profile that draws funding -// will create a funding record. -// -// status - "outstanding", "settled", or "rejected" -func (c *CoinbasePro) GetFundingRecords(status string) ([]Funding, error) { - var resp []Funding - params := url.Values{} - params.Set("status", status) - - path := common.EncodeURLValues(c.API.Endpoints.URL+coinbaseproFunding, params) - uri := common.GetURIPath(path) - - return resp, - c.SendAuthenticatedHTTPRequest(http.MethodGet, uri[1:], nil, &resp) -} - -// //////////////////////// Not receiving reply from server ///////////////// -// RepayFunding repays the older funding records first -// -// amount - amount of currency to repay -// currency - currency, example USD -// func (c *CoinbasePro) RepayFunding(amount, currency string) (Funding, error) { -// resp := Funding{} -// params := make(map[string]interface{}) -// params["amount"] = amount -// params["currency"] = currency -// -// return resp, -// c.SendAuthenticatedHTTPRequest(http.MethodPost, coinbaseproFundingRepay, params, &resp) -// } - // MarginTransfer sends funds between a standard/default profile and a margin // profile. // A deposit will transfer funds from the default profile into the margin diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 72d78416..27629591 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -8,6 +8,7 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/core" "github.com/thrasher-corp/gocryptotrader/currency" @@ -165,10 +166,6 @@ func TestAuthRequests(t *testing.T) { if err == nil { t.Error("Expecting error") } - _, err = c.GetFundingRecords("rejected") - if err == nil { - t.Error("Expecting error") - } marginTransferResponse, err := c.MarginTransfer(1, "withdraw", "13371337-1337-1337-1337-133713371337", "BTC") if marginTransferResponse.ID != "" { t.Error("Expecting no data returned") @@ -925,3 +922,17 @@ func TestStatusToStandardStatus(t *testing.T) { } } } + +func TestParseTime(t *testing.T) { + // Rest examples use 2014-11-07T22:19:28.578544Z" and can be safely + // unmarhsalled into time.Time + + // All events except for activate use the above, in the below test + // we'll use their API docs example + r := convert.TimeFromUnixTimestampDecimal(1483736448.299000).UTC() + if r.Year() != 2017 || + r.Month().String() != "January" || + r.Day() != 6 { + t.Error("unexpected result") + } +} diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index f56080f4..b8cb052f 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -30,11 +30,11 @@ type Ticker struct { // Trade holds executed trade information type Trade struct { - TradeID int64 `json:"trade_id"` - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - Time string `json:"time"` - Side string `json:"side"` + TradeID int64 `json:"trade_id"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + Time time.Time `json:"time"` + Side string `json:"side"` } // History holds historic rate information @@ -49,10 +49,12 @@ type History struct { // Stats holds last 24 hr data for coinbasepro type Stats struct { - Open float64 `json:"open,string"` - High float64 `json:"high,string"` - Low float64 `json:"low,string"` - Volume float64 `json:"volume,string"` + Open float64 `json:"open,string"` + High float64 `json:"high,string"` + Low float64 `json:"low,string"` + Volume float64 `json:"volume,string"` + Last float64 `json:"last,string"` + Volume30Day float64 `json:"volume_30day,string"` } // Currency holds singular currency product information @@ -64,8 +66,8 @@ type Currency struct { // ServerTime holds current requested server time information type ServerTime struct { - ISO string `json:"iso"` - Epoch float64 `json:"epoch"` + ISO time.Time `json:"iso"` + Epoch float64 `json:"epoch"` } // AccountResponse holds the details for the trading accounts @@ -84,7 +86,7 @@ type AccountResponse struct { // AccountLedgerResponse holds account history information type AccountLedgerResponse struct { ID string `json:"id"` - CreatedAt string `json:"created_at"` + CreatedAt time.Time `json:"created_at"` Amount float64 `json:"amount,string"` Balance float64 `json:"balance,string"` Type string `json:"type"` @@ -93,68 +95,68 @@ type AccountLedgerResponse struct { // AccountHolds contains the hold information about an account type AccountHolds struct { - ID string `json:"id"` - AccountID string `json:"account_id"` - CreatedAt string `json:"created_at"` - UpdatedAt string `json:"updated_at"` - Amount float64 `json:"amount,string"` - Type string `json:"type"` - Reference string `json:"ref"` + ID string `json:"id"` + AccountID string `json:"account_id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt string `json:"updated_at"` + Amount float64 `json:"amount,string"` + Type string `json:"type"` + Reference string `json:"ref"` } // GeneralizedOrderResponse is the generalized return type across order // placement and information collation type GeneralizedOrderResponse struct { - ID string `json:"id"` - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - ProductID string `json:"product_id"` - Side string `json:"side"` - Stp string `json:"stp"` - Type string `json:"type"` - TimeInForce string `json:"time_in_force"` - PostOnly bool `json:"post_only"` - CreatedAt string `json:"created_at"` - FillFees float64 `json:"fill_fees,string"` - FilledSize float64 `json:"filled_size,string"` - ExecutedValue float64 `json:"executed_value,string"` - Status string `json:"status"` - Settled bool `json:"settled"` - Funds float64 `json:"funds,string"` - SpecifiedFunds float64 `json:"specified_funds,string"` - DoneReason string `json:"done_reason"` - DoneAt string `json:"done_at"` + ID string `json:"id"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + ProductID string `json:"product_id"` + Side string `json:"side"` + Stp string `json:"stp"` + Type string `json:"type"` + TimeInForce string `json:"time_in_force"` + PostOnly bool `json:"post_only"` + CreatedAt time.Time `json:"created_at"` + FillFees float64 `json:"fill_fees,string"` + FilledSize float64 `json:"filled_size,string"` + ExecutedValue float64 `json:"executed_value,string"` + Status string `json:"status"` + Settled bool `json:"settled"` + Funds float64 `json:"funds,string"` + SpecifiedFunds float64 `json:"specified_funds,string"` + DoneReason string `json:"done_reason"` + DoneAt string `json:"done_at"` } // Funding holds funding data type Funding struct { - ID string `json:"id"` - OrderID string `json:"order_id"` - ProfileID string `json:"profile_id"` - Amount float64 `json:"amount,string"` - Status string `json:"status"` - CreatedAt string `json:"created_at"` - Currency string `json:"currency"` - RepaidAmount float64 `json:"repaid_amount"` - DefaultAmount float64 `json:"default_amount,string"` - RepaidDefault bool `json:"repaid_default"` + ID string `json:"id"` + OrderID string `json:"order_id"` + ProfileID string `json:"profile_id"` + Amount float64 `json:"amount,string"` + Status string `json:"status"` + CreatedAt time.Time `json:"created_at"` + Currency string `json:"currency"` + RepaidAmount float64 `json:"repaid_amount"` + DefaultAmount float64 `json:"default_amount,string"` + RepaidDefault bool `json:"repaid_default"` } // MarginTransfer holds margin transfer details type MarginTransfer struct { - CreatedAt string `json:"created_at"` - ID string `json:"id"` - UserID string `json:"user_id"` - ProfileID string `json:"profile_id"` - MarginProfileID string `json:"margin_profile_id"` - Type string `json:"type"` - Amount float64 `json:"amount,string"` - Currency string `json:"currency"` - AccountID string `json:"account_id"` - MarginAccountID string `json:"margin_account_id"` - MarginProductID string `json:"margin_product_id"` - Status string `json:"status"` - Nonce int `json:"nonce"` + CreatedAt time.Time `json:"created_at"` + ID string `json:"id"` + UserID string `json:"user_id"` + ProfileID string `json:"profile_id"` + MarginProfileID string `json:"margin_profile_id"` + Type string `json:"type"` + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + AccountID string `json:"account_id"` + MarginAccountID string `json:"margin_account_id"` + MarginProductID string `json:"margin_product_id"` + Status string `json:"status"` + Nonce int `json:"nonce"` } // AccountOverview holds account information returned from position @@ -164,12 +166,12 @@ type AccountOverview struct { MaxFundingValue float64 `json:"max_funding_value,string"` FundingValue float64 `json:"funding_value,string"` OldestOutstanding struct { - ID string `json:"id"` - OrderID string `json:"order_id"` - CreatedAt string `json:"created_at"` - Currency string `json:"currency"` - AccountID string `json:"account_id"` - Amount float64 `json:"amount,string"` + ID string `json:"id"` + OrderID string `json:"order_id"` + CreatedAt time.Time `json:"created_at"` + Currency string `json:"currency"` + AccountID string `json:"account_id"` + Amount float64 `json:"amount,string"` } `json:"oldest_outstanding"` } `json:"funding"` Accounts struct { @@ -236,10 +238,10 @@ type LimitInfo struct { // DepositWithdrawalInfo holds returned deposit information type DepositWithdrawalInfo struct { - ID string `json:"id"` - Amount float64 `json:"amount,string"` - Currency string `json:"currency"` - PayoutAt string `json:"payout_at"` + ID string `json:"id"` + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + PayoutAt time.Time `json:"payout_at"` } // CoinbaseAccounts holds coinbase account information @@ -278,16 +280,16 @@ type CoinbaseAccounts struct { // Report holds historical information type Report struct { - ID string `json:"id"` - Type string `json:"type"` - Status string `json:"status"` - CreatedAt string `json:"created_at"` - CompletedAt string `json:"completed_at"` - ExpiresAt string `json:"expires_at"` - FileURL string `json:"file_url"` + ID string `json:"id"` + Type string `json:"type"` + Status string `json:"status"` + CreatedAt time.Time `json:"created_at"` + CompletedAt time.Time `json:"completed_at"` + ExpiresAt time.Time `json:"expires_at"` + FileURL string `json:"file_url"` Params struct { - StartDate string `json:"start_date"` - EndDate string `json:"end_date"` + StartDate time.Time `json:"start_date"` + EndDate time.Time `json:"end_date"` } `json:"params"` } @@ -336,16 +338,16 @@ type OrderbookResponse struct { // FillResponse contains fill information from the exchange type FillResponse struct { - TradeID int `json:"trade_id"` - ProductID string `json:"product_id"` - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - OrderID string `json:"order_id"` - CreatedAt string `json:"created_at"` - Liquidity string `json:"liquidity"` - Fee float64 `json:"fee,string"` - Settled bool `json:"settled"` - Side string `json:"side"` + TradeID int `json:"trade_id"` + ProductID string `json:"product_id"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + OrderID string `json:"order_id"` + CreatedAt time.Time `json:"created_at"` + Liquidity string `json:"liquidity"` + Fee float64 `json:"fee,string"` + Settled bool `json:"settled"` + Side string `json:"side"` } // WebsocketSubscribe takes in subscription information diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index bb0f01c6..c785cf4a 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -177,12 +177,7 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error { } ts := wsOrder.Time if wsOrder.Type == "activate" { - var one, two int64 - one, two, err = convert.SplitFloatDecimals(wsOrder.Timestamp) - if err != nil { - return err - } - ts = time.Unix(one, two) + ts = convert.TimeFromUnixTimestampDecimal(wsOrder.Timestamp) } var p currency.Pair diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 99c30bf9..0477bf86 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -311,7 +311,7 @@ func (c *CoinbasePro) UpdateTicker(p currency.Pair, assetType asset.Item) (*tick } tickerPrice := &ticker.Price{ - Last: tick.Size, + Last: stats.Last, High: stats.High, Low: stats.Low, Bid: tick.Bid, @@ -498,12 +498,8 @@ func (c *CoinbasePro) GetOrderInfo(orderID string) (order.Detail, error) { if errTSi != nil { return response, fmt.Errorf("error parsing order Side: %s", errTSi) } - td, errTd := time.Parse(time.RFC3339, fillResponse[i].CreatedAt) - if errTd != nil { - return response, fmt.Errorf("error parsing trade created time: %s", errTd) - } response.Trades = append(response.Trades, order.TradeHistory{ - Timestamp: td, + Timestamp: fillResponse[i].CreatedAt, TID: string(fillResponse[i].TradeID), Price: fillResponse[i].Price, Amount: fillResponse[i].Size, @@ -607,22 +603,12 @@ func (c *CoinbasePro) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Deta c.GetPairFormat(asset.Spot, false).Delimiter) orderSide := order.Side(strings.ToUpper(respOrders[i].Side)) orderType := order.Type(strings.ToUpper(respOrders[i].Type)) - orderDate, err := time.Parse(time.RFC3339, respOrders[i].CreatedAt) - if err != nil { - log.Errorf(log.ExchangeSys, - "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", - c.Name, - "GetActiveOrders", - respOrders[i].ID, - respOrders[i].CreatedAt) - } - orders = append(orders, order.Detail{ ID: respOrders[i].ID, Amount: respOrders[i].Size, ExecutedAmount: respOrders[i].FilledSize, Type: orderType, - Date: orderDate, + Date: respOrders[i].CreatedAt, Side: orderSide, Pair: curr, Exchange: c.Name, @@ -654,22 +640,12 @@ func (c *CoinbasePro) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Deta c.GetPairFormat(asset.Spot, false).Delimiter) orderSide := order.Side(strings.ToUpper(respOrders[i].Side)) orderType := order.Type(strings.ToUpper(respOrders[i].Type)) - orderDate, err := time.Parse(time.RFC3339, respOrders[i].CreatedAt) - if err != nil { - log.Errorf(log.ExchangeSys, - "Exchange %v Func %v Order %v Could not parse date to unix with value of %v", - c.Name, - "GetActiveOrders", - respOrders[i].ID, - respOrders[i].CreatedAt) - } - orders = append(orders, order.Detail{ ID: respOrders[i].ID, Amount: respOrders[i].Size, ExecutedAmount: respOrders[i].FilledSize, Type: orderType, - Date: orderDate, + Date: respOrders[i].CreatedAt, Side: orderSide, Pair: curr, Exchange: c.Name, diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index 6c4057a7..dd194a17 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -229,45 +229,60 @@ func (e *EXMO) FetchOrderbook(p currency.Pair, assetType asset.Item) (*orderbook // UpdateOrderbook updates and returns the orderbook for a currency pair func (e *EXMO) UpdateOrderbook(p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - orderBook := new(orderbook.Base) - pairsCollated, err := e.FormatExchangeCurrencies(e.GetEnabledPairs(assetType), - assetType) + enabledPairs := e.GetEnabledPairs(assetType) + pairsCollated, err := e.FormatExchangeCurrencies(enabledPairs, assetType) if err != nil { - return orderBook, err + return nil, err } result, err := e.GetOrderbook(pairsCollated) if err != nil { - return orderBook, err + return nil, err } - enabledPairs := e.GetEnabledPairs(assetType) + for i := range enabledPairs { - curr := e.FormatExchangeCurrency(enabledPairs[i], assetType) - data, ok := result[curr.String()] + data, ok := result[e.FormatExchangeCurrency(enabledPairs[i], assetType).String()] if !ok { continue } - var obItems []orderbook.Item + orderBook := new(orderbook.Base) for y := range data.Ask { - z := data.Ask[y] - price, _ := strconv.ParseFloat(z[0], 64) - amount, _ := strconv.ParseFloat(z[1], 64) - obItems = append(obItems, - orderbook.Item{Price: price, Amount: amount}) + var price, amount float64 + price, err = strconv.ParseFloat(data.Ask[y][0], 64) + if err != nil { + return orderBook, err + } + + amount, err = strconv.ParseFloat(data.Ask[y][1], 64) + if err != nil { + return orderBook, err + } + + orderBook.Asks = append(orderBook.Asks, orderbook.Item{ + Price: price, + Amount: amount, + }) } - orderBook.Asks = obItems - obItems = []orderbook.Item{} for y := range data.Bid { - z := data.Bid[y] - price, _ := strconv.ParseFloat(z[0], 64) - amount, _ := strconv.ParseFloat(z[1], 64) - obItems = append(obItems, - orderbook.Item{Price: price, Amount: amount}) + var price, amount float64 + price, err = strconv.ParseFloat(data.Bid[y][0], 64) + if err != nil { + return orderBook, err + } + + amount, err = strconv.ParseFloat(data.Bid[y][1], 64) + if err != nil { + return orderBook, err + } + + orderBook.Bids = append(orderBook.Bids, orderbook.Item{ + Price: price, + Amount: amount, + }) } - orderBook.Bids = obItems orderBook.Pair = enabledPairs[i] orderBook.ExchangeName = e.Name orderBook.AssetType = assetType diff --git a/exchanges/gateio/gateio_test.go b/exchanges/gateio/gateio_test.go index e48870b1..ca4e3a36 100644 --- a/exchanges/gateio/gateio_test.go +++ b/exchanges/gateio/gateio_test.go @@ -8,6 +8,7 @@ import ( "github.com/gorilla/websocket" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/core" "github.com/thrasher-corp/gocryptotrader/currency" @@ -743,3 +744,21 @@ func TestWsBalanceUpdate(t *testing.T) { t.Error(err) } } + +func TestParseTime(t *testing.T) { + // Test REST example + r := convert.TimeFromUnixTimestampDecimal(1574846296.995313).UTC() + if r.Year() != 2019 || + r.Month().String() != "November" || + r.Day() != 27 { + t.Error("unexpected result") + } + + // Test websocket example + r = convert.TimeFromUnixTimestampDecimal(1523887354.256974).UTC() + if r.Year() != 2018 || + r.Month().String() != "April" || + r.Day() != 16 { + t.Error("unexpected result") + } +} diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index 1bb58f34..ac2bd682 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -223,16 +223,7 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { case 2: oSide = order.Buy } - var cTime, cTimeDec, mTime, mTimeDec int64 var price, amount, filledTotal, left, fee float64 - cTime, cTimeDec, err = convert.SplitFloatDecimals(invalidJSON["ctime"].(float64)) - if err != nil { - return err - } - mTime, mTimeDec, err = convert.SplitFloatDecimals(invalidJSON["mtime"].(float64)) - if err != nil { - return err - } price, err = strconv.ParseFloat(invalidJSON["price"].(string), 64) if err != nil { return err @@ -271,8 +262,8 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { Side: oSide, Status: oStatus, AssetType: a, - Date: time.Unix(cTime, cTimeDec), - LastUpdated: time.Unix(mTime, mTimeDec), + Date: convert.TimeFromUnixTimestampDecimal(invalidJSON["ctime"].(float64)), + LastUpdated: convert.TimeFromUnixTimestampDecimal(invalidJSON["mtime"].(float64)), Pair: p, } case strings.Contains(result.Method, "depth"): diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 5ca7964f..eb6a342f 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -592,11 +592,6 @@ func (g *Gateio) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e if resp.WebSocketOrderQueryRecords[j].OrderType == 1 { orderType = order.Limit } - firstNum, decNum, err := convert.SplitFloatDecimals(resp.WebSocketOrderQueryRecords[j].Ctime) - if err != nil { - return orders, err - } - orderDate := time.Unix(firstNum, decNum) orders = append(orders, order.Detail{ Exchange: g.Name, AccountID: strconv.FormatInt(resp.WebSocketOrderQueryRecords[j].User, 10), @@ -604,7 +599,7 @@ func (g *Gateio) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e Pair: currency.NewPairFromString(resp.WebSocketOrderQueryRecords[j].Market), Side: orderSide, Type: orderType, - Date: orderDate, + Date: convert.TimeFromUnixTimestampDecimal(resp.WebSocketOrderQueryRecords[j].Ctime), Price: resp.WebSocketOrderQueryRecords[j].Price, Amount: resp.WebSocketOrderQueryRecords[j].Amount, ExecutedAmount: resp.WebSocketOrderQueryRecords[j].FilledAmount, diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index e8ff4361..a0c927a1 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -519,7 +519,7 @@ func (h *HUOBI) SubmitOrder(s *order.Submit) (order.SubmitResponse, error) { var params = SpotNewOrderRequestParams{ Amount: s.Amount, Source: "api", - Symbol: s.Pair.Lower().String(), + Symbol: h.FormatExchangeCurrency(s.Pair, s.AssetType).String(), AccountID: int(accountID), } @@ -797,28 +797,28 @@ func (h *HUOBI) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, er } else { for i := range req.Pairs { resp, err := h.GetOpenOrders(h.API.Credentials.ClientID, - req.Pairs[i].Lower().String(), + h.FormatExchangeCurrency(req.Pairs[i], asset.Spot).String(), side, 500) if err != nil { return nil, err } - for i := range resp { + for x := range resp { orderDetail := order.Detail{ - ID: strconv.FormatInt(resp[i].ID, 10), - Price: resp[i].Price, - Amount: resp[i].Amount, + ID: strconv.FormatInt(resp[x].ID, 10), + Price: resp[x].Price, + Amount: resp[x].Amount, Pair: req.Pairs[i], Exchange: h.Name, - ExecutedAmount: resp[i].FilledAmount, - Date: time.Unix(0, resp[i].CreatedAt*int64(time.Millisecond)), - Status: order.Status(resp[i].State), - AccountID: strconv.FormatInt(resp[i].AccountID, 10), - Fee: resp[i].FilledFees, + ExecutedAmount: resp[x].FilledAmount, + Date: time.Unix(0, resp[x].CreatedAt*int64(time.Millisecond)), + Status: order.Status(resp[x].State), + AccountID: strconv.FormatInt(resp[x].AccountID, 10), + Fee: resp[x].FilledFees, } - setOrderSideAndType(resp[i].Type, &orderDetail) + setOrderSideAndType(resp[x].Type, &orderDetail) orders = append(orders, orderDetail) } @@ -839,7 +839,8 @@ func (h *HUOBI) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, er states := "partial-canceled,filled,canceled" var orders []order.Detail for i := range req.Pairs { - resp, err := h.GetOrders(req.Pairs[i].Lower().String(), + resp, err := h.GetOrders( + h.FormatExchangeCurrency(req.Pairs[i], asset.Spot).String(), "", "", "", @@ -851,21 +852,21 @@ func (h *HUOBI) GetOrderHistory(req *order.GetOrdersRequest) ([]order.Detail, er return nil, err } - for i := range resp { + for x := range resp { orderDetail := order.Detail{ - ID: strconv.FormatInt(resp[i].ID, 10), - Price: resp[i].Price, - Amount: resp[i].Amount, + ID: strconv.FormatInt(resp[x].ID, 10), + Price: resp[x].Price, + Amount: resp[x].Amount, Pair: req.Pairs[i], Exchange: h.Name, - ExecutedAmount: resp[i].FilledAmount, - Date: time.Unix(0, resp[i].CreatedAt*int64(time.Millisecond)), - Status: order.Status(resp[i].State), - AccountID: strconv.FormatInt(resp[i].AccountID, 10), - Fee: resp[i].FilledFees, + ExecutedAmount: resp[x].FilledAmount, + Date: time.Unix(0, resp[x].CreatedAt*int64(time.Millisecond)), + Status: order.Status(resp[x].State), + AccountID: strconv.FormatInt(resp[x].AccountID, 10), + Fee: resp[x].FilledFees, } - setOrderSideAndType(resp[i].Type, &orderDetail) + setOrderSideAndType(resp[x].Type, &orderDetail) orders = append(orders, orderDetail) } diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index 1c592e71..2465a197 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -57,7 +57,9 @@ const ( krakenRequestRate = 1 ) -var assetPairMap map[string]string +var ( + assetTranslator assetTranslatorStore +) // Kraken is the overarching type across the alphapoint package type Kraken struct { @@ -83,41 +85,54 @@ func (k *Kraken) GetServerTime() (TimeResponse, error) { return response.Result, GetError(response.Error) } +// SeedAssets seeds Kraken's asset list and stores it in the +// asset translator +func (k *Kraken) SeedAssets() error { + assets, err := k.GetAssets() + if err != nil { + return err + } + for k, v := range assets { + assetTranslator.Seed(k, v.Altname) + } + + assetPairs, err := k.GetAssetPairs() + if err != nil { + return err + } + for k, v := range assetPairs { + assetTranslator.Seed(k, v.Altname) + } + return nil +} + // GetAssets returns a full asset list -func (k *Kraken) GetAssets() (map[string]Asset, error) { +func (k *Kraken) GetAssets() (map[string]*Asset, error) { path := fmt.Sprintf("%s/%s/public/%s", k.API.Endpoints.URL, krakenAPIVersion, krakenAssets) var response struct { - Error []string `json:"error"` - Result map[string]Asset `json:"result"` + Error []string `json:"error"` + Result map[string]*Asset `json:"result"` } if err := k.SendHTTPRequest(path, &response); err != nil { return response.Result, err } - return response.Result, GetError(response.Error) } // GetAssetPairs returns a full asset pair list -func (k *Kraken) GetAssetPairs() (map[string]AssetPairs, error) { +func (k *Kraken) GetAssetPairs() (map[string]*AssetPairs, error) { path := fmt.Sprintf("%s/%s/public/%s", k.API.Endpoints.URL, krakenAPIVersion, krakenAssetPairs) var response struct { - Error []string `json:"error"` - Result map[string]AssetPairs `json:"result"` + Error []string `json:"error"` + Result map[string]*AssetPairs `json:"result"` } if err := k.SendHTTPRequest(path, &response); err != nil { return response.Result, err } - for i := range response.Result { - if assetPairMap == nil { - assetPairMap = make(map[string]string) - } - assetPairMap[i] = response.Result[i].Altname - } - return response.Result, GetError(response.Error) } @@ -1054,3 +1069,53 @@ func (k *Kraken) GetWebsocketToken() (string, error) { } return response.Result.Token, nil } + +// LookupAltname converts a currency into its altname (ZUSD -> USD) +func (a *assetTranslatorStore) LookupAltname(target string) string { + a.l.RLock() + alt, ok := a.Assets[target] + if !ok { + a.l.RUnlock() + return "" + } + a.l.RUnlock() + return alt +} + +// LookupAltname converts an altname to its original type (USD -> ZUSD) +func (a *assetTranslatorStore) LookupCurrency(target string) string { + a.l.RLock() + for k, v := range a.Assets { + if v == target { + a.l.RUnlock() + return k + } + } + a.l.RUnlock() + return "" +} + +// Seed seeds a currency translation pair +func (a *assetTranslatorStore) Seed(orig, alt string) { + a.l.Lock() + if a.Assets == nil { + a.Assets = make(map[string]string) + } + + _, ok := a.Assets[orig] + if ok { + a.l.Unlock() + return + } + + a.Assets[orig] = alt + a.l.Unlock() +} + +// Seeded returns whether or not the asset translator has been seeded +func (a *assetTranslatorStore) Seeded() bool { + a.l.RLock() + isSeeded := len(a.Assets) > 0 + a.l.RUnlock() + return isSeeded +} diff --git a/exchanges/kraken/kraken_test.go b/exchanges/kraken/kraken_test.go index cad8da64..931407bd 100644 --- a/exchanges/kraken/kraken_test.go +++ b/exchanges/kraken/kraken_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/gorilla/websocket" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/core" "github.com/thrasher-corp/gocryptotrader/currency" @@ -25,7 +26,6 @@ var wsSetupRan bool const ( apiKey = "" apiSecret = "" - clientID = "" canManipulateRealOrders = false ) @@ -35,20 +35,19 @@ func TestMain(m *testing.M) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("Kraken load config error", err) + log.Fatal(err) } krakenConfig, err := cfg.GetExchangeConfig("Kraken") if err != nil { - log.Fatal("kraken Setup() init error", err) + log.Fatal(err) } krakenConfig.API.AuthenticatedSupport = true krakenConfig.API.Credentials.Key = apiKey krakenConfig.API.Credentials.Secret = apiSecret - krakenConfig.API.Credentials.ClientID = clientID krakenConfig.API.Endpoints.WebsocketURL = k.API.Endpoints.WebsocketURL err = k.Setup(krakenConfig) if err != nil { - log.Fatal("Kraken setup error", err) + log.Fatal(err) } k.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() k.Websocket.TrafficAlert = sharedtestvalues.GetWebsocketStructChannelOverride() @@ -73,6 +72,64 @@ func TestGetAssets(t *testing.T) { } } +func TestSeedAssetTranslator(t *testing.T) { + t.Parallel() + // Test currency pair + if r := assetTranslator.LookupAltname("XXBTZUSD"); r != "XBTUSD" { + t.Error("unexpected result") + } + if r := assetTranslator.LookupCurrency("XBTUSD"); r != "XXBTZUSD" { + t.Error("unexpected result") + } + + // Test fiat currency + if r := assetTranslator.LookupAltname("ZUSD"); r != "USD" { + t.Error("unexpected result") + } + if r := assetTranslator.LookupCurrency("USD"); r != "ZUSD" { + t.Error("unexpected result") + } + + // Test cryptocurrency + if r := assetTranslator.LookupAltname("XXBT"); r != "XBT" { + t.Error("unexpected result") + } + if r := assetTranslator.LookupCurrency("XBT"); r != "XXBT" { + t.Error("unexpected result") + } +} + +func TestSeedAssets(t *testing.T) { + t.Parallel() + var a assetTranslatorStore + if r := a.LookupAltname("ZUSD"); r != "" { + t.Error("unexpected result") + } + a.Seed("ZUSD", "USD") + if r := a.LookupAltname("ZUSD"); r != "USD" { + t.Error("unexpected result") + } + a.Seed("ZUSD", "BLA") + if r := a.LookupAltname("ZUSD"); r != "USD" { + t.Error("unexpected result") + } +} + +func TestLookupCurrency(t *testing.T) { + t.Parallel() + var a assetTranslatorStore + if r := a.LookupCurrency("USD"); r != "" { + t.Error("unexpected result") + } + a.Seed("ZUSD", "USD") + if r := a.LookupCurrency("USD"); r != "ZUSD" { + t.Error("unexpected result") + } + if r := a.LookupCurrency("EUR"); r != "" { + t.Error("unexpected result") + } +} + // TestGetAssetPairs API endpoint test func TestGetAssetPairs(t *testing.T) { t.Parallel() @@ -413,12 +470,16 @@ func TestGetOrderInfo(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - _, err := k.GetOrderInfo("ImACoolOrderID") + _, err := k.GetOrderInfo("OZPTPJ-HVYHF-EDIGXS") if !areTestAPIKeysSet() && err == nil { t.Error("Expecting error") } - if areTestAPIKeysSet() && !strings.Contains(err.Error(), "- Order ID not found:") { - t.Error("Expected Order ID not found error") + if areTestAPIKeysSet() && err != nil { + if !strings.Contains(err.Error(), "- Order ID not found:") { + t.Error("Expected Order ID not found error") + } else { + t.Error(err) + } } } @@ -459,12 +520,8 @@ func TestCancelExchangeOrder(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - currencyPair := currency.NewPair(currency.LTC, currency.BTC) var orderCancellation = &order.Cancel{ - ID: "1", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: currencyPair, + ID: "OGEX6P-B5Q74-IGZ72R", } err := k.CancelOrder(orderCancellation) @@ -482,16 +539,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { t.Skip("API keys set, canManipulateRealOrders false, skipping test") } - currencyPair := currency.NewPair(currency.LTC, currency.BTC) - var orderCancellation = &order.Cancel{ - ID: "1", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: currencyPair, - } - - resp, err := k.CancelAllOrders(orderCancellation) - + resp, err := k.CancelAllOrders(&order.Cancel{}) if !areTestAPIKeysSet() && err == nil { t.Error("Expecting an error when no keys are set") } @@ -506,7 +554,7 @@ func TestCancelAllExchangeOrders(t *testing.T) { // TestGetAccountInfo wrapper test func TestGetAccountInfo(t *testing.T) { - if areTestAPIKeysSet() || clientID != "" { + if areTestAPIKeysSet() { _, err := k.UpdateAccountInfo() if err != nil { t.Error("GetAccountInfo() error", err) @@ -1355,3 +1403,21 @@ func TestWsCancelOrderJSON(t *testing.T) { t.Error(err) } } + +func TestParseTime(t *testing.T) { + // Test REST example + r := convert.TimeFromUnixTimestampDecimal(1373750306.9819).UTC() + if r.Year() != 2013 || + r.Month().String() != "July" || + r.Day() != 13 { + t.Error("unexpected result") + } + + // Test Websocket time example + r = convert.TimeFromUnixTimestampDecimal(1534614098.345543).UTC() + if r.Year() != 2018 || + r.Month().String() != "August" || + r.Day() != 18 { + t.Error("unexpected result") + } +} diff --git a/exchanges/kraken/kraken_types.go b/exchanges/kraken/kraken_types.go index 0bc0162c..2077ce82 100644 --- a/exchanges/kraken/kraken_types.go +++ b/exchanges/kraken/kraken_types.go @@ -1,11 +1,17 @@ package kraken import ( + "sync" "time" "github.com/thrasher-corp/gocryptotrader/currency" ) +type assetTranslatorStore struct { + l sync.RWMutex + Assets map[string]string +} + // TimeResponse type type TimeResponse struct { Unixtime int64 `json:"unixtime"` @@ -133,6 +139,7 @@ type OrderInfo struct { UserRef int32 `json:"userref"` Status string `json:"status"` OpenTime float64 `json:"opentm"` + CloseTime float64 `json:"closetm"` StartTime float64 `json:"starttm"` ExpireTime float64 `json:"expiretm"` Description struct { diff --git a/exchanges/kraken/kraken_websocket.go b/exchanges/kraken/kraken_websocket.go index c280e651..4db9b84b 100644 --- a/exchanges/kraken/kraken_websocket.go +++ b/exchanges/kraken/kraken_websocket.go @@ -4,7 +4,6 @@ import ( "encoding/json" "errors" "fmt" - "math" "net/http" "strconv" "strings" @@ -303,10 +302,6 @@ func (k *Kraken) wsProcessOwnTrades(ownOrders interface{}) error { Err: err, } } - txTime, txTimeNano, err := convert.SplitFloatDecimals(val.Time) - if err != nil { - return err - } trade := order.TradeHistory{ Price: val.Price, Amount: val.Vol, @@ -315,7 +310,7 @@ func (k *Kraken) wsProcessOwnTrades(ownOrders interface{}) error { TID: key, Type: oType, Side: oSide, - Timestamp: time.Unix(txTime, txTimeNano), + Timestamp: convert.TimeFromUnixTimestampDecimal(val.Time), } k.Websocket.DataHandler <- &order.Modify{ Exchange: k.Name, @@ -352,10 +347,6 @@ func (k *Kraken) wsProcessOpenOrders(ownOrders interface{}) error { } } if val.Description.Price > 0 { - startTime, startTimeNano, err := convert.SplitFloatDecimals(val.StartTime) - if err != nil { - return err - } oSide, err := order.StringToOrderSide(val.Description.Type) if err != nil { k.Websocket.DataHandler <- order.ClassificationError{ @@ -395,7 +386,7 @@ func (k *Kraken) wsProcessOpenOrders(ownOrders interface{}) error { Side: oSide, Status: oStatus, AssetType: a, - Date: time.Unix(startTime, startTimeNano), + Date: convert.TimeFromUnixTimestampDecimal(val.OpenTime), Pair: p, } } else { @@ -496,8 +487,6 @@ func (k *Kraken) wsProcessSpread(channelData *WebsocketChannelData, data []inter bidVolume := data[3].(string) askVolume := data[4].(string) - sec, dec := math.Modf(timeData) - spreadTimestamp := time.Unix(int64(sec), int64(dec*(1e9))) if k.Verbose { log.Debugf(log.ExchangeSys, "%v Spread data for '%v' received. Best bid: '%v' Best ask: '%v' Time: '%v', Bid volume '%v', Ask volume '%v'", @@ -505,7 +494,7 @@ func (k *Kraken) wsProcessSpread(channelData *WebsocketChannelData, data []inter channelData.Pair, bestBid, bestAsk, - spreadTimestamp, + convert.TimeFromUnixTimestampDecimal(timeData), bidVolume, askVolume) } @@ -520,8 +509,6 @@ func (k *Kraken) wsProcessTrades(channelData *WebsocketChannelData, data []inter k.Websocket.DataHandler <- err return } - sec, dec := math.Modf(timeData) - timeUnix := time.Unix(int64(sec), int64(dec*(1e9))) price, err := strconv.ParseFloat(trade[0].(string), 64) if err != nil { @@ -544,7 +531,7 @@ func (k *Kraken) wsProcessTrades(channelData *WebsocketChannelData, data []inter Exchange: k.Name, Price: price, Amount: amount, - Timestamp: timeUnix, + Timestamp: convert.TimeFromUnixTimestampDecimal(timeData), Side: tSide, } } @@ -607,8 +594,7 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, as if err != nil { return err } - sec, dec := math.Modf(timeData) - askUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9))) + askUpdatedTime := convert.TimeFromUnixTimestampDecimal(timeData) if highestLastUpdate.Before(askUpdatedTime) { highestLastUpdate = askUpdatedTime } @@ -632,8 +618,7 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, as if err != nil { return err } - sec, dec := math.Modf(timeData) - bidUpdateTime := time.Unix(int64(sec), int64(dec*(1e9))) + bidUpdateTime := convert.TimeFromUnixTimestampDecimal(timeData) if highestLastUpdate.Before(bidUpdateTime) { highestLastUpdate = bidUpdateTime } @@ -682,8 +667,7 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, ask return err } - sec, dec := math.Modf(timeData) - askUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9))) + askUpdatedTime := convert.TimeFromUnixTimestampDecimal(timeData) if highestLastUpdate.Before(askUpdatedTime) { highestLastUpdate = askUpdatedTime } @@ -711,8 +695,7 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, ask return err } - sec, dec := math.Modf(timeData) - bidUpdatedTime := time.Unix(int64(sec), int64(dec*(1e9))) + bidUpdatedTime := convert.TimeFromUnixTimestampDecimal(timeData) if highestLastUpdate.Before(bidUpdatedTime) { highestLastUpdate = bidUpdatedTime } @@ -737,15 +720,11 @@ func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data []inte if err != nil { return err } - sec, dec := math.Modf(startTime) - startTimeUnix := time.Unix(int64(sec), int64(dec*(1e9))) endTime, err := strconv.ParseFloat(data[1].(string), 64) if err != nil { return err } - sec, dec = math.Modf(endTime) - endTimeUnix := time.Unix(int64(sec), int64(dec*(1e9))) openPrice, err := strconv.ParseFloat(data[2].(string), 64) if err != nil { @@ -777,8 +756,8 @@ func (k *Kraken) wsProcessCandles(channelData *WebsocketChannelData, data []inte Pair: channelData.Pair, Timestamp: time.Now(), Exchange: k.Name, - StartTime: startTimeUnix, - CloseTime: endTimeUnix, + StartTime: convert.TimeFromUnixTimestampDecimal(startTime), + CloseTime: convert.TimeFromUnixTimestampDecimal(endTime), // Candles are sent every 60 seconds Interval: "60", HighPrice: highPrice, diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index 6400429b..8a2451d6 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -150,6 +150,11 @@ func (k *Kraken) Setup(exch *config.ExchangeConfig) error { return err } + err = k.SeedAssets() + if err != nil { + return err + } + err = k.Websocket.Setup( &wshandler.WebsocketSetup{ Enabled: exch.Features.Enabled.Websocket, @@ -216,7 +221,8 @@ func (k *Kraken) Run() { forceUpdate := false delim := k.GetPairFormat(asset.Spot, false).Delimiter if !common.StringDataContains(k.GetEnabledPairs(asset.Spot).Strings(), delim) || - !common.StringDataContains(k.GetAvailablePairs(asset.Spot).Strings(), delim) { + !common.StringDataContains(k.GetAvailablePairs(asset.Spot).Strings(), delim) || + common.StringDataContains(k.GetAvailablePairs(asset.Spot).Strings(), "ZUSD") { enabledPairs := currency.NewPairsFromStrings( []string{currency.XBT.String() + delim + currency.USD.String()}, ) @@ -247,6 +253,12 @@ func (k *Kraken) Run() { // FetchTradablePairs returns a list of the exchanges tradable pairs func (k *Kraken) FetchTradablePairs(asset asset.Item) ([]string, error) { + if !assetTranslator.Seeded() { + if err := k.SeedAssets(); err != nil { + return nil, err + } + } + pairs, err := k.GetAssetPairs() if err != nil { return nil, err @@ -254,21 +266,29 @@ func (k *Kraken) FetchTradablePairs(asset asset.Item) ([]string, error) { var products []string for i := range pairs { - v := pairs[i] - if strings.Contains(v.Altname, ".d") { + if strings.Contains(pairs[i].Altname, ".d") { continue } - if v.Base[0] == 'X' { - if len(v.Base) > 3 { - v.Base = v.Base[1:] - } + + base := assetTranslator.LookupAltname(pairs[i].Base) + if base == "" { + log.Warnf(log.ExchangeSys, + "%s unable to lookup altname for base currency %s", + k.Name, + pairs[i].Base) + continue } - if v.Quote[0] == 'Z' || v.Quote[0] == 'X' { - v.Quote = v.Quote[1:] + + quote := assetTranslator.LookupAltname(pairs[i].Quote) + if quote == "" { + log.Warnf(log.ExchangeSys, + "%s unable to lookup altname for quote currency %s", + k.Name, + pairs[i].Quote) + continue } - products = append(products, v.Base+ - k.GetPairFormat(asset, false).Delimiter+ - v.Quote) + products = append(products, + base+k.GetPairFormat(asset, false).Delimiter+quote) } return products, nil } @@ -301,8 +321,8 @@ func (k *Kraken) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Pr for c, t := range tickers { pairFmt := k.FormatExchangeCurrency(pairs[i], assetType).String() if !strings.EqualFold(pairFmt, c) { - altCurrency, ok := assetPairMap[c] - if !ok { + altCurrency := assetTranslator.LookupAltname(c) + if altCurrency == "" { continue } if !strings.EqualFold(pairFmt, altCurrency) { @@ -389,8 +409,15 @@ func (k *Kraken) UpdateAccountInfo() (account.Holdings, error) { var balances []account.Balance for key := range bal { + translatedCurrency := assetTranslator.LookupAltname(key) + if translatedCurrency == "" { + log.Warnf(log.ExchangeSys, "%s unable to translate currency: %s\n", + k.Name, + key) + continue + } balances = append(balances, account.Balance{ - CurrencyName: currency.NewCode(key), + CurrencyName: currency.NewCode(translatedCurrency), TotalValue: bal[key], }) } @@ -530,10 +557,6 @@ func (k *Kraken) GetOrderInfo(orderID string) (order.Detail, error) { TID: orderInfo.Trades[i], }) } - firstNum, decNum, err := convert.SplitFloatDecimals(orderInfo.StartTime) - if err != nil { - return orderDetail, err - } side, err := order.StringToOrderSide(orderInfo.Description.Type) if err != nil { return orderDetail, err @@ -548,12 +571,13 @@ func (k *Kraken) GetOrderInfo(orderID string) (order.Detail, error) { } orderDetail = order.Detail{ - Exchange: k.Name, - ID: orderID, - Pair: currency.NewPairFromString(orderInfo.Description.Pair), + Exchange: k.Name, + ID: orderID, + Pair: currency.NewPairFromFormattedPairs(orderInfo.Description.Pair, + k.GetAvailablePairs(asset.Spot), k.GetPairFormat(asset.Spot, true)), Side: side, Type: oType, - Date: time.Unix(firstNum, decNum), + Date: convert.TimeFromUnixTimestampDecimal(orderInfo.OpenTime), Status: status, Price: orderInfo.Price, Amount: orderInfo.Volume, @@ -647,22 +671,20 @@ func (k *Kraken) GetActiveOrders(req *order.GetOrdersRequest) ([]order.Detail, e var orders []order.Detail for i := range resp.Open { - symbol := currency.NewPairFromString(resp.Open[i].Description.Pair) - orderDate := time.Unix(int64(resp.Open[i].StartTime), 0) side := order.Side(strings.ToUpper(resp.Open[i].Description.Type)) orderType := order.Type(strings.ToUpper(resp.Open[i].Description.OrderType)) - orders = append(orders, order.Detail{ ID: i, Amount: resp.Open[i].Volume, RemainingAmount: (resp.Open[i].Volume - resp.Open[i].VolumeExecuted), ExecutedAmount: resp.Open[i].VolumeExecuted, Exchange: k.Name, - Date: orderDate, + Date: convert.TimeFromUnixTimestampDecimal(resp.Open[i].OpenTime), Price: resp.Open[i].Description.Price, Side: side, Type: orderType, - Pair: symbol, + Pair: currency.NewPairFromFormattedPairs(resp.Open[i].Description.Pair, + k.GetAvailablePairs(asset.Spot), k.GetPairFormat(asset.Spot, true)), }) } @@ -690,22 +712,21 @@ func (k *Kraken) GetOrderHistory(getOrdersRequest *order.GetOrdersRequest) ([]or var orders []order.Detail for i := range resp.Closed { - symbol := currency.NewPairFromString(resp.Closed[i].Description.Pair) - orderDate := time.Unix(int64(resp.Closed[i].StartTime), 0) side := order.Side(strings.ToUpper(resp.Closed[i].Description.Type)) orderType := order.Type(strings.ToUpper(resp.Closed[i].Description.OrderType)) - orders = append(orders, order.Detail{ ID: i, Amount: resp.Closed[i].Volume, RemainingAmount: (resp.Closed[i].Volume - resp.Closed[i].VolumeExecuted), ExecutedAmount: resp.Closed[i].VolumeExecuted, Exchange: k.Name, - Date: orderDate, + Date: convert.TimeFromUnixTimestampDecimal(resp.Closed[i].OpenTime), + CloseTime: convert.TimeFromUnixTimestampDecimal(resp.Closed[i].CloseTime), Price: resp.Closed[i].Description.Price, Side: side, Type: orderType, - Pair: symbol, + Pair: currency.NewPairFromFormattedPairs(resp.Closed[i].Description.Pair, + k.GetAvailablePairs(asset.Spot), k.GetPairFormat(asset.Spot, true)), }) } diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index 82b4006e..2116e4b4 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -130,6 +130,7 @@ type Detail struct { Status Status AssetType asset.Item Date time.Time + CloseTime time.Time LastUpdated time.Time Pair currency.Pair Trades []TradeHistory diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index 437cfea4..e82c7799 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -48,6 +48,7 @@ const ( poloniexActiveLoans = "returnActiveLoans" poloniexLendingHistory = "returnLendingHistory" poloniexAutoRenew = "toggleAutoRenew" + poloniexMaxOrderbookDepth = 100 ) // Poloniex is the overarching type across the poloniex package @@ -96,27 +97,29 @@ func (p *Poloniex) GetOrderbook(currencyPair string, depth int) (OrderbookAll, e if resp.Error != "" { return oba, fmt.Errorf("%s GetOrderbook() error: %s", p.Name, resp.Error) } - ob := Orderbook{} + var ob Orderbook for x := range resp.Asks { - data := resp.Asks[x] - price, err := strconv.ParseFloat(data[0].(string), 64) + price, err := strconv.ParseFloat(resp.Asks[x][0].(string), 64) if err != nil { return oba, err } - amount := data[1].(float64) - ob.Asks = append(ob.Asks, OrderbookItem{Price: price, Amount: amount}) + ob.Asks = append(ob.Asks, OrderbookItem{ + Price: price, + Amount: resp.Asks[x][1].(float64), + }) } for x := range resp.Bids { - data := resp.Bids[x] - price, err := strconv.ParseFloat(data[0].(string), 64) + price, err := strconv.ParseFloat(resp.Bids[x][0].(string), 64) if err != nil { return oba, err } - amount := data[1].(float64) - ob.Bids = append(ob.Bids, OrderbookItem{Price: price, Amount: amount}) + ob.Bids = append(ob.Bids, OrderbookItem{ + Price: price, + Amount: resp.Bids[x][1].(float64), + }) } - oba.Data[currencyPair] = Orderbook{Bids: ob.Bids, Asks: ob.Asks} + oba.Data[currencyPair] = ob } else { vals.Set("currencyPair", "all") resp := OrderbookResponseAll{} @@ -143,12 +146,12 @@ func (p *Poloniex) GetOrderbook(currencyPair string, depth int) (OrderbookAll, e if err != nil { return oba, err } - ob.Asks = append(ob.Asks, OrderbookItem{ + ob.Bids = append(ob.Bids, OrderbookItem{ Price: price, Amount: orderbook.Bids[x][1].(float64), }) } - oba.Data[currency] = Orderbook{Bids: ob.Bids, Asks: ob.Asks} + oba.Data[currency] = ob } } return oba, nil diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 224ce5a6..17413be1 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -286,10 +286,9 @@ func (p *Poloniex) FetchOrderbook(currencyPair currency.Pair, assetType asset.It // UpdateOrderbook updates and returns the orderbook for a currency pair func (p *Poloniex) UpdateOrderbook(currencyPair currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - orderBook := new(orderbook.Base) - orderbookNew, err := p.GetOrderbook("", 1000) + orderbookNew, err := p.GetOrderbook("", poloniexMaxOrderbookDepth) if err != nil { - return orderBook, err + return nil, err } enabledPairs := p.GetEnabledPairs(assetType) @@ -299,19 +298,20 @@ func (p *Poloniex) UpdateOrderbook(currencyPair currency.Pair, assetType asset.I continue } - var obItems []orderbook.Item + orderBook := new(orderbook.Base) for y := range data.Bids { - obItems = append(obItems, orderbook.Item{ - Amount: data.Bids[y].Amount, Price: data.Bids[y].Price}) + orderBook.Bids = append(orderBook.Bids, orderbook.Item{ + Amount: data.Bids[y].Amount, + Price: data.Bids[y].Price, + }) } - orderBook.Bids = obItems - obItems = []orderbook.Item{} for y := range data.Asks { - obItems = append(obItems, orderbook.Item{ - Amount: data.Asks[y].Amount, Price: data.Asks[y].Price}) + orderBook.Asks = append(orderBook.Asks, orderbook.Item{ + Amount: data.Asks[y].Amount, + Price: data.Asks[y].Price, + }) } - orderBook.Asks = obItems orderBook.Pair = enabledPairs[i] orderBook.ExchangeName = p.Name orderBook.AssetType = assetType diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index 15c901ec..4b43cf2b 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -179,18 +179,17 @@ func (y *Yobit) UpdateTradablePairs(forceUpdate bool) error { // UpdateTicker updates and returns the ticker for a currency pair func (y *Yobit) UpdateTicker(p currency.Pair, assetType asset.Item) (*ticker.Price, error) { - tickerPrice := new(ticker.Price) - pairsCollated, err := y.FormatExchangeCurrencies(y.GetEnabledPairs(assetType), assetType) + enabledPairs := y.GetEnabledPairs(assetType) + pairsCollated, err := y.FormatExchangeCurrencies(enabledPairs, assetType) if err != nil { - return tickerPrice, err + return nil, err } result, err := y.GetTicker(pairsCollated) if err != nil { - return tickerPrice, err + return nil, err } - enabledPairs := y.GetEnabledPairs(assetType) for i := range enabledPairs { curr := y.FormatExchangeCurrency(enabledPairs[i], assetType).Lower().String() if _, ok := result[curr]; !ok { diff --git a/gctscript/modules/wrapper_types.go b/gctscript/modules/wrapper_types.go index 760548dc..976c4789 100644 --- a/gctscript/modules/wrapper_types.go +++ b/gctscript/modules/wrapper_types.go @@ -15,7 +15,8 @@ import ( const ( // ErrParameterConvertFailed error to return when type conversion fails - ErrParameterConvertFailed = "%v failed conversion" + ErrParameterConvertFailed = "%v failed conversion" + // ErrParameterWithPositionConvertFailed error to return when a positional conversion fails ErrParameterWithPositionConvertFailed = "%v at position %v failed conversion" ) diff --git a/go.sum b/go.sum index dd0b3fb7..0c3b5df1 100644 --- a/go.sum +++ b/go.sum @@ -23,7 +23,6 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 h1:P5U+E4x5OkVEKQDklVPmzs71WM56RTTRqV4OrDC//Y4= github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro= -github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apmckinlay/gsuneido v0.0.0-20180907175622-1f10244968e3/go.mod h1:hJnaqxrCRgMCTWtpNz9XUFkBCREiQdlcyK6YNmOfroM= github.com/apmckinlay/gsuneido v0.0.0-20190404155041-0b6cd442a18f/go.mod h1:JU2DOj5Fc6rol0yaT79Csr47QR0vONGwJtBNGRD7jmc= @@ -41,7 +40,6 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -55,13 +53,8 @@ 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.3.2 h1:1OoQaAWROVnenXH5Z/ak+92sayUESu0LSOi83Wy54hk= -github.com/d5/tengo/v2 v2.3.2/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8= -github.com/d5/tengo/v2 v2.3.3 h1:DB5Yi2p/cLdTfEA6FGVjIfI9OEkrxLQ6cCz5eQv7wSk= -github.com/d5/tengo/v2 v2.3.3/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8= github.com/d5/tengo/v2 v2.5.0 h1:psncU93ixOaLbWZ2AOVjTtQdK0orJUW0jykj6EMTbss= github.com/d5/tengo/v2 v2.5.0/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -76,7 +69,6 @@ github.com/ericlagergren/decimal v0.0.0-20180907214518-0bb163153a5d/go.mod h1:1y github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -85,7 +77,6 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84= github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= @@ -93,30 +84,24 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -134,20 +119,18 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc= github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 h1:0IKlLyQ3Hs9nDaiK5cSHAGmcQEIC8l2Ts1u6x5Dfrqg= 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.5 h1:aiLxiiVzAXb7wb3lAmubA69IokWOoUNe+E7TdGKh8yw= -github.com/grpc-ecosystem/grpc-gateway v1.14.5/go.mod h1:UJ0EZAp832vCd54Wev9N1BMKEyvcZ5+IM0AwDrnlkEc= github.com/grpc-ecosystem/grpc-gateway v1.14.6 h1:8ERzHx8aj1Sc47mu9n/AksaKCSWrMchFtkdrS4BIj5o= github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= @@ -173,7 +156,6 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= @@ -204,12 +186,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -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.5.2 h1:yTSXVswvWUOQ3k1sd7vJfDrbSl8lKuscqFJRqjC0ifw= -github.com/lib/pq v1.5.2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.6.0 h1:I5DPxhYJChW9KYc66se+oKFFQX6VuQrKiprsX6ivRZc= github.com/lib/pq v1.6.0/go.mod h1:4vXEAYvW1fRQ2/FhZ78H73A60MHw1geSm145z2mdY1g= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= @@ -240,7 +218,6 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9 github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -284,7 +261,6 @@ github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= @@ -293,16 +269,13 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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.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= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -342,8 +315,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA= golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -380,15 +351,13 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191002035440-2ec189313ef0 h1:2mqDk8w/o6UmeUCu5Qiq2y7iMf6anbx+YA8d1JFoFrs= golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -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/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/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= @@ -404,18 +373,15 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190927073244-c990c680b611 h1:q9u40nxWT5zRClI/uU9dHCiYGottAg6Nzz4YUQyHxdA= golang.org/x/sys v0.0.0-20190927073244-c990c680b611/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191003212358-c178f38b412c h1:6Zx7DRlKXf79yfxuQ/7GqV3w2y7aDsk6bGg0MzF5RVU= golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= @@ -450,7 +416,6 @@ google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -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= @@ -463,16 +428,14 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 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-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884 h1:fiNLklpBwWK1mth30Hlwk+fcdBmIALlgF5iy77O37Ig= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= @@ -481,13 +444,11 @@ google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLY google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 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.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.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= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -501,12 +462,10 @@ gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mN gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7 h1:+t9dhfO+GNOIGJof6kPOAenx7YgrZMTdRPV+EsnPabk= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/testdata/configtest.json b/testdata/configtest.json index e3793c62..8dd9c220 100644 --- a/testdata/configtest.json +++ b/testdata/configtest.json @@ -21,9 +21,11 @@ "output": "console", "fileSettings": { "filename": "log.txt", - "rotate": false + "rotate": false, + "maxsize": 100 }, "advancedSettings": { + "showLogSystemName": false, "spacer": " | ", "timeStampFormat": " 02/01/2006 15:04:05 ", "headers": { @@ -61,6 +63,14 @@ "allowedDifference": 50000000, "allowedNegativeDifference": 50000000 }, + "gctscript": { + "enabled": false, + "timeout": 30000000000, + "max_virtual_machines": 10, + "allow_imports": false, + "auto_load": null, + "verbose": false + }, "currencyConfig": { "forexProviders": [ { @@ -193,25 +203,37 @@ "Address": "1JCe8z4jJVNXSjohjM4i9Hh813dLCNx2Sy", "CoinType": "BTC", "Balance": 53000.01741264, - "Description": "" + "Description": "", + "WhiteListed": false, + "ColdStorage": false, + "SupportedExchanges": "" }, { "Address": "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v", "CoinType": "BTC", "Balance": 107848.28963408, - "Description": "" + "Description": "", + "WhiteListed": false, + "ColdStorage": false, + "SupportedExchanges": "" }, { "Address": "LgY8ahfHRhvjVQC1zJnBhFMG5pCTMuKRqh", "CoinType": "LTC", "Balance": 0.03665026, - "Description": "" + "Description": "", + "WhiteListed": false, + "ColdStorage": false, + "SupportedExchanges": "" }, { "Address": "0xb794f5ea0ba39494ce839613fffba74279579268", "CoinType": "ETH", "Balance": 0.25555604051326, - "Description": "" + "Description": "", + "WhiteListed": false, + "ColdStorage": false, + "SupportedExchanges": "" } ] }, @@ -1488,7 +1510,7 @@ "uppercase": true }, "useGlobalFormat": true, - "lastUpdated": 1566798411, + "lastUpdated": 1591062026, "assetTypes": [ "spot" ], diff --git a/testdata/preengine_config.json b/testdata/preengine_config.json index 7f058795..82a31197 100644 --- a/testdata/preengine_config.json +++ b/testdata/preengine_config.json @@ -1007,7 +1007,7 @@ "baseCurrencies": "USD,SGD", "assetTypes": "SPOT", "supportsAutoPairUpdates": false, - "pairsLastUpdated": 1566798411, + "pairsLastUpdated": 1591062026, "configCurrencyPairFormat": { "uppercase": true },