diff --git a/.github/workflows/config-versions-lint.yml b/.github/workflows/config-versions-lint.yml index 5059dd67..ac56a1d9 100644 --- a/.github/workflows/config-versions-lint.yml +++ b/.github/workflows/config-versions-lint.yml @@ -13,4 +13,4 @@ jobs: with: go-version: ${{ env.GO_VERSION }} - name: Check config versions are continuous - run: go test ./config/versions/ -tags config_versions -run Continuity + run: go test ./config/versions/ -tags config_versions -run Continuity \ No newline at end of file diff --git a/.github/workflows/configs-json-lint.yml b/.github/workflows/configs-json-lint.yml deleted file mode 100644 index c0265c53..00000000 --- a/.github/workflows/configs-json-lint.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: configs-json-lint -on: [push, pull_request] - -jobs: - lint: - name: configs JSON lint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - - name: Check configs JSON format - run: | - files=("config_example.json" "testdata/configtest.json") - for file in "${files[@]}"; do - processed_file="${file%.*}_processed.${file##*.}" - jq '.exchanges |= sort_by(.name)' --indent 1 $file > $processed_file - if ! diff $file $processed_file; then - echo "jq differences found in $file! Please run 'make lint_configs'" - exit 1 - else - rm $processed_file - echo "No differences found in $file 🌞" - fi - done \ No newline at end of file diff --git a/.github/workflows/misc.yml b/.github/workflows/misc.yml index 05757f51..90b39aba 100644 --- a/.github/workflows/misc.yml +++ b/.github/workflows/misc.yml @@ -6,6 +6,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: 1.25.x + - name: Check for currency.NewPair(BTC, USD) used instead of currency.NewBTCUSD run: | grep -r -n --color=always -E "currency.NewPair\(currency.BTC, currency.USDT?\)" * || exit 0 @@ -62,4 +67,24 @@ jobs: grep -r -n -I --color=always --exclude-dir=.git -P "$PATTERN" . || exit 0 echo "::error::Remove zero-width/format, separator or combining-mark characters" exit 1 + + - name: Check configs JSON format + run: | + files=("config_example.json" "testdata/configtest.json") + for file in "${files[@]}"; do + processed_file="${file%.*}_processed.${file##*.}" + jq '.exchanges |= sort_by(.name)' --indent 1 $file > $processed_file + if ! diff $file $processed_file; then + echo "jq differences found in $file! Please run 'make lint_configs'" + exit 1 + else + rm $processed_file + echo "No differences found in $file 🌞" + fi + done + + - name: Check Go modernise tool issues + run: | + go run golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest -test ./... + diff --git a/Makefile b/Makefile index 95bddbff..2981a259 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ DRIVER ?= psql RACE_FLAG := $(if $(NO_RACE_TEST),,-race) CONFIG_FLAG = $(if $(CONFIG),-config $(CONFIG),) -.PHONY: all lint lint_docker check test build install fmt gofumpt update_deps +.PHONY: all lint lint_docker check test build install fmt gofumpt update_deps modernise all: check build @@ -41,6 +41,10 @@ gofumpt: @command -v gofumpt >/dev/null 2>&1 || go install mvdan.cc/gofumpt@latest $(GOFUMPTBIN) -l -w $(GO_FILES_TO_FORMAT) +modernise: + @command -v modernize >/dev/null 2>&1 || go install golang.org/x/tools/gopls/internal/analysis/modernize/cmd/modernize@latest + modernize -test ./... + update_deps: go mod verify go mod tidy diff --git a/backtester/engine/backtest.go b/backtester/engine/backtest.go index beefe1b0..9991e85f 100644 --- a/backtester/engine/backtest.go +++ b/backtester/engine/backtest.go @@ -81,25 +81,20 @@ func (bt *BackTest) RunLive() error { if bt.LiveDataHandler == nil { return errLiveOnly } - var err error if bt.LiveDataHandler.IsRealOrders() { - err = bt.LiveDataHandler.UpdateFunding(false) - if err != nil { + if err := bt.LiveDataHandler.UpdateFunding(false); err != nil { return err } } - err = bt.LiveDataHandler.Start() - if err != nil { + if err := bt.LiveDataHandler.Start(); err != nil { return err } - bt.wg.Add(1) - go func() { - err = bt.liveCheck() - if err != nil { + + bt.wg.Go(func() { + if err := bt.liveCheck(); err != nil { log.Errorln(common.LiveStrategy, err) } - bt.wg.Done() - }() + }) return nil } diff --git a/backtester/engine/backtest_test.go b/backtester/engine/backtest_test.go index ac654cb0..326c8ef6 100644 --- a/backtester/engine/backtest_test.go +++ b/backtester/engine/backtest_test.go @@ -1321,37 +1321,27 @@ func TestLiveLoop(t *testing.T) { // dataUpdated case var wg sync.WaitGroup - wg.Add(1) - go func() { - err = bt.liveCheck() - assert.NoError(t, err) - - wg.Done() - }() + wg.Go(func() { + assert.NoError(t, bt.liveCheck()) + }) dc.dataUpdated <- true dc.shutdown <- true wg.Wait() // shutdown from error case - wg.Add(1) dc.started = 0 - go func() { - defer wg.Done() - err = bt.liveCheck() - assert.NoError(t, err) - }() + wg.Go(func() { + assert.NoError(t, bt.liveCheck()) + }) dc.shutdownErr <- true wg.Wait() // shutdown case dc.started = 1 bt.shutdown = make(chan struct{}) - wg.Add(1) - go func() { - defer wg.Done() - err = bt.liveCheck() - assert.NoError(t, err) - }() + wg.Go(func() { + assert.NoError(t, bt.liveCheck()) + }) dc.shutdown <- true wg.Wait() diff --git a/backtester/engine/live_test.go b/backtester/engine/live_test.go index 2a3f54c6..890d472d 100644 --- a/backtester/engine/live_test.go +++ b/backtester/engine/live_test.go @@ -135,12 +135,9 @@ func TestLiveHandlerStopFromError(t *testing.T) { dc.started = 1 var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - err = dc.SignalStopFromError(errNoCredsNoLive) - assert.NoError(t, err) - }() + wg.Go(func() { + assert.NoError(t, dc.SignalStopFromError(errNoCredsNoLive)) + }) wg.Wait() var dh *dataChecker @@ -177,19 +174,15 @@ func TestUpdated(t *testing.T) { dataUpdated: make(chan bool, 10), } var wg sync.WaitGroup - wg.Add(1) - go func() { + wg.Go(func() { _ = dc.Updated() - wg.Done() - }() + }) wg.Wait() dc = nil - wg.Add(1) - go func() { + wg.Go(func() { _ = dc.Updated() - wg.Done() - }() + }) wg.Wait() } diff --git a/cmd/exchange_wrapper_coverage/main.go b/cmd/exchange_wrapper_coverage/main.go index 494a1259..2471b8c6 100644 --- a/cmd/exchange_wrapper_coverage/main.go +++ b/cmd/exchange_wrapper_coverage/main.go @@ -34,13 +34,11 @@ func main() { var wg sync.WaitGroup for i := range exchange.Exchanges { name := exchange.Exchanges[i] - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { if err := engine.Bot.LoadExchange(name); err != nil { log.Printf("Failed to load exchange %s. Err: %s", name, err) } - }() + }) } wg.Wait() log.Println("Done.") diff --git a/cmd/exchange_wrapper_issues/main.go b/cmd/exchange_wrapper_issues/main.go index 7395b10d..0680dd2d 100644 --- a/cmd/exchange_wrapper_issues/main.go +++ b/cmd/exchange_wrapper_issues/main.go @@ -74,13 +74,11 @@ func main() { wrapperConfig.Exchanges[strings.ToLower(name)] = &config.APICredentialsConfig{} } if shouldLoadExchange(name) { - wg.Add(1) - go func() { - defer wg.Done() + wg.Go(func() { if err = bot.LoadExchange(name); err != nil { log.Printf("Failed to load exchange %s. Err: %s", name, err) } - }() + }) } } wg.Wait() diff --git a/docs/CODING_GUIDELINES.md b/docs/CODING_GUIDELINES.md index 962b18d6..bdd827a2 100644 --- a/docs/CODING_GUIDELINES.md +++ b/docs/CODING_GUIDELINES.md @@ -179,7 +179,7 @@ Run the following after completing changes: This ensures proper formatting across the codebase. -## Linters +## Linters and other miscellaneous checks Run the following to check for linting issues: @@ -187,6 +187,14 @@ Run the following to check for linting issues: golangci-lint run ./... (or make lint) ``` +Run the following tool to check for Go modernise issues: + +```console + make modernise +``` + +Several other miscellaneous checks will be ran via [GitHub actions](/.github/workflows/misc.yml). + - All lint warnings and errors must be resolved before merging. - Use `//nolint:linter-name` sparingly and always explain the reason in a comment next to the code. - Examples of valid use: diff --git a/engine/apiserver.go b/engine/apiserver.go index a43cec04..00769f76 100644 --- a/engine/apiserver.go +++ b/engine/apiserver.go @@ -180,9 +180,7 @@ func (m *apiServerManager) StartRESTServer() error { ReadHeaderTimeout: time.Minute, } } - m.wgRest.Add(1) - go func() { - defer m.wgRest.Done() + m.wgRest.Go(func() { err := m.restHTTPServer.ListenAndServe() if err != nil { atomic.StoreInt32(&m.restStarted, 0) @@ -190,7 +188,7 @@ func (m *apiServerManager) StartRESTServer() error { log.Errorln(log.APIServerMgr, err) } } - }() + }) return nil } @@ -422,9 +420,7 @@ func (m *apiServerManager) StartWebsocketServer() error { } } - m.wgWebsocket.Add(1) - go func() { - defer m.wgWebsocket.Done() + m.wgWebsocket.Go(func() { err := m.websocketHTTPServer.ListenAndServe() if err != nil { atomic.StoreInt32(&m.websocketStarted, 0) @@ -432,7 +428,7 @@ func (m *apiServerManager) StartWebsocketServer() error { log.Errorln(log.APIServerMgr, err) } } - }() + }) return nil } diff --git a/engine/engine.go b/engine/engine.go index 7c405959..92d9c91d 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -731,12 +731,8 @@ func (bot *Engine) LoadExchange(name string) error { return ErrExchangeFailedToLoad } - var localWG sync.WaitGroup - localWG.Add(1) - go func() { - exch.SetDefaults() - localWG.Done() - }() + exch.SetDefaults() + exchCfg, err := bot.Config.GetExchangeConfig(name) if err != nil { return err @@ -789,7 +785,6 @@ func (bot *Engine) LoadExchange(name string) error { exchCfg.HTTPDebugging = bot.Settings.EnableExchangeHTTPDebugging } - localWG.Wait() if !bot.Settings.EnableExchangeHTTPRateLimiter { err = exch.DisableRateLimiter() if err != nil { diff --git a/engine/websocketroutine_manager.go b/engine/websocketroutine_manager.go index d255a135..7c9efd7f 100644 --- a/engine/websocketroutine_manager.go +++ b/engine/websocketroutine_manager.go @@ -134,19 +134,15 @@ func (m *WebsocketRoutineManager) websocketRoutine() { continue } - wg.Add(1) - go func() { - defer wg.Done() - err = m.websocketDataReceiver(ws) - if err != nil { + wg.Go(func() { + if err := m.websocketDataReceiver(ws); err != nil { log.Errorf(log.WebsocketMgr, "%v", err) } - err = ws.Connect() - if err != nil { + if err := ws.Connect(); err != nil { log.Errorf(log.WebsocketMgr, "%v", err) } - }() + }) } wg.Wait() } @@ -166,9 +162,7 @@ func (m *WebsocketRoutineManager) websocketDataReceiver(ws *websocket.Manager) e return errRoutineManagerNotStarted } - m.wg.Add(1) - go func() { - defer m.wg.Done() + m.wg.Go(func() { for { select { case <-m.shutdown: @@ -187,7 +181,7 @@ func (m *WebsocketRoutineManager) websocketDataReceiver(ws *websocket.Manager) e m.mu.RUnlock() } } - }() + }) return nil } diff --git a/exchange/websocket/connection.go b/exchange/websocket/connection.go index ab70bae5..e1e7968e 100644 --- a/exchange/websocket/connection.go +++ b/exchange/websocket/connection.go @@ -233,9 +233,7 @@ func (c *connection) SetupPingHandler(epl request.EndpointLimit, handler PingHan }) return } - c.Wg.Add(1) - go func() { - defer c.Wg.Done() + c.Wg.Go(func() { ticker := time.NewTicker(handler.Delay) for { select { @@ -250,7 +248,7 @@ func (c *connection) SetupPingHandler(epl request.EndpointLimit, handler PingHan } } } - }() + }) } // setConnectedStatus sets connection status if changed it will return true. diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index 56c72f30..ffa4af28 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -703,9 +703,7 @@ func (o *orderbookManager) setNeedsFetchingBook(pair currency.Pair) error { // SynchroniseWebsocketOrderbook synchronises full orderbook for currency pair // asset func (e *Exchange) SynchroniseWebsocketOrderbook(ctx context.Context) { - e.Websocket.Wg.Add(1) - go func() { - defer e.Websocket.Wg.Done() + e.Websocket.Wg.Go(func() { for { select { case <-e.Websocket.ShutdownC: @@ -717,15 +715,12 @@ func (e *Exchange) SynchroniseWebsocketOrderbook(ctx context.Context) { } } case j := <-e.obm.jobs: - err := e.processJob(ctx, j.Pair) - if err != nil { - log.Errorf(log.WebsocketMgr, - "%s processing websocket orderbook error %v", - e.Name, err) + if err := e.processJob(ctx, j.Pair); err != nil { + log.Errorf(log.WebsocketMgr, "%s processing websocket orderbook error: %v", e.Name, err) } } } - }() + }) } // processJob fetches and processes orderbook updates diff --git a/exchanges/binanceus/binanceus_websocket.go b/exchanges/binanceus/binanceus_websocket.go index 972440c9..1f28e571 100644 --- a/exchanges/binanceus/binanceus_websocket.go +++ b/exchanges/binanceus/binanceus_websocket.go @@ -632,9 +632,7 @@ func (e *Exchange) setupOrderbookManager(ctx context.Context) { // SynchroniseWebsocketOrderbook synchronises full orderbook for currency pair asset func (e *Exchange) SynchroniseWebsocketOrderbook(ctx context.Context) { - e.Websocket.Wg.Add(1) - go func() { - defer e.Websocket.Wg.Done() + e.Websocket.Wg.Go(func() { for { select { case <-e.Websocket.ShutdownC: @@ -646,15 +644,12 @@ func (e *Exchange) SynchroniseWebsocketOrderbook(ctx context.Context) { } } case j := <-e.obm.jobs: - err := e.processJob(ctx, j.Pair) - if err != nil { - log.Errorf(log.WebsocketMgr, - "%s processing websocket orderbook error %v", - e.Name, err) + if err := e.processJob(ctx, j.Pair); err != nil { + log.Errorf(log.WebsocketMgr, "%s processing websocket orderbook error: %v", e.Name, err) } } } - }() + }) } // ProcessOrderbookUpdate processes the websocket orderbook update diff --git a/exchanges/bithumb/bithumb_ws_orderbook.go b/exchanges/bithumb/bithumb_ws_orderbook.go index 08cc5b5b..aaefcb78 100644 --- a/exchanges/bithumb/bithumb_ws_orderbook.go +++ b/exchanges/bithumb/bithumb_ws_orderbook.go @@ -105,9 +105,7 @@ func (e *Exchange) applyBufferUpdate(pair currency.Pair) error { // SynchroniseWebsocketOrderbook synchronises full orderbook for currency pair // asset func (e *Exchange) SynchroniseWebsocketOrderbook(ctx context.Context) { - e.Websocket.Wg.Add(1) - go func() { - defer e.Websocket.Wg.Done() + e.Websocket.Wg.Go(func() { for { select { case <-e.Websocket.ShutdownC: @@ -119,13 +117,12 @@ func (e *Exchange) SynchroniseWebsocketOrderbook(ctx context.Context) { } } case j := <-e.obm.jobs: - err := e.processJob(ctx, j.Pair) - if err != nil { - log.Errorf(log.WebsocketMgr, "%s processing websocket orderbook error %v", e.Name, err) + if err := e.processJob(ctx, j.Pair); err != nil { + log.Errorf(log.WebsocketMgr, "%s processing websocket orderbook error: %v", e.Name, err) } } } - }() + }) } // processJob fetches and processes orderbook updates diff --git a/exchanges/gateio/gateio_websocket_request_types.go b/exchanges/gateio/gateio_websocket_request_types.go index 7ca7fa9c..df24070c 100644 --- a/exchanges/gateio/gateio_websocket_request_types.go +++ b/exchanges/gateio/gateio_websocket_request_types.go @@ -171,7 +171,7 @@ type WebsocketFuturesAmendOrder struct { // WebsocketFutureOrdersList defines a websocket future orders list type WebsocketFutureOrdersList struct { - Contract currency.Pair `json:"contract,omitempty"` + Contract currency.Pair `json:"contract,omitzero"` Asset asset.Item `json:"-"` // Only used internally for routing Status string `json:"status"` Limit int64 `json:"limit,omitempty"` diff --git a/exchanges/kucoin/kucoin_futures.go b/exchanges/kucoin/kucoin_futures.go index 40494abb..8287483a 100644 --- a/exchanges/kucoin/kucoin_futures.go +++ b/exchanges/kucoin/kucoin_futures.go @@ -72,10 +72,7 @@ func (e *Exchange) GetFuturesTickers(ctx context.Context) ([]*ticker.Price, erro errC <- err break } - wg.Add(1) - go func() { - defer wg.Done() - + wg.Go(func() { if tick, err2 := e.GetFuturesTicker(ctx, p.String()); err2 != nil { errC <- err2 } else { @@ -92,7 +89,7 @@ func (e *Exchange) GetFuturesTickers(ctx context.Context) ([]*ticker.Price, erro AssetType: asset.Futures, } } - }() + }) } wg.Wait() diff --git a/exchanges/kucoin/kucoin_websocket.go b/exchanges/kucoin/kucoin_websocket.go index 11679224..cd252016 100644 --- a/exchanges/kucoin/kucoin_websocket.go +++ b/exchanges/kucoin/kucoin_websocket.go @@ -1248,9 +1248,7 @@ func (o *orderbookManager) setNeedsFetchingBook(pair currency.Pair, assetType as // SynchroniseWebsocketOrderbook synchronises full orderbook for currency pair // asset func (e *Exchange) SynchroniseWebsocketOrderbook(ctx context.Context) { - e.Websocket.Wg.Add(1) - go func() { - defer e.Websocket.Wg.Done() + e.Websocket.Wg.Go(func() { for { select { case <-e.Websocket.ShutdownC: @@ -1262,15 +1260,12 @@ func (e *Exchange) SynchroniseWebsocketOrderbook(ctx context.Context) { } } case j := <-e.obm.jobs: - err := e.processJob(ctx, j.Pair, j.AssetType) - if err != nil { - log.Errorf(log.WebsocketMgr, - "%s processing websocket orderbook error %v", - e.Name, err) + if err := e.processJob(ctx, j.Pair, j.AssetType); err != nil { + log.Errorf(log.WebsocketMgr, "%s processing websocket orderbook error: %v", e.Name, err) } } } - }() + }) } // SeedLocalCache seeds depth data diff --git a/exchanges/orderbook/orderbook_test.go b/exchanges/orderbook/orderbook_test.go index 0e4d3d0f..b832c29d 100644 --- a/exchanges/orderbook/orderbook_test.go +++ b/exchanges/orderbook/orderbook_test.go @@ -329,8 +329,7 @@ func TestProcessOrderbook(t *testing.T) { break } m.Unlock() - wg.Add(1) - go func() { + wg.Go(func() { newName := "Exchange" + strconv.FormatInt(rand.Int63(), 10) //nolint:gosec // no need to import crypo/rand for testing newPairs := currency.NewPair(currency.NewCode("BTC"+strconv.FormatInt(rand.Int63(), 10)), currency.NewCode("USD"+strconv.FormatInt(rand.Int63(), 10))) //nolint:gosec // no need to import crypo/rand for testing @@ -351,13 +350,11 @@ func TestProcessOrderbook(t *testing.T) { t.Error(err) catastrophicFailure = true m.Unlock() - wg.Done() return } testArray = append(testArray, quick{Name: newName, P: newPairs, Bids: bids, Asks: asks}) m.Unlock() - wg.Done() - }() + }) } wg.Wait() diff --git a/exchanges/ticker/ticker_test.go b/exchanges/ticker/ticker_test.go index 0c87b2e7..3dcf8fa2 100644 --- a/exchanges/ticker/ticker_test.go +++ b/exchanges/ticker/ticker_test.go @@ -362,8 +362,7 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers break } - wg.Add(1) - go func() { + wg.Go(func() { //nolint:gosec // no need to import crypo/rand for testing newName := "Exchange" + strconv.FormatInt(rand.Int63(), 10) newPairs, err := currency.NewPairFromStrings("BTC"+strconv.FormatInt(rand.Int63(), 10), //nolint:gosec // no need to import crypo/rand for testing @@ -389,8 +388,7 @@ func TestProcessTicker(t *testing.T) { // non-appending function to tickers testArray = append(testArray, quick{Name: newName, P: newPairs, TP: tp}) sm.Unlock() - wg.Done() - }() + }) } if catastrophicFailure {