Skip to content

Commit

Permalink
engine: Adds shutdown method to exchange manager and unload all excha…
Browse files Browse the repository at this point in the history
…nges when engine is stopped (thrasher-corp#1112)

* engine: shutdown and unload exchange when engine is stopped

* linter: fixes

* engine/exchMan: add nil check

* engine/exchanges: add shutdown method to exchanges, rm len check lock not needed, expanded code coverage, address some nits

* exchMan: report all failed shutdowns across exchanges, implement timer and monitoring routines.

* exchMan: improve shutdown sequence and aloc.

* further improvement

* exchman: log from warn to error

* websockconnection: Suppress error return when closure is caused by library

* linter: fix

* fix racies

* add note on why not parallel tests

* glorious: nits

* spelling kween

* thrasher: nits

* engine: change print of setting using reflection, I keep forgetting to implement this so program around forgetfulness

* engine/exchange_management: remove wait group and just rely on intermediary lock

* glorious: nits

* Update common/common.go

Co-authored-by: Adrian Gallagher <[email protected]>

* Update main.go

Co-authored-by: Adrian Gallagher <[email protected]>

---------

Co-authored-by: Ryan O'Hara-Reid <[email protected]>
Co-authored-by: Adrian Gallagher <[email protected]>
  • Loading branch information
3 people authored Apr 5, 2023
1 parent 4a50a72 commit d23898e
Show file tree
Hide file tree
Showing 35 changed files with 801 additions and 354 deletions.
4 changes: 2 additions & 2 deletions backtester/data/kline/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const testExchange = "binanceus"

func TestLoadCandles(t *testing.T) {
t.Parallel()
em := engine.SetupExchangeManager()
em := engine.NewExchangeManager()
exch, err := em.NewExchangeByName(testExchange)
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -56,7 +56,7 @@ func TestLoadCandles(t *testing.T) {

func TestLoadTrades(t *testing.T) {
t.Parallel()
em := engine.SetupExchangeManager()
em := engine.NewExchangeManager()
exch, err := em.NewExchangeByName(testExchange)
if err != nil {
t.Fatal(err)
Expand Down
4 changes: 2 additions & 2 deletions backtester/data/kline/live/live_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestLoadCandles(t *testing.T) {
interval := gctkline.OneHour
cp := currency.NewPair(currency.BTC, currency.USDT)
a := asset.Spot
em := engine.SetupExchangeManager()
em := engine.NewExchangeManager()
exch, err := em.NewExchangeByName(testExchange)
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -56,7 +56,7 @@ func TestLoadTrades(t *testing.T) {
interval := gctkline.OneMin
cp := currency.NewPair(currency.BTC, currency.USDT)
a := asset.Spot
em := engine.SetupExchangeManager()
em := engine.NewExchangeManager()
exch, err := em.NewExchangeByName(testExchange)
if err != nil {
t.Fatal(err)
Expand Down
16 changes: 11 additions & 5 deletions backtester/engine/backtest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ func TestLoadDataLive(t *testing.T) {
Funding: &funding.FundManager{},
DataHolder: &data.HandlerHolder{},
Statistic: &fakeStats{},
exchangeManager: engine.SetupExchangeManager(),
exchangeManager: engine.NewExchangeManager(),
shutdown: make(chan struct{}),
}

Expand Down Expand Up @@ -1103,13 +1103,16 @@ func TestProcessFillEvent(t *testing.T) {
ev := &fill.Fill{
Base: de.Base,
}
em := engine.SetupExchangeManager()
em := engine.NewExchangeManager()
exch, err := em.NewExchangeByName(testExchange)
if err != nil {
t.Fatal(err)
}
exch.SetDefaults()
em.Add(exch)
err = em.Add(exch)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
b, err := funding.CreateItem(testExchange, a, cp.Base, decimal.Zero, decimal.Zero)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
Expand Down Expand Up @@ -1210,13 +1213,16 @@ func TestProcessFuturesFillEvent(t *testing.T) {
ev := &fill.Fill{
Base: de.Base,
}
em := engine.SetupExchangeManager()
em := engine.NewExchangeManager()
exch, err := em.NewExchangeByName(testExchange)
if err != nil {
t.Fatal(err)
}
exch.SetDefaults()
em.Add(exch)
err = em.Add(exch)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
b, err := funding.CreateItem(testExchange, a, cp.Base, decimal.Zero, decimal.Zero)
if !errors.Is(err, expectedError) {
t.Errorf("received '%v' expected '%v'", err, expectedError)
Expand Down
2 changes: 1 addition & 1 deletion backtester/engine/live_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ func TestSetupLiveDataHandler(t *testing.T) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
}

bt.exchangeManager = engine.SetupExchangeManager()
bt.exchangeManager = engine.NewExchangeManager()
err = bt.SetupLiveDataHandler(-1, -1, false, false)
if !errors.Is(err, gctcommon.ErrNilPointer) {
t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer)
Expand Down
16 changes: 12 additions & 4 deletions backtester/engine/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ func NewBacktester() (*BackTest, error) {
if err != nil {
return nil, err
}
bt.exchangeManager = engine.SetupExchangeManager()
bt.exchangeManager = engine.NewExchangeManager()

return bt, nil
}
Expand Down Expand Up @@ -166,7 +166,10 @@ func (bt *BackTest) SetupFromConfig(cfg *config.Config, templatePath, output str
return err
}
}
bt.exchangeManager.Add(exch)
err = bt.exchangeManager.Add(exch)
if err != nil {
return err
}
} else {
return err
}
Expand Down Expand Up @@ -824,9 +827,14 @@ func (bt *BackTest) loadData(cfg *config.Config, exch gctexchange.IBotExchange,
}
case cfg.DataSettings.LiveData != nil:
if !b.Features.Enabled.Kline.Intervals.ExchangeSupported(cfg.DataSettings.Interval) {
return nil, fmt.Errorf("%w don't trade live on custom candle interval of %v", gctkline.ErrCannotConstructInterval, cfg.DataSettings.Interval)
return nil, fmt.Errorf("%w don't trade live on custom candle interval of %v",
gctkline.ErrCannotConstructInterval,
cfg.DataSettings.Interval)
}
err = bt.exchangeManager.Add(exch)
if err != nil {
return nil, err
}
bt.exchangeManager.Add(exch)
err = bt.LiveDataHandler.AppendDataSource(&liveDataSourceSetup{
exchange: exch,
interval: cfg.DataSettings.Interval,
Expand Down
21 changes: 15 additions & 6 deletions backtester/eventhandlers/exchange/exchange_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,15 +184,18 @@ func TestPlaceOrder(t *testing.T) {
t.Parallel()
bot := &engine.Engine{}
var err error
em := engine.SetupExchangeManager()
em := engine.NewExchangeManager()
exch, err := em.NewExchangeByName(testExchange)
if err != nil {
t.Fatal(err)
}
exch.SetDefaults()
exchB := exch.GetBase()
exchB.States = currencystate.NewCurrencyStates()
em.Add(exch)
err = em.Add(exch)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
bot.ExchangeManager = em
bot.OrderManager, err = engine.SetupOrderManager(em, &engine.CommunicationManager{}, &bot.ServicesWG, false, false, 0)
if !errors.Is(err, nil) {
Expand Down Expand Up @@ -238,7 +241,7 @@ func TestExecuteOrder(t *testing.T) {
t.Parallel()
bot := &engine.Engine{}
var err error
em := engine.SetupExchangeManager()
em := engine.NewExchangeManager()
const testExchange = "binanceus"
exch, err := em.NewExchangeByName(testExchange)
if err != nil {
Expand All @@ -247,7 +250,10 @@ func TestExecuteOrder(t *testing.T) {
exch.SetDefaults()
exchB := exch.GetBase()
exchB.States = currencystate.NewCurrencyStates()
em.Add(exch)
err = em.Add(exch)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
bot.ExchangeManager = em
bot.OrderManager, err = engine.SetupOrderManager(em, &engine.CommunicationManager{}, &bot.ServicesWG, false, false, 0)
if !errors.Is(err, nil) {
Expand Down Expand Up @@ -359,7 +365,7 @@ func TestExecuteOrderBuySellSizeLimit(t *testing.T) {
t.Parallel()
bot := &engine.Engine{}
var err error
em := engine.SetupExchangeManager()
em := engine.NewExchangeManager()
const testExchange = "BTC Markets"
exch, err := em.NewExchangeByName(testExchange)
if err != nil {
Expand All @@ -368,7 +374,10 @@ func TestExecuteOrderBuySellSizeLimit(t *testing.T) {
exch.SetDefaults()
exchB := exch.GetBase()
exchB.States = currencystate.NewCurrencyStates()
em.Add(exch)
err = em.Add(exch)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
bot.ExchangeManager = em
bot.OrderManager, err = engine.SetupOrderManager(em, &engine.CommunicationManager{}, &bot.ServicesWG, false, false, 0)
if !errors.Is(err, nil) {
Expand Down
21 changes: 15 additions & 6 deletions backtester/funding/funding_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -779,13 +779,16 @@ func TestUpdateCollateral(t *testing.T) {
currency: currency.BTC,
available: decimal.NewFromInt(1336),
})
em := engine.SetupExchangeManager()
em := engine.NewExchangeManager()
exch, err := em.NewExchangeByName(exchName)
if err != nil {
t.Fatal(err)
}
exch.SetDefaults()
em.Add(exch)
err = em.Add(exch)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
f.exchangeManager = em

expectedError = nil
Expand Down Expand Up @@ -917,15 +920,18 @@ func TestUpdateFundingFromLiveData(t *testing.T) {
t.Errorf("received '%v', expected '%v'", err, engine.ErrNilSubsystem)
}

f.exchangeManager = engine.SetupExchangeManager()
f.exchangeManager = engine.NewExchangeManager()
err = f.UpdateFundingFromLiveData(false)
if !errors.Is(err, nil) {
t.Errorf("received '%v', expected '%v'", err, nil)
}

ff := &binance.Binance{}
ff.SetDefaults()
f.exchangeManager.Add(ff)
err = f.exchangeManager.Add(ff)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
err = f.UpdateFundingFromLiveData(false)
if !errors.Is(err, exchange.ErrCredentialsAreEmpty) {
t.Errorf("received '%v', expected '%v'", err, exchange.ErrCredentialsAreEmpty)
Expand Down Expand Up @@ -960,15 +966,18 @@ func TestUpdateAllCollateral(t *testing.T) {
t.Errorf("received '%v', expected '%v'", err, engine.ErrNilSubsystem)
}

f.exchangeManager = engine.SetupExchangeManager()
f.exchangeManager = engine.NewExchangeManager()
err = f.UpdateAllCollateral(false, false)
if !errors.Is(err, nil) {
t.Errorf("received '%v', expected '%v'", err, nil)
}

ff := &binance.Binance{}
ff.SetDefaults()
f.exchangeManager.Add(ff)
err = f.exchangeManager.Add(ff)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
err = f.UpdateAllCollateral(false, false)
if !errors.Is(err, gctcommon.ErrNotYetImplemented) {
t.Errorf("received '%v', expected '%v'", err, gctcommon.ErrNotYetImplemented)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestCreateUSDTrackingPairs(t *testing.T) {
t.Errorf("received '%v' expected '%v'", err, errExchangeManagerRequired)
}

em := engine.SetupExchangeManager()
em := engine.NewExchangeManager()
_, err = CreateUSDTrackingPairs([]TrackingPair{{Exchange: eName}}, em)
if !errors.Is(err, engine.ErrExchangeNotFound) {
t.Errorf("received '%v' expected '%v'", err, engine.ErrExchangeNotFound)
Expand Down Expand Up @@ -61,7 +61,10 @@ func TestCreateUSDTrackingPairs(t *testing.T) {
eba.Enabled = eba.Enabled.Add(cp3)
eba.AssetEnabled = convert.BoolPtr(true)

em.Add(exch)
err = em.Add(exch)
if !errors.Is(err, nil) {
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
}
resp, err := CreateUSDTrackingPairs([]TrackingPair{s1}, em)
if !errors.Is(err, nil) {
t.Errorf("received '%v' expected '%v'", err, nil)
Expand Down
8 changes: 5 additions & 3 deletions cmd/exchange_wrapper_coverage/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ func main() {
}

engine.Bot.Settings = engine.Settings{
DisableExchangeAutoPairUpdates: true,
EnableDryRun: true,
CoreSettings: engine.CoreSettings{EnableDryRun: true},
ExchangeTuningSettings: engine.ExchangeTuningSettings{
DisableExchangeAutoPairUpdates: true,
},
}

engine.Bot.Config.PurgeExchangeAPICredentials()
engine.Bot.ExchangeManager = engine.SetupExchangeManager()
engine.Bot.ExchangeManager = engine.NewExchangeManager()

log.Printf("Loading exchanges..")
var wg sync.WaitGroup
Expand Down
10 changes: 6 additions & 4 deletions cmd/exchange_wrapper_issues/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,14 @@ func main() {
log.Fatalf("Failed to initialise engine. Err: %s", err)
}
engine.Bot = bot
bot.ExchangeManager = engine.SetupExchangeManager()
bot.ExchangeManager = engine.NewExchangeManager()

bot.Settings = engine.Settings{
DisableExchangeAutoPairUpdates: true,
Verbose: verboseOverride,
EnableExchangeHTTPRateLimiter: true,
CoreSettings: engine.CoreSettings{Verbose: verboseOverride},
ExchangeTuningSettings: engine.ExchangeTuningSettings{
DisableExchangeAutoPairUpdates: true,
EnableExchangeHTTPRateLimiter: true,
},
}

log.Println("Loading config...")
Expand Down
32 changes: 32 additions & 0 deletions common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"strings"
"sync"
"time"
"unicode"

"github.com/thrasher-corp/gocryptotrader/common/file"
"github.com/thrasher-corp/gocryptotrader/log"
Expand Down Expand Up @@ -412,6 +413,37 @@ func SplitStringSliceByLimit(in []string, limit uint) [][]string {
return sliceSlice
}

// AddPaddingOnUpperCase adds padding to a string when detecting an upper case letter. If
// there are multiple upper case items like `ThisIsHTTPExample`, it will only
// pad between like this `This Is HTTP Example`.
func AddPaddingOnUpperCase(s string) string {
if s == "" {
return ""
}
var result []string
left := 0
for x := 0; x < len(s); x++ {
if x == 0 {
continue
}

if unicode.IsUpper(rune(s[x])) {
if !unicode.IsUpper(rune(s[x-1])) {
result = append(result, s[left:x])
left = x
}
} else if x > 1 && unicode.IsUpper(rune(s[x-1])) {
if s[left:x-1] == "" {
continue
}
result = append(result, s[left:x-1])
left = x - 1
}
}
result = append(result, s[left:])
return strings.Join(result, " ")
}

// InArray checks if _val_ belongs to _array_
func InArray(val, array interface{}) (exists bool, index int) {
exists = false
Expand Down
31 changes: 31 additions & 0 deletions common/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,37 @@ func TestSplitStringSliceByLimit(t *testing.T) {
}
}

func TestAddPaddingOnUpperCase(t *testing.T) {
t.Parallel()

testCases := []struct {
Supplied string
Expected string
}{
{
// empty
},
{
Supplied: "ExpectedHTTPRainbow",
Expected: "Expected HTTP Rainbow",
},
{
Supplied: "SmellyCatSmellsBad",
Expected: "Smelly Cat Smells Bad",
},
{
Supplied: "Gronk",
Expected: "Gronk",
},
}

for x := range testCases {
if received := AddPaddingOnUpperCase(testCases[x].Supplied); received != testCases[x].Expected {
t.Fatalf("received '%v' but expected '%v'", received, testCases[x].Expected)
}
}
}

func TestInArray(t *testing.T) {
t.Parallel()
InArray(nil, nil)
Expand Down
Loading

0 comments on commit d23898e

Please sign in to comment.