engine: Add overridable exchanges command line flag (#1630)

* engine: Add overridable exchanges command line flag

* Improve tests

* engine: Add additional SetupExchanges edge case test and prevent suppressed dry run warnings
This commit is contained in:
Adrian Gallagher
2024-08-27 12:54:38 +10:00
committed by GitHub
parent 15e0eedc8b
commit 649f3df266
4 changed files with 153 additions and 5 deletions

View File

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

View File

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

View File

@@ -60,6 +60,7 @@ type CoreSettings struct {
EnableDispatcher bool
DispatchMaxWorkerAmount int
DispatchJobsLimit int
Exchanges string
}
// ExchangeSyncerSettings defines settings for the exchange pair synchronisation

View File

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