diff --git a/engine/engine.go b/engine/engine.go index 2b836600..0149ae6f 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -854,11 +854,12 @@ func (bot *Engine) dryRunParamInteraction(param string) { return } + gctlog.Warnf(gctlog.Global, + "Command line argument '-%s' induces dry run mode."+ + " Set -dryrun=false if you wish to override this.", + param) + if !bot.Settings.EnableDryRun { - gctlog.Warnf(gctlog.Global, - "Command line argument '-%s' induces dry run mode."+ - " Set -dryrun=false if you wish to override this.", - param) bot.Settings.EnableDryRun = true } } @@ -897,12 +898,40 @@ func (bot *Engine) SetupExchanges() error { bot.dryRunParamInteraction("exchangehttpdebugging") } + var exchangesOverride []string + if bot.Settings.Exchanges != "" { + bot.dryRunParamInteraction("exchanges") + exchangesOverride = strings.Split(bot.Settings.Exchanges, ",") + for x := range exchangesOverride { + if !common.StringDataCompareInsensitive(exchange.Exchanges, exchangesOverride[x]) { + return fmt.Errorf("exchange %s not found", exchangesOverride[x]) + } + } + } + + if bot.Settings.EnableAllExchanges && len(exchangesOverride) > 0 { + return errors.New("cannot enable all exchanges and specific exchanges concurrently") + } + var wg sync.WaitGroup for x := range configs { - if !configs[x].Enabled && !bot.Settings.EnableAllExchanges { + shouldLoad := false + if len(exchangesOverride) > 0 { + for y := range exchangesOverride { + if strings.EqualFold(configs[x].Name, exchangesOverride[y]) { + shouldLoad = true + break + } + } + } else { + shouldLoad = configs[x].Enabled || bot.Settings.EnableAllExchanges + } + + if !shouldLoad { gctlog.Debugf(gctlog.ExchangeSys, "%s: Exchange support: Disabled\n", configs[x].Name) continue } + wg.Add(1) go func(c config.Exchange) { defer wg.Done() diff --git a/engine/engine_test.go b/engine/engine_test.go index bf6bbfcc..86bc7706 100644 --- a/engine/engine_test.go +++ b/engine/engine_test.go @@ -9,9 +9,12 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/bitfinex" + "github.com/thrasher-corp/gocryptotrader/exchanges/bitstamp" ) // blockedCIExchanges are exchanges that are not able to be tested on CI @@ -415,3 +418,117 @@ func TestGetDefaultConfigurations(t *testing.T) { }) } } + +func TestSetupExchanges(t *testing.T) { + t.Parallel() + + t.Run("No enabled exchanges", func(t *testing.T) { + t.Parallel() + e := &Engine{ + Config: &config.Config{Exchanges: []config.Exchange{{Name: testExchange}}}, + } + assert.ErrorIs(t, e.SetupExchanges(), ErrNoExchangesLoaded) + }) + + t.Run("EnableAllExchanges with specific exchanges set", func(t *testing.T) { + t.Parallel() + e := &Engine{ + Config: &config.Config{}, + Settings: Settings{ + CoreSettings: CoreSettings{ + EnableAllExchanges: true, + Exchanges: "Bitstamp,Bitfinex", + }, + }, + } + assert.EqualError(t, e.SetupExchanges(), "cannot enable all exchanges and specific exchanges concurrently") + }) + + t.Run("Settings dry run toggling", func(t *testing.T) { + t.Parallel() + e := &Engine{ + Config: &config.Config{}, + Settings: Settings{ + CoreSettings: CoreSettings{ + EnableAllPairs: true, + EnableAllExchanges: true, + }, + ExchangeTuningSettings: ExchangeTuningSettings{ + EnableExchangeVerbose: true, + EnableExchangeWebsocketSupport: true, + EnableExchangeAutoPairUpdates: true, + DisableExchangeAutoPairUpdates: true, + HTTPUserAgent: "test", + HTTPProxy: "test", + HTTPTimeout: 1, + EnableExchangeHTTPDebugging: true, + }, + }, + } + assert.ErrorIs(t, e.SetupExchanges(), ErrNoExchangesLoaded) + assert.False(t, e.Settings.EnableDryRun) + e.Settings.CheckParamInteraction = true + assert.ErrorIs(t, e.SetupExchanges(), ErrNoExchangesLoaded) + assert.True(t, e.Settings.EnableDryRun) + }) + + // Test that overridden exchange inputs are handled correctly + testCases := []struct { + name string + exchangeString string + expectedError string + }{ + {"Invalid exchange pair", "bob|jill", "exchange bob|jill not found"}, + {"Single invalid exchange", "bob", "exchange bob not found"}, + {"Mixed valid and invalid exchanges", "bob,bitstamp", "exchange bob not found"}, + {"Valid exchange", "BiTSTaMp", "no exchanges have been loaded"}, // Proper exchange name, but not loaded + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + e := &Engine{ + Config: &config.Config{}, + Settings: Settings{CoreSettings: CoreSettings{Exchanges: tc.exchangeString}}, + } + assert.ErrorContains(t, e.SetupExchanges(), tc.expectedError) + }) + } + + t.Run("Two valid exchanges with exchanges flag toggled", func(t *testing.T) { + t.Parallel() + e := &Engine{Config: &config.Config{}} + + exchLoader := func(exch exchange.IBotExchange) { + exch.SetDefaults() + exch.GetBase().Features.Supports.RESTCapabilities.AutoPairUpdates = false + cfg, err := exchange.GetDefaultConfig(context.Background(), exch) + require.NoError(t, err) + e.Config.Exchanges = append(e.Config.Exchanges, *cfg) + } + + e.ExchangeManager = NewExchangeManager() + exchLoader(new(bitstamp.Bitstamp)) + exchLoader(new(bitfinex.Bitfinex)) + assert.ElementsMatch(t, []string{"Bitstamp", "Bitfinex"}, e.Config.GetEnabledExchanges()) + + t.Run("Load specific exchange", func(t *testing.T) { + e.Settings.Exchanges = "BiTfInEx" + assert.NoError(t, e.SetupExchanges(), "SetupExchanges with a valid exchange should not error") + exchanges, err := e.ExchangeManager.GetExchanges() + require.NoError(t, err) + require.Len(t, exchanges, 1) + assert.Equal(t, "Bitfinex", exchanges[0].GetName()) + }) + + t.Run("Load all enabled exchanges", func(t *testing.T) { + e.Settings.Exchanges = "" + assert.NoError(t, e.SetupExchanges(), "SetupExchanges with all enabled exchanges should not error") + exchanges, err := e.ExchangeManager.GetExchanges() + require.NoError(t, err) + require.Len(t, exchanges, 2) + exchangeNames := []string{exchanges[0].GetName(), exchanges[1].GetName()} + assert.ElementsMatch(t, []string{"Bitstamp", "Bitfinex"}, exchangeNames) + }) + }) +} diff --git a/engine/engine_types.go b/engine/engine_types.go index 02841188..c831a662 100644 --- a/engine/engine_types.go +++ b/engine/engine_types.go @@ -60,6 +60,7 @@ type CoreSettings struct { EnableDispatcher bool DispatchMaxWorkerAmount int DispatchJobsLimit int + Exchanges string } // ExchangeSyncerSettings defines settings for the exchange pair synchronisation diff --git a/main.go b/main.go index d84ac9cf..86575a81 100644 --- a/main.go +++ b/main.go @@ -96,6 +96,7 @@ func main() { flag.DurationVar(&settings.TradeBufferProcessingInterval, "tradeprocessinginterval", trade.DefaultProcessorIntervalTime, "sets the interval to save trade buffer data to the database") flag.IntVar(&settings.AlertSystemPreAllocationCommsBuffer, "alertbuffer", alert.PreAllocCommsDefaultBuffer, "sets the size of the pre-allocation communications buffer") flag.DurationVar(&settings.ExchangeShutdownTimeout, "exchangeshutdowntimeout", time.Second*10, "sets the maximum amount of time the program will wait for an exchange to shut down gracefully") + flag.StringVar(&settings.Exchanges, "exchanges", "", "sets a comma-separated list of exchanges to load. If left empty, all enabled exchanges will be loaded from the config file") // Common tuning settings flag.DurationVar(&settings.GlobalHTTPTimeout, "globalhttptimeout", 0, "sets common HTTP timeout value for HTTP requests")