mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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" ]
|
||||
|
||||
20
README.md
20
README.md
@@ -47,9 +47,10 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader
|
||||
| Yobit | Yes | NA | NA |
|
||||
| ZB.COM | Yes | Yes | NA |
|
||||
|
||||
We are aiming to support the top 20 highest volume exchanges based off the [CoinMarketCap exchange data](https://coinmarketcap.com/exchanges/volume/24-hour/).
|
||||
We are aiming to support the top 30 exchanges sorted by average liquidity as [ranked by CoinMarketCap](https://coinmarketcap.com/rankings/exchanges/).
|
||||
However, we welcome pull requests for any exchange which does not match this criterion. If you need help with this, please join us on [Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk).
|
||||
|
||||
** NA means not applicable as the Exchange does not support the feature.
|
||||
** NA means not applicable as the exchange does not support the feature.
|
||||
|
||||
## Current Features
|
||||
|
||||
@@ -138,16 +139,17 @@ Binaries will be published once the codebase reaches a stable condition.
|
||||
|
||||
|User|Contribution Amount|
|
||||
|--|--|
|
||||
| [thrasher-](https://github.com/thrasher-) | 637 |
|
||||
| [shazbert](https://github.com/shazbert) | 188 |
|
||||
| [gloriousCode](https://github.com/gloriousCode) | 167 |
|
||||
| [xtda](https://github.com/xtda) | 42 |
|
||||
| [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 23 |
|
||||
| [thrasher-](https://github.com/thrasher-) | 639 |
|
||||
| [shazbert](https://github.com/shazbert) | 191 |
|
||||
| [gloriousCode](https://github.com/gloriousCode) | 169 |
|
||||
| [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 46 |
|
||||
| [xtda](https://github.com/xtda) | 43 |
|
||||
| [ermalguni](https://github.com/ermalguni) | 14 |
|
||||
| [vadimzhukck](https://github.com/vadimzhukck) | 10 |
|
||||
| [MadCozBadd](https://github.com/MadCozBadd) | 8 |
|
||||
| [140am](https://github.com/140am) | 8 |
|
||||
| [marcofranssen](https://github.com/marcofranssen) | 8 |
|
||||
| [MadCozBadd](https://github.com/MadCozBadd) | 7 |
|
||||
| [dackroyd](https://github.com/dackroyd) | 5 |
|
||||
| [cranktakular](https://github.com/cranktakular) | 5 |
|
||||
| [woshidama323](https://github.com/woshidama323) | 3 |
|
||||
| [vazha](https://github.com/vazha) | 3 |
|
||||
@@ -167,7 +169,6 @@ Binaries will be published once the codebase reaches a stable condition.
|
||||
| [CodeLingoTeam](https://github.com/CodeLingoTeam) | 1 |
|
||||
| [Daanikus](https://github.com/Daanikus) | 1 |
|
||||
| [daniel-cohen](https://github.com/daniel-cohen) | 1 |
|
||||
| [merkeld](https://github.com/merkeld) | 1 |
|
||||
| [DirectX](https://github.com/DirectX) | 1 |
|
||||
| [frankzougc](https://github.com/frankzougc) | 1 |
|
||||
| [idoall](https://github.com/idoall) | 1 |
|
||||
@@ -179,3 +180,4 @@ Binaries will be published once the codebase reaches a stable condition.
|
||||
| [starit](https://github.com/starit) | 1 |
|
||||
| [Jimexist](https://github.com/Jimexist) | 1 |
|
||||
| [lookfirst](https://github.com/lookfirst) | 1 |
|
||||
| [merkeld](https://github.com/merkeld) | 1 |
|
||||
|
||||
@@ -226,6 +226,11 @@ func main() {
|
||||
URL: "https://github.com/lookfirst",
|
||||
Contributions: 1,
|
||||
},
|
||||
{
|
||||
Login: "merkeld",
|
||||
URL: "https://github.com/merkeld",
|
||||
Contributions: 1,
|
||||
},
|
||||
}...)
|
||||
|
||||
if verbose {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
74
cmd/exchange_template/exchange_template_test.go
Normal file
74
cmd/exchange_template/exchange_template_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1607,7 +1607,7 @@
|
||||
"uppercase": true
|
||||
},
|
||||
"useGlobalFormat": true,
|
||||
"lastUpdated": 1566798411,
|
||||
"lastUpdated": 1591062026,
|
||||
"assetTypes": [
|
||||
"spot"
|
||||
],
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
49
go.sum
@@ -23,7 +23,6 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5 h1:P5U+E4x5OkVEKQDklVPmzs71WM56RTTRqV4OrDC//Y4=
|
||||
github.com/alexbrainman/sspi v0.0.0-20180613141037-e580b900e9f5/go.mod h1:976q2ETgjT2snVCf2ZaBnyBbVoPERGjUz+0sofzEfro=
|
||||
github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/apmckinlay/gsuneido v0.0.0-20180907175622-1f10244968e3/go.mod h1:hJnaqxrCRgMCTWtpNz9XUFkBCREiQdlcyK6YNmOfroM=
|
||||
github.com/apmckinlay/gsuneido v0.0.0-20190404155041-0b6cd442a18f/go.mod h1:JU2DOj5Fc6rol0yaT79Csr47QR0vONGwJtBNGRD7jmc=
|
||||
@@ -41,7 +40,6 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
@@ -55,13 +53,8 @@ github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/d5/tengo/v2 v2.3.2 h1:1OoQaAWROVnenXH5Z/ak+92sayUESu0LSOi83Wy54hk=
|
||||
github.com/d5/tengo/v2 v2.3.2/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8=
|
||||
github.com/d5/tengo/v2 v2.3.3 h1:DB5Yi2p/cLdTfEA6FGVjIfI9OEkrxLQ6cCz5eQv7wSk=
|
||||
github.com/d5/tengo/v2 v2.3.3/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8=
|
||||
github.com/d5/tengo/v2 v2.5.0 h1:psncU93ixOaLbWZ2AOVjTtQdK0orJUW0jykj6EMTbss=
|
||||
github.com/d5/tengo/v2 v2.5.0/go.mod h1:XRGjEs5I9jYIKTxly6HCF8oiiilk5E/RYXOZ5b0DZC8=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -76,7 +69,6 @@ github.com/ericlagergren/decimal v0.0.0-20180907214518-0bb163153a5d/go.mod h1:1y
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
@@ -85,7 +77,6 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
github.com/gofrs/uuid v3.3.0+incompatible h1:8K4tyRfvU1CYPgJsveYFQMhpFd/wXNM7iK6rR7UHz84=
|
||||
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||
@@ -93,30 +84,24 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
|
||||
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
|
||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@@ -134,20 +119,18 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
|
||||
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ=
|
||||
github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 h1:0IKlLyQ3Hs9nDaiK5cSHAGmcQEIC8l2Ts1u6x5Dfrqg=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.14.5 h1:aiLxiiVzAXb7wb3lAmubA69IokWOoUNe+E7TdGKh8yw=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.14.5/go.mod h1:UJ0EZAp832vCd54Wev9N1BMKEyvcZ5+IM0AwDrnlkEc=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.14.6 h1:8ERzHx8aj1Sc47mu9n/AksaKCSWrMchFtkdrS4BIj5o=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.14.6/go.mod h1:zdiPV4Yse/1gnckTHtghG4GkDEdKCRJduHpTxT3/jcw=
|
||||
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
|
||||
@@ -173,7 +156,6 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
|
||||
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
|
||||
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
|
||||
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
|
||||
@@ -204,12 +186,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.5.2 h1:yTSXVswvWUOQ3k1sd7vJfDrbSl8lKuscqFJRqjC0ifw=
|
||||
github.com/lib/pq v1.5.2/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.6.0 h1:I5DPxhYJChW9KYc66se+oKFFQX6VuQrKiprsX6ivRZc=
|
||||
github.com/lib/pq v1.6.0/go.mod h1:4vXEAYvW1fRQ2/FhZ78H73A60MHw1geSm145z2mdY1g=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
@@ -240,7 +218,6 @@ github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/9
|
||||
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
|
||||
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -284,7 +261,6 @@ github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
|
||||
@@ -293,16 +269,13 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
|
||||
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
@@ -342,8 +315,6 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4 h1:QmwruyY+bKbDDL0BaglrbZABEali68eoMFhTZpCjYVA=
|
||||
golang.org/x/crypto v0.0.0-20200311171314-f7b00557c8c4/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
@@ -380,15 +351,13 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0 h1:2mqDk8w/o6UmeUCu5Qiq2y7iMf6anbx+YA8d1JFoFrs=
|
||||
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA=
|
||||
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -404,18 +373,15 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190927073244-c990c680b611 h1:q9u40nxWT5zRClI/uU9dHCiYGottAg6Nzz4YUQyHxdA=
|
||||
golang.org/x/sys v0.0.0-20190927073244-c990c680b611/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191003212358-c178f38b412c h1:6Zx7DRlKXf79yfxuQ/7GqV3w2y7aDsk6bGg0MzF5RVU=
|
||||
golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
@@ -450,7 +416,6 @@ google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn
|
||||
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
|
||||
@@ -463,16 +428,14 @@ google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRn
|
||||
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4=
|
||||
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884 h1:fiNLklpBwWK1mth30Hlwk+fcdBmIALlgF5iy77O37Ig=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
|
||||
@@ -481,13 +444,11 @@ google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLY
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -501,12 +462,10 @@ gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mN
|
||||
gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
|
||||
gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7 h1:+t9dhfO+GNOIGJof6kPOAenx7YgrZMTdRPV+EsnPabk=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
||||
34
testdata/configtest.json
vendored
34
testdata/configtest.json
vendored
@@ -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"
|
||||
],
|
||||
|
||||
2
testdata/preengine_config.json
vendored
2
testdata/preengine_config.json
vendored
@@ -1007,7 +1007,7 @@
|
||||
"baseCurrencies": "USD,SGD",
|
||||
"assetTypes": "SPOT",
|
||||
"supportsAutoPairUpdates": false,
|
||||
"pairsLastUpdated": 1566798411,
|
||||
"pairsLastUpdated": 1591062026,
|
||||
"configCurrencyPairFormat": {
|
||||
"uppercase": true
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user