Add dynamic loading/unloading and reloading of exchanges

This commit is contained in:
Adrian Gallagher
2018-01-16 12:05:30 +11:00
parent 34eeed287a
commit 4d4c85f458
10 changed files with 467 additions and 183 deletions

224
exchange.go Normal file
View File

@@ -0,0 +1,224 @@
package main
import (
"errors"
"log"
"github.com/thrasher-/gocryptotrader/common"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/anx"
"github.com/thrasher-/gocryptotrader/exchanges/bitfinex"
"github.com/thrasher-/gocryptotrader/exchanges/bitstamp"
"github.com/thrasher-/gocryptotrader/exchanges/bittrex"
"github.com/thrasher-/gocryptotrader/exchanges/btcc"
"github.com/thrasher-/gocryptotrader/exchanges/btcmarkets"
"github.com/thrasher-/gocryptotrader/exchanges/coinut"
"github.com/thrasher-/gocryptotrader/exchanges/gdax"
"github.com/thrasher-/gocryptotrader/exchanges/huobi"
"github.com/thrasher-/gocryptotrader/exchanges/itbit"
"github.com/thrasher-/gocryptotrader/exchanges/kraken"
"github.com/thrasher-/gocryptotrader/exchanges/lakebtc"
"github.com/thrasher-/gocryptotrader/exchanges/liqui"
"github.com/thrasher-/gocryptotrader/exchanges/localbitcoins"
"github.com/thrasher-/gocryptotrader/exchanges/okcoin"
"github.com/thrasher-/gocryptotrader/exchanges/poloniex"
"github.com/thrasher-/gocryptotrader/exchanges/wex"
)
// vars related to exchange functions
var (
ErrNoExchangesLoaded = errors.New("no exchanges have been loaded")
ErrExchangeNotFound = errors.New("exchange not found")
ErrExchangeAlreadyLoaded = errors.New("exchange already loaded")
ErrExchangeFailedToLoad = errors.New("exchange failed to load")
)
// CheckExchangeExists returns true whether or not an exchange has already
// been loaded
func CheckExchangeExists(exchName string) bool {
for x := range bot.exchanges {
if common.StringToLower(bot.exchanges[x].GetName()) == common.StringToLower(exchName) {
return true
}
}
return false
}
// GetExchangeByName returns an exchange given an exchange name
func GetExchangeByName(exchName string) exchange.IBotExchange {
for x := range bot.exchanges {
if common.StringToLower(bot.exchanges[x].GetName()) == common.StringToLower(exchName) {
return bot.exchanges[x]
}
}
return nil
}
// ReloadExchange loads an exchange config by name
func ReloadExchange(name string) error {
nameLower := common.StringToLower(name)
if len(bot.exchanges) == 0 {
return ErrNoExchangesLoaded
}
if !CheckExchangeExists(nameLower) {
return ErrExchangeNotFound
}
exchCfg, err := bot.config.GetExchangeConfig(name)
if err != nil {
return err
}
e := GetExchangeByName(nameLower)
e.Setup(exchCfg)
log.Printf("%s exchange reloaded successfully.\n", name)
return nil
}
// UnloadExchange unloads an exchange by
func UnloadExchange(name string) error {
nameLower := common.StringToLower(name)
if len(bot.exchanges) == 0 {
return ErrNoExchangesLoaded
}
if !CheckExchangeExists(nameLower) {
return ErrExchangeNotFound
}
exchCfg, err := bot.config.GetExchangeConfig(name)
if err != nil {
return err
}
exchCfg.Enabled = false
err = bot.config.UpdateExchangeConfig(exchCfg)
if err != nil {
return err
}
for x := range bot.exchanges {
if bot.exchanges[x].GetName() == name {
bot.exchanges[x].SetEnabled(false)
bot.exchanges = append(bot.exchanges[:x], bot.exchanges[x+1:]...)
return nil
}
}
return ErrExchangeNotFound
}
// LoadExchange loads an exchange by name
func LoadExchange(name string) error {
nameLower := common.StringToLower(name)
var exch exchange.IBotExchange
if len(bot.exchanges) > 0 {
if CheckExchangeExists(nameLower) {
return ErrExchangeAlreadyLoaded
}
}
switch nameLower {
case "anx":
exch = new(anx.ANX)
case "bitfinex":
exch = new(bitfinex.Bitfinex)
case "bitstamp":
exch = new(bitstamp.Bitstamp)
case "bittrex":
exch = new(bittrex.Bittrex)
case "btcc":
exch = new(btcc.BTCC)
case "btc markets":
exch = new(btcmarkets.BTCMarkets)
case "coinut":
exch = new(coinut.COINUT)
case "gdax":
exch = new(gdax.GDAX)
case "gemini":
exch = new(gdax.GDAX)
case "huobi":
exch = new(huobi.HUOBI)
case "itbit":
exch = new(itbit.ItBit)
case "kraken":
exch = new(kraken.Kraken)
case "lakebtc":
exch = new(lakebtc.LakeBTC)
case "liqui":
exch = new(liqui.Liqui)
case "localbitcoins":
exch = new(localbitcoins.LocalBitcoins)
case "okcoin china":
exch = new(okcoin.OKCoin)
case "okcoin international":
exch = new(okcoin.OKCoin)
case "poloniex":
exch = new(poloniex.Poloniex)
case "wex":
exch = new(wex.WEX)
default:
return ErrExchangeNotFound
}
if exch == nil {
return ErrExchangeFailedToLoad
}
exch.SetDefaults()
bot.exchanges = append(bot.exchanges, exch)
exchCfg, err := bot.config.GetExchangeConfig(name)
if err != nil {
return err
}
exchCfg.Enabled = true
exch.Setup(exchCfg)
return nil
}
// SetupExchanges sets up the exchanges used by the bot
func SetupExchanges() {
for _, exch := range bot.config.Exchanges {
if CheckExchangeExists(exch.Name) {
e := GetExchangeByName(exch.Name)
if e == nil {
log.Println(ErrExchangeNotFound)
continue
}
err := ReloadExchange(exch.Name)
if err != nil {
log.Printf("ReloadExchange %s failed: %s", exch.Name, err)
continue
}
if !e.IsEnabled() {
UnloadExchange(exch.Name)
continue
}
return
}
if !exch.Enabled {
log.Printf("%s: Exchange support: Disabled", exch.Name)
continue
} else {
err := LoadExchange(exch.Name)
if err != nil {
log.Printf("LoadExchange %s failed: %s", exch.Name, err)
continue
}
}
log.Printf(
"%s: Exchange support: Enabled (Authenticated API support: %s - Verbose mode: %s).\n",
exch.Name,
common.IsEnabled(exch.AuthenticatedAPISupport),
common.IsEnabled(exch.Verbose),
)
}
}

