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
This commit is contained in:
Adrian Gallagher
2020-06-03 12:48:36 +10:00
committed by GitHub
parent c437b408ca
commit 8afee0b4b2
41 changed files with 879 additions and 678 deletions

View File

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

View File

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

View File

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

View File

@@ -226,6 +226,11 @@ func main() {
URL: "https://github.com/lookfirst",
Contributions: 1,
},
{
Login: "merkeld",
URL: "https://github.com/merkeld",
Contributions: 1,
},
}...)
if verbose {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1607,7 +1607,7 @@
"uppercase": true
},
"useGlobalFormat": true,
"lastUpdated": 1566798411,
"lastUpdated": 1591062026,
"assetTypes": [
"spot"
],

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

49
go.sum
View File

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

View File

@@ -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"
],

View File

@@ -1007,7 +1007,7 @@
"baseCurrencies": "USD,SGD",
"assetTypes": "SPOT",
"supportsAutoPairUpdates": false,
"pairsLastUpdated": 1566798411,
"pairsLastUpdated": 1591062026,
"configCurrencyPairFormat": {
"uppercase": true
},