From 40c56416e07a5f974815ee0ca11992c8825e57c2 Mon Sep 17 00:00:00 2001 From: Nikhil Saraf <1028334+nikhilsaraf@users.noreply.github.com> Date: Tue, 19 Mar 2019 15:36:35 -0700 Subject: [PATCH] lazy-load ccxt exchange list, breaking hard dependency on ccxt-rest, closes #126 (#128) --- cmd/exchanges.go | 3 +++ plugins/factory.go | 26 +++++++++++++++++--------- support/sdk/ccxt.go | 32 +++++++++++++++++++++++++------- 3 files changed, 45 insertions(+), 16 deletions(-) diff --git a/cmd/exchanges.go b/cmd/exchanges.go index 17cb904f9..f8f402554 100644 --- a/cmd/exchanges.go +++ b/cmd/exchanges.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/stellar/kelp/plugins" + "github.com/stellar/kelp/support/sdk" "github.com/spf13/cobra" ) @@ -15,6 +16,8 @@ var exchanagesCmd = &cobra.Command{ func init() { exchanagesCmd.Run = func(ccmd *cobra.Command, args []string) { + // call sdk.GetExchangeList() here so we pre-load exchanges before displaying the table + sdk.GetExchangeList() fmt.Printf(" Exchange\t\t\tTested\t\tTrading\t\tDescription\n") fmt.Printf(" --------------------------------------------------------------------------------\n") exchanges := plugins.Exchanges() diff --git a/plugins/factory.go b/plugins/factory.go index 2b154b17e..4e819c73c 100644 --- a/plugins/factory.go +++ b/plugins/factory.go @@ -165,15 +165,23 @@ type ExchangeContainer struct { } // exchanges is a map of all the exchange integrations available -var exchanges map[string]ExchangeContainer +var exchanges *map[string]ExchangeContainer -func init() { +// getExchanges returns a map of all the exchange integrations available +func getExchanges() map[string]ExchangeContainer { + if exchanges == nil { + loadExchanges() + } + return *exchanges +} + +func loadExchanges() { // marked as tested if key exists in this map (regardless of bool value) testedCcxtExchanges := map[string]bool{ "binance": true, } - exchanges = map[string]ExchangeContainer{ + exchanges = &map[string]ExchangeContainer{ "kraken": ExchangeContainer{ SortOrder: 0, Description: "Kraken is a popular centralized cryptocurrency exchange", @@ -186,13 +194,13 @@ func init() { } // add all CCXT exchanges - sortOrderOffset := len(exchanges) - for i, exchangeName := range sdk.ExchangeList { + sortOrderOffset := len(*exchanges) + for i, exchangeName := range sdk.GetExchangeList() { key := fmt.Sprintf("ccxt-%s", exchangeName) _, tested := testedCcxtExchanges[exchangeName] boundExchangeName := exchangeName - exchanges[key] = ExchangeContainer{ + (*exchanges)[key] = ExchangeContainer{ SortOrder: uint16(i + sortOrderOffset), Description: exchangeName + " is automatically added via ccxt-rest", TradeEnabled: true, @@ -211,7 +219,7 @@ func init() { // MakeExchange is a factory method to make an exchange based on a given type func MakeExchange(exchangeType string, simMode bool) (api.Exchange, error) { - if exchange, ok := exchanges[exchangeType]; ok { + if exchange, ok := getExchanges()[exchangeType]; ok { exchangeAPIKey := api.ExchangeAPIKey{Key: "", Secret: ""} x, e := exchange.makeFn(exchangeFactoryData{ simMode: simMode, @@ -228,7 +236,7 @@ func MakeExchange(exchangeType string, simMode bool) (api.Exchange, error) { // MakeTradingExchange is a factory method to make an exchange based on a given type func MakeTradingExchange(exchangeType string, apiKeys []api.ExchangeAPIKey, simMode bool) (api.Exchange, error) { - if exchange, ok := exchanges[exchangeType]; ok { + if exchange, ok := getExchanges()[exchangeType]; ok { if !exchange.TradeEnabled { return nil, fmt.Errorf("trading is not enabled on this exchange: %s", exchangeType) } @@ -252,5 +260,5 @@ func MakeTradingExchange(exchangeType string, apiKeys []api.ExchangeAPIKey, simM // Exchanges returns the list of exchanges func Exchanges() map[string]ExchangeContainer { - return exchanges + return getExchanges() } diff --git a/support/sdk/ccxt.go b/support/sdk/ccxt.go index 199a7ec13..ca5ab6d33 100644 --- a/support/sdk/ccxt.go +++ b/support/sdk/ccxt.go @@ -75,27 +75,45 @@ func MakeInitializedCcxtExchange(exchangeName string, apiKey api.ExchangeAPIKey) return c, nil } -// ExchangeList contains a list of supported exchanges -var ExchangeList []string +// exchangeList contains a list of supported exchanges +var exchangeList *[]string -func init() { - e := networking.JSONRequest(http.DefaultClient, "GET", ccxtBaseURL+pathExchanges, "", map[string]string{}, &ExchangeList, "error") +// GetExchangeList gets a list of all supported exchanges +func GetExchangeList() []string { + if exchangeList == nil { + loadExchangeList() + } + return *exchangeList +} + +func loadExchangeList() { + var output []string + e := networking.JSONRequest(http.DefaultClient, "GET", ccxtBaseURL+pathExchanges, "", map[string]string{}, &output, "error") if e != nil { - panic(fmt.Errorf("error getting list of supported exchanges by CCXT: %s", e)) + eMsg1 := strings.Contains(e.Error(), "could not execute http request") + eMsg2 := strings.Contains(e.Error(), "http://localhost:3000/exchanges: dial tcp") + eMsg3 := strings.Contains(e.Error(), "connection refused") + if eMsg1 && eMsg2 && eMsg3 { + log.Printf("ccxt-rest is not running on port 3000 so we cannot include those exchanges") + } else { + panic(fmt.Errorf("error getting list of supported exchanges by CCXT: %s", e)) + } } + exchangeList = &output } func (c *Ccxt) initialize(apiKey api.ExchangeAPIKey) error { // validate that exchange name is in the exchange list exchangeListed := false - for _, name := range ExchangeList { + el := GetExchangeList() + for _, name := range el { if name == c.exchangeName { exchangeListed = true break } } if !exchangeListed { - return fmt.Errorf("exchange name '%s' is not in the list of %d exchanges available: %v", c.exchangeName, len(ExchangeList), ExchangeList) + return fmt.Errorf("exchange name '%s' is not in the list of %d exchanges available: %v", c.exchangeName, len(el), el) } // list all the instances of the exchange