138
exchange_test.go Normal file
View File

@@ -0,0 +1,138 @@
package main
import (
"testing"
"github.com/thrasher-/gocryptotrader/config"
)
var testSetup = false
func SetupTest(t *testing.T) {
if !testSetup {
bot.config = &config.Cfg
err := bot.config.LoadConfig("./testdata/configtest.json")
if err != nil {
t.Fatalf("Test failed. SetupTest: Failed to load config: %s", err)
}
testSetup = true
}
if CheckExchangeExists("Bitfinex") {
return
}
err := LoadExchange("Bitfinex")
if err != nil {
t.Errorf("Test failed. SetupTest: Failed to load exchange: %s", err)
}
}
func CleanupTest(t *testing.T) {
if !CheckExchangeExists("Bitfinex") {
return
}
err := UnloadExchange("Bitfinex")
if err != nil {
t.Fatalf("Test failed. CleanupTest: Failed to unload exchange: %s",
err)
}
}
func TestCheckExchangeExists(t *testing.T) {
SetupTest(t)
if !CheckExchangeExists("Bitfinex") {
t.Errorf("Test failed. TestGetExchangeExists: Unable to find exchange")
}
if CheckExchangeExists("Asdsad") {
t.Errorf("Test failed. TestGetExchangeExists: Non-existant exchange found")
}
CleanupTest(t)
}
func TestGetExchangeByName(t *testing.T) {
SetupTest(t)
exch := GetExchangeByName("Bitfinex")
if exch == nil {
t.Errorf("Test failed. TestGetExchangeByName: Failed to get exchange")
}
if !exch.IsEnabled() {
t.Errorf("Test failed. TestGetExchangeByName: Unexpected result")
}
exch.SetEnabled(false)
bfx := GetExchangeByName("Bitfinex")
if bfx.IsEnabled() {
t.Errorf("Test failed. TestGetExchangeByName: Unexpected result")
}
if exch.GetName() != "Bitfinex" {
t.Errorf("Test failed. TestGetExchangeByName: Unexpected result")
}
exch = GetExchangeByName("Asdasd")
if exch != nil {
t.Errorf("Test failed. TestGetExchangeByName: Non-existant exchange found")
}
CleanupTest(t)
}
func TestReloadExchange(t *testing.T) {
SetupTest(t)
err := ReloadExchange("asdf")
if err != ErrExchangeNotFound {
t.Errorf("Test failed. TestReloadExchange: Incorrect result: %s",
err)
}
err = ReloadExchange("Bitfinex")
if err != nil {
t.Errorf("Test failed. TestReloadExchange: Incorrect result: %s",
err)
}
CleanupTest(t)
err = ReloadExchange("asdf")
if err != ErrNoExchangesLoaded {
t.Errorf("Test failed. TestReloadExchange: Incorrect result: %s",
err)
}
}
func TestUnloadExchange(t *testing.T) {
SetupTest(t)
err := UnloadExchange("asdf")
if err != ErrExchangeNotFound {
t.Errorf("Test failed. TestUnloadExchange: Incorrect result: %s",
err)
}
err = UnloadExchange("Bitfinex")
if err != nil {
t.Errorf("Test failed. TestUnloadExchange: Failed to get exchange. %s",
err)
}
err = UnloadExchange("asdf")
if err != ErrNoExchangesLoaded {
t.Errorf("Test failed. TestUnloadExchange: Incorrect result: %s",
err)
}
CleanupTest(t)
}
func TestSetupExchanges(t *testing.T) {
SetupTest(t)
SetupExchanges()
CleanupTest(t)
}

View File

@@ -64,6 +64,7 @@ type IBotExchange interface {
SetDefaults()
GetName() string
IsEnabled() bool
SetEnabled(bool)
GetTickerPrice(currency pair.CurrencyPair, assetType string) (ticker.Price, error)
UpdateTicker(currency pair.CurrencyPair, assetType string) (ticker.Price, error)
GetOrderbookEx(currency pair.CurrencyPair, assetType string) (orderbook.Base, error)

View File

@@ -96,7 +96,7 @@ func (o *OKCoin) SetDefaults() {
o.FuturesValues = []string{"this_week", "next_week", "quarter"}
o.AssetTypes = []string{ticker.Spot}
if !okcoinDefaultsSet {
if okcoinDefaultsSet {
o.AssetTypes = append(o.AssetTypes, o.FuturesValues...)
o.APIUrl = OKCOIN_API_URL
o.Name = "OKCOIN International"

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"github.com/thrasher-/gocryptotrader/common"
"github.com/thrasher-/gocryptotrader/currency/pair"
"github.com/thrasher-/gocryptotrader/currency/translation"
exchange "github.com/thrasher-/gocryptotrader/exchanges"
@@ -31,25 +30,15 @@ func GetRelatableCurrencies(p pair.CurrencyPair) []pair.CurrencyPair {
return pairs
}
// GetExchangeByName returns an exchange given an exchange name
func GetExchangeByName(exchName string) exchange.IBotExchange {
for x := range bot.exchanges {
if common.StringToLower(bot.exchanges[x].GetName()) == common.StringToLower(exchName) {
return bot.exchanges[x]
}
}
return nil
}
// GetSpecificOrderbook returns a specific orderbook given the currency,
// exchangeName and assetType
func GetSpecificOrderbook(currency, exchangeName, assetType string) (orderbook.Base, error) {
var specificOrderbook orderbook.Base
var err error
for i := 0; i < len(bot.exchanges); i++ {
if bot.exchanges[i] != nil {
if bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName {
specificOrderbook, err = bot.exchanges[i].GetOrderbookEx(
for x := range bot.exchanges {
if bot.exchanges[x] != nil {
if bot.exchanges[x].GetName() == exchangeName {
specificOrderbook, err = bot.exchanges[x].GetOrderbookEx(
pair.NewCurrencyPairFromString(currency),
assetType,
)
@@ -65,10 +54,10 @@ func GetSpecificOrderbook(currency, exchangeName, assetType string) (orderbook.B
func GetSpecificTicker(currency, exchangeName, assetType string) (ticker.Price, error) {
var specificTicker ticker.Price
var err error
for i := 0; i < len(bot.exchanges); i++ {
if bot.exchanges[i] != nil {
if bot.exchanges[i].IsEnabled() && bot.exchanges[i].GetName() == exchangeName {
specificTicker, err = bot.exchanges[i].GetTickerPrice(
for x := range bot.exchanges {
if bot.exchanges[x] != nil {
if bot.exchanges[x].GetName() == exchangeName {
specificTicker, err = bot.exchanges[x].GetTickerPrice(
pair.NewCurrencyPairFromString(currency),
assetType,
)

106
main.go
View File

@@ -14,93 +14,23 @@ import (
"github.com/thrasher-/gocryptotrader/config"
"github.com/thrasher-/gocryptotrader/currency"
"github.com/thrasher-/gocryptotrader/exchanges"
"github.com/thrasher-/gocryptotrader/exchanges/anx"
"github.com/thrasher-/gocryptotrader/exchanges/bitfinex"
"github.com/thrasher-/gocryptotrader/exchanges/bitstamp"
"github.com/thrasher-/gocryptotrader/exchanges/bittrex"
"github.com/thrasher-/gocryptotrader/exchanges/btcc"
"github.com/thrasher-/gocryptotrader/exchanges/btcmarkets"
"github.com/thrasher-/gocryptotrader/exchanges/coinut"
"github.com/thrasher-/gocryptotrader/exchanges/gdax"
"github.com/thrasher-/gocryptotrader/exchanges/gemini"
"github.com/thrasher-/gocryptotrader/exchanges/huobi"
"github.com/thrasher-/gocryptotrader/exchanges/itbit"
"github.com/thrasher-/gocryptotrader/exchanges/kraken"
"github.com/thrasher-/gocryptotrader/exchanges/lakebtc"
"github.com/thrasher-/gocryptotrader/exchanges/liqui"
"github.com/thrasher-/gocryptotrader/exchanges/localbitcoins"
"github.com/thrasher-/gocryptotrader/exchanges/okcoin"
"github.com/thrasher-/gocryptotrader/exchanges/poloniex"
"github.com/thrasher-/gocryptotrader/exchanges/ticker"
"github.com/thrasher-/gocryptotrader/exchanges/wex"
"github.com/thrasher-/gocryptotrader/portfolio"
"github.com/thrasher-/gocryptotrader/smsglobal"
)
// ExchangeMain contains all the necessary exchange packages
type ExchangeMain struct {
anx anx.ANX
btcc btcc.BTCC
bitstamp bitstamp.Bitstamp
bitfinex bitfinex.Bitfinex
bittrex bittrex.Bittrex
wex wex.WEX
btcmarkets btcmarkets.BTCMarkets
coinut coinut.COINUT
gdax gdax.GDAX
gemini gemini.Gemini
okcoinChina okcoin.OKCoin
okcoinIntl okcoin.OKCoin
itbit itbit.ItBit
lakebtc lakebtc.LakeBTC
liqui liqui.Liqui
localbitcoins localbitcoins.LocalBitcoins
poloniex poloniex.Poloniex
huobi huobi.HUOBI
kraken kraken.Kraken
}
// Bot contains configuration, portfolio, exchange & ticker data and is the
// overarching type across this code base.
type Bot struct {
config *config.Config
smsglobal *smsglobal.Base
portfolio *portfolio.Base
exchange ExchangeMain
exchanges []exchange.IBotExchange
tickers []ticker.Ticker
shutdown chan bool
configFile string
}
var bot Bot
func setupBotExchanges() {
for _, exch := range bot.config.Exchanges {
for i := 0; i < len(bot.exchanges); i++ {
if bot.exchanges[i] != nil {
if bot.exchanges[i].GetName() == exch.Name {
bot.exchanges[i].Setup(exch)
if bot.exchanges[i].IsEnabled() {
log.Printf(
"%s: Exchange support: %s (Authenticated API support: %s - Verbose mode: %s).\n",
exch.Name, common.IsEnabled(exch.Enabled),
common.IsEnabled(exch.AuthenticatedAPISupport),
common.IsEnabled(exch.Verbose),
)
bot.exchanges[i].Start()
} else {
log.Printf(
"%s: Exchange support: %s\n", exch.Name,
common.IsEnabled(exch.Enabled),
)
}
}
}
}
}
}
func main() {
HandleInterrupt()
@@ -135,42 +65,12 @@ func main() {
"Available Exchanges: %d. Enabled Exchanges: %d.\n",
len(bot.config.Exchanges), bot.config.GetConfigEnabledExchanges(),
)
log.Println("Bot Exchange support:")
bot.exchanges = []exchange.IBotExchange{
new(anx.ANX),
new(kraken.Kraken),
new(btcc.BTCC),
new(bitstamp.Bitstamp),
new(bitfinex.Bitfinex),
new(bittrex.Bittrex),
new(wex.WEX),
new(btcmarkets.BTCMarkets),
new(coinut.COINUT),
new(gdax.GDAX),
new(gemini.Gemini),
new(okcoin.OKCoin),
new(okcoin.OKCoin),
new(itbit.ItBit),
new(lakebtc.LakeBTC),
new(liqui.Liqui),
new(localbitcoins.LocalBitcoins),
new(poloniex.Poloniex),
new(huobi.HUOBI),
SetupExchanges()
if len(bot.exchanges) == 0 {
log.Fatalf("No exchanges were able to be loaded. Exiting")
}
for i := 0; i < len(bot.exchanges); i++ {
if bot.exchanges[i] != nil {
bot.exchanges[i].SetDefaults()
log.Printf(
"Exchange %s successfully set default settings.\n",
bot.exchanges[i].GetName(),
)
}
}
setupBotExchanges()
if bot.config.CurrencyExchangeProvider == "yahoo" {
currency.SetProvider(true)
} else {

View File

@@ -86,6 +86,8 @@ func RESTSaveAllSettings(w http.ResponseWriter, r *http.Request) {
if err != nil {
RESTfulError(r.Method, err)
}
SetupExchanges()
}
// RESTGetOrderbook returns orderbook info for a given currency, exchange and

View File

@@ -173,44 +173,47 @@ func relayWebsocketEvent(result interface{}, event, assetType, exchangeName stri
}
}
// TickerUpdaterRoutine fetches and updates the ticker for all enabled
// currency pairs and exchanges
func TickerUpdaterRoutine() {
log.Println("Starting ticker updater routine")
for {
for x := range bot.exchanges {
if bot.exchanges[x].IsEnabled() {
exchangeName := bot.exchanges[x].GetName()
enabledCurrencies := bot.exchanges[x].GetEnabledCurrencies()
if bot.exchanges[x] == nil {
continue
}
exchangeName := bot.exchanges[x].GetName()
enabledCurrencies := bot.exchanges[x].GetEnabledCurrencies()
var result ticker.Price
var err error
var assetTypes []string
var result ticker.Price
var err error
var assetTypes []string
assetTypes, err = exchange.GetExchangeAssetTypes(exchangeName)
if err != nil {
log.Printf("failed to get %s exchange asset types. Error: %s",
exchangeName, err)
}
assetTypes, err = exchange.GetExchangeAssetTypes(exchangeName)
if err != nil {
log.Printf("failed to get %s exchange asset types. Error: %s",
exchangeName, err)
}
for y := range enabledCurrencies {
currency := enabledCurrencies[y]
for y := range enabledCurrencies {
currency := enabledCurrencies[y]
if len(assetTypes) > 1 {
for z := range assetTypes {
result, err = bot.exchanges[x].UpdateTicker(currency,
assetTypes[z])
printSummary(result, currency, assetTypes[z], exchangeName, err)
if err == nil {
relayWebsocketEvent(result, "ticker_update", assetTypes[z], exchangeName)
}
}
} else {
if len(assetTypes) > 1 {
for z := range assetTypes {
result, err = bot.exchanges[x].UpdateTicker(currency,
assetTypes[0])
printSummary(result, currency, assetTypes[0], exchangeName, err)
assetTypes[z])
printSummary(result, currency, assetTypes[z], exchangeName, err)
if err == nil {
relayWebsocketEvent(result, "ticker_update", assetTypes[0], exchangeName)
relayWebsocketEvent(result, "ticker_update", assetTypes[z], exchangeName)
}
}
} else {
result, err = bot.exchanges[x].UpdateTicker(currency,
assetTypes[0])
printSummary(result, currency, assetTypes[0], exchangeName, err)
if err == nil {
relayWebsocketEvent(result, "ticker_update", assetTypes[0], exchangeName)
}
}
}
}
@@ -218,47 +221,51 @@ func TickerUpdaterRoutine() {
}
}
// OrderbookUpdaterRoutine fetches and updates the orderbooks for all enabled
// currency pairs and exchanges
func OrderbookUpdaterRoutine() {
log.Println("Starting orderbook updater routine")
for {
for x := range bot.exchanges {
if bot.exchanges[x].IsEnabled() {
if bot.exchanges[x].GetName() == "ANX" {
continue
}
if bot.exchanges[x] == nil {
continue
}
exchangeName := bot.exchanges[x].GetName()
enabledCurrencies := bot.exchanges[x].GetEnabledCurrencies()
var result orderbook.Base
var err error
var assetTypes []string
if bot.exchanges[x].GetName() == "ANX" {
continue
}
assetTypes, err = exchange.GetExchangeAssetTypes(exchangeName)
if err != nil {
log.Printf("failed to get %s exchange asset types. Error: %s",
exchangeName, err)
}
exchangeName := bot.exchanges[x].GetName()
enabledCurrencies := bot.exchanges[x].GetEnabledCurrencies()
var result orderbook.Base
var err error
var assetTypes []string
for y := range enabledCurrencies {
currency := enabledCurrencies[y]
assetTypes, err = exchange.GetExchangeAssetTypes(exchangeName)
if err != nil {
log.Printf("failed to get %s exchange asset types. Error: %s",
exchangeName, err)
}
if len(assetTypes) > 1 {
for z := range assetTypes {
result, err = bot.exchanges[x].UpdateOrderbook(currency,
assetTypes[z])
printOrderbookSummary(result, currency, assetTypes[z], exchangeName, err)
if err == nil {
relayWebsocketEvent(result, "orderbook_update", assetTypes[z], exchangeName)
}
}
} else {
for y := range enabledCurrencies {
currency := enabledCurrencies[y]
if len(assetTypes) > 1 {
for z := range assetTypes {
result, err = bot.exchanges[x].UpdateOrderbook(currency,
assetTypes[0])
printOrderbookSummary(result, currency, assetTypes[0], exchangeName, err)
assetTypes[z])
printOrderbookSummary(result, currency, assetTypes[z], exchangeName, err)
if err == nil {
relayWebsocketEvent(result, "orderbook_update", assetTypes[0], exchangeName)
relayWebsocketEvent(result, "orderbook_update", assetTypes[z], exchangeName)
}
}
} else {
result, err = bot.exchanges[x].UpdateOrderbook(currency,
assetTypes[0])
printOrderbookSummary(result, currency, assetTypes[0], exchangeName, err)
if err == nil {
relayWebsocketEvent(result, "orderbook_update", assetTypes[0], exchangeName)
}
}
}
}

View File

@@ -13,19 +13,19 @@
{
"Address": "1JCe8z4jJVNXSjohjM4i9Hh813dLCNx2Sy",
"CoinType": "BTC",
"Balance": 124178.00647714,
"Balance": 124178.00354266,
"Description": ""
},
{
"Address": "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v",
"CoinType": "BTC",
"Balance": 107843.84030984,
"Balance": 123439.8370977,
"Description": ""
},
{
"Address": "LgY8ahfHRhvjVQC1zJnBhFMG5pCTMuKRqh",
"CoinType": "LTC",
"Balance": 100000.052,
"Balance": 0.03665026,
"Description": ""
},
{
@@ -211,6 +211,28 @@
"Uppercase": true
}
},
{
"Name": "COINUT",
"Enabled": true,
"Verbose": false,
"Websocket": false,
"UseSandbox": false,
"RESTPollingDelay": 10,
"AuthenticatedAPISupport": false,
"APIKey": "Key",
"APISecret": "Secret",
"ClientID": "ClientID",
"AvailablePairs": "LTCBTC,ETCBTC,ETHBTC",
"EnabledPairs": "LTCBTC,ETCBTC,ETHBTC",
"BaseCurrencies": "USD",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
},
"RequestCurrencyPairFormat": {
"Uppercase": true
}
},
{
"Name": "GDAX",
"Enabled": true,
@@ -313,7 +335,8 @@
"BaseCurrencies": "EUR,USD,CAD,GBP,JPY",
"AssetTypes": "SPOT",
"ConfigCurrencyPairFormat": {
"Uppercase": true
"Uppercase": true,
"Delimiter": "-"
},
"RequestCurrencyPairFormat": {
"Uppercase": true,

View File

@@ -174,7 +174,7 @@ func wsSaveConfig(wsClient *websocket.Conn, data interface{}) error {
}
}
setupBotExchanges()
SetupExchanges()
wsResp.Data = WebsocketResponseSuccess
return wsClient.WriteJSON(wsResp)
}