diff --git a/.appveyor.yml b/.appveyor.yml index 411f138b..f9b5938d 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -28,13 +28,15 @@ environment: PSQL_SSLMODE: disable PSQL_SKIPSQLCMD: true PSQL_TESTDBNAME: gct_dev_ci -stack: go 1.17.x + +stack: go 1.17.8 # this is not actually used on Windows images services: - postgresql96 init: - - SET PATH=%POSTGRES_PATH%\bin;%PATH% + - set PATH=%POSTGRES_PATH%\bin;%PATH% + - set PATH=C:\go118\bin;%PATH% install: - set Path=C:\mingw-w64\x86_64-8.1.0-posix-seh-rt_v6-rev0\mingw64\bin;%Path% @@ -53,7 +55,7 @@ before_test: test_script: # test back-end - - go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.42.1 + - go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.45.2 - '%GOPATH%\bin\golangci-lint.exe run --verbose' - ps: >- if($env:APPVEYOR_SCHEDULED_BUILD -eq 'true') { diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 37375808..3a4602b8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,10 +1,13 @@ version: 2 updates: +- package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" - package-ecosystem: gomod directory: "/" schedule: - interval: daily - time: "19:30" + interval: "weekly" open-pull-requests-limit: 10 - package-ecosystem: npm directory: "/web" @@ -12,4 +15,3 @@ updates: interval: "weekly" # deprecated for now as it requires a more in-depth overhaul open-pull-requests-limit: 0 - diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 08eb5e8c..8307ca05 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -5,8 +5,11 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: golangci-lint - uses: golangci/golangci-lint-action@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-go@v3 with: - version: v1.42.1 + go-version: '1.18.x' + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: v1.45.2 diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 45edfaf2..1a4044d2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,7 +1,7 @@ on: [push, pull_request] name: CI env: - GO_VERSION: 1.17.x + GO_VERSION: 1.18.x jobs: backend-psql: name: GoCryptoTrader back-end with PSQL diff --git a/.golangci.yml b/.golangci.yml index 732bd8c0..59392aaf 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,11 +1,12 @@ run: - timeout: 6m + timeout: 8m issues-exit-code: 1 tests: true skip-dirs: - vendor - web/ - testdata + - database/models/ linters: disable-all: true @@ -24,12 +25,17 @@ linters: # disabled by default linters - asciicheck + - bidichk - bodyclose + - containedctx +# - contextcheck # - cyclop + - decorder - depguard - dogsled # - dupl - durationcheck + - errchkjson - errname # - errorlint # - exhaustive @@ -58,10 +64,13 @@ linters: - gomodguard - goprintffuncname - gosec + - grouper - ifshort # - importas # - interfacer // deprecated by its owner +# - ireturn # - lll +# - maintidx - makezero # - maligned - misspell @@ -72,7 +81,7 @@ linters: - noctx - nolintlint # - paralleltest -# - prealloc + - prealloc - predeclared # - promlinter - revive diff --git a/Dockerfile b/Dockerfile index 9687cb5b..d06c22e3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.17 as build +FROM golang:1.18 as build WORKDIR /go/src/github.com/thrasher-corp/gocryptotrader COPY . . RUN GO111MODULE=on go mod vendor diff --git a/Makefile b/Makefile index 839919bd..c17e1b3c 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ LDFLAGS = -ldflags "-w -s" GCTPKG = github.com/thrasher-corp/gocryptotrader -LINTPKG = github.com/golangci/golangci-lint/cmd/golangci-lint@v1.42.1 +LINTPKG = github.com/golangci/golangci-lint/cmd/golangci-lint@v1.45.2 LINTBIN = $(GOPATH)/bin/golangci-lint GCTLISTENPORT=9050 GCTPROFILERLISTENPORT=8085 diff --git a/backtester/backtest/backtest.go b/backtester/backtest/backtest.go index 1f9bdae9..fd6f79d4 100644 --- a/backtester/backtest/backtest.go +++ b/backtester/backtest/backtest.go @@ -648,7 +648,7 @@ func (bt *BackTest) loadData(cfg *config.Config, exch gctexchange.IBotExchange, if cfg.DataSettings.DatabaseData.Path == "" { cfg.DataSettings.DatabaseData.Path = filepath.Join(gctcommon.GetDefaultDataDir(runtime.GOOS), "database") } - gctdatabase.DB.DataPath = filepath.Join(cfg.DataSettings.DatabaseData.Path) + gctdatabase.DB.DataPath = cfg.DataSettings.DatabaseData.Path err = gctdatabase.DB.SetConfig(&cfg.DataSettings.DatabaseData.Config) if err != nil { return nil, err diff --git a/backtester/config/config.go b/backtester/config/config.go index 4236ceff..c42b5118 100644 --- a/backtester/config/config.go +++ b/backtester/config/config.go @@ -4,7 +4,7 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "os" "strings" "github.com/shopspring/decimal" @@ -21,7 +21,7 @@ func ReadConfigFromFile(path string) (*Config, error) { return nil, errors.New("file not found") } - fileData, err := ioutil.ReadFile(path) + fileData, err := os.ReadFile(path) if err != nil { return nil, err } diff --git a/backtester/config/config_test.go b/backtester/config/config_test.go index 941aeca1..e4f27bf3 100644 --- a/backtester/config/config_test.go +++ b/backtester/config/config_test.go @@ -13,6 +13,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/top2bottom2" + "github.com/thrasher-corp/gocryptotrader/common/file" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/database" "github.com/thrasher-corp/gocryptotrader/database/drivers" @@ -210,7 +211,7 @@ func TestGenerateConfigForDCAAPICandles(t *testing.T) { if err != nil { t.Fatal(err) } - err = ioutil.WriteFile(filepath.Join(p, "examples", "dca-api-candles.strat"), result, 0770) + err = os.WriteFile(filepath.Join(p, "examples", "dca-api-candles.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -288,7 +289,7 @@ func TestGenerateConfigForDCAAPICandlesExchangeLevelFunding(t *testing.T) { if err != nil { t.Fatal(err) } - err = ioutil.WriteFile(filepath.Join(p, "examples", "dca-api-candles-exchange-level-funding.strat"), result, 0770) + err = os.WriteFile(filepath.Join(p, "examples", "dca-api-candles-exchange-level-funding.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -356,7 +357,7 @@ func TestGenerateConfigForDCAAPITrades(t *testing.T) { if err != nil { t.Fatal(err) } - err = ioutil.WriteFile(filepath.Join(p, "examples", "dca-api-trades.strat"), result, 0770) + err = os.WriteFile(filepath.Join(p, "examples", "dca-api-trades.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -429,7 +430,7 @@ func TestGenerateConfigForDCAAPICandlesMultipleCurrencies(t *testing.T) { if err != nil { t.Fatal(err) } - err = ioutil.WriteFile(filepath.Join(p, "examples", "dca-api-candles-multiple-currencies.strat"), result, 0770) + err = os.WriteFile(filepath.Join(p, "examples", "dca-api-candles-multiple-currencies.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -503,7 +504,7 @@ func TestGenerateConfigForDCAAPICandlesSimultaneousProcessing(t *testing.T) { if err != nil { t.Fatal(err) } - err = ioutil.WriteFile(filepath.Join(p, "examples", "dca-api-candles-simultaneous-processing.strat"), result, 0770) + err = os.WriteFile(filepath.Join(p, "examples", "dca-api-candles-simultaneous-processing.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -566,7 +567,7 @@ func TestGenerateConfigForDCALiveCandles(t *testing.T) { if err != nil { t.Fatal(err) } - err = ioutil.WriteFile(filepath.Join(p, "examples", "dca-candles-live.strat"), result, 0770) + err = os.WriteFile(filepath.Join(p, "examples", "dca-candles-live.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -645,7 +646,7 @@ func TestGenerateConfigForRSIAPICustomSettings(t *testing.T) { if err != nil { t.Fatal(err) } - err = ioutil.WriteFile(filepath.Join(p, "examples", "rsi-api-candles.strat"), result, 0770) + err = os.WriteFile(filepath.Join(p, "examples", "rsi-api-candles.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -704,7 +705,7 @@ func TestGenerateConfigForDCACSVCandles(t *testing.T) { if err != nil { t.Fatal(err) } - err = ioutil.WriteFile(filepath.Join(p, "examples", "dca-csv-candles.strat"), result, 0770) + err = os.WriteFile(filepath.Join(p, "examples", "dca-csv-candles.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -759,7 +760,7 @@ func TestGenerateConfigForDCACSVTrades(t *testing.T) { if err != nil { t.Fatal(err) } - err = ioutil.WriteFile(filepath.Join(p, "examples", "dca-csv-trades.strat"), result, 0770) + err = os.WriteFile(filepath.Join(p, "examples", "dca-csv-trades.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -827,7 +828,7 @@ func TestGenerateConfigForDCADatabaseCandles(t *testing.T) { if err != nil { t.Fatal(err) } - err = ioutil.WriteFile(filepath.Join(p, "examples", "dca-database-candles.strat"), result, 0770) + err = os.WriteFile(filepath.Join(p, "examples", "dca-database-candles.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } @@ -956,7 +957,7 @@ func TestGenerateConfigForTop2Bottom2(t *testing.T) { if err != nil { t.Fatal(err) } - err = ioutil.WriteFile(filepath.Join(p, "examples", "t2b2-api-candles-exchange-funding.strat"), result, 0770) + err = os.WriteFile(filepath.Join(p, "examples", "t2b2-api-candles-exchange-funding.strat"), result, file.DefaultPermissionOctal) if err != nil { t.Error(err) } diff --git a/backtester/config/configbuilder/main.go b/backtester/config/configbuilder/main.go index 10016732..226e751c 100644 --- a/backtester/config/configbuilder/main.go +++ b/backtester/config/configbuilder/main.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" "log" "os" "path/filepath" @@ -19,6 +18,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/backtester/config" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies" gctcommon "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/file" "github.com/thrasher-corp/gocryptotrader/database" dbPSQL "github.com/thrasher-corp/gocryptotrader/database/drivers/postgres" dbsqlite3 "github.com/thrasher-corp/gocryptotrader/database/drivers/sqlite3" @@ -141,7 +141,7 @@ func main() { if path == "" { path = wd } - err = ioutil.WriteFile(path, resp, 0770) + err = os.WriteFile(path, resp, file.DefaultPermissionOctal) if err != nil { log.Fatal(err) } @@ -235,10 +235,10 @@ func parseExchangeSettings(reader *bufio.Reader, cfg *config.Config) error { func parseStrategySettings(cfg *config.Config, reader *bufio.Reader) error { fmt.Println("Firstly, please select which strategy you wish to use") strats := strategies.GetStrategies() - var strategiesToUse []string + strategiesToUse := make([]string, len(strats)) for i := range strats { fmt.Printf("%v. %s\n", i+1, strats[i].Name()) - strategiesToUse = append(strategiesToUse, strats[i].Name()) + strategiesToUse[i] = strats[i].Name() } var err error cfg.StrategySettings.Name, err = parseStratName(quickParse(reader), strategiesToUse) diff --git a/backtester/data/kline/kline.go b/backtester/data/kline/kline.go index 9adcebc1..e4e3234a 100644 --- a/backtester/data/kline/kline.go +++ b/backtester/data/kline/kline.go @@ -58,7 +58,7 @@ func (d *DataFromKline) AppendResults(ki *gctkline.Item) { if d.addedTimes == nil { d.addedTimes = make(map[time.Time]bool) } - var klineData []common.DataEventHandler + var gctCandles []gctkline.Candle for i := range ki.Candles { if _, ok := d.addedTimes[ki.Candles[i].Time]; !ok { @@ -66,10 +66,11 @@ func (d *DataFromKline) AppendResults(ki *gctkline.Item) { d.addedTimes[ki.Candles[i].Time] = true } } - var candleTimes []time.Time + klineData := make([]common.DataEventHandler, len(gctCandles)) + candleTimes := make([]time.Time, len(gctCandles)) for i := range gctCandles { - klineData = append(klineData, &kline.Kline{ + klineData[i] = &kline.Kline{ Base: event.Base{ Offset: int64(i + 1), Exchange: ki.Exchange, @@ -84,8 +85,8 @@ func (d *DataFromKline) AppendResults(ki *gctkline.Item) { Close: decimal.NewFromFloat(gctCandles[i].Close), Volume: decimal.NewFromFloat(gctCandles[i].Volume), ValidationIssues: gctCandles[i].ValidationIssues, - }) - candleTimes = append(candleTimes, gctCandles[i].Time) + } + candleTimes[i] = gctCandles[i].Time } for i := range d.RangeHolder.Ranges { for j := range d.RangeHolder.Ranges[i].Intervals { diff --git a/backtester/data/kline/kline_test.go b/backtester/data/kline/kline_test.go index 33c0066a..4aae0887 100644 --- a/backtester/data/kline/kline_test.go +++ b/backtester/data/kline/kline_test.go @@ -135,8 +135,7 @@ func TestStreamOpen(t *testing.T) { a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) d := DataFromKline{} - bad := d.StreamOpen() - if len(bad) > 0 { + if bad := d.StreamOpen(); len(bad) > 0 { t.Error("expected no stream") } d.SetStream([]common.DataEventHandler{ @@ -156,8 +155,7 @@ func TestStreamOpen(t *testing.T) { }, }) d.Next() - open := d.StreamOpen() - if len(open) == 0 { + if open := d.StreamOpen(); len(open) == 0 { t.Error("expected open") } } @@ -168,8 +166,7 @@ func TestStreamVolume(t *testing.T) { a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) d := DataFromKline{} - bad := d.StreamVol() - if len(bad) > 0 { + if bad := d.StreamVol(); len(bad) > 0 { t.Error("expected no stream") } d.SetStream([]common.DataEventHandler{ @@ -189,8 +186,7 @@ func TestStreamVolume(t *testing.T) { }, }) d.Next() - open := d.StreamVol() - if len(open) == 0 { + if open := d.StreamVol(); len(open) == 0 { t.Error("expected volume") } } @@ -201,8 +197,7 @@ func TestStreamClose(t *testing.T) { a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) d := DataFromKline{} - bad := d.StreamClose() - if len(bad) > 0 { + if bad := d.StreamClose(); len(bad) > 0 { t.Error("expected no stream") } d.SetStream([]common.DataEventHandler{ @@ -222,8 +217,7 @@ func TestStreamClose(t *testing.T) { }, }) d.Next() - open := d.StreamClose() - if len(open) == 0 { + if open := d.StreamClose(); len(open) == 0 { t.Error("expected close") } } @@ -234,8 +228,7 @@ func TestStreamHigh(t *testing.T) { a := asset.Spot p := currency.NewPair(currency.BTC, currency.USDT) d := DataFromKline{} - bad := d.StreamHigh() - if len(bad) > 0 { + if bad := d.StreamHigh(); len(bad) > 0 { t.Error("expected no stream") } d.SetStream([]common.DataEventHandler{ @@ -255,8 +248,7 @@ func TestStreamHigh(t *testing.T) { }, }) d.Next() - open := d.StreamHigh() - if len(open) == 0 { + if open := d.StreamHigh(); len(open) == 0 { t.Error("expected high") } } @@ -269,8 +261,7 @@ func TestStreamLow(t *testing.T) { d := DataFromKline{ RangeHolder: &gctkline.IntervalRangeHolder{}, } - bad := d.StreamLow() - if len(bad) > 0 { + if bad := d.StreamLow(); len(bad) > 0 { t.Error("expected no stream") } d.SetStream([]common.DataEventHandler{ @@ -290,8 +281,7 @@ func TestStreamLow(t *testing.T) { }, }) d.Next() - open := d.StreamLow() - if len(open) == 0 { + if open := d.StreamLow(); len(open) == 0 { t.Error("expected low") } } diff --git a/backtester/eventhandlers/exchange/exchange.go b/backtester/eventhandlers/exchange/exchange.go index 877c7c32..c1b928c6 100644 --- a/backtester/eventhandlers/exchange/exchange.go +++ b/backtester/eventhandlers/exchange/exchange.go @@ -229,12 +229,11 @@ func (e *Exchange) placeOrder(ctx context.Context, price, amount decimal.Decimal return "", err } var orderID string - p, _ := price.Float64() - a, _ := amount.Float64() - fee, _ := f.ExchangeFee.Float64() + p := price.InexactFloat64() + fee := f.ExchangeFee.InexactFloat64() o := &gctorder.Submit{ Price: p, - Amount: a, + Amount: amount.InexactFloat64(), Fee: fee, Exchange: f.Exchange, ID: u.String(), @@ -255,11 +254,10 @@ func (e *Exchange) placeOrder(ctx context.Context, price, amount decimal.Decimal return orderID, err } } else { - rate, _ := f.Amount.Float64() submitResponse := gctorder.SubmitResponse{ IsOrderPlaced: true, OrderID: u.String(), - Rate: rate, + Rate: f.Amount.InexactFloat64(), Fee: fee, Cost: p, FullyMatched: true, diff --git a/backtester/eventhandlers/exchange/slippage/slippage.go b/backtester/eventhandlers/exchange/slippage/slippage.go index 7e094dbd..efed0e02 100644 --- a/backtester/eventhandlers/exchange/slippage/slippage.go +++ b/backtester/eventhandlers/exchange/slippage/slippage.go @@ -32,11 +32,9 @@ func EstimateSlippagePercentage(maximumSlippageRate, minimumSlippageRate decimal // CalculateSlippageByOrderbook will analyse a provided orderbook and return the result of attempting to // place the order on there func CalculateSlippageByOrderbook(ob *orderbook.Base, side gctorder.Side, amountOfFunds, feeRate decimal.Decimal) (price, amount decimal.Decimal) { - funds, _ := amountOfFunds.Float64() - fee, _ := feeRate.Float64() - result := ob.SimulateOrder(funds, side == gctorder.Buy) + result := ob.SimulateOrder(amountOfFunds.InexactFloat64(), side == gctorder.Buy) rate := (result.MinimumPrice - result.MaximumPrice) / result.MaximumPrice price = decimal.NewFromFloat(result.MinimumPrice * (rate + 1)) - amount = decimal.NewFromFloat(result.Amount * (1 - fee)) + amount = decimal.NewFromFloat(result.Amount * (1 - feeRate.InexactFloat64())) return } diff --git a/backtester/eventhandlers/statistics/currencystatistics.go b/backtester/eventhandlers/statistics/currencystatistics.go index 254cd73b..e593b858 100644 --- a/backtester/eventhandlers/statistics/currencystatistics.go +++ b/backtester/eventhandlers/statistics/currencystatistics.go @@ -55,10 +55,10 @@ func (c *CurrencyPairStatistic) CalculateResults(riskFreeRate decimal.Decimal) e returnsPerCandle := make([]decimal.Decimal, len(c.Events)) benchmarkRates := make([]decimal.Decimal, len(c.Events)) - var allDataEvents []common.DataEventHandler + allDataEvents := make([]common.DataEventHandler, len(c.Events)) for i := range c.Events { returnsPerCandle[i] = c.Events[i].Holdings.ChangeInTotalValuePercent - allDataEvents = append(allDataEvents, c.Events[i].DataEvent) + allDataEvents[i] = c.Events[i].DataEvent if i == 0 { continue } diff --git a/backtester/eventhandlers/strategies/rsi/rsi.go b/backtester/eventhandlers/strategies/rsi/rsi.go index 0fbb3448..159acc67 100644 --- a/backtester/eventhandlers/strategies/rsi/rsi.go +++ b/backtester/eventhandlers/strategies/rsi/rsi.go @@ -159,7 +159,7 @@ func (s *Strategy) SetDefaults() { // the decision to handle missing data occurs at the strategy level, not all strategies // may wish to modify data func (s *Strategy) massageMissingData(data []decimal.Decimal, t time.Time) ([]float64, error) { - var resp []float64 + resp := make([]float64, len(data)) var missingDataStreak int64 for i := range data { if data[i].IsZero() && i > int(s.rsiPeriod.IntPart()) { @@ -174,8 +174,7 @@ func (s *Strategy) massageMissingData(data []decimal.Decimal, t time.Time) ([]fl t.Format(gctcommon.SimpleTimeFormat), base.ErrTooMuchBadData) } - d, _ := data[i].Float64() - resp = append(resp, d) + resp[i] = data[i].InexactFloat64() } return resp, nil } diff --git a/backtester/eventhandlers/strategies/strategies_test.go b/backtester/eventhandlers/strategies/strategies_test.go index 0259143c..4fd37049 100644 --- a/backtester/eventhandlers/strategies/strategies_test.go +++ b/backtester/eventhandlers/strategies/strategies_test.go @@ -11,8 +11,7 @@ import ( func TestGetStrategies(t *testing.T) { t.Parallel() - resp := GetStrategies() - if len(resp) < 2 { + if resp := GetStrategies(); len(resp) < 2 { t.Error("expected at least 2 strategies to be loaded") } } diff --git a/backtester/eventhandlers/strategies/top2bottom2/top2bottom2.go b/backtester/eventhandlers/strategies/top2bottom2/top2bottom2.go index 746874be..5b90ade1 100644 --- a/backtester/eventhandlers/strategies/top2bottom2/top2bottom2.go +++ b/backtester/eventhandlers/strategies/top2bottom2/top2bottom2.go @@ -92,7 +92,7 @@ func (s *Strategy) OnSimultaneousSignals(d []data.Handler, f funding.IFundTransf if len(d) < 4 { return nil, errStrategyCurrencyRequirements } - var mfiFundEvents []mfiFundEvent + mfiFundEvents := make([]mfiFundEvent, 0, len(d)) var resp []signal.Event for i := range d { if d == nil { @@ -233,7 +233,7 @@ func (s *Strategy) SetDefaults() { // the decision to handle missing data occurs at the strategy level, not all strategies // may wish to modify data func (s *Strategy) massageMissingData(data []decimal.Decimal, t time.Time) ([]float64, error) { - var resp []float64 + resp := make([]float64, len(data)) var missingDataStreak int64 for i := range data { if data[i].IsZero() && i > int(s.mfiPeriod.IntPart()) { @@ -248,8 +248,7 @@ func (s *Strategy) massageMissingData(data []decimal.Decimal, t time.Time) ([]fl t.Format(gctcommon.SimpleTimeFormat), base.ErrTooMuchBadData) } - d, _ := data[i].Float64() - resp = append(resp, d) + resp[i] = data[i].InexactFloat64() } return resp, nil } diff --git a/backtester/eventtypes/event/event_test.go b/backtester/eventtypes/event/event_test.go index 70fdf984..a3369ea2 100644 --- a/backtester/eventtypes/event/event_test.go +++ b/backtester/eventtypes/event/event_test.go @@ -65,8 +65,7 @@ func TestEvent_GetTime(t *testing.T) { func TestEvent_IsEvent(t *testing.T) { t.Parallel() e := &Base{} - y := e.IsEvent() - if !y { + if y := e.IsEvent(); !y { t.Error("it is an event") } } diff --git a/backtester/funding/funding.go b/backtester/funding/funding.go index 5aa25d5a..c2399620 100644 --- a/backtester/funding/funding.go +++ b/backtester/funding/funding.go @@ -199,7 +199,7 @@ func (f *FundManager) GenerateReport() *Report { UsingExchangeLevelFunding: f.usingExchangeLevelFunding, DisableUSDTracking: f.disableUSDTracking, } - var items []ReportItem + items := make([]ReportItem, len(f.items)) for i := range f.items { item := ReportItem{ Exchange: f.items[i].exchange, @@ -243,7 +243,7 @@ func (f *FundManager) GenerateReport() *Report { item.PairedWith = f.items[i].pairedWith.currency } - items = append(items, item) + items[i] = item } report.Items = items return &report diff --git a/backtester/report/report.go b/backtester/report/report.go index 3be4ab40..fa65f43a 100644 --- a/backtester/report/report.go +++ b/backtester/report/report.go @@ -46,9 +46,7 @@ func (d *Data) GenerateReport() error { d.HoldingsOverTimeChart = d.CreateHoldingsOverTimeChart() tmpl := template.Must( - template.ParseFiles( - filepath.Join(d.TemplatePath), - ), + template.ParseFiles(d.TemplatePath), ) var nickName string if d.Config.Nickname != "" { @@ -89,31 +87,33 @@ func (d *Data) CreateUSDTotalsChart() []TotalsChart { if d.Statistics.FundingStatistics == nil || d.Statistics.FundingStatistics.Report.DisableUSDTracking { return nil } - var response []TotalsChart - var usdTotalChartPlot []ChartPlot + + usdTotalChartPlot := make([]ChartPlot, len(d.Statistics.FundingStatistics.TotalUSDStatistics.HoldingValues)) for i := range d.Statistics.FundingStatistics.TotalUSDStatistics.HoldingValues { - usdTotalChartPlot = append(usdTotalChartPlot, ChartPlot{ + usdTotalChartPlot[i] = ChartPlot{ Value: d.Statistics.FundingStatistics.TotalUSDStatistics.HoldingValues[i].Value.InexactFloat64(), UnixMilli: d.Statistics.FundingStatistics.TotalUSDStatistics.HoldingValues[i].Time.UTC().UnixMilli(), - }) + } } - response = append(response, TotalsChart{ + + response := make([]TotalsChart, len(d.Statistics.FundingStatistics.Items)+1) + response[0] = TotalsChart{ Name: "Total USD value", DataPoints: usdTotalChartPlot, - }) + } for i := range d.Statistics.FundingStatistics.Items { - var plots []ChartPlot + plots := make([]ChartPlot, len(d.Statistics.FundingStatistics.Items[i].ReportItem.Snapshots)) for j := range d.Statistics.FundingStatistics.Items[i].ReportItem.Snapshots { - plots = append(plots, ChartPlot{ + plots[j] = ChartPlot{ Value: d.Statistics.FundingStatistics.Items[i].ReportItem.Snapshots[j].USDValue.InexactFloat64(), UnixMilli: d.Statistics.FundingStatistics.Items[i].ReportItem.Snapshots[j].Time.UTC().UnixMilli(), - }) + } } - response = append(response, TotalsChart{ + response[i+1] = TotalsChart{ Name: fmt.Sprintf("%v %v %v USD value", d.Statistics.FundingStatistics.Items[i].ReportItem.Exchange, d.Statistics.FundingStatistics.Items[i].ReportItem.Asset, d.Statistics.FundingStatistics.Items[i].ReportItem.Currency), DataPoints: plots, - }) + } } return response @@ -125,19 +125,19 @@ func (d *Data) CreateHoldingsOverTimeChart() []TotalsChart { if d.Statistics.FundingStatistics == nil { return nil } - var response []TotalsChart + response := make([]TotalsChart, len(d.Statistics.FundingStatistics.Items)) for i := range d.Statistics.FundingStatistics.Items { - var plots []ChartPlot + plots := make([]ChartPlot, len(d.Statistics.FundingStatistics.Items[i].ReportItem.Snapshots)) for j := range d.Statistics.FundingStatistics.Items[i].ReportItem.Snapshots { - plots = append(plots, ChartPlot{ + plots[j] = ChartPlot{ Value: d.Statistics.FundingStatistics.Items[i].ReportItem.Snapshots[j].Available.InexactFloat64(), UnixMilli: d.Statistics.FundingStatistics.Items[i].ReportItem.Snapshots[j].Time.UTC().UnixMilli(), - }) + } } - response = append(response, TotalsChart{ + response[i] = TotalsChart{ Name: fmt.Sprintf("%v %v %v holdings", d.Statistics.FundingStatistics.Items[i].ReportItem.Exchange, d.Statistics.FundingStatistics.Items[i].ReportItem.Asset, d.Statistics.FundingStatistics.Items[i].ReportItem.Currency), DataPoints: plots, - }) + } } return response @@ -177,7 +177,7 @@ func (d *Data) enhanceCandles() error { Asset: lookup.Asset, Pair: lookup.Pair, Interval: lookup.Interval, - Watermark: fmt.Sprintf("%v - %v - %v", strings.Title(lookup.Exchange), lookup.Asset.String(), strings.ToUpper(lookup.Pair.String())), + Watermark: fmt.Sprintf("%s - %s - %s", strings.Title(lookup.Exchange), lookup.Asset.String(), lookup.Pair.Upper()), // nolint // Title usage } statsForCandles := diff --git a/backtester/report/report_test.go b/backtester/report/report_test.go index 998a08ea..407b1f34 100644 --- a/backtester/report/report_test.go +++ b/backtester/report/report_test.go @@ -40,7 +40,7 @@ func TestGenerateReport(t *testing.T) { d := Data{ Config: &config.Config{}, OutputPath: filepath.Join("..", "results"), - TemplatePath: filepath.Join("tpl.gohtml"), + TemplatePath: "tpl.gohtml", OriginalCandles: []*gctkline.Item{ { Candles: []gctkline.Candle{ diff --git a/cmd/apichecker/apicheck.go b/cmd/apichecker/apicheck.go index f34dccf2..59de6383 100644 --- a/cmd/apichecker/apicheck.go +++ b/cmd/apichecker/apicheck.go @@ -6,7 +6,7 @@ import ( "errors" "flag" "fmt" - "io/ioutil" + "io" "net/http" "net/url" "os" @@ -18,6 +18,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" + gctfile "github.com/thrasher-corp/gocryptotrader/common/file" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/log" @@ -304,9 +305,9 @@ func checkExistingExchanges(exchName string) bool { // checkMissingExchanges checks if any supported exchanges are missing api checker functionality func checkMissingExchanges() []string { - var tempArray []string + tempArray := make([]string, len(usageData.Exchanges)) for x := range usageData.Exchanges { - tempArray = append(tempArray, usageData.Exchanges[x].Name) + tempArray[x] = usageData.Exchanges[x].Name } supportedExchs := exchange.Exchanges for z := 0; z < len(supportedExchs); { @@ -322,7 +323,7 @@ func checkMissingExchanges() []string { // readFileData reads the file data from the given json file func readFileData(fileName string) (Config, error) { var c Config - data, err := ioutil.ReadFile(fileName) + data, err := os.ReadFile(fileName) if err != nil { return c, err } @@ -448,7 +449,7 @@ func checkUpdates(fileName string) error { unsup := checkMissingExchanges() log.Warnf(log.Global, "The following exchanges are not supported by apichecker: %v\n", unsup) log.Debugf(log.Global, "Saving the updates to the following file: %s\n", fileName) - return ioutil.WriteFile(fileName, file, 0770) + return os.WriteFile(fileName, file, gctfile.DefaultPermissionOctal) } // checkChangeLog checks the exchanges which support changelog updates.json @@ -559,9 +560,9 @@ func addExch(exchName, checkType string, data interface{}, isUpdate bool) error return err } } - return ioutil.WriteFile(jsonFile, file, 0770) + return os.WriteFile(jsonFile, file, gctfile.DefaultPermissionOctal) } - return ioutil.WriteFile(testJSONFile, file, 0770) + return os.WriteFile(testJSONFile, file, gctfile.DefaultPermissionOctal) } // fillData fills exchange data based on the given checkType @@ -749,7 +750,7 @@ func htmlScrapeHitBTC(htmlData *HTMLScrapingData) ([]string, error) { } defer temp.Body.Close() - a, err := ioutil.ReadAll(temp.Body) + a, err := io.ReadAll(temp.Body) if err != nil { return nil, err } @@ -784,7 +785,7 @@ func htmlScrapeBTCMarkets(htmlData *HTMLScrapingData) ([]string, error) { return nil, err } defer temp.Body.Close() - tempData, err := ioutil.ReadAll(temp.Body) + tempData, err := io.ReadAll(temp.Body) if err != nil { return resp, err } @@ -862,7 +863,7 @@ func htmlScrapeANX(htmlData *HTMLScrapingData) ([]string, error) { } defer temp.Body.Close() - a, err := ioutil.ReadAll(temp.Body) + a, err := io.ReadAll(temp.Body) if err != nil { return nil, err } @@ -901,7 +902,7 @@ func htmlScrapeExmo(htmlData *HTMLScrapingData) ([]string, error) { } defer httpResp.Body.Close() - a, err := ioutil.ReadAll(httpResp.Body) + a, err := io.ReadAll(httpResp.Body) if err != nil { return nil, err } @@ -1010,7 +1011,7 @@ func htmlScrapeBitstamp(htmlData *HTMLScrapingData) ([]string, error) { } defer temp.Body.Close() - a, err := ioutil.ReadAll(temp.Body) + a, err := io.ReadAll(temp.Body) if err != nil { return nil, err } @@ -1145,7 +1146,7 @@ func htmlScrapeLocalBitcoins(htmlData *HTMLScrapingData) ([]string, error) { } defer temp.Body.Close() - a, err := ioutil.ReadAll(temp.Body) + a, err := io.ReadAll(temp.Body) if err != nil { return nil, err } @@ -1284,7 +1285,7 @@ func updateFile(name string) error { if err != nil { return err } - return ioutil.WriteFile(name, file, 0770) + return os.WriteFile(name, file, gctfile.DefaultPermissionOctal) } // SendGetReq sends get req @@ -1690,7 +1691,7 @@ func htmlScrapeBitfinex(htmlData *HTMLScrapingData) ([]string, error) { } defer temp.Body.Close() - a, err := ioutil.ReadAll(temp.Body) + a, err := io.ReadAll(temp.Body) if err != nil { return nil, err } @@ -1775,7 +1776,7 @@ func sendHTTPGetRequest(path string, headers map[string]string) (*http.Response, req, err := http.NewRequestWithContext(context.TODO(), http.MethodGet, path, - nil) + http.NoBody) if err != nil { return nil, err } diff --git a/cmd/apichecker/apicheck_test.go b/cmd/apichecker/apicheck_test.go index d4811737..87990a5d 100644 --- a/cmd/apichecker/apicheck_test.go +++ b/cmd/apichecker/apicheck_test.go @@ -2,12 +2,12 @@ package main import ( "encoding/json" - "io/ioutil" "os" "reflect" "testing" "github.com/thrasher-corp/gocryptotrader/common/convert" + gctfile "github.com/thrasher-corp/gocryptotrader/common/file" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -86,7 +86,7 @@ func removeTestFileVars() error { if err != nil { return err } - return ioutil.WriteFile(testJSONFile, file, 0770) + return os.WriteFile(testJSONFile, file, gctfile.DefaultPermissionOctal) } func canTestTrello() bool { @@ -439,8 +439,7 @@ func TestUpdate(t *testing.T) { func TestCheckMissingExchanges(t *testing.T) { t.Parallel() - a := checkMissingExchanges() - if len(a) > len(exchange.Exchanges) { + if a := checkMissingExchanges(); len(a) > len(exchange.Exchanges) { t.Fatal("invalid response") } } diff --git a/cmd/config/config.go b/cmd/config/config.go index 640caf64..b7a90cb8 100644 --- a/cmd/config/config.go +++ b/cmd/config/config.go @@ -3,8 +3,8 @@ package main import ( "encoding/json" "flag" - "io/ioutil" "log" + "os" "github.com/thrasher-corp/gocryptotrader/common/file" "github.com/thrasher-corp/gocryptotrader/config" @@ -38,7 +38,7 @@ func main() { key = string(result) } - fileData, err := ioutil.ReadFile(inFile) + fileData, err := os.ReadFile(inFile) if err != nil { log.Fatalf("Unable to read input file %s. Error: %s.", inFile, err) } diff --git a/cmd/config_builder/builder.go b/cmd/config_builder/builder.go index 717c5681..42a312c1 100644 --- a/cmd/config_builder/builder.go +++ b/cmd/config_builder/builder.go @@ -30,8 +30,8 @@ func main() { wg.Wait() log.Println("Done.") - var cfgs []config.Exchange exchanges := engine.Bot.GetExchanges() + cfgs := make([]config.Exchange, 0, len(exchanges)) for x := range exchanges { var cfg *config.Exchange cfg, err = exchanges[x].GetDefaultConfig() diff --git a/cmd/dbseed/exchange.go b/cmd/dbseed/exchange.go index aa94cccf..dbaa1002 100644 --- a/cmd/dbseed/exchange.go +++ b/cmd/dbseed/exchange.go @@ -51,11 +51,11 @@ func seedExchangeFromDefaultList(c *cli.Context) error { if err != nil { return err } - var allExchanges []exchangeDB.Details + allExchanges := make([]exchangeDB.Details, len(exchange.Exchanges)) for x := range exchange.Exchanges { - allExchanges = append(allExchanges, exchangeDB.Details{ + allExchanges[x] = exchangeDB.Details{ Name: exchange.Exchanges[x], - }) + } } err = exchangeDB.InsertMany(allExchanges) if err != nil { diff --git a/cmd/dbseed/main.go b/cmd/dbseed/main.go index c232ba08..802422f4 100644 --- a/cmd/dbseed/main.go +++ b/cmd/dbseed/main.go @@ -4,7 +4,6 @@ import ( "fmt" "log" "os" - "path/filepath" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/core" @@ -44,7 +43,7 @@ func main() { workingDir, err = os.Getwd() if err != nil { log.Println("error getting current working path") - workingDir = filepath.Join(".") + workingDir = "." } fmt.Println("GoCryptoTrader database seeding tool") diff --git a/cmd/documentation/documentation.go b/cmd/documentation/documentation.go index 26be68a4..aee1d889 100644 --- a/cmd/documentation/documentation.go +++ b/cmd/documentation/documentation.go @@ -7,7 +7,6 @@ import ( "flag" "fmt" "html/template" - "io/ioutil" "log" "net/http" "os" @@ -329,7 +328,7 @@ func GetConfiguration() (Config, error) { configFilePath := filepath.Join(toolDir, "config.json") if file.Exists(configFilePath) { - config, err := ioutil.ReadFile(configFilePath) + config, err := os.ReadFile(configFilePath) if err != nil { return c, err } @@ -360,7 +359,7 @@ func GetConfiguration() (Config, error) { return c, err } - if err := ioutil.WriteFile(configFilePath, data, 0770); err != nil { + if err := os.WriteFile(configFilePath, data, file.DefaultPermissionOctal); err != nil { return c, err } @@ -485,7 +484,7 @@ func GetPackageName(name string, capital bool) string { i = len(newStrings) - 1 } if capital { - return strings.Replace(strings.Title(newStrings[i]), "_", " ", -1) + return strings.Replace(strings.Title(newStrings[i]), "_", " ", -1) // nolint // ignore staticcheck strings.Title warning } return newStrings[i] } @@ -540,7 +539,7 @@ func UpdateDocumentation(details DocumentationDetails) { continue } if name == engineFolder { - d, err := ioutil.ReadDir(details.Directories[i]) + d, err := os.ReadDir(details.Directories[i]) if err != nil { fmt.Println("Excluding file:", err) } diff --git a/cmd/exchange_template/exchange_template.go b/cmd/exchange_template/exchange_template.go index 1edb0b02..8fbefa40 100644 --- a/cmd/exchange_template/exchange_template.go +++ b/cmd/exchange_template/exchange_template.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/thrasher-corp/gocryptotrader/common" + "github.com/thrasher-corp/gocryptotrader/common/file" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/core" "github.com/thrasher-corp/gocryptotrader/currency" @@ -138,14 +139,14 @@ func makeExchange(exchangeDirectory string, configTestFile *config.Config, exch if !os.IsNotExist(err) { return nil, errors.New("directory already exists") } - err = os.MkdirAll(exchangeDirectory, 0770) + err = os.MkdirAll(exchangeDirectory, file.DefaultPermissionOctal) if err != nil { return nil, err } fmt.Printf("Output directory: %s\n", exchangeDirectory) - exch.CapitalName = strings.Title(exch.Name) + exch.CapitalName = strings.Title(exch.Name) // nolint:staticcheck // Ignore Title usage warning exch.Variable = exch.Name[0:2] newExchConfig := &config.Exchange{} newExchConfig.Name = exch.CapitalName @@ -214,7 +215,7 @@ func makeExchange(exchangeDirectory string, configTestFile *config.Config, exch outputFile := filepath.Join(exchangeDirectory, filename) newFile(outputFile) var f *os.File - f, err = os.OpenFile(outputFile, os.O_WRONLY, 0770) + f, err = os.OpenFile(outputFile, os.O_WRONLY, file.DefaultPermissionOctal) if err != nil { return nil, err } diff --git a/cmd/exchange_template/wrapper_file.tmpl b/cmd/exchange_template/wrapper_file.tmpl index b5aeb733..546ddfc4 100644 --- a/cmd/exchange_template/wrapper_file.tmpl +++ b/cmd/exchange_template/wrapper_file.tmpl @@ -334,18 +334,20 @@ func ({{.Variable}} *{{.CapitalName}}) UpdateOrderbook(ctx context.Context, pair return book, err } + book.Bids = make([]orderbook.Item, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[x] = orderbook.Item{ Amount: orderbookNew.Bids[x].Quantity, Price: orderbookNew.Bids[x].Price, - }) + } } + book.Asks = make([]orderbook.Item, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[x] = orderbook.Item{ Amount: orderBookNew.Asks[x].Quantity, Price: orderBookNew.Asks[x].Price, - }) + } } */ diff --git a/cmd/exchange_wrapper_issues/main.go b/cmd/exchange_wrapper_issues/main.go index fad6a103..ab1e5089 100644 --- a/cmd/exchange_wrapper_issues/main.go +++ b/cmd/exchange_wrapper_issues/main.go @@ -6,7 +6,6 @@ import ( "errors" "flag" "fmt" - "io/ioutil" "log" "os" "path/filepath" @@ -283,7 +282,7 @@ func parseOrderType(orderType string) order.Type { } func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config) []ExchangeAssetPairResponses { - var response []ExchangeAssetPairResponses + response := make([]ExchangeAssetPairResponses, 0) testOrderSide := parseOrderSide(config.OrderSubmission.OrderSide) testOrderType := parseOrderType(config.OrderSubmission.OrderType) assetTypes := base.GetAssetTypes(false) @@ -835,13 +834,13 @@ func testWrappers(e exchange.IBotExchange, base *exchange.Base, config *Config) } func jsonifyInterface(params []interface{}) json.RawMessage { - response, _ := json.MarshalIndent(params, "", " ") + response, _ := json.MarshalIndent(params, "", " ") // nolint:errchkjson // TODO: ignore this for now return response } func loadConfig() (Config, error) { var config Config - keys, err := ioutil.ReadFile("wrapperconfig.json") + keys, err := os.ReadFile("wrapperconfig.json") if err != nil { return config, err } diff --git a/cmd/gctcli/commands.go b/cmd/gctcli/commands.go index caa1d192..532f7496 100644 --- a/cmd/gctcli/commands.go +++ b/cmd/gctcli/commands.go @@ -3,7 +3,7 @@ package main import ( "errors" "fmt" - "io/ioutil" + "io" "math" "os" "path/filepath" @@ -4226,7 +4226,7 @@ func gctScriptUpload(c *cli.Context) error { defer closeConn(conn, cancel) client := gctrpc.NewGoCryptoTraderClient(conn) - data, err := ioutil.ReadAll(file) + data, err := io.ReadAll(file) if err != nil { return err } diff --git a/cmd/gctcli/pair_management.go b/cmd/gctcli/pair_management.go index 7baf550f..c47baf38 100644 --- a/cmd/gctcli/pair_management.go +++ b/cmd/gctcli/pair_management.go @@ -198,7 +198,7 @@ func enableDisableExchangePair(c *cli.Context) error { pairList := strings.Split(pairs, ",") - var validPairs []*gctrpc.CurrencyPair + validPairs := make([]*gctrpc.CurrencyPair, len(pairList)) for i := range pairList { if !validPair(pairList[i]) { return errInvalidPair @@ -209,11 +209,11 @@ func enableDisableExchangePair(c *cli.Context) error { return err } - validPairs = append(validPairs, &gctrpc.CurrencyPair{ + validPairs[i] = &gctrpc.CurrencyPair{ Delimiter: p.Delimiter, Base: p.Base.String(), Quote: p.Quote.String(), - }) + } } conn, cancel, err := setupClient(c) diff --git a/cmd/gen_sqlboiler_config/main.go b/cmd/gen_sqlboiler_config/main.go index ac62e31f..dbec3632 100644 --- a/cmd/gen_sqlboiler_config/main.go +++ b/cmd/gen_sqlboiler_config/main.go @@ -4,10 +4,10 @@ import ( "encoding/json" "flag" "fmt" - "io/ioutil" "os" "path/filepath" + "github.com/thrasher-corp/gocryptotrader/common/file" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/core" "github.com/thrasher-corp/gocryptotrader/database" @@ -57,7 +57,7 @@ func main() { } path := filepath.Join(outputFolder, "sqlboiler.json") - err = ioutil.WriteFile(path, jsonOutput, 0770) + err = os.WriteFile(path, jsonOutput, file.DefaultPermissionOctal) if err != nil { fmt.Printf("Write failed: %v", err) os.Exit(1) diff --git a/common/cache/cache_test.go b/common/cache/cache_test.go index 766d77e5..1771cc27 100644 --- a/common/cache/cache_test.go +++ b/common/cache/cache_test.go @@ -2,6 +2,8 @@ package cache import ( "testing" + + "github.com/thrasher-corp/gocryptotrader/common/convert" ) func TestCache(t *testing.T) { @@ -16,12 +18,11 @@ func TestCache(t *testing.T) { if v == nil { t.Fatal("expected cache to contain \"hello\" key") } - if v.(string) != "world" { + if convert.InterfaceToStringOrZeroValue(v) != "world" { t.Fatal("expected \"hello\" key to contain value \"world\"") } - r := lruCache.Remove("hello") - if !r { + if r := lruCache.Remove("hello"); !r { t.Fatal("expected \"hello\" key to be removed from cache") } @@ -77,39 +78,39 @@ func TestAdd(t *testing.T) { if v == nil { t.Fatal("expected cache to contain \"2\" key") } - if v.(int) != 2 { + if convert.InterfaceToIntOrZeroValue(v) != 2 { t.Fatal("expected \"2\" key to contain value \"2\"") } k, v := lruCache.getNewest() - if k.(int) != 2 { + if convert.InterfaceToIntOrZeroValue(k) != 2 { t.Fatal("expected latest key to be 2") } - if v.(int) != 2 { + if convert.InterfaceToIntOrZeroValue(v) != 2 { t.Fatal("expected latest value to be 2") } lruCache.Add(3, 3) k, _ = lruCache.getNewest() - if k.(int) != 3 { + if convert.InterfaceToIntOrZeroValue(k) != 3 { t.Fatal("expected latest key to be 3") } k, _ = lruCache.getOldest() - if k.(int) != 2 { + if convert.InterfaceToIntOrZeroValue(k) != 2 { t.Fatal("expected oldest key to be 2") } k, v = lruCache.getOldest() - if k.(int) != 2 { + if convert.InterfaceToIntOrZeroValue(k) != 2 { t.Fatal("expected oldest key to be 2") } - if v.(int) != 2 { + if convert.InterfaceToIntOrZeroValue(v) != 2 { t.Fatal("expected latest value to be 2") } lruCache.Add(2, 2) k, _ = lruCache.getNewest() - if k.(int) != 2 { + if convert.InterfaceToIntOrZeroValue(k) != 2 { t.Fatal("expected latest key to be 2") } k, _ = lruCache.getOldest() - if k.(int) != 3 { + if convert.InterfaceToIntOrZeroValue(k) != 3 { t.Fatal("expected oldest key to be 3") } } diff --git a/common/cache/lru.go b/common/cache/lru.go index 06cdd8e9..d4a264bd 100644 --- a/common/cache/lru.go +++ b/common/cache/lru.go @@ -24,7 +24,9 @@ func NewLRUCache(capacity uint64) *LRU { func (l *LRU) Add(key, value interface{}) { if f, o := l.items[key]; o { l.l.MoveToFront(f) - f.Value.(*item).value = value + if v, ok := f.Value.(*item); ok { + v.value = value + } return } @@ -40,7 +42,9 @@ func (l *LRU) Add(key, value interface{}) { func (l *LRU) Get(key interface{}) interface{} { if i, f := l.items[key]; f { l.l.MoveToFront(i) - return i.Value.(*item).value + if v, ok := i.Value.(*item); ok { + return v.value + } } return nil } @@ -48,7 +52,9 @@ func (l *LRU) Get(key interface{}) interface{} { // GetOldest returns the oldest entry func (l *LRU) getOldest() (key, value interface{}) { if x := l.l.Back(); x != nil { - return x.Value.(*item).key, x.Value.(*item).value + if v, ok := x.Value.(*item); ok { + return v.key, v.value + } } return } @@ -56,7 +62,9 @@ func (l *LRU) getOldest() (key, value interface{}) { // GetNewest returns the newest entry func (l *LRU) getNewest() (key, value interface{}) { if x := l.l.Front(); x != nil { - return x.Value.(*item).key, x.Value.(*item).value + if v, ok := x.Value.(*item); ok { + return v.key, v.value + } } return } @@ -99,5 +107,7 @@ func (l *LRU) removeOldestEntry() { // removeElement element from the cache func (l *LRU) removeElement(e *list.Element) { l.l.Remove(e) - delete(l.items, e.Value.(*item).key) + if v, ok := e.Value.(*item); ok { + delete(l.items, v.key) + } } diff --git a/common/common.go b/common/common.go index 252d7665..20c633d1 100644 --- a/common/common.go +++ b/common/common.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net/http" "net/url" "os" @@ -18,6 +17,7 @@ import ( "sync" "time" + "github.com/thrasher-corp/gocryptotrader/common/file" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -257,7 +257,7 @@ func SendHTTPRequest(ctx context.Context, method, urlPath string, headers map[st } defer resp.Body.Close() - contents, err := ioutil.ReadAll(resp.Body) + contents, err := io.ReadAll(resp.Body) if verbose { log.Debugf(log.Global, "HTTP status: %s, Code: %v", @@ -348,7 +348,7 @@ func CreateDir(dir string) error { } log.Warnf(log.Global, "Directory %s does not exist.. creating.\n", dir) - return os.MkdirAll(dir, 0770) + return os.MkdirAll(dir, file.DefaultPermissionOctal) } // ChangePermission lists all the directories and files in an array @@ -357,8 +357,8 @@ func ChangePermission(directory string) error { if err != nil { return err } - if info.Mode().Perm() != 0770 { - return os.Chmod(path, 0770) + if info.Mode().Perm() != file.DefaultPermissionOctal { + return os.Chmod(path, file.DefaultPermissionOctal) } return nil }) diff --git a/common/common_test.go b/common/common_test.go index 200c107f..799614be 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -14,6 +14,8 @@ import ( "strings" "testing" "time" + + "github.com/thrasher-corp/gocryptotrader/common/file" ) func TestSendHTTPRequest(t *testing.T) { @@ -509,7 +511,7 @@ func TestChangePermission(t *testing.T) { if err == nil { t.Fatal("expected an error on non-existent path") } - err = os.Mkdir(testDir, 0777) + err = os.Mkdir(testDir, 0o777) if err != nil { t.Fatalf("Mkdir failed. Err: %v", err) } @@ -530,7 +532,7 @@ func TestChangePermission(t *testing.T) { if err == nil { t.Fatal("expected an error on non-existent path") } - err = os.Mkdir(testDir, 0777) + err = os.Mkdir(testDir, 0o777) if err != nil { t.Fatalf("Mkdir failed. Err: %v", err) } @@ -543,8 +545,8 @@ func TestChangePermission(t *testing.T) { if err != nil { t.Fatalf("os.Stat failed. Err: %v", err) } - if a.Mode().Perm() != 0770 { - t.Fatalf("expected file permissions differ. expecting 0770 got %#o", a.Mode().Perm()) + if a.Mode().Perm() != file.DefaultPermissionOctal { + t.Fatalf("expected file permissions differ. expecting file.DefaultPermissionOctal got %#o", a.Mode().Perm()) } err = os.Remove(testDir) if err != nil { diff --git a/common/convert/convert.go b/common/convert/convert.go index a4283105..82a4a37a 100644 --- a/common/convert/convert.go +++ b/common/convert/convert.go @@ -119,8 +119,7 @@ func DecimalToHumanFriendlyString(number decimal.Decimal, rounding int, decPoint neg = true } str := number.String() - rnd := strings.Split(str, ".") - if len(rnd) == 1 { + if rnd := strings.Split(str, "."); len(rnd) == 1 { rounding = 0 } else if len(rnd[1]) < rounding { rounding = len(rnd[1]) @@ -164,3 +163,27 @@ func numberToHumanFriendlyString(str string, dec int, decPoint, thousandsSep str return s } + +// InterfaceToFloat64OrZeroValue returns the type assertion value or variable zero value +func InterfaceToFloat64OrZeroValue(r interface{}) float64 { + if v, ok := r.(float64); ok { + return v + } + return 0 +} + +// InterfaceToIntOrZeroValue returns the type assertion value or variable zero value +func InterfaceToIntOrZeroValue(r interface{}) int { + if v, ok := r.(int); ok { + return v + } + return 0 +} + +// InterfaceToStringOrZeroValue returns the type assertion value or variable zero value +func InterfaceToStringOrZeroValue(r interface{}) string { + if v, ok := r.(string); ok { + return v + } + return "" +} diff --git a/common/convert/convert_test.go b/common/convert/convert_test.go index 7ef73ca7..1fdd6f1c 100644 --- a/common/convert/convert_test.go +++ b/common/convert/convert_test.go @@ -282,3 +282,36 @@ func TestNumberToHumanFriendlyString(t *testing.T) { t.Error("expected no comma") } } + +func TestInterfaceToFloat64OrZeroValue(t *testing.T) { + var x interface{} + if r := InterfaceToFloat64OrZeroValue(x); r != 0 { + t.Errorf("expected 0, got: %v", r) + } + x = float64(420) + if r := InterfaceToFloat64OrZeroValue(x); r != 420 { + t.Errorf("expected 420, got: %v", x) + } +} + +func TestInterfaceToIntOrZeroValue(t *testing.T) { + var x interface{} + if r := InterfaceToIntOrZeroValue(x); r != 0 { + t.Errorf("expected 0, got: %v", r) + } + x = int(420) + if r := InterfaceToIntOrZeroValue(x); r != 420 { + t.Errorf("expected 420, got: %v", x) + } +} + +func TestInterfaceToStringOrZeroValue(t *testing.T) { + var x interface{} + if r := InterfaceToStringOrZeroValue(x); r != "" { + t.Errorf("expected empty string, got: %v", r) + } + x = string("meow") + if r := InterfaceToStringOrZeroValue(x); r != "meow" { + t.Errorf("expected meow, got: %v", x) + } +} diff --git a/common/crypto/crypto_test.go b/common/crypto/crypto_test.go index 3931a2e6..202f5d85 100644 --- a/common/crypto/crypto_test.go +++ b/common/crypto/crypto_test.go @@ -148,7 +148,7 @@ func TestGetHMAC(t *testing.T) { if err != nil { t.Fatal(err) } - if string(sha1) != string(expectedSha1) { + if !bytes.Equal(sha1, expectedSha1) { t.Errorf("Common GetHMAC error: Expected '%x'. Actual '%x'", expectedSha1, sha1, ) @@ -157,7 +157,7 @@ func TestGetHMAC(t *testing.T) { if err != nil { t.Fatal(err) } - if string(sha256) != string(expectedsha256) { + if !bytes.Equal(sha256, expectedsha256) { t.Errorf("Common GetHMAC error: Expected '%x'. Actual '%x'", expectedsha256, sha256, ) @@ -166,7 +166,7 @@ func TestGetHMAC(t *testing.T) { if err != nil { t.Fatal(err) } - if string(sha512) != string(expectedsha512) { + if !bytes.Equal(sha512, expectedsha512) { t.Errorf("Common GetHMAC error: Expected '%x'. Actual '%x'", expectedsha512, sha512, ) @@ -175,7 +175,7 @@ func TestGetHMAC(t *testing.T) { if err != nil { t.Fatal(err) } - if string(sha512384) != string(expectedsha512384) { + if !bytes.Equal(sha512384, expectedsha512384) { t.Errorf("Common GetHMAC error: Expected '%x'. Actual '%x'", expectedsha512384, sha512384, ) @@ -184,7 +184,7 @@ func TestGetHMAC(t *testing.T) { if err != nil { t.Fatal(err) } - if string(md5) != string(expectedmd5) { + if !bytes.Equal(md5, expectedmd5) { t.Errorf("Common GetHMAC error: Expected '%x'. Actual '%x'", expectedmd5, md5, ) diff --git a/common/file/archive/zip.go b/common/file/archive/zip.go index bddd9e1b..6adc99aa 100644 --- a/common/file/archive/zip.go +++ b/common/file/archive/zip.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strings" + "github.com/thrasher-corp/gocryptotrader/common/file" "github.com/thrasher-corp/gocryptotrader/log" ) @@ -52,7 +53,7 @@ func UnZip(src, dest string) (fileList []string, err error) { continue } - err = os.MkdirAll(filepath.Dir(fPath), 0770) + err = os.MkdirAll(filepath.Dir(fPath), file.DefaultPermissionOctal) if err != nil { return } diff --git a/common/file/file.go b/common/file/file.go index 5855dee2..fd7f132d 100644 --- a/common/file/file.go +++ b/common/file/file.go @@ -6,22 +6,25 @@ import ( "errors" "fmt" "io" - "io/ioutil" + "io/fs" "os" "path/filepath" ) +// DefaultPermissionOctal is the default file and folder permission octal used throughout GCT +const DefaultPermissionOctal fs.FileMode = 0o770 + // Write writes selected data to a file or returns an error if it fails. This // func also ensures that all files are set to this permission (only rw access // for the running user and the group the user is a member of) func Write(file string, data []byte) error { basePath := filepath.Dir(file) if !Exists(basePath) { - if err := os.MkdirAll(basePath, 0770); err != nil { + if err := os.MkdirAll(basePath, DefaultPermissionOctal); err != nil { return err } } - return ioutil.WriteFile(file, data, 0770) + return os.WriteFile(file, data, DefaultPermissionOctal) } // Writer creates a writer to a file or returns an error if it fails. This @@ -30,11 +33,11 @@ func Write(file string, data []byte) error { func Writer(file string) (*os.File, error) { basePath := filepath.Dir(file) if !Exists(basePath) { - if err := os.MkdirAll(basePath, 0770); err != nil { + if err := os.MkdirAll(basePath, DefaultPermissionOctal); err != nil { return nil, err } } - return os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0770) + return os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, DefaultPermissionOctal) } // Move moves a file from a source path to a destination path @@ -59,7 +62,7 @@ func Move(sourcePath, destPath string) error { destDir := filepath.Dir(destPath) if !Exists(destDir) { - err = os.MkdirAll(destDir, 0770) + err = os.MkdirAll(destDir, DefaultPermissionOctal) if err != nil { return err } diff --git a/common/file/file_test.go b/common/file/file_test.go index ace4b74d..58a0e653 100644 --- a/common/file/file_test.go +++ b/common/file/file_test.go @@ -56,7 +56,7 @@ func TestWrite(t *testing.T) { func TestMove(t *testing.T) { tester := func(in, out string, write bool) error { if write { - if err := ioutil.WriteFile(in, []byte("GoCryptoTrader"), 0770); err != nil { + if err := os.WriteFile(in, []byte("GoCryptoTrader"), DefaultPermissionOctal); err != nil { return err } } @@ -65,7 +65,7 @@ func TestMove(t *testing.T) { return err } - contents, err := ioutil.ReadFile(out) + contents, err := os.ReadFile(out) if err != nil { return err } @@ -124,7 +124,7 @@ func TestExists(t *testing.T) { t.Error("non-existent file should not exist") } tmpFile := filepath.Join(os.TempDir(), "gct-test.txt") - if err := ioutil.WriteFile(tmpFile, []byte("hello world"), os.ModeAppend); err != nil { + if err := os.WriteFile(tmpFile, []byte("hello world"), os.ModeAppend); err != nil { t.Fatal(err) } if e := Exists(tmpFile); !e { @@ -258,7 +258,7 @@ func TestWriter(t *testing.T) { if err != nil { t.Fatal(err) } - if data, err := ioutil.ReadFile(got.Name()); err != nil || string(data) != testData { + if data, err := os.ReadFile(got.Name()); err != nil || string(data) != testData { t.Errorf("Could not write the file, or contents were wrong: expected = %s, got =%s", testData, string(data)) } }) @@ -274,7 +274,7 @@ func TestWriterNoPermissionFails(t *testing.T) { t.Fatal(err) } defer os.RemoveAll(temp) - err = os.Chmod(temp, 0555) + err = os.Chmod(temp, 0o555) if err != nil { t.Fatal(err) } diff --git a/common/math/math.go b/common/math/math.go index 4990ee99..188d14af 100644 --- a/common/math/math.go +++ b/common/math/math.go @@ -89,9 +89,9 @@ func InformationRatio(returnsRates, benchmarkRates []float64, averageValues, ave if len(benchmarkRates) != len(returnsRates) { return 0, errInformationBadLength } - var diffs []float64 + diffs := make([]float64, len(returnsRates)) for i := range returnsRates { - diffs = append(diffs, returnsRates[i]-benchmarkRates[i]) + diffs[i] = returnsRates[i] - benchmarkRates[i] } stdDev, err := PopulationStandardDeviation(diffs) if err != nil { @@ -135,11 +135,11 @@ func SampleStandardDeviation(values []float64) (float64, error) { if err != nil { return 0, err } - var superMean []float64 + superMean := make([]float64, len(values)) var combined float64 for i := range values { result := math.Pow(values[i]-mean, 2) - superMean = append(superMean, result) + superMean[i] = result combined += result } avg := combined / (float64(len(superMean)) - 1) @@ -232,9 +232,9 @@ func SharpeRatio(movementPerCandle []float64, riskFreeRatePerInterval, average f if totalIntervals == 0 { return 0, errZeroValue } - var excessReturns []float64 + excessReturns := make([]float64, len(movementPerCandle)) for i := range movementPerCandle { - excessReturns = append(excessReturns, movementPerCandle[i]-riskFreeRatePerInterval) + excessReturns[i] = movementPerCandle[i] - riskFreeRatePerInterval } standardDeviation, err := PopulationStandardDeviation(excessReturns) if err != nil { @@ -284,9 +284,9 @@ func DecimalInformationRatio(returnsRates, benchmarkRates []decimal.Decimal, ave if len(benchmarkRates) != len(returnsRates) { return decimal.Zero, errInformationBadLength } - var diffs []decimal.Decimal + diffs := make([]decimal.Decimal, len(returnsRates)) for i := range returnsRates { - diffs = append(diffs, returnsRates[i].Sub(benchmarkRates[i])) + diffs[i] = returnsRates[i].Sub(benchmarkRates[i]) } stdDev, err := DecimalPopulationStandardDeviation(diffs) if err != nil && !errors.Is(err, ErrInexactConversion) { @@ -339,11 +339,11 @@ func DecimalSampleStandardDeviation(values []decimal.Decimal) (decimal.Decimal, if err != nil { return decimal.Zero, err } - var superMean []decimal.Decimal + superMean := make([]decimal.Decimal, len(values)) var combined decimal.Decimal for i := range values { pow := values[i].Sub(mean).Pow(decimal.NewFromInt(2)) - superMean = append(superMean, pow) + superMean[i] = pow combined.Add(pow) } avg := combined.Div(decimal.NewFromInt(int64(len(superMean))).Sub(decimal.NewFromInt(1))) @@ -380,9 +380,7 @@ func DecimalGeometricMean(values []decimal.Decimal) (decimal.Decimal, error) { // DecimalPow is lovely because shopspring decimal cannot // handle ^0.x and instead returns 1 func DecimalPow(x, y decimal.Decimal) decimal.Decimal { - fX, _ := x.Float64() - fY, _ := y.Float64() - pow := math.Pow(fX, fY) + pow := math.Pow(x.InexactFloat64(), y.InexactFloat64()) return decimal.NewFromFloat(pow) } @@ -405,7 +403,7 @@ func DecimalFinancialGeometricMean(values []decimal.Decimal) (decimal.Decimal, e // as we cannot have negative or zero value geometric numbers // adding a 1 to the percentage movements allows for differentiation between // negative numbers (eg -0.1 translates to 0.9) and positive numbers (eg 0.1 becomes 1.1) - modVal, _ := values[i].Add(decimal.NewFromInt(1)).Float64() + modVal := values[i].Add(decimal.NewFromInt(1)).InexactFloat64() product *= modVal } prod := 1 / float64(len(values)) @@ -461,9 +459,9 @@ func DecimalSharpeRatio(movementPerCandle []decimal.Decimal, riskFreeRatePerInte if totalIntervals.IsZero() { return decimal.Zero, errZeroValue } - var excessReturns []decimal.Decimal + excessReturns := make([]decimal.Decimal, len(movementPerCandle)) for i := range movementPerCandle { - excessReturns = append(excessReturns, movementPerCandle[i].Sub(riskFreeRatePerInterval)) + excessReturns[i] = movementPerCandle[i].Sub(riskFreeRatePerInterval) } standardDeviation, err := DecimalPopulationStandardDeviation(excessReturns) if err != nil && !errors.Is(err, ErrInexactConversion) { diff --git a/common/math/math_test.go b/common/math/math_test.go index 3a2fc1f0..5a33a792 100644 --- a/common/math/math_test.go +++ b/common/math/math_test.go @@ -184,9 +184,9 @@ func TestInformationRatio(t *testing.T) { t.Error(avgComparison) } - var eachDiff []float64 + eachDiff := make([]float64, len(figures)) for i := range figures { - eachDiff = append(eachDiff, figures[i]-comparisonFigures[i]) + eachDiff[i] = figures[i] - comparisonFigures[i] } stdDev, err := PopulationStandardDeviation(eachDiff) if err != nil { @@ -583,9 +583,9 @@ func TestDecimalInformationRatio(t *testing.T) { t.Error(avgComparison) } - var eachDiff []decimal.Decimal + eachDiff := make([]decimal.Decimal, len(figures)) for i := range figures { - eachDiff = append(eachDiff, figures[i].Sub(comparisonFigures[i])) + eachDiff[i] = figures[i].Sub(comparisonFigures[i]) } stdDev, err := DecimalPopulationStandardDeviation(eachDiff) if err != nil && !errors.Is(err, ErrInexactConversion) { @@ -703,14 +703,13 @@ func TestDecimalStandardDeviation2(t *testing.T) { if err != nil { t.Error(err) } - var superMean []decimal.Decimal + superMean := make([]decimal.Decimal, len(r)) for i := range r { result := r[i].Sub(mean).Pow(decimal.NewFromInt(2)) - superMean = append(superMean, result) + superMean[i] = result } superMeany := superMean[0].Add(superMean[1].Add(superMean[2].Add(superMean[3].Add(superMean[4].Add(superMean[5]))))).Div(decimal.NewFromInt(5)) - fSuperMeany, _ := superMeany.Float64() - manualCalculation := decimal.NewFromFloat(math.Sqrt(fSuperMeany)) + manualCalculation := decimal.NewFromFloat(math.Sqrt(superMeany.InexactFloat64())) var codeCalcu decimal.Decimal codeCalcu, err = DecimalSampleStandardDeviation(r) if err != nil { diff --git a/communications/base/base_test.go b/communications/base/base_test.go index 980fc20b..24c0856c 100644 --- a/communications/base/base_test.go +++ b/communications/base/base_test.go @@ -108,8 +108,11 @@ func TestSetup(t *testing.T) { for idx, provider := range ic { exp := testConfigs[idx].shouldConnectCalled - act := provider.(*CommunicationProvider).ConnectCalled - if exp != act { + act, ok := provider.(*CommunicationProvider) + if !ok { + t.Fatal("unable to type assert provider") + } + if exp != act.ConnectCalled { t.Fatalf("provider should be enabled and not be connected: exp=%v, act=%v", exp, act) } } @@ -139,8 +142,11 @@ func TestPushEvent(t *testing.T) { for idx, provider := range ic { exp := testConfigs[idx].PushEventCalled - act := provider.(*CommunicationProvider).PushEventCalled - if exp != act { + act, ok := provider.(*CommunicationProvider) + if !ok { + t.Fatal("unable to type assert provider") + } + if exp != act.PushEventCalled { t.Fatalf("provider should be enabled and connected: exp=%v, act=%v", exp, act) } } diff --git a/communications/slack/slack.go b/communications/slack/slack.go index 22677a65..ce14fdb5 100644 --- a/communications/slack/slack.go +++ b/communications/slack/slack.go @@ -47,7 +47,7 @@ type Slack struct { WebsocketConn *websocket.Conn Connected bool Shutdown bool - sync.Mutex + mu sync.Mutex } // IsConnected returns whether or not the connection is connected @@ -91,9 +91,9 @@ func (s *Slack) BuildURL(token string) string { // GetChannelsString returns a list of all channels on the slack workspace func (s *Slack) GetChannelsString() []string { - var channels []string + channels := make([]string, len(s.Details.Channels)) for i := range s.Details.Channels { - channels = append(channels, s.Details.Channels[i].NameNormalized) + channels[i] = s.Details.Channels[i].NameNormalized } return channels } @@ -355,8 +355,8 @@ func (s *Slack) WebsocketKeepAlive() { // WebsocketSend sends a message via the websocket connection func (s *Slack) WebsocketSend(eventType, text string) error { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() newMessage := SendMessage{ ID: time.Now().Unix(), Type: eventType, diff --git a/communications/slack/slack_test.go b/communications/slack/slack_test.go index b9f33a48..d3298b47 100644 --- a/communications/slack/slack_test.go +++ b/communications/slack/slack_test.go @@ -206,7 +206,10 @@ func TestHandlePresenceChange(t *testing.T) { t.Error("slack handlePresenceChange(), unmarshalled malformed json") } - data, _ := json.Marshal(pres) + data, err := json.Marshal(pres) + if err != nil { + t.Fatal(err) + } err = s.handlePresenceChange(data) if err != nil { t.Errorf("slack handlePresenceChange() Error: %s", err) @@ -235,7 +238,10 @@ func TestHandleMessageResponse(t *testing.T) { var msg Message msg.User = "1337" msg.Text = "Hello World!" - resp, _ := json.Marshal(msg) + resp, err := json.Marshal(msg) + if err != nil { + t.Fatal(err) + } err = s.handleMessageResponse(resp, data) if err != nil { @@ -243,7 +249,10 @@ func TestHandleMessageResponse(t *testing.T) { } msg.Text = "!notacommand" - resp, _ = json.Marshal(msg) + resp, err = json.Marshal(msg) + if err != nil { + t.Fatal(err) + } err = s.handleMessageResponse(resp, data) if err == nil { @@ -286,7 +295,10 @@ func TestHandleReconnectResponse(t *testing.T) { } testURL.URL = "https://www.thrasher.io" - data, _ := json.Marshal(testURL) + data, err := json.Marshal(testURL) + if err != nil { + t.Fatal(err) + } err = s.handleReconnectResponse(data) if err != nil || s.ReconnectURL != "https://www.thrasher.io" { diff --git a/communications/smsglobal/smsglobal.go b/communications/smsglobal/smsglobal.go index d1a3983f..463d0277 100644 --- a/communications/smsglobal/smsglobal.go +++ b/communications/smsglobal/smsglobal.go @@ -42,15 +42,13 @@ func (s *SMSGlobal) Setup(cfg *base.CommunicationsConfig) { s.Password = cfg.SMSGlobalConfig.Password s.SendFrom = cfg.SMSGlobalConfig.From - var contacts []Contact + contacts := make([]Contact, len(cfg.SMSGlobalConfig.Contacts)) for x := range cfg.SMSGlobalConfig.Contacts { - contacts = append(contacts, - Contact{ - Name: cfg.SMSGlobalConfig.Contacts[x].Name, - Number: cfg.SMSGlobalConfig.Contacts[x].Number, - Enabled: cfg.SMSGlobalConfig.Contacts[x].Enabled, - }, - ) + contacts[x] = Contact{ + Name: cfg.SMSGlobalConfig.Contacts[x].Name, + Number: cfg.SMSGlobalConfig.Contacts[x].Number, + Enabled: cfg.SMSGlobalConfig.Contacts[x].Enabled, + } log.Debugf(log.CommunicationMgr, "SMSGlobal: SMS Contact: %s. Number: %s. Enabled: %v\n", cfg.SMSGlobalConfig.Contacts[x].Name, cfg.SMSGlobalConfig.Contacts[x].Number, diff --git a/config/config.go b/config/config.go index 1c3035dd..9bb5b617 100644 --- a/config/config.go +++ b/config/config.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "os" "path/filepath" "runtime" @@ -1420,7 +1419,7 @@ func GetFilePath(configFile string) (configPath string, isImplicitDefaultPath bo // config directory as `File` or `EncryptedFile` depending on whether the config // is encrypted func migrateConfig(configFile, targetDir string) (string, error) { - data, err := ioutil.ReadFile(configFile) + data, err := os.ReadFile(configFile) if err != nil { return "", err } @@ -1518,7 +1517,7 @@ func ReadConfig(configReader io.Reader, keyProvider func() ([]byte, error)) (*Co // readEncryptedConf reads encrypted configuration and requests key from provider func readEncryptedConfWithKey(reader *bufio.Reader, keyProvider func() ([]byte, error)) (*Config, error) { - fileData, err := ioutil.ReadAll(reader) + fileData, err := io.ReadAll(reader) if err != nil { return nil, err } diff --git a/config/config_encryption.go b/config/config_encryption.go index 821584aa..55ff4726 100644 --- a/config/config_encryption.go +++ b/config/config_encryption.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "log" "github.com/thrasher-corp/gocryptotrader/common" @@ -137,7 +136,7 @@ func (c *Config) decryptConfigData(configReader io.Reader, key []byte) ([]byte, return nil, err } origKey := key - configData, err := ioutil.ReadAll(configReader) + configData, err := io.ReadAll(configReader) if err != nil { return nil, err } diff --git a/config/config_encryption_test.go b/config/config_encryption_test.go index 58e022b6..a0f00996 100644 --- a/config/config_encryption_test.go +++ b/config/config_encryption_test.go @@ -179,11 +179,11 @@ func TestEncryptTwiceReusesSaltButNewCipher(t *testing.T) { if err != nil { t.Fatalf("Problem storing config in file %s: %s\n", enc2, err) } - data1, err := ioutil.ReadFile(enc1) + data1, err := os.ReadFile(enc1) if err != nil { t.Fatalf("Problem reading file %s: %s\n", enc1, err) } - data2, err := ioutil.ReadFile(enc2) + data2, err := os.ReadFile(enc2) if err != nil { t.Fatalf("Problem reading file %s: %s\n", enc2, err) } @@ -281,7 +281,7 @@ func TestReadConfigWithPrompt(t *testing.T) { } // Verify results - data, err := ioutil.ReadFile(testConfigFile) + data, err := os.ReadFile(testConfigFile) if err != nil { t.Fatalf("Problem reading saved file at %s: %s\n", testConfigFile, err) } @@ -349,7 +349,7 @@ func TestSaveConfigToFileWithErrorInPasswordPrompt(t *testing.T) { if err != nil { t.Fatal(err) } - data, err := ioutil.ReadFile(targetFile) + data, err := os.ReadFile(targetFile) if err != nil { t.Fatal(err) } diff --git a/config/config_test.go b/config/config_test.go index a3ac049e..3dccaee9 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -920,7 +920,7 @@ func TestSupportsPair(t *testing.T) { }, }, } - assetType := asset.Spot // nolint // ifshort false positive + assetType := asset.Spot if cfg.SupportsPair("asdf", currency.NewPair(currency.BTC, currency.USD), assetType) { t.Error( diff --git a/connchecker/connchecker.go b/connchecker/connchecker.go index 611d9271..59705ce6 100644 --- a/connchecker/connchecker.go +++ b/connchecker/connchecker.go @@ -74,7 +74,7 @@ type Checker struct { shutdown chan struct{} wg sync.WaitGroup connected bool - sync.Mutex + mu sync.Mutex } // Shutdown cleanly shutsdown monitor routine @@ -136,12 +136,12 @@ func (c *Checker) connectionTest() { for i := range c.DNSList { err := c.CheckDNS(c.DNSList[i]) if err == nil { - c.Lock() + c.mu.Lock() if !c.connected { log.Debugln(log.Global, ConnRe) c.connected = true } - c.Unlock() + c.mu.Unlock() return } } @@ -149,22 +149,22 @@ func (c *Checker) connectionTest() { for i := range c.DomainList { err := c.CheckHost(c.DomainList[i]) if err == nil { - c.Lock() + c.mu.Lock() if !c.connected { log.Debugln(log.Global, ConnRe) c.connected = true } - c.Unlock() + c.mu.Unlock() return } } - c.Lock() + c.mu.Lock() if c.connected { log.Warnln(log.Global, ConnLost) c.connected = false } - c.Unlock() + c.mu.Unlock() } // CheckDNS checks current dns for connectivity @@ -185,8 +185,8 @@ func (c *Checker) CheckHost(host string) error { // IsConnected returns if there is internet connectivity func (c *Checker) IsConnected() bool { - c.Lock() + c.mu.Lock() isConnected := c.connected - c.Unlock() + c.mu.Unlock() return isConnected } diff --git a/currency/code_test.go b/currency/code_test.go index af49d49a..edbf8b92 100644 --- a/currency/code_test.go +++ b/currency/code_test.go @@ -354,7 +354,11 @@ func TestBaseCode(t *testing.T) { len(full.UnsetCurrency)) } - if full.LastMainUpdate.(int64) != -62135596800 { + lastMainUpdate, ok := full.LastMainUpdate.(int64) + if !ok { + t.Error("unable to type assert LastMainUpdate") + } + if lastMainUpdate != -62135596800 { t.Errorf("BaseCode GetFullCurrencyData() error expected -62135596800 but received %d", full.LastMainUpdate) } diff --git a/currency/coinmarketcap/coinmarketcap.go b/currency/coinmarketcap/coinmarketcap.go index 980233ee..47464dc0 100644 --- a/currency/coinmarketcap/coinmarketcap.go +++ b/currency/coinmarketcap/coinmarketcap.go @@ -75,9 +75,9 @@ func (c *Coinmarketcap) GetCryptocurrencyInfo(currencyID ...int64) (CryptoCurren return resp.Data, err } - var currStr []string + currStr := make([]string, len(currencyID)) for i := range currencyID { - currStr = append(currStr, strconv.FormatInt(currencyID[i], 10)) + currStr[i] = strconv.FormatInt(currencyID[i], 10) } val := url.Values{} @@ -315,9 +315,9 @@ func (c *Coinmarketcap) GetCryptocurrencyLatestQuotes(currencyID ...int64) (Cryp return resp.Data, err } - var currStr []string - for _, d := range currencyID { - currStr = append(currStr, strconv.FormatInt(d, 10)) + currStr := make([]string, len(currencyID)) + for i := range currencyID { + currStr[i] = strconv.FormatInt(currencyID[i], 10) } val := url.Values{} @@ -387,9 +387,9 @@ func (c *Coinmarketcap) GetExchangeInfo(exchangeID ...int64) (ExchangeInfo, erro return resp.Data, err } - var exchStr []string - for _, d := range exchangeID { - exchStr = append(exchStr, strconv.FormatInt(d, 10)) + exchStr := make([]string, len(exchangeID)) + for x := range exchangeID { + exchStr[x] = strconv.FormatInt(exchangeID[x], 10) } val := url.Values{} @@ -524,9 +524,9 @@ func (c *Coinmarketcap) GetExchangeLatestQuotes(exchangeID ...int64) (ExchangeLa return resp.Data, err } - var exchStr []string - for _, d := range exchangeID { - exchStr = append(exchStr, strconv.FormatInt(d, 10)) + exchStr := make([]string, len(exchangeID)) + for x := range exchangeID { + exchStr[x] = strconv.FormatInt(exchangeID[x], 10) } val := url.Values{} diff --git a/currency/forexprovider/currencyconverterapi/currencyconverterapi.go b/currency/forexprovider/currencyconverterapi/currencyconverterapi.go index 5e65e3df..84ab9959 100644 --- a/currency/forexprovider/currencyconverterapi/currencyconverterapi.go +++ b/currency/forexprovider/currencyconverterapi/currencyconverterapi.go @@ -38,9 +38,9 @@ func (c *CurrencyConverter) GetRates(baseCurrency, symbols string) (map[string]f return c.Convert(baseCurrency, symbols) } - var completedStrings []string + completedStrings := make([]string, len(splitSymbols)) for x := range splitSymbols { - completedStrings = append(completedStrings, baseCurrency+"_"+splitSymbols[x]) + completedStrings[x] = baseCurrency + "_" + splitSymbols[x] } if (c.APIKey != "" && c.APIKey != "Key") || len(completedStrings) == 2 { @@ -126,7 +126,7 @@ func (c *CurrencyConverter) GetSupportedCurrencies() ([]string, error) { return nil, err } - var currencies []string + currencies := make([]string, 0, len(result.Results)) for key := range result.Results { currencies = append(currencies, key) } diff --git a/currency/forexprovider/currencylayer/currencylayer.go b/currency/forexprovider/currencylayer/currencylayer.go index 5fb5ccea..789ead63 100644 --- a/currency/forexprovider/currencylayer/currencylayer.go +++ b/currency/forexprovider/currencylayer/currencylayer.go @@ -66,7 +66,7 @@ func (c *CurrencyLayer) GetSupportedCurrencies() ([]string, error) { return nil, errors.New(resp.Error.Info) } - var currencies []string + currencies := make([]string, 0, len(resp.Currencies)) for key := range resp.Currencies { currencies = append(currencies, key) } diff --git a/currency/forexprovider/exchangerate.host/exchangerate.go b/currency/forexprovider/exchangerate.host/exchangerate.go index e4b78bd9..9afa7fbb 100644 --- a/currency/forexprovider/exchangerate.host/exchangerate.go +++ b/currency/forexprovider/exchangerate.host/exchangerate.go @@ -229,7 +229,7 @@ func (e *ExchangeRateHost) GetSupportedCurrencies() ([]string, error) { return nil, err } - var symbols []string + symbols := make([]string, 0, len(s.Symbols)) for x := range s.Symbols { symbols = append(symbols, x) } diff --git a/currency/forexprovider/exchangerate.host/exchangerate_test.go b/currency/forexprovider/exchangerate.host/exchangerate_test.go index d732778d..c5535909 100644 --- a/currency/forexprovider/exchangerate.host/exchangerate_test.go +++ b/currency/forexprovider/exchangerate.host/exchangerate_test.go @@ -84,8 +84,7 @@ func TestGetSupportedSymbols(t *testing.T) { if err != nil { t.Fatal(err) } - _, ok := r.Symbols["AUD"] - if !ok { + if _, ok := r.Symbols["AUD"]; !ok { t.Error("should contain AUD") } } diff --git a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go index 77f565c0..74b691d5 100644 --- a/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go +++ b/currency/forexprovider/exchangeratesapi.io/exchangeratesapi.go @@ -45,9 +45,10 @@ func (e *ExchangeRates) cleanCurrencies(baseCurrency, symbols string) string { e.supportedCurrencies = supportedCurrencies } } - var cleanedCurrencies []string + symbols = strings.Replace(symbols, "RUR", "RUB", -1) var s = strings.Split(symbols, ",") + cleanedCurrencies := make([]string, 0, len(s)) for _, x := range s { // first make sure that the baseCurrency is not in the symbols list // if it is set @@ -244,7 +245,7 @@ func (e *ExchangeRates) GetSupportedCurrencies() ([]string, error) { return nil, err } - var supportedCurrencies []string + supportedCurrencies := make([]string, 0, len(symbols)) for x := range symbols { supportedCurrencies = append(supportedCurrencies, x) } diff --git a/currency/forexprovider/fixer.io/fixer.go b/currency/forexprovider/fixer.io/fixer.go index 8730fc08..20b4f027 100644 --- a/currency/forexprovider/fixer.io/fixer.go +++ b/currency/forexprovider/fixer.io/fixer.go @@ -55,7 +55,7 @@ func (f *Fixer) GetSupportedCurrencies() ([]string, error) { return nil, errors.New(resp.Error.Type + resp.Error.Info) } - var currencies []string + currencies := make([]string, 0, len(resp.Map)) for key := range resp.Map { currencies = append(currencies, key) } diff --git a/currency/storage.go b/currency/storage.go index 13ee017e..9df9c982 100644 --- a/currency/storage.go +++ b/currency/storage.go @@ -4,7 +4,7 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "os" "path/filepath" "time" @@ -51,7 +51,7 @@ var ( func (s *Storage) SetDefaults() { s.defaultBaseCurrency = USD s.baseCurrency = s.defaultBaseCurrency - var fiatCurrencies []Code + fiatCurrencies := make([]Code, 0, len(symbols)) for item := range symbols { if item == USDT.Item { continue @@ -125,7 +125,7 @@ func (s *Storage) RunUpdater(overrides BotOverrides, settings *Config, filePath } } - var fxSettings []base.Settings + fxSettings := make([]base.Settings, 0, len(settings.ForexProviders)) var primaryProvider bool for i := range settings.ForexProviders { enabled := (settings.ForexProviders[i].Name == "CurrencyConverter" && overrides.CurrencyConverter) || @@ -331,7 +331,7 @@ func (s *Storage) ForeignExchangeUpdater() { // SeedCurrencyAnalysisData sets a new instance of a coinmarketcap data. func (s *Storage) SeedCurrencyAnalysisData() error { if s.currencyCodes.LastMainUpdate.IsZero() { - b, err := ioutil.ReadFile(s.path) + b, err := os.ReadFile(s.path) if err != nil { return s.FetchCurrencyAnalysisData() } diff --git a/database/repository/audit/audit.go b/database/repository/audit/audit.go index 793350e9..06c87a09 100644 --- a/database/repository/audit/audit.go +++ b/database/repository/audit/audit.go @@ -19,7 +19,7 @@ func Event(id, msgtype, message string) { return } - ctx := context.Background() + ctx := context.TODO() ctx = boil.SkipTimestamps(ctx) tx, err := database.DB.SQL.BeginTx(ctx, nil) @@ -76,7 +76,7 @@ func GetEvent(startTime, endTime time.Time, order string, limit int) (interface{ orderByQuery := qm.OrderBy(orderByQueryString) limitQuery := qm.Limit(limit) - ctx := context.Background() + ctx := context.TODO() if repository.GetSQLDialect() == database.DBSQLite3 { return modelSQLite.AuditEvents(query, orderByQuery, limitQuery).All(ctx, database.DB.SQL) } diff --git a/database/repository/candle/candle.go b/database/repository/candle/candle.go index 3fc3d587..98faa834 100644 --- a/database/repository/candle/candle.go +++ b/database/repository/candle/candle.go @@ -45,7 +45,7 @@ func Series(exchangeName, base, quote string, interval int64, asset string, star queries = append(queries, qm.Where("exchange_name_id = ?", exchangeUUID.String())) if repository.GetSQLDialect() == database.DBSQLite3 { queries = append(queries, qm.Where("timestamp between ? and ?", start.UTC().Format(time.RFC3339), end.UTC().Format(time.RFC3339))) - retCandle, errC := modelSQLite.Candles(queries...).All(context.Background(), database.DB.SQL) + retCandle, errC := modelSQLite.Candles(queries...).All(context.TODO(), database.DB.SQL) if errC != nil { return out, errC } @@ -68,7 +68,7 @@ func Series(exchangeName, base, quote string, interval int64, asset string, star } } else { queries = append(queries, qm.Where("timestamp between ? and ?", start.UTC(), end.UTC())) - retCandle, errC := modelPSQL.Candles(queries...).All(context.Background(), database.DB.SQL) + retCandle, errC := modelPSQL.Candles(queries...).All(context.TODO(), database.DB.SQL) if errC != nil { return out, errC } @@ -108,7 +108,7 @@ func DeleteCandles(in *Item) (int64, error) { return 0, errNoCandleData } - ctx := context.Background() + ctx := context.TODO() queries := []qm.QueryMod{ qm.Where("base = ?", strings.ToUpper(in.Base)), qm.Where("quote = ?", strings.ToUpper(in.Quote)), @@ -126,7 +126,7 @@ func DeleteCandles(in *Item) (int64, error) { } func deleteSQLite(ctx context.Context, queries []qm.QueryMod) (int64, error) { - retCandle, err := modelSQLite.Candles(queries...).All(context.Background(), database.DB.SQL) + retCandle, err := modelSQLite.Candles(queries...).All(ctx, database.DB.SQL) if err != nil { return 0, err } @@ -153,7 +153,7 @@ func deleteSQLite(ctx context.Context, queries []qm.QueryMod) (int64, error) { } func deletePostgres(ctx context.Context, queries []qm.QueryMod) (int64, error) { - retCandle, err := modelPSQL.Candles(queries...).All(context.Background(), database.DB.SQL) + retCandle, err := modelPSQL.Candles(queries...).All(ctx, database.DB.SQL) if err != nil { return 0, err } @@ -189,7 +189,7 @@ func Insert(in *Item) (uint64, error) { return 0, errNoCandleData } - ctx := context.Background() + ctx := context.TODO() tx, err := database.DB.SQL.BeginTx(ctx, nil) if err != nil { return 0, err diff --git a/database/repository/datahistoryjob/datahistoryjob.go b/database/repository/datahistoryjob/datahistoryjob.go index a7c26c5a..5e1b511f 100644 --- a/database/repository/datahistoryjob/datahistoryjob.go +++ b/database/repository/datahistoryjob/datahistoryjob.go @@ -38,7 +38,7 @@ func Setup(db database.IDatabase) (*DBService, error) { // Upsert inserts or updates jobs into the database func (db *DBService) Upsert(jobs ...*DataHistoryJob) error { - ctx := context.Background() + ctx := context.TODO() tx, err := db.sql.BeginTx(ctx, nil) if err != nil { @@ -156,7 +156,7 @@ func (db *DBService) GetPrerequisiteJob(nickname string) (*DataHistoryJob, error // SetRelationshipByID removes a relationship in the event of a changed // relationship during upsertion func (db *DBService) SetRelationshipByID(prerequisiteJobID, followingJobID string, status int64) error { - ctx := context.Background() + ctx := context.TODO() if strings.EqualFold(prerequisiteJobID, followingJobID) { return errCannotSetSamePrerequisite } @@ -191,7 +191,7 @@ func (db *DBService) SetRelationshipByID(prerequisiteJobID, followingJobID strin // SetRelationshipByNickname removes a relationship in the event of a changed // relationship during upsertion func (db *DBService) SetRelationshipByNickname(prerequisiteNickname, followingNickname string, status int64) error { - ctx := context.Background() + ctx := context.TODO() if strings.EqualFold(prerequisiteNickname, followingNickname) { return errCannotSetSamePrerequisite } @@ -331,7 +331,7 @@ func upsertPostgres(ctx context.Context, tx *sql.Tx, jobs ...*DataHistoryJob) er return nil } func (db *DBService) getByNicknameSQLite(nickname string) (*DataHistoryJob, error) { - result, err := sqlite3.Datahistoryjobs(qm.Where("nickname = ?", strings.ToLower(nickname))).One(context.Background(), db.sql) + result, err := sqlite3.Datahistoryjobs(qm.Where("nickname = ?", strings.ToLower(nickname))).One(context.TODO(), db.sql) if err != nil { return nil, err } @@ -341,7 +341,7 @@ func (db *DBService) getByNicknameSQLite(nickname string) (*DataHistoryJob, erro func (db *DBService) getByNicknamePostgres(nickname string) (*DataHistoryJob, error) { query := postgres.Datahistoryjobs(qm.Where("nickname = ?", strings.ToLower(nickname))) - result, err := query.One(context.Background(), db.sql) + result, err := query.One(context.TODO(), db.sql) if err != nil { return nil, err } @@ -349,7 +349,7 @@ func (db *DBService) getByNicknamePostgres(nickname string) (*DataHistoryJob, er } func (db *DBService) getByIDSQLite(id string) (*DataHistoryJob, error) { - result, err := sqlite3.Datahistoryjobs(qm.Where("id = ?", id)).One(context.Background(), db.sql) + result, err := sqlite3.Datahistoryjobs(qm.Where("id = ?", id)).One(context.TODO(), db.sql) if err != nil { return nil, err } @@ -359,7 +359,7 @@ func (db *DBService) getByIDSQLite(id string) (*DataHistoryJob, error) { func (db *DBService) getByIDPostgres(id string) (*DataHistoryJob, error) { query := postgres.Datahistoryjobs(qm.Where("id = ?", id)) - result, err := query.One(context.Background(), db.sql) + result, err := query.One(context.TODO(), db.sql) if err != nil { return nil, err } @@ -368,13 +368,13 @@ func (db *DBService) getByIDPostgres(id string) (*DataHistoryJob, error) { } func (db *DBService) getJobsBetweenSQLite(startDate, endDate time.Time) ([]DataHistoryJob, error) { - var jobs []DataHistoryJob query := sqlite3.Datahistoryjobs(qm.Where("created BETWEEN ? AND ? ", startDate.UTC().Format(time.RFC3339), endDate.UTC().Format(time.RFC3339))) - results, err := query.All(context.Background(), db.sql) + results, err := query.All(context.TODO(), db.sql) if err != nil { - return jobs, err + return nil, err } + jobs := make([]DataHistoryJob, 0, len(results)) for i := range results { job, err := db.createSQLiteDataHistoryJobResponse(results[i]) if err != nil { @@ -387,13 +387,13 @@ func (db *DBService) getJobsBetweenSQLite(startDate, endDate time.Time) ([]DataH } func (db *DBService) getJobsBetweenPostgres(startDate, endDate time.Time) ([]DataHistoryJob, error) { - var jobs []DataHistoryJob query := postgres.Datahistoryjobs(qm.Where("created BETWEEN ? AND ? ", startDate, endDate)) - results, err := query.All(context.Background(), db.sql) + results, err := query.All(context.TODO(), db.sql) if err != nil { - return jobs, err + return nil, err } + jobs := make([]DataHistoryJob, 0, len(results)) for i := range results { job, err := db.createPostgresDataHistoryJobResponse(results[i]) if err != nil { @@ -410,7 +410,7 @@ func (db *DBService) getJobAndAllResultsSQLite(nickname string) (*DataHistoryJob qm.Load(sqlite3.DatahistoryjobRels.JobDatahistoryjobresults), qm.Load(sqlite3.DatahistoryjobRels.ExchangeName), qm.Where("nickname = ?", strings.ToLower(nickname))) - result, err := query.One(context.Background(), db.sql) + result, err := query.One(context.TODO(), db.sql) if err != nil { return nil, err } @@ -422,7 +422,7 @@ func (db *DBService) getJobAndAllResultsPostgres(nickname string) (*DataHistoryJ query := postgres.Datahistoryjobs( qm.Load(postgres.DatahistoryjobRels.JobDatahistoryjobresults), qm.Where("nickname = ?", strings.ToLower(nickname))) - result, err := query.One(context.Background(), db.sql) + result, err := query.One(context.TODO(), db.sql) if err != nil { return nil, err } @@ -435,12 +435,12 @@ func (db *DBService) getAllIncompleteJobsAndResultsSQLite() ([]DataHistoryJob, e qm.Load(sqlite3.DatahistoryjobRels.ExchangeName), qm.Load(sqlite3.DatahistoryjobRels.JobDatahistoryjobresults), qm.Where("status = ?", 0)) - results, err := query.All(context.Background(), db.sql) + results, err := query.All(context.TODO(), db.sql) if err != nil { return nil, err } - var jobs []DataHistoryJob + jobs := make([]DataHistoryJob, 0, len(results)) for i := range results { job, err := db.createSQLiteDataHistoryJobResponse(results[i]) if err != nil { @@ -457,12 +457,12 @@ func (db *DBService) getAllIncompleteJobsAndResultsPostgres() ([]DataHistoryJob, query := postgres.Datahistoryjobs( qm.Load(postgres.DatahistoryjobRels.JobDatahistoryjobresults), qm.Where("status = ?", 0)) - results, err := query.All(context.Background(), db.sql) + results, err := query.All(context.TODO(), db.sql) if err != nil { return nil, err } - var jobs []DataHistoryJob + jobs := make([]DataHistoryJob, 0, len(results)) for i := range results { job, err := db.createPostgresDataHistoryJobResponse(results[i]) if err != nil { @@ -475,15 +475,15 @@ func (db *DBService) getAllIncompleteJobsAndResultsPostgres() ([]DataHistoryJob, } func (db *DBService) getRelatedUpcomingJobsSQLite(nickname string) ([]*DataHistoryJob, error) { - job, err := sqlite3.Datahistoryjobs(qm.Where("nickname = ?", nickname)).One(context.Background(), db.sql) + job, err := sqlite3.Datahistoryjobs(qm.Where("nickname = ?", nickname)).One(context.TODO(), db.sql) if err != nil { return nil, err } - results, err := job.JobDatahistoryjobs().All(context.Background(), db.sql) + results, err := job.JobDatahistoryjobs().All(context.TODO(), db.sql) if err != nil { return nil, err } - var resp []*DataHistoryJob + resp := make([]*DataHistoryJob, 0, len(results)) for i := range results { job, err := db.createSQLiteDataHistoryJobResponse(results[i]) if err != nil { @@ -496,11 +496,11 @@ func (db *DBService) getRelatedUpcomingJobsSQLite(nickname string) ([]*DataHisto func (db *DBService) getRelatedUpcomingJobsPostgres(nickname string) ([]*DataHistoryJob, error) { q := postgres.Datahistoryjobs(qm.Load(postgres.DatahistoryjobRels.JobDatahistoryjobs), qm.Where("nickname = ?", nickname)) - jobWithRelations, err := q.One(context.Background(), db.sql) + jobWithRelations, err := q.One(context.TODO(), db.sql) if err != nil { return nil, err } - var response []*DataHistoryJob + response := make([]*DataHistoryJob, 0, len(jobWithRelations.R.JobDatahistoryjobs)) for i := range jobWithRelations.R.JobDatahistoryjobs { job, err := db.getByIDPostgres(jobWithRelations.R.JobDatahistoryjobs[i].ID) if err != nil { @@ -556,11 +556,11 @@ func setRelationshipByIDPostgres(ctx context.Context, tx *sql.Tx, prerequisiteJo } func (db *DBService) getPrerequisiteJobSQLite(nickname string) (*DataHistoryJob, error) { - result, err := sqlite3.Datahistoryjobs(qm.Where("nickname = ?", nickname)).One(context.Background(), db.sql) + result, err := sqlite3.Datahistoryjobs(qm.Where("nickname = ?", nickname)).One(context.TODO(), db.sql) if err != nil { return nil, err } - job, err := result.PrerequisiteJobDatahistoryjobs().One(context.Background(), db.sql) + job, err := result.PrerequisiteJobDatahistoryjobs().One(context.TODO(), db.sql) if err != nil { return nil, err } @@ -569,11 +569,11 @@ func (db *DBService) getPrerequisiteJobSQLite(nickname string) (*DataHistoryJob, } func (db *DBService) getPrerequisiteJobPostgres(nickname string) (*DataHistoryJob, error) { - job, err := postgres.Datahistoryjobs(qm.Where("nickname = ?", nickname)).One(context.Background(), db.sql) + job, err := postgres.Datahistoryjobs(qm.Where("nickname = ?", nickname)).One(context.TODO(), db.sql) if err != nil { return nil, err } - result, err := job.PrerequisiteJobDatahistoryjobs().One(context.Background(), db.sql) + result, err := job.PrerequisiteJobDatahistoryjobs().One(context.TODO(), db.sql) if err != nil { return nil, err } @@ -631,7 +631,7 @@ func (db *DBService) createSQLiteDataHistoryJobResponse(result *sqlite3.Datahist if result.R != nil && result.R.ExchangeName != nil { exchange = result.R.ExchangeName } else { - exchange, err = result.ExchangeName().One(context.Background(), db.sql) + exchange, err = result.ExchangeName().One(context.TODO(), db.sql) if err != nil { return nil, fmt.Errorf("could not retrieve exchange '%v' %w", result.ExchangeNameID, err) } @@ -639,7 +639,7 @@ func (db *DBService) createSQLiteDataHistoryJobResponse(result *sqlite3.Datahist var secondaryExchangeName string if result.SecondaryExchangeID.String != "" { var secondaryExchangeResult *sqlite3.Exchange - secondaryExchangeResult, err = result.SecondaryExchange().One(context.Background(), db.sql) + secondaryExchangeResult, err = result.SecondaryExchange().One(context.TODO(), db.sql) if err != nil { return nil, fmt.Errorf("could not retrieve secondary exchange '%v' %w", result.SecondaryExchangeID, err) } @@ -661,7 +661,7 @@ func (db *DBService) createSQLiteDataHistoryJobResponse(result *sqlite3.Datahist return nil, err } - prereqJob, err := result.PrerequisiteJobDatahistoryjobs().One(context.Background(), db.sql) + prereqJob, err := result.PrerequisiteJobDatahistoryjobs().One(context.TODO(), db.sql) if err != nil && err != sql.ErrNoRows { return nil, err } @@ -735,7 +735,7 @@ func (db *DBService) createPostgresDataHistoryJobResponse(result *postgres.Datah if result.R != nil && result.R.ExchangeName != nil { exchange = result.R.ExchangeName } else { - exchange, err = result.ExchangeName().One(context.Background(), db.sql) + exchange, err = result.ExchangeName().One(context.TODO(), db.sql) if err != nil { return nil, fmt.Errorf("could not retrieve exchange '%v' %w", result.ExchangeNameID, err) } @@ -744,7 +744,7 @@ func (db *DBService) createPostgresDataHistoryJobResponse(result *postgres.Datah var secondaryExchangeName string if result.SecondaryExchangeID.String != "" { var secondaryExchangeResult *postgres.Exchange - secondaryExchangeResult, err = result.SecondaryExchange().One(context.Background(), db.sql) + secondaryExchangeResult, err = result.SecondaryExchange().One(context.TODO(), db.sql) if err != nil { return nil, fmt.Errorf("could not retrieve secondary exchange '%v' %w", result.SecondaryExchangeID, err) } @@ -753,7 +753,7 @@ func (db *DBService) createPostgresDataHistoryJobResponse(result *postgres.Datah } } - prereqJob, err := result.PrerequisiteJobDatahistoryjobs().One(context.Background(), db.sql) + prereqJob, err := result.PrerequisiteJobDatahistoryjobs().One(context.TODO(), db.sql) if err != nil && err != sql.ErrNoRows { return nil, err } diff --git a/database/repository/datahistoryjobresult/datahistoryjobresult.go b/database/repository/datahistoryjobresult/datahistoryjobresult.go index 1c956b11..2cb3960b 100644 --- a/database/repository/datahistoryjobresult/datahistoryjobresult.go +++ b/database/repository/datahistoryjobresult/datahistoryjobresult.go @@ -40,7 +40,7 @@ func (db *DBService) Upsert(jobs ...*DataHistoryJobResult) error { if len(jobs) == 0 { return nil } - ctx := context.Background() + ctx := context.TODO() tx, err := db.sql.BeginTx(ctx, nil) if err != nil { @@ -165,11 +165,11 @@ func upsertPostgres(ctx context.Context, tx *sql.Tx, results ...*DataHistoryJobR func (db *DBService) getByJobIDSQLite(jobID string) ([]DataHistoryJobResult, error) { query := sqlite3.Datahistoryjobresults(qm.Where("job_id = ?", jobID)) - results, err := query.All(context.Background(), db.sql) + results, err := query.All(context.TODO(), db.sql) if err != nil { return nil, err } - var resp []DataHistoryJobResult + resp := make([]DataHistoryJobResult, len(results)) for i := range results { var start, end, run time.Time start, err = time.Parse(time.RFC3339, results[i].IntervalStartTime) @@ -184,7 +184,7 @@ func (db *DBService) getByJobIDSQLite(jobID string) ([]DataHistoryJobResult, err if err != nil { return nil, err } - resp = append(resp, DataHistoryJobResult{ + resp[i] = DataHistoryJobResult{ ID: results[i].ID, JobID: results[i].JobID, IntervalStartDate: start, @@ -192,7 +192,7 @@ func (db *DBService) getByJobIDSQLite(jobID string) ([]DataHistoryJobResult, err Status: int64(results[i].Status), Result: results[i].Result.String, Date: run, - }) + } } return resp, nil @@ -200,13 +200,13 @@ func (db *DBService) getByJobIDSQLite(jobID string) ([]DataHistoryJobResult, err func (db *DBService) getByJobIDPostgres(jobID string) ([]DataHistoryJobResult, error) { query := postgres.Datahistoryjobresults(qm.Where("job_id = ?", jobID)) - results, err := query.All(context.Background(), db.sql) + results, err := query.All(context.TODO(), db.sql) if err != nil { return nil, err } - var resp []DataHistoryJobResult + resp := make([]DataHistoryJobResult, len(results)) for i := range results { - resp = append(resp, DataHistoryJobResult{ + resp[i] = DataHistoryJobResult{ ID: results[i].ID, JobID: results[i].JobID, IntervalStartDate: results[i].IntervalStartTime, @@ -214,20 +214,20 @@ func (db *DBService) getByJobIDPostgres(jobID string) ([]DataHistoryJobResult, e Status: int64(results[i].Status), Result: results[i].Result.String, Date: results[i].RunTime, - }) + } } return resp, nil } func (db *DBService) getJobResultsBetweenSQLite(jobID string, startDate, endDate time.Time) ([]DataHistoryJobResult, error) { - var results []DataHistoryJobResult query := sqlite3.Datahistoryjobresults(qm.Where("job_id = ? AND run_time BETWEEN ? AND ? ", jobID, startDate.UTC().Format(time.RFC3339), endDate.UTC().Format(time.RFC3339))) - resp, err := query.All(context.Background(), db.sql) + resp, err := query.All(context.TODO(), db.sql) if err != nil { - return results, err + return nil, err } + results := make([]DataHistoryJobResult, len(resp)) for i := range resp { var start, end, run time.Time start, err = time.Parse(time.RFC3339, resp[i].IntervalStartTime) @@ -242,7 +242,7 @@ func (db *DBService) getJobResultsBetweenSQLite(jobID string, startDate, endDate if err != nil { return nil, err } - results = append(results, DataHistoryJobResult{ + results[i] = DataHistoryJobResult{ ID: resp[i].ID, JobID: resp[i].JobID, IntervalStartDate: start, @@ -250,22 +250,22 @@ func (db *DBService) getJobResultsBetweenSQLite(jobID string, startDate, endDate Status: int64(resp[i].Status), Result: resp[i].Result.String, Date: run, - }) + } } return results, nil } func (db *DBService) getJobResultsBetweenPostgres(jobID string, startDate, endDate time.Time) ([]DataHistoryJobResult, error) { - var jobs []DataHistoryJobResult query := postgres.Datahistoryjobresults(qm.Where("job_id = ? AND run_time BETWEEN ? AND ? ", jobID, startDate, endDate)) - results, err := query.All(context.Background(), db.sql) + results, err := query.All(context.TODO(), db.sql) if err != nil { - return jobs, err + return nil, err } + jobs := make([]DataHistoryJobResult, len(results)) for i := range results { - jobs = append(jobs, DataHistoryJobResult{ + jobs[i] = DataHistoryJobResult{ ID: results[i].ID, JobID: results[i].JobID, IntervalStartDate: results[i].IntervalStartTime, @@ -273,7 +273,7 @@ func (db *DBService) getJobResultsBetweenPostgres(jobID string, startDate, endDa Status: int64(results[i].Status), Result: results[i].Result.String, Date: results[i].RunTime, - }) + } } return jobs, nil diff --git a/database/repository/exchange/exchange.go b/database/repository/exchange/exchange.go index 2a69cd07..c9650f64 100644 --- a/database/repository/exchange/exchange.go +++ b/database/repository/exchange/exchange.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "encoding/csv" + "errors" "io" "os" "strings" @@ -37,7 +38,7 @@ func one(in, clause string) (out Details, err error) { whereQM := qm.Where(clause+"= ?", in) if repository.GetSQLDialect() == database.DBSQLite3 { - ret, errS := modelSQLite.Exchanges(whereQM).One(context.Background(), database.DB.SQL) + ret, errS := modelSQLite.Exchanges(whereQM).One(context.TODO(), database.DB.SQL) if errS != nil { return out, errS } @@ -47,7 +48,7 @@ func one(in, clause string) (out Details, err error) { return out, errS } } else { - ret, errS := modelPSQL.Exchanges(whereQM).One(context.Background(), database.DB.SQL) + ret, errS := modelPSQL.Exchanges(whereQM).One(context.TODO(), database.DB.SQL) if errS != nil { return out, errS } @@ -67,7 +68,7 @@ func Insert(in Details) error { return database.ErrDatabaseSupportDisabled } - ctx := context.Background() + ctx := context.TODO() tx, err := database.DB.SQL.BeginTx(ctx, nil) if err != nil { return err @@ -100,7 +101,7 @@ func InsertMany(in []Details) error { return database.ErrDatabaseSupportDisabled } - ctx := context.Background() + ctx := context.TODO() tx, err := database.DB.SQL.BeginTx(ctx, nil) if err != nil { return err @@ -178,7 +179,11 @@ func UUIDByName(exchange string) (uuid.UUID, error) { exchange = strings.ToLower(exchange) v := exchangeCache.Get(exchange) if v != nil { - return v.(uuid.UUID), nil + u, ok := v.(uuid.UUID) + if !ok { + return uuid.UUID{}, errors.New("unable to type assert uuid") + } + return u, nil } ret, err := One(exchange) if err != nil { diff --git a/database/repository/script/script.go b/database/repository/script/script.go index 9bc575b8..dc4aae16 100644 --- a/database/repository/script/script.go +++ b/database/repository/script/script.go @@ -20,7 +20,7 @@ func Event(id, name, path string, data null.Bytes, executionType, status string, return } - ctx := context.Background() + ctx := context.TODO() ctx = boil.SkipTimestamps(ctx) tx, err := database.DB.SQL.BeginTx(ctx, nil) if err != nil { diff --git a/database/repository/trade/trade.go b/database/repository/trade/trade.go index 816140ae..ad26eba9 100644 --- a/database/repository/trade/trade.go +++ b/database/repository/trade/trade.go @@ -34,7 +34,7 @@ func Insert(trades ...Data) error { } } - ctx := context.Background() + ctx := context.TODO() ctx = boil.SkipTimestamps(ctx) tx, err := database.DB.SQL.BeginTx(ctx, nil) @@ -65,7 +65,7 @@ func Insert(trades ...Data) error { // VerifyTradeInIntervals will query for ONE trade within each kline interval and verify if data exists // if it does, it will set the range holder property "HasData" to true func VerifyTradeInIntervals(exchangeName, assetType, base, quote string, irh *kline.IntervalRangeHolder) error { - ctx := context.Background() + ctx := context.TODO() ctx = boil.SkipTimestamps(ctx) tx, err := database.DB.SQL.BeginTx(ctx, nil) @@ -237,7 +237,7 @@ func getByUUIDSQLite(uuid string) (Data, error) { var td Data var ts time.Time query := sqlite3.Trades(qm.Where("id = ?", uuid)) - result, err := query.One(context.Background(), database.DB.SQL) + result, err := query.One(context.TODO(), database.DB.SQL) if err != nil { return td, err } @@ -265,7 +265,7 @@ func getByUUIDSQLite(uuid string) (Data, error) { func getByUUIDPostgres(uuid string) (td Data, err error) { query := postgres.Trades(qm.Where("id = ?", uuid)) var result *postgres.Trade - result, err = query.One(context.Background(), database.DB.SQL) + result, err = query.One(context.TODO(), database.DB.SQL) if err != nil { return td, err } @@ -318,7 +318,7 @@ func getInRangeSQLite(exchangeName, assetType, base, quote string, startDate, en q := generateQuery(wheres, startDate, endDate, true) query := sqlite3.Trades(q...) var result []*sqlite3.Trade - result, err = query.All(context.Background(), database.DB.SQL) + result, err = query.All(context.TODO(), database.DB.SQL) if err != nil { return td, err } @@ -361,7 +361,7 @@ func getInRangePostgres(exchangeName, assetType, base, quote string, startDate, q := generateQuery(wheres, startDate, endDate, false) query := postgres.Trades(q...) var result []*postgres.Trade - result, err = query.All(context.Background(), database.DB.SQL) + result, err = query.All(context.TODO(), database.DB.SQL) if err != nil { return td, err } @@ -386,7 +386,7 @@ func getInRangePostgres(exchangeName, assetType, base, quote string, startDate, // DeleteTrades will remove trades from the database using trade.Data func DeleteTrades(trades ...Data) error { - ctx := context.Background() + ctx := context.TODO() ctx = boil.SkipTimestamps(ctx) tx, err := database.DB.SQL.BeginTx(ctx, nil) @@ -402,9 +402,9 @@ func DeleteTrades(trades ...Data) error { } }() if repository.GetSQLDialect() == database.DBSQLite3 || repository.GetSQLDialect() == database.DBSQLite { - err = deleteTradesSQLite(context.Background(), tx, trades...) + err = deleteTradesSQLite(context.TODO(), tx, trades...) } else { - err = deleteTradesPostgres(context.Background(), tx, trades...) + err = deleteTradesPostgres(context.TODO(), tx, trades...) } if err != nil { return err @@ -414,9 +414,9 @@ func DeleteTrades(trades ...Data) error { } func deleteTradesSQLite(ctx context.Context, tx *sql.Tx, trades ...Data) error { - var tradeIDs []interface{} + tradeIDs := make([]interface{}, len(trades)) for i := range trades { - tradeIDs = append(tradeIDs, trades[i].ID) + tradeIDs[i] = trades[i].ID } query := sqlite3.Trades(qm.WhereIn(`id in ?`, tradeIDs...)) _, err := query.DeleteAll(ctx, tx) @@ -424,9 +424,9 @@ func deleteTradesSQLite(ctx context.Context, tx *sql.Tx, trades ...Data) error { } func deleteTradesPostgres(ctx context.Context, tx *sql.Tx, trades ...Data) error { - var tradeIDs []interface{} + tradeIDs := make([]interface{}, len(trades)) for i := range trades { - tradeIDs = append(tradeIDs, trades[i].ID) + tradeIDs[i] = trades[i].ID } query := postgres.Trades(qm.WhereIn(`id in ?`, tradeIDs...)) _, err := query.DeleteAll(ctx, tx) diff --git a/database/repository/withdraw/withdraw.go b/database/repository/withdraw/withdraw.go index 22825dd6..578aa3af 100644 --- a/database/repository/withdraw/withdraw.go +++ b/database/repository/withdraw/withdraw.go @@ -267,7 +267,11 @@ func GetEventsByDate(exchange string, start, end time.Time, limit int) ([]*withd } func generateWhereQuery(columns, id []string, limit int) []qm.QueryMod { - var queries []qm.QueryMod + x := len(columns) + if limit > 0 { + x++ + } + queries := make([]qm.QueryMod, 0, x) if limit > 0 { queries = append(queries, qm.Limit(limit)) } diff --git a/dispatch/dispatch.go b/dispatch/dispatch.go index 38d7cb32..c0e02df5 100644 --- a/dispatch/dispatch.go +++ b/dispatch/dispatch.go @@ -285,11 +285,11 @@ func (d *Dispatcher) subscribe(id uuid.UUID) (chan interface{}, error) { // Read lock to read route list d.rMtx.RLock() - _, ok := d.routes[id] - d.rMtx.RUnlock() - if !ok { + if _, ok := d.routes[id]; !ok { + d.rMtx.RUnlock() return nil, errors.New("dispatcher uuid not found in route list") } + d.rMtx.RUnlock() // Get an unused channel from the channel pool unusedChan, ok := d.outbound.Get().(chan interface{}) @@ -314,11 +314,11 @@ func (d *Dispatcher) unsubscribe(id uuid.UUID, usedChan chan interface{}) error // Read lock to read route list d.rMtx.RLock() - _, ok := d.routes[id] - d.rMtx.RUnlock() - if !ok { + if _, ok := d.routes[id]; !ok { + d.rMtx.RUnlock() return errors.New("dispatcher uuid does not reference any channels") } + d.rMtx.RUnlock() // Lock for write to delete references d.rMtx.Lock() diff --git a/dispatch/dispatch_types.go b/dispatch/dispatch_types.go index 1ab4c221..cba4a07f 100644 --- a/dispatch/dispatch_types.go +++ b/dispatch/dispatch_types.go @@ -71,7 +71,6 @@ type job struct { type Mux struct { // Reference to the main running dispatch service d *Dispatcher - sync.RWMutex } // Pipe defines an outbound object to the desired routine diff --git a/engine/apiserver.go b/engine/apiserver.go index ad43757f..c088ed48 100644 --- a/engine/apiserver.go +++ b/engine/apiserver.go @@ -112,8 +112,8 @@ func (m *apiServerManager) newRouter(isREST bool) *mux.Router { if common.ExtractPort(m.websocketListenAddress) == 80 { m.websocketListenAddress = common.ExtractHost(m.websocketListenAddress) } else { - m.websocketListenAddress = strings.Join([]string{common.ExtractHost(m.websocketListenAddress), - strconv.Itoa(common.ExtractPort(m.websocketListenAddress))}, ":") + m.websocketListenAddress = common.ExtractHost(m.websocketListenAddress) + ":" + + strconv.Itoa(common.ExtractPort(m.websocketListenAddress)) } if isREST { @@ -302,11 +302,13 @@ func (m *apiServerManager) getIndex(w http.ResponseWriter, _ *http.Request) { // getAllActiveOrderbooks returns all enabled exchanges orderbooks func getAllActiveOrderbooks(m iExchangeManager) []EnabledExchangeOrderbooks { - var orderbookData []EnabledExchangeOrderbooks exchanges, err := m.GetExchanges() if err != nil { log.Errorf(log.APIServerMgr, "Cannot get exchanges: %v", err) + return nil } + + orderbookData := make([]EnabledExchangeOrderbooks, 0, len(exchanges)) for x := range exchanges { assets := exchanges[x].GetAssetTypes(true) exchName := exchanges[x].GetName() @@ -342,11 +344,13 @@ func getAllActiveOrderbooks(m iExchangeManager) []EnabledExchangeOrderbooks { // getAllActiveTickers returns all enabled exchanges tickers func getAllActiveTickers(m iExchangeManager) []EnabledExchangeCurrencies { - var tickers []EnabledExchangeCurrencies exchanges, err := m.GetExchanges() if err != nil { log.Errorf(log.APIServerMgr, "Cannot get exchanges: %v", err) + return nil } + + tickers := make([]EnabledExchangeCurrencies, 0, len(exchanges)) for x := range exchanges { assets := exchanges[x].GetAssetTypes(true) exchName := exchanges[x].GetName() @@ -382,11 +386,13 @@ func getAllActiveTickers(m iExchangeManager) []EnabledExchangeCurrencies { // getAllActiveAccounts returns all enabled exchanges accounts func getAllActiveAccounts(m iExchangeManager) []AllEnabledExchangeAccounts { - var accounts []AllEnabledExchangeAccounts exchanges, err := m.GetExchanges() if err != nil { log.Errorf(log.APIServerMgr, "Cannot get exchanges: %v", err) + return nil } + + accounts := make([]AllEnabledExchangeAccounts, 0, len(exchanges)) for x := range exchanges { assets := exchanges[x].GetAssetTypes(true) exchName := exchanges[x].GetName() @@ -680,12 +686,17 @@ func (m *apiServerManager) WebsocketClientHandler(w http.ResponseWriter, r *http } func wsAuth(client *websocketClient, data interface{}) error { + d, ok := data.([]byte) + if !ok { + return errors.New("unable to type assert data") + } + wsResp := WebsocketEventResponse{ Event: "auth", } var auth WebsocketAuth - err := json.Unmarshal(data.([]byte), &auth) + err := json.Unmarshal(d, &auth) if err != nil { wsResp.Error = err.Error() sendErr := client.SendWebsocketMessage(wsResp) @@ -738,11 +749,16 @@ func wsGetConfig(client *websocketClient, _ interface{}) error { } func wsSaveConfig(client *websocketClient, data interface{}) error { + d, ok := data.([]byte) + if !ok { + return errors.New("unable to type assert data") + } + wsResp := WebsocketEventResponse{ Event: "SaveConfig", } var respCfg config.Config - err := json.Unmarshal(data.([]byte), &respCfg) + err := json.Unmarshal(d, &respCfg) if err != nil { wsResp.Error = err.Error() sendErr := client.SendWebsocketMessage(wsResp) @@ -794,11 +810,16 @@ func wsGetTickers(client *websocketClient, data interface{}) error { } func wsGetTicker(client *websocketClient, data interface{}) error { + d, ok := data.([]byte) + if !ok { + return errors.New("unable to type assert data") + } + wsResp := WebsocketEventResponse{ Event: "GetTicker", } var tickerReq WebsocketOrderbookTickerRequest - err := json.Unmarshal(data.([]byte), &tickerReq) + err := json.Unmarshal(d, &tickerReq) if err != nil { wsResp.Error = err.Error() sendErr := client.SendWebsocketMessage(wsResp) @@ -849,11 +870,16 @@ func wsGetOrderbooks(client *websocketClient, data interface{}) error { } func wsGetOrderbook(client *websocketClient, data interface{}) error { + d, ok := data.([]byte) + if !ok { + return errors.New("unable to type assert data") + } + wsResp := WebsocketEventResponse{ Event: "GetOrderbook", } var orderbookReq WebsocketOrderbookTickerRequest - err := json.Unmarshal(data.([]byte), &orderbookReq) + err := json.Unmarshal(d, &orderbookReq) if err != nil { wsResp.Error = err.Error() sendErr := client.SendWebsocketMessage(wsResp) diff --git a/engine/apiserver_test.go b/engine/apiserver_test.go index 33decd6f..ca955713 100644 --- a/engine/apiserver_test.go +++ b/engine/apiserver_test.go @@ -3,7 +3,7 @@ package engine import ( "encoding/json" "errors" - "io/ioutil" + "io" "net/http" "net/http/httptest" "os" @@ -253,7 +253,7 @@ func TestConfigAllJsonResponse(t *testing.T) { t.Error(err) } resp := makeHTTPGetRequest(t, c) - body, err := ioutil.ReadAll(resp.Body) + body, err := io.ReadAll(resp.Body) if err != nil { t.Error("Body not readable", err) } diff --git a/engine/datahistory_manager.go b/engine/datahistory_manager.go index 5a831826..69678220 100644 --- a/engine/datahistory_manager.go +++ b/engine/datahistory_manager.go @@ -121,7 +121,7 @@ func (m *DataHistoryManager) retrieveJobs() ([]*DataHistoryJob, error) { return nil, err } - var response []*DataHistoryJob + response := make([]*DataHistoryJob, 0, len(dbJobs)) for i := range dbJobs { dbJob, err := m.convertDBModelToJob(&dbJobs[i]) if err != nil { @@ -1332,13 +1332,13 @@ func (m *DataHistoryManager) GetAllJobStatusBetween(start, end time.Time) ([]*Da if err != nil { return nil, err } - var results []*DataHistoryJob + results := make([]*DataHistoryJob, len(dbJobs)) for i := range dbJobs { dbJob, err := m.convertDBModelToJob(&dbJobs[i]) if err != nil { return nil, err } - results = append(results, dbJob) + results[i] = dbJob } return results, nil } diff --git a/engine/exchange_manager.go b/engine/exchange_manager.go index 8b3dbfa8..3bf2d3ed 100644 --- a/engine/exchange_manager.go +++ b/engine/exchange_manager.go @@ -82,7 +82,7 @@ func (m *ExchangeManager) GetExchanges() ([]exchange.IBotExchange, error) { } m.m.Lock() defer m.m.Unlock() - var exchs []exchange.IBotExchange + exchs := make([]exchange.IBotExchange, 0, len(m.exchanges)) for _, x := range m.exchanges { exchs = append(exchs, x) } diff --git a/engine/exchange_manager_test.go b/engine/exchange_manager_test.go index 9f717281..14e9593a 100644 --- a/engine/exchange_manager_test.go +++ b/engine/exchange_manager_test.go @@ -44,7 +44,7 @@ func TestExchangeManagerGetExchanges(t *testing.T) { if err != nil { t.Error("no exchange manager found") } - if exchanges != nil { + if len(exchanges) != 0 { t.Error("unexpected value") } b := new(bitfinex.Bitfinex) diff --git a/engine/helpers.go b/engine/helpers.go index f8282dba..ddc2ab12 100644 --- a/engine/helpers.go +++ b/engine/helpers.go @@ -10,7 +10,6 @@ import ( "encoding/pem" "errors" "fmt" - "io/ioutil" "math/big" "net" "os" @@ -326,8 +325,8 @@ func (bot *Engine) GetExchangeOTPByName(exchName string) (string, error) { // GetAuthAPISupportedExchanges returns a list of auth api enabled exchanges func (bot *Engine) GetAuthAPISupportedExchanges() []string { - var exchangeNames []string exchanges := bot.GetExchanges() + exchangeNames := make([]string, 0, len(exchanges)) for x := range exchanges { if !exchanges[x].GetAuthenticatedAPISupport(exchange.RestAuthentication) && !exchanges[x].GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { @@ -443,7 +442,7 @@ func (bot *Engine) MapCurrenciesByExchange(p currency.Pairs, enabledExchangesOnl // GetExchangeNamesByCurrency returns a list of exchanges supporting // a currency pair based on whether the exchange is enabled or not func (bot *Engine) GetExchangeNamesByCurrency(p currency.Pair, enabled bool, assetType asset.Item) []string { - var exchanges []string + exchanges := make([]string, 0, len(bot.Config.Exchanges)) for x := range bot.Config.Exchanges { if enabled != bot.Config.Exchanges[x].Enabled { continue @@ -862,7 +861,7 @@ func checkCerts(certDir string) error { return genCert(certDir) } - pemData, err := ioutil.ReadFile(certFile) + pemData, err := os.ReadFile(certFile) if err != nil { return fmt.Errorf("unable to open TLS cert file: %s", err) } diff --git a/engine/helpers_test.go b/engine/helpers_test.go index 23dec7af..4babb96b 100644 --- a/engine/helpers_test.go +++ b/engine/helpers_test.go @@ -755,12 +755,9 @@ func TestGetSpecificOrderbook(t *testing.T) { t.Parallel() e := CreateTestBot(t) - var bids []orderbook.Item - bids = append(bids, orderbook.Item{Price: 1000, Amount: 1}) - base := orderbook.Base{ Pair: currency.NewPair(currency.BTC, currency.USD), - Bids: bids, + Bids: []orderbook.Item{{Price: 1000, Amount: 1}}, Exchange: "Bitstamp", Asset: asset.Spot, } diff --git a/engine/order_manager_test.go b/engine/order_manager_test.go index 9dc52595..d645bf87 100644 --- a/engine/order_manager_test.go +++ b/engine/order_manager_test.go @@ -383,8 +383,7 @@ func TestExists(t *testing.T) { if err := m.orderStore.add(o); err != nil { t.Error(err) } - b := m.orderStore.exists(o) - if !b { + if b := m.orderStore.exists(o); !b { t.Error("Expected true") } } diff --git a/engine/portfolio_manager.go b/engine/portfolio_manager.go index 4affcf4a..bfdeb309 100644 --- a/engine/portfolio_manager.go +++ b/engine/portfolio_manager.go @@ -240,7 +240,7 @@ func (m *portfolioManager) seedExchangeAccountInfo(accounts []account.Holdings) // getExchangeAccountInfo returns all the current enabled exchanges func (m *portfolioManager) getExchangeAccountInfo(exchanges []exchange.IBotExchange) []account.Holdings { - var response []account.Holdings + response := make([]account.Holdings, 0, len(exchanges)) for x := range exchanges { if exchanges[x] == nil || !exchanges[x].IsEnabled() { continue diff --git a/engine/rpcserver.go b/engine/rpcserver.go index 0ec5daf4..49659ab8 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" "net" "net/http" "os" @@ -398,15 +397,16 @@ func (s *RPCServer) GetTicker(ctx context.Context, r *gctrpc.GetTickerRequest) ( // enabled currency pairs func (s *RPCServer) GetTickers(ctx context.Context, _ *gctrpc.GetTickersRequest) (*gctrpc.GetTickersResponse, error) { activeTickers := s.GetAllActiveTickers(ctx) - var tickers []*gctrpc.Tickers + tickers := make([]*gctrpc.Tickers, len(activeTickers)) for x := range activeTickers { t := &gctrpc.Tickers{ Exchange: activeTickers[x].ExchangeName, + Tickers: make([]*gctrpc.TickerResponse, len(activeTickers[x].ExchangeValues)), } for y := range activeTickers[x].ExchangeValues { val := activeTickers[x].ExchangeValues[y] - t.Tickers = append(t.Tickers, &gctrpc.TickerResponse{ + t.Tickers[y] = &gctrpc.TickerResponse{ Pair: &gctrpc.CurrencyPair{ Delimiter: val.Pair.Delimiter, Base: val.Pair.Base.String(), @@ -420,9 +420,9 @@ func (s *RPCServer) GetTickers(ctx context.Context, _ *gctrpc.GetTickersRequest) Ask: val.Ask, Volume: val.Volume, PriceAth: val.PriceATH, - }) + } } - tickers = append(tickers, t) + tickers[x] = t } return &gctrpc.GetTickersResponse{Tickers: tickers}, nil @@ -489,7 +489,7 @@ func (s *RPCServer) GetOrderbooks(ctx context.Context, _ *gctrpc.GetOrderbooksRe if err != nil { return nil, err } - var obResponse []*gctrpc.Orderbooks + obResponse := make([]*gctrpc.Orderbooks, 0, len(exchanges)) var obs []*gctrpc.OrderbookResponse for x := range exchanges { if !exchanges[x].IsEnabled() { @@ -523,19 +523,20 @@ func (s *RPCServer) GetOrderbooks(ctx context.Context, _ *gctrpc.GetOrderbooksRe }, AssetType: assets[y].String(), LastUpdated: s.unixTimestamp(resp.LastUpdated), + Bids: make([]*gctrpc.OrderbookItem, len(resp.Bids)), + Asks: make([]*gctrpc.OrderbookItem, len(resp.Asks)), } for i := range resp.Bids { - ob.Bids = append(ob.Bids, &gctrpc.OrderbookItem{ + ob.Bids[i] = &gctrpc.OrderbookItem{ Amount: resp.Bids[i].Amount, Price: resp.Bids[i].Price, - }) + } } - for i := range resp.Asks { - ob.Asks = append(ob.Asks, &gctrpc.OrderbookItem{ + ob.Asks[i] = &gctrpc.OrderbookItem{ Amount: resp.Asks[i].Amount, Price: resp.Asks[i].Price, - }) + } } obs = append(obs, ob) } @@ -600,7 +601,7 @@ func (s *RPCServer) UpdateAccountInfo(ctx context.Context, r *gctrpc.GetAccountI } func createAccountInfoRequest(h account.Holdings) (*gctrpc.GetAccountInfoResponse, error) { - var accounts []*gctrpc.Account + accounts := make([]*gctrpc.Account, len(h.Accounts)) for x := range h.Accounts { var a gctrpc.Account a.Id = h.Accounts[x].ID @@ -621,7 +622,7 @@ func createAccountInfoRequest(h account.Holdings) (*gctrpc.GetAccountInfoRespons Borrowed: y.Borrowed, }) } - accounts = append(accounts, &a) + accounts[x] = &a } return &gctrpc.GetAccountInfoResponse{Exchange: h.Exchange, Accounts: accounts}, nil @@ -649,20 +650,20 @@ func (s *RPCServer) GetAccountInfoStream(r *gctrpc.GetAccountInfoRequest, stream return err } - var accounts []*gctrpc.Account + accounts := make([]*gctrpc.Account, len(initAcc.Accounts)) for x := range initAcc.Accounts { - var subAccounts []*gctrpc.AccountCurrencyInfo + subAccounts := make([]*gctrpc.AccountCurrencyInfo, len(initAcc.Accounts[x].Currencies)) for y := range initAcc.Accounts[x].Currencies { - subAccounts = append(subAccounts, &gctrpc.AccountCurrencyInfo{ + subAccounts[y] = &gctrpc.AccountCurrencyInfo{ Currency: initAcc.Accounts[x].Currencies[y].CurrencyName.String(), TotalValue: initAcc.Accounts[x].Currencies[y].Total, Hold: initAcc.Accounts[x].Currencies[y].Hold, - }) + } } - accounts = append(accounts, &gctrpc.Account{ + accounts[x] = &gctrpc.Account{ Id: initAcc.Accounts[x].ID, Currencies: subAccounts, - }) + } } err = stream.Send(&gctrpc.GetAccountInfoResponse{ @@ -691,37 +692,37 @@ func (s *RPCServer) GetAccountInfoStream(r *gctrpc.GetAccountInfoRequest, stream return errDispatchSystem } - d := *data.(*interface{}) - if d == nil { + d, ok := data.(*interface{}) + if !ok { return errors.New("unable to type assert data") } - acc, ok := d.(account.Holdings) + dd := *d + acc, ok := dd.(account.Holdings) if !ok { return errors.New("unable to type assert account holdings data") } - var accounts []*gctrpc.Account + accounts := make([]*gctrpc.Account, len(acc.Accounts)) for x := range acc.Accounts { - var subAccounts []*gctrpc.AccountCurrencyInfo + subAccounts := make([]*gctrpc.AccountCurrencyInfo, len(acc.Accounts[x].Currencies)) for y := range acc.Accounts[x].Currencies { - subAccounts = append(subAccounts, &gctrpc.AccountCurrencyInfo{ + subAccounts[y] = &gctrpc.AccountCurrencyInfo{ Currency: acc.Accounts[x].Currencies[y].CurrencyName.String(), TotalValue: acc.Accounts[x].Currencies[y].Total, Hold: acc.Accounts[x].Currencies[y].Hold, - }) + } } - accounts = append(accounts, &gctrpc.Account{ + accounts[x] = &gctrpc.Account{ Id: acc.Accounts[x].ID, Currencies: subAccounts, - }) + } } - err := stream.Send(&gctrpc.GetAccountInfoResponse{ + if err := stream.Send(&gctrpc.GetAccountInfoResponse{ Exchange: acc.Exchange, Accounts: accounts, - }) - if err != nil { + }); err != nil { return err } } @@ -734,15 +735,15 @@ func (s *RPCServer) GetConfig(_ context.Context, _ *gctrpc.GetConfigRequest) (*g // GetPortfolio returns the portfoliomanager details func (s *RPCServer) GetPortfolio(_ context.Context, _ *gctrpc.GetPortfolioRequest) (*gctrpc.GetPortfolioResponse, error) { - var addrs []*gctrpc.PortfolioAddress botAddrs := s.portfolioManager.GetAddresses() + addrs := make([]*gctrpc.PortfolioAddress, len(botAddrs)) for x := range botAddrs { - addrs = append(addrs, &gctrpc.PortfolioAddress{ + addrs[x] = &gctrpc.PortfolioAddress{ Address: botAddrs[x].Address, CoinType: botAddrs[x].CoinType.String(), Description: botAddrs[x].Description, Balance: botAddrs[x].Balance, - }) + } } resp := &gctrpc.GetPortfolioResponse{ @@ -838,9 +839,9 @@ func (s *RPCServer) GetForexProviders(_ context.Context, _ *gctrpc.GetForexProvi return nil, fmt.Errorf("forex providers is empty") } - var forexProviders []*gctrpc.ForexProvider + forexProviders := make([]*gctrpc.ForexProvider, len(providers)) for x := range providers { - forexProviders = append(forexProviders, &gctrpc.ForexProvider{ + forexProviders[x] = &gctrpc.ForexProvider{ Name: providers[x].Name, Enabled: providers[x].Enabled, Verbose: providers[x].Verbose, @@ -848,7 +849,7 @@ func (s *RPCServer) GetForexProviders(_ context.Context, _ *gctrpc.GetForexProvi ApiKey: providers[x].APIKey, ApiKeyLevel: int64(providers[x].APIKeyLvl), PrimaryProvider: providers[x].PrimaryProvider, - }) + } } return &gctrpc.GetForexProvidersResponse{ForexProviders: forexProviders}, nil } @@ -864,7 +865,7 @@ func (s *RPCServer) GetForexRates(_ context.Context, _ *gctrpc.GetForexRatesRequ return nil, fmt.Errorf("forex rates is empty") } - var forexRates []*gctrpc.ForexRatesConversion + forexRates := make([]*gctrpc.ForexRatesConversion, 0, len(rates)) for x := range rates { rate, err := rates[x].GetRate() if err != nil { @@ -952,9 +953,9 @@ func (s *RPCServer) GetOrders(ctx context.Context, r *gctrpc.GetOrdersRequest) ( return nil, err } - var orders []*gctrpc.OrderDetails + orders := make([]*gctrpc.OrderDetails, len(resp)) for x := range resp { - var trades []*gctrpc.TradeHistory + trades := make([]*gctrpc.TradeHistory, len(resp[x].Trades)) for i := range resp[x].Trades { t := &gctrpc.TradeHistory{ Id: resp[x].Trades[i].TID, @@ -969,7 +970,7 @@ func (s *RPCServer) GetOrders(ctx context.Context, r *gctrpc.GetOrdersRequest) ( if !resp[x].Trades[i].Timestamp.IsZero() { t.CreationTime = s.unixTimestamp(resp[x].Trades[i].Timestamp) } - trades = append(trades, t) + trades[i] = t } o := &gctrpc.OrderDetails{ Exchange: r.Exchange, @@ -994,7 +995,7 @@ func (s *RPCServer) GetOrders(ctx context.Context, r *gctrpc.GetOrdersRequest) ( if !resp[x].LastUpdated.IsZero() { o.UpdateTime = s.unixTimestamp(resp[x].LastUpdated) } - orders = append(orders, o) + orders[x] = o } return &gctrpc.GetOrdersResponse{Orders: orders}, nil @@ -1041,9 +1042,9 @@ func (s *RPCServer) GetManagedOrders(_ context.Context, r *gctrpc.GetOrdersReque return nil, err } - var orders []*gctrpc.OrderDetails + orders := make([]*gctrpc.OrderDetails, len(resp)) for x := range resp { - var trades []*gctrpc.TradeHistory + trades := make([]*gctrpc.TradeHistory, len(resp[x].Trades)) for i := range resp[x].Trades { t := &gctrpc.TradeHistory{ Id: resp[x].Trades[i].TID, @@ -1058,7 +1059,7 @@ func (s *RPCServer) GetManagedOrders(_ context.Context, r *gctrpc.GetOrdersReque if !resp[x].Trades[i].Timestamp.IsZero() { t.CreationTime = s.unixTimestamp(resp[x].Trades[i].Timestamp) } - trades = append(trades, t) + trades[i] = t } o := &gctrpc.OrderDetails{ Exchange: r.Exchange, @@ -1083,7 +1084,7 @@ func (s *RPCServer) GetManagedOrders(_ context.Context, r *gctrpc.GetOrdersReque if !resp[x].LastUpdated.IsZero() { o.UpdateTime = s.unixTimestamp(resp[x].LastUpdated) } - orders = append(orders, o) + orders[x] = o } return &gctrpc.GetOrdersResponse{Orders: orders}, nil @@ -1128,9 +1129,9 @@ func (s *RPCServer) GetOrder(ctx context.Context, r *gctrpc.GetOrderRequest) (*g if err != nil { return nil, fmt.Errorf("error whilst trying to retrieve info for order %s: %w", r.OrderId, err) } - var trades []*gctrpc.TradeHistory + trades := make([]*gctrpc.TradeHistory, len(result.Trades)) for i := range result.Trades { - trades = append(trades, &gctrpc.TradeHistory{ + trades[i] = &gctrpc.TradeHistory{ CreationTime: s.unixTimestamp(result.Trades[i].Timestamp), Id: result.Trades[i].TID, Price: result.Trades[i].Price, @@ -1140,7 +1141,7 @@ func (s *RPCServer) GetOrder(ctx context.Context, r *gctrpc.GetOrderRequest) (*g OrderSide: result.Trades[i].Side.String(), Fee: result.Trades[i].Fee, Total: result.Trades[i].Total, - }) + } } var creationTime, updateTime int64 @@ -1217,14 +1218,14 @@ func (s *RPCServer) SubmitOrder(ctx context.Context, r *gctrpc.SubmitOrderReques return &gctrpc.SubmitOrderResponse{}, err } - var trades []*gctrpc.Trades + trades := make([]*gctrpc.Trades, len(resp.Trades)) for i := range resp.Trades { - trades = append(trades, &gctrpc.Trades{ + trades[i] = &gctrpc.Trades{ Amount: resp.Trades[i].Amount, Price: resp.Trades[i].Price, Fee: resp.Trades[i].Fee, FeeAsset: resp.Trades[i].FeeAsset, - }) + } } return &gctrpc.SubmitOrderResponse{ @@ -1408,18 +1409,19 @@ func (s *RPCServer) CancelBatchOrders(ctx context.Context, r *gctrpc.CancelBatch } status := make(map[string]string) - var request []order.Cancel orders := strings.Split(r.OrdersId, ",") - for _, orderID := range orders { + request := make([]order.Cancel, len(orders)) + for x := range orders { + orderID := orders[x] status[orderID] = order.Cancelled.String() - request = append(request, order.Cancel{ + request[x] = order.Cancel{ AccountID: r.AccountId, ID: orderID, Side: order.Side(r.Side), WalletAddress: r.WalletAddress, Pair: pair, AssetType: assetType, - }) + } } // TODO: Change to order manager @@ -2118,12 +2120,13 @@ func (s *RPCServer) GetExchangeOrderbookStream(r *gctrpc.GetExchangeOrderbookStr return errDispatchSystem } - d := *data.(*interface{}) - if d == nil { + d, ok := data.(*interface{}) + if !ok { return errors.New("unable to type assert data") } - ob, ok := d.(orderbook.Base) + dd := *d + ob, ok := dd.(orderbook.Base) if !ok { return errors.New("unable to type assert orderbook data") } @@ -2201,12 +2204,13 @@ func (s *RPCServer) GetTickerStream(r *gctrpc.GetTickerStreamRequest, stream gct return errDispatchSystem } - d := *data.(*interface{}) - if d == nil { + d, ok := data.(*interface{}) + if !ok { return errors.New("unable to type assert data") } - t, ok := d.(ticker.Price) + dd := *d + t, ok := dd.(ticker.Price) if !ok { return errors.New("unable to type assert ticker data") } @@ -2259,12 +2263,13 @@ func (s *RPCServer) GetExchangeTickerStream(r *gctrpc.GetExchangeTickerStreamReq return errDispatchSystem } - d := *data.(*interface{}) - if d == nil { + d, ok := data.(*interface{}) + if !ok { return errors.New("unable to type assert data") } - t, ok := d.(ticker.Price) + dd := *d + t, ok := dd.(ticker.Price) if !ok { return errors.New("unable to type assert ticker data") } @@ -2453,16 +2458,16 @@ func (s *RPCServer) GetHistoricCandles(ctx context.Context, r *gctrpc.GetHistori } func fillMissingCandlesWithStoredTrades(startTime, endTime time.Time, klineItem *kline.Item) (*kline.Item, error) { - var response kline.Item - var candleTimes []time.Time + candleTimes := make([]time.Time, len(klineItem.Candles)) for i := range klineItem.Candles { - candleTimes = append(candleTimes, klineItem.Candles[i].Time) + candleTimes[i] = klineItem.Candles[i].Time } ranges, err := timeperiods.FindTimeRangesContainingData(startTime, endTime, klineItem.Interval.Duration(), candleTimes) if err != nil { return nil, err } + var response kline.Item for i := range ranges { if ranges[i].HasDataInRange { continue @@ -2552,24 +2557,30 @@ func (s *RPCServer) GCTScriptQuery(_ context.Context, r *gctrpc.GCTScriptQueryRe return &gctrpc.GCTScriptQueryResponse{Status: MsgStatusError, Data: err.Error()}, nil } - if v, f := gctscript.AllVMSync.Load(UUID); f { - resp := &gctrpc.GCTScriptQueryResponse{ - Status: MsgStatusOK, - Script: &gctrpc.GCTScript{ - Name: v.(*gctscript.VM).ShortName(), - UUID: v.(*gctscript.VM).ID.String(), - Path: v.(*gctscript.VM).Path, - NextRun: v.(*gctscript.VM).NextRun.String(), - }, - } - data, err := v.(*gctscript.VM).Read() - if err != nil { - return nil, err - } - resp.Data = string(data) - return resp, nil + v, f := gctscript.AllVMSync.Load(UUID) + if !f { + return &gctrpc.GCTScriptQueryResponse{Status: MsgStatusError, Data: "UUID not found"}, nil } - return &gctrpc.GCTScriptQueryResponse{Status: MsgStatusError, Data: "UUID not found"}, nil + + vm, ok := v.(*gctscript.VM) + if !ok { + return nil, errors.New("unable to type assert gctscript.VM") + } + resp := &gctrpc.GCTScriptQueryResponse{ + Status: MsgStatusOK, + Script: &gctrpc.GCTScript{ + Name: vm.ShortName(), + UUID: vm.ID.String(), + Path: vm.Path, + NextRun: vm.NextRun.String(), + }, + } + data, err := vm.Read() + if err != nil { + return nil, err + } + resp.Data = string(data) + return resp, nil } // GCTScriptExecute execute a script @@ -2614,15 +2625,21 @@ func (s *RPCServer) GCTScriptStop(_ context.Context, r *gctrpc.GCTScriptStopRequ return &gctrpc.GenericResponse{Status: MsgStatusError, Data: err.Error()}, nil // nolint:nilerr // error is returned in the generic response } - if v, f := gctscript.AllVMSync.Load(UUID); f { - err = v.(*gctscript.VM).Shutdown() - status := " terminated" - if err != nil { - status = " " + err.Error() - } - return &gctrpc.GenericResponse{Status: MsgStatusOK, Data: v.(*gctscript.VM).ID.String() + status}, nil + v, f := gctscript.AllVMSync.Load(UUID) + if !f { + return &gctrpc.GenericResponse{Status: MsgStatusError, Data: "no running script found"}, nil } - return &gctrpc.GenericResponse{Status: MsgStatusError, Data: "no running script found"}, nil + + vm, ok := v.(*gctscript.VM) + if !ok { + return nil, errors.New("unable to type assert gctscript.VM") + } + err = vm.Shutdown() + status := " terminated" + if err != nil { + status = " " + err.Error() + } + return &gctrpc.GenericResponse{Status: MsgStatusOK, Data: vm.ID.String() + status}, nil } // GCTScriptUpload upload a new script to ScriptPath @@ -2642,7 +2659,7 @@ func (s *RPCServer) GCTScriptUpload(_ context.Context, r *gctrpc.GCTScriptUpload return nil, fmt.Errorf("%s script found and overwrite set to false", r.ScriptName) } f := filepath.Join(gctscript.ScriptPath, "version_history") - err = os.MkdirAll(f, 0770) + err = os.MkdirAll(f, file.DefaultPermissionOctal) if err != nil { return nil, err } @@ -2726,7 +2743,7 @@ func (s *RPCServer) GCTScriptReadScript(_ context.Context, r *gctrpc.GCTScriptRe if !strings.HasPrefix(filename, filepath.Clean(gctscript.ScriptPath)+string(os.PathSeparator)) { return nil, fmt.Errorf("%s: invalid file path", filename) } - data, err := ioutil.ReadFile(filename) + data, err := os.ReadFile(filename) if err != nil { return nil, err } @@ -3271,9 +3288,9 @@ func (s *RPCServer) FindMissingSavedCandleIntervals(_ context.Context, r *gctrpc Pair: r.Pair, MissingPeriods: []string{}, } - var candleTimes []time.Time + candleTimes := make([]time.Time, len(klineItem.Candles)) for i := range klineItem.Candles { - candleTimes = append(candleTimes, klineItem.Candles[i].Time) + candleTimes[i] = klineItem.Candles[i].Time } var ranges []timeperiods.TimeRange ranges, err = timeperiods.FindTimeRangesContainingData(start, end, klineItem.Interval.Duration(), candleTimes) @@ -3373,9 +3390,9 @@ func (s *RPCServer) FindMissingSavedTradeIntervals(_ context.Context, r *gctrpc. Pair: r.Pair, MissingPeriods: []string{}, } - var tradeTimes []time.Time + tradeTimes := make([]time.Time, len(trades)) for i := range trades { - tradeTimes = append(tradeTimes, trades[i].Timestamp) + tradeTimes[i] = trades[i].Timestamp } var ranges []timeperiods.TimeRange ranges, err = timeperiods.FindTimeRangesContainingData(start, end, time.Hour, tradeTimes) @@ -3892,9 +3909,9 @@ func (s *RPCServer) GetActiveDataHistoryJobs(_ context.Context, _ *gctrpc.GetInf return nil, err } - var response []*gctrpc.DataHistoryJob + response := make([]*gctrpc.DataHistoryJob, len(jobs)) for i := range jobs { - response = append(response, &gctrpc.DataHistoryJob{ + response[i] = &gctrpc.DataHistoryJob{ Id: jobs[i].ID.String(), Nickname: jobs[i].Nickname, Exchange: jobs[i].Exchange, @@ -3919,7 +3936,7 @@ func (s *RPCServer) GetActiveDataHistoryJobs(_ context.Context, _ *gctrpc.GetInf SecondaryExchangeName: jobs[i].SecondaryExchangeSource, IssueTolerancePercentage: jobs[i].IssueTolerancePercentage, ReplaceOnIssue: jobs[i].ReplaceOnIssue, - }) + } } return &gctrpc.DataHistoryJobs{Results: response}, nil } @@ -3946,9 +3963,9 @@ func (s *RPCServer) GetDataHistoryJobsBetween(_ context.Context, r *gctrpc.GetDa if err != nil { return nil, err } - var respJobs []*gctrpc.DataHistoryJob + respJobs := make([]*gctrpc.DataHistoryJob, len(jobs)) for i := range jobs { - respJobs = append(respJobs, &gctrpc.DataHistoryJob{ + respJobs[i] = &gctrpc.DataHistoryJob{ Id: jobs[i].ID.String(), Nickname: jobs[i].Nickname, Exchange: jobs[i].Exchange, @@ -3973,7 +3990,7 @@ func (s *RPCServer) GetDataHistoryJobsBetween(_ context.Context, r *gctrpc.GetDa SecondaryExchangeName: jobs[i].SecondaryExchangeSource, IssueTolerancePercentage: jobs[i].IssueTolerancePercentage, ReplaceOnIssue: jobs[i].ReplaceOnIssue, - }) + } } return &gctrpc.DataHistoryJobs{ Results: respJobs, @@ -4309,17 +4326,15 @@ func (s *RPCServer) GetCollateral(ctx context.Context, r *gctrpc.GetCollateralRe if err != nil { return nil, err } - var calculators []order.CollateralCalculator - var acc *account.SubAccount - var subAccounts []string - creds, err := exch.GetBase().GetCredentials(ctx) if err != nil { return nil, err } + subAccounts := make([]string, len(ai.Accounts)) + var acc *account.SubAccount for i := range ai.Accounts { - subAccounts = append(subAccounts, ai.Accounts[i].ID) + subAccounts[i] = ai.Accounts[i].ID if ai.Accounts[i].ID == "main" && creds.SubAccount == "" { acc = &ai.Accounts[i] break @@ -4344,6 +4359,7 @@ func (s *RPCServer) GetCollateral(ctx context.Context, r *gctrpc.GetCollateralRe } } + calculators := make([]order.CollateralCalculator, 0, len(acc.Currencies)) for i := range acc.Currencies { total := decimal.NewFromFloat(acc.Currencies[i].Total) free := decimal.NewFromFloat(acc.Currencies[i].AvailableWithoutBorrow) diff --git a/engine/withdraw_manager.go b/engine/withdraw_manager.go index ce287d19..934fe486 100644 --- a/engine/withdraw_manager.go +++ b/engine/withdraw_manager.go @@ -99,7 +99,11 @@ func (m *WithdrawManager) WithdrawalEventByID(id string) (*withdraw.Response, er return nil, ErrNilSubsystem } if v := withdraw.Cache.Get(id); v != nil { - return v.(*withdraw.Response), nil + wdResp, ok := v.(*withdraw.Response) + if !ok { + return nil, errors.New("unable to type assert withdraw.Response") + } + return wdResp, nil } l, err := dbwithdraw.GetEventByUUID(id) diff --git a/exchanges/account/account.go b/exchanges/account/account.go index 4ca3b0c1..6848961a 100644 --- a/exchanges/account/account.go +++ b/exchanges/account/account.go @@ -26,15 +26,13 @@ func CollectBalances(accountBalances map[string][]Balance, assetType asset.Item) return nil, fmt.Errorf("%s, %w", assetType, asset.ErrNotSupported) } - accounts = make([]SubAccount, len(accountBalances)) - i := 0 + accounts = make([]SubAccount, 0, len(accountBalances)) for accountID, balances := range accountBalances { - accounts[i] = SubAccount{ + accounts = append(accounts, SubAccount{ ID: accountID, AssetType: assetType, Currencies: balances, - } - i++ + }) } return } @@ -42,16 +40,16 @@ func CollectBalances(accountBalances map[string][]Balance, assetType asset.Item) // SubscribeToExchangeAccount subcribes to your exchange account func SubscribeToExchangeAccount(exchange string) (dispatch.Pipe, error) { exchange = strings.ToLower(exchange) - service.Lock() + service.mu.Lock() acc, ok := service.accounts[exchange] if !ok { - service.Unlock() + service.mu.Unlock() return dispatch.Pipe{}, fmt.Errorf("%s exchange account holdings not found", exchange) } - defer service.Unlock() + defer service.mu.Unlock() return service.mux.Subscribe(acc.ID) } @@ -80,8 +78,8 @@ func GetHoldings(exch string, assetType asset.Item) (Holdings, error) { return Holdings{}, fmt.Errorf("assetType %v is invalid", assetType) } - service.Lock() - defer service.Unlock() + service.mu.Lock() + defer service.mu.Unlock() h, ok := service.accounts[exch] if !ok { return Holdings{}, errors.New("exchange account holdings not found") @@ -97,22 +95,22 @@ func GetHoldings(exch string, assetType asset.Item) (Holdings, error) { // Update updates holdings with new account info func (s *Service) Update(a *Holdings) error { exch := strings.ToLower(a.Exchange) - s.Lock() + s.mu.Lock() acc, ok := s.accounts[exch] if !ok { id, err := s.mux.GetID() if err != nil { - s.Unlock() + s.mu.Unlock() return err } s.accounts[exch] = &Account{h: a, ID: id} - s.Unlock() + s.mu.Unlock() return nil } acc.h.Accounts = a.Accounts - defer s.Unlock() + defer s.mu.Unlock() return s.mux.Publish([]uuid.UUID{acc.ID}, acc.h) } diff --git a/exchanges/account/account_types.go b/exchanges/account/account_types.go index bd9c2eaf..adb19ff7 100644 --- a/exchanges/account/account_types.go +++ b/exchanges/account/account_types.go @@ -20,7 +20,7 @@ var ( type Service struct { accounts map[string]*Account mux *dispatch.Mux - sync.Mutex + mu sync.Mutex } // Account holds a stream ID and a pointer to the exchange holdings diff --git a/exchanges/alphapoint/alphapoint_wrapper.go b/exchanges/alphapoint/alphapoint_wrapper.go index 0109fcf5..652be1b0 100644 --- a/exchanges/alphapoint/alphapoint_wrapper.go +++ b/exchanges/alphapoint/alphapoint_wrapper.go @@ -101,14 +101,14 @@ func (a *Alphapoint) UpdateAccountInfo(ctx context.Context, assetType asset.Item return response, err } - var balances []account.Balance + balances := make([]account.Balance, len(acc.Currencies)) for i := range acc.Currencies { - balances = append(balances, account.Balance{ + balances[i] = account.Balance{ CurrencyName: currency.NewCode(acc.Currencies[i].Name), Total: float64(acc.Currencies[i].Balance), Hold: float64(acc.Currencies[i].Hold), Free: float64(acc.Currencies[i].Balance) - float64(acc.Currencies[i].Hold), - }) + } } response.Accounts = append(response.Accounts, account.SubAccount{ @@ -181,18 +181,20 @@ func (a *Alphapoint) UpdateOrderbook(ctx context.Context, p currency.Pair, asset return orderBook, err } + orderBook.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { - orderBook.Bids = append(orderBook.Bids, orderbook.Item{ + orderBook.Bids[x] = orderbook.Item{ Amount: orderbookNew.Bids[x].Quantity, Price: orderbookNew.Bids[x].Price, - }) + } } + orderBook.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { - orderBook.Asks = append(orderBook.Asks, orderbook.Item{ + orderBook.Asks[x] = orderbook.Item{ Amount: orderbookNew.Asks[x].Quantity, Price: orderbookNew.Asks[x].Price, - }) + } } orderBook.Pair = p diff --git a/exchanges/asset/asset.go b/exchanges/asset/asset.go index 772cd991..b9106281 100644 --- a/exchanges/asset/asset.go +++ b/exchanges/asset/asset.go @@ -60,9 +60,9 @@ func (a Item) String() string { // Strings converts an asset type array to a string array func (a Items) Strings() []string { - var assets []string + assets := make([]string, len(a)) for x := range a { - assets = append(assets, string(a[x])) + assets[x] = a[x].String() } return assets } diff --git a/exchanges/binance/binance.go b/exchanges/binance/binance.go index 31794289..1df921b9 100644 --- a/exchanges/binance/binance.go +++ b/exchanges/binance/binance.go @@ -119,16 +119,15 @@ func (b *Binance) GetExchangeInfo(ctx context.Context) (ExchangeInfo, error) { // OrderBookDataRequestParams contains the following members // symbol: string of currency pair // limit: returned limit amount -func (b *Binance) GetOrderBook(ctx context.Context, obd OrderBookDataRequestParams) (OrderBook, error) { - var orderbook OrderBook +func (b *Binance) GetOrderBook(ctx context.Context, obd OrderBookDataRequestParams) (*OrderBook, error) { if err := b.CheckLimit(obd.Limit); err != nil { - return orderbook, err + return nil, err } params := url.Values{} symbol, err := b.FormatSymbol(obd.Symbol, asset.Spot) if err != nil { - return orderbook, err + return nil, err } params.Set("symbol", symbol) params.Set("limit", fmt.Sprintf("%d", obd.Limit)) @@ -138,52 +137,54 @@ func (b *Binance) GetOrderBook(ctx context.Context, obd OrderBookDataRequestPara exchange.RestSpotSupplementary, orderBookDepth+"?"+params.Encode(), orderbookLimit(obd.Limit), &resp); err != nil { - return orderbook, err + return nil, err } + orderbook := OrderBook{ + Bids: make([]OrderbookItem, len(resp.Bids)), + Asks: make([]OrderbookItem, len(resp.Asks)), + LastUpdateID: resp.LastUpdateID, + } for x := range resp.Bids { price, err := strconv.ParseFloat(resp.Bids[x][0], 64) if err != nil { - return orderbook, err + return nil, err } amount, err := strconv.ParseFloat(resp.Bids[x][1], 64) if err != nil { - return orderbook, err + return nil, err } - orderbook.Bids = append(orderbook.Bids, OrderbookItem{ + orderbook.Bids[x] = OrderbookItem{ Price: price, Quantity: amount, - }) + } } for x := range resp.Asks { price, err := strconv.ParseFloat(resp.Asks[x][0], 64) if err != nil { - return orderbook, err + return nil, err } amount, err := strconv.ParseFloat(resp.Asks[x][1], 64) if err != nil { - return orderbook, err + return nil, err } - orderbook.Asks = append(orderbook.Asks, OrderbookItem{ + orderbook.Asks[x] = OrderbookItem{ Price: price, Quantity: amount, - }) + } } - orderbook.LastUpdateID = resp.LastUpdateID - return orderbook, nil + return &orderbook, nil } // GetMostRecentTrades returns recent trade activity // limit: Up to 500 results returned func (b *Binance) GetMostRecentTrades(ctx context.Context, rtr RecentTradeRequestParams) ([]RecentTrade, error) { - var resp []RecentTrade - params := url.Values{} symbol, err := b.FormatSymbol(rtr.Symbol, asset.Spot) if err != nil { @@ -194,6 +195,7 @@ func (b *Binance) GetMostRecentTrades(ctx context.Context, rtr RecentTradeReques path := recentTrades + "?" + params.Encode() + var resp []RecentTrade return resp, b.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, spotDefaultRate, &resp) } @@ -356,14 +358,12 @@ func (b *Binance) batchAggregateTrades(ctx context.Context, arg *AggregatedTrade // startTime: startTime filter for kline data // endTime: endTime filter for the kline data func (b *Binance) GetSpotKline(ctx context.Context, arg *KlinesRequestParams) ([]CandleStick, error) { - var resp interface{} - var klineData []CandleStick - - params := url.Values{} symbol, err := b.FormatSymbol(arg.Symbol, asset.Spot) if err != nil { return nil, err } + + params := url.Values{} params.Set("symbol", symbol) params.Set("interval", arg.Interval) if arg.Limit != 0 { @@ -377,6 +377,7 @@ func (b *Binance) GetSpotKline(ctx context.Context, arg *KlinesRequestParams) ([ } path := candleStick + "?" + params.Encode() + var resp interface{} err = b.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, @@ -390,6 +391,8 @@ func (b *Binance) GetSpotKline(ctx context.Context, arg *KlinesRequestParams) ([ if !ok { return nil, errors.New("unable to type assert responseData") } + + klineData := make([]CandleStick, len(responseData)) for x := range responseData { individualData, ok := responseData[x].([]interface{}) if !ok { @@ -432,7 +435,7 @@ func (b *Binance) GetSpotKline(ctx context.Context, arg *KlinesRequestParams) ([ if candle.TakerBuyQuoteAssetVolume, err = convert.FloatFromString(individualData[10]); err != nil { return nil, err } - klineData = append(klineData, candle) + klineData[x] = candle } return klineData, nil } diff --git a/exchanges/binance/binance_cfutures.go b/exchanges/binance/binance_cfutures.go index a0f47406..00f62b7e 100644 --- a/exchanges/binance/binance_cfutures.go +++ b/exchanges/binance/binance_cfutures.go @@ -82,10 +82,18 @@ func (b *Binance) FuturesExchangeInfo(ctx context.Context) (CExchangeInfo, error } // GetFuturesOrderbook gets orderbook data for CoinMarginedFutures, -func (b *Binance) GetFuturesOrderbook(ctx context.Context, symbol currency.Pair, limit int64) (OrderBook, error) { - var resp OrderBook - var data OrderbookData +func (b *Binance) GetFuturesOrderbook(ctx context.Context, symbol currency.Pair, limit int64) (*OrderBook, error) { + symbolValue, err := b.FormatSymbol(symbol, asset.CoinMarginedFutures) + if err != nil { + return nil, err + } + params := url.Values{} + params.Set("symbol", symbolValue) + if limit > 0 && limit <= 1000 { + params.Set("limit", strconv.FormatInt(limit, 10)) + } + rateBudget := cFuturesDefaultRate switch { case limit == 5, limit == 10, limit == 20, limit == 50: @@ -97,48 +105,47 @@ func (b *Binance) GetFuturesOrderbook(ctx context.Context, symbol currency.Pair, case limit == 1000: rateBudget = cFuturesOrderbook1000Rate } - symbolValue, err := b.FormatSymbol(symbol, asset.CoinMarginedFutures) - if err != nil { - return resp, err - } - params.Set("symbol", symbolValue) - if limit > 0 && limit <= 1000 { - params.Set("limit", strconv.FormatInt(limit, 10)) - } + + var data OrderbookData err = b.SendHTTPRequest(ctx, exchange.RestCoinMargined, cfuturesOrderbook+params.Encode(), rateBudget, &data) if err != nil { - return resp, err + return nil, err } + + var resp OrderBook var price, quantity float64 + resp.Asks = make([]OrderbookItem, len(data.Asks)) for x := range data.Asks { price, err = strconv.ParseFloat(data.Asks[x][0], 64) if err != nil { - return resp, err + return nil, err } quantity, err = strconv.ParseFloat(data.Asks[x][1], 64) if err != nil { - return resp, err + return nil, err } - resp.Asks = append(resp.Asks, OrderbookItem{ + resp.Asks[x] = OrderbookItem{ Price: price, Quantity: quantity, - }) + } } + + resp.Bids = make([]OrderbookItem, len(data.Bids)) for y := range data.Bids { price, err = strconv.ParseFloat(data.Bids[y][0], 64) if err != nil { - return resp, err + return nil, err } quantity, err = strconv.ParseFloat(data.Bids[y][1], 64) if err != nil { - return resp, err + return nil, err } - resp.Bids = append(resp.Bids, OrderbookItem{ + resp.Bids[y] = OrderbookItem{ Price: price, Quantity: quantity, - }) + } } - return resp, nil + return &resp, nil } // GetFuturesPublicTrades gets recent public trades for CoinMarginedFutures, @@ -232,13 +239,11 @@ func (b *Binance) GetIndexAndMarkPrice(ctx context.Context, symbol, pair string) // GetFuturesKlineData gets futures kline data for CoinMarginedFutures, func (b *Binance) GetFuturesKlineData(ctx context.Context, symbol currency.Pair, interval string, limit int64, startTime, endTime time.Time) ([]FuturesCandleStick, error) { - var data [][10]interface{} - var resp []FuturesCandleStick params := url.Values{} if !symbol.IsEmpty() { symbolValue, err := b.FormatSymbol(symbol, asset.CoinMarginedFutures) if err != nil { - return resp, err + return nil, err } params.Set("symbol", symbolValue) } @@ -246,453 +251,462 @@ func (b *Binance) GetFuturesKlineData(ctx context.Context, symbol currency.Pair, params.Set("limit", strconv.FormatInt(limit, 10)) } if !common.StringDataCompare(validFuturesIntervals, interval) { - return resp, errors.New("invalid interval parsed") + return nil, errors.New("invalid interval parsed") } params.Set("interval", interval) if !startTime.IsZero() && !endTime.IsZero() { if startTime.After(endTime) { - return resp, errors.New("startTime cannot be after endTime") + return nil, errors.New("startTime cannot be after endTime") } params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10)) params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10)) } + + var data [][10]interface{} rateBudget := getKlineRateBudget(limit) err := b.SendHTTPRequest(ctx, exchange.RestCoinMargined, cfuturesKlineData+params.Encode(), rateBudget, &data) if err != nil { - return resp, err + return nil, err } - var floatData float64 - var strData string - var ok bool - var tempData FuturesCandleStick + + resp := make([]FuturesCandleStick, len(data)) for x := range data { + var floatData float64 + var strData string + var ok bool + var tempData FuturesCandleStick floatData, ok = data[x][0].(float64) if !ok { - return resp, errors.New("type assertion failed for open time") + return nil, errors.New("type assertion failed for open time") } tempData.OpenTime = time.Unix(int64(floatData), 0) strData, ok = data[x][1].(string) if !ok { - return resp, errors.New("type assertion failed for open") + return nil, errors.New("type assertion failed for open") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Open = floatData strData, ok = data[x][2].(string) if !ok { - return resp, errors.New("type assertion failed for high") + return nil, errors.New("type assertion failed for high") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.High = floatData strData, ok = data[x][3].(string) if !ok { - return resp, errors.New("type assertion failed for low") + return nil, errors.New("type assertion failed for low") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Low = floatData strData, ok = data[x][4].(string) if !ok { - return resp, errors.New("type assertion failed for close") + return nil, errors.New("type assertion failed for close") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Close = floatData strData, ok = data[x][5].(string) if !ok { - return resp, errors.New("type assertion failed for volume") + return nil, errors.New("type assertion failed for volume") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Volume = floatData floatData, ok = data[x][6].(float64) if !ok { - return resp, errors.New("type assertion failed for close time") + return nil, errors.New("type assertion failed for close time") } tempData.CloseTime = time.Unix(int64(floatData), 0) strData, ok = data[x][7].(string) if !ok { - return resp, errors.New("type assertion failed for base asset volume") + return nil, errors.New("type assertion failed for base asset volume") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.BaseAssetVolume = floatData floatData, ok = data[x][8].(float64) if !ok { - return resp, errors.New("type assertion failed for taker buy volume") + return nil, errors.New("type assertion failed for taker buy volume") } tempData.TakerBuyVolume = floatData strData, ok = data[x][9].(string) if !ok { - return resp, errors.New("type assertion failed for taker buy base asset volume") + return nil, errors.New("type assertion failed for taker buy base asset volume") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.TakerBuyBaseAssetVolume = floatData - resp = append(resp, tempData) + resp[x] = tempData } return resp, nil } // GetContinuousKlineData gets continuous kline data func (b *Binance) GetContinuousKlineData(ctx context.Context, pair, contractType, interval string, limit int64, startTime, endTime time.Time) ([]FuturesCandleStick, error) { - var data [][10]interface{} - var resp []FuturesCandleStick params := url.Values{} params.Set("pair", pair) if !common.StringDataCompare(validContractType, contractType) { - return resp, errors.New("invalid contractType") + return nil, errors.New("invalid contractType") } params.Set("contractType", contractType) if limit > 0 && limit <= 1500 { params.Set("limit", strconv.FormatInt(limit, 10)) } if !common.StringDataCompare(validFuturesIntervals, interval) { - return resp, errors.New("invalid interval parsed") + return nil, errors.New("invalid interval parsed") } params.Set("interval", interval) if !startTime.IsZero() && !endTime.IsZero() { if startTime.After(endTime) { - return resp, errors.New("startTime cannot be after endTime") + return nil, errors.New("startTime cannot be after endTime") } params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10)) params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10)) } rateBudget := getKlineRateBudget(limit) + var data [][10]interface{} err := b.SendHTTPRequest(ctx, exchange.RestCoinMargined, cfuturesContinuousKline+params.Encode(), rateBudget, &data) if err != nil { - return resp, err + return nil, err } - var floatData float64 - var strData string - var ok bool - var tempData FuturesCandleStick + + resp := make([]FuturesCandleStick, len(data)) for x := range data { + var floatData float64 + var strData string + var ok bool + var tempData FuturesCandleStick floatData, ok = data[x][0].(float64) if !ok { - return resp, errors.New("type assertion failed for open time") + return nil, errors.New("type assertion failed for open time") } tempData.OpenTime = time.Unix(int64(floatData), 0) strData, ok = data[x][1].(string) if !ok { - return resp, errors.New("type assertion failed for open") + return nil, errors.New("type assertion failed for open") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Open = floatData strData, ok = data[x][2].(string) if !ok { - return resp, errors.New("type assertion failed for high") + return nil, errors.New("type assertion failed for high") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.High = floatData strData, ok = data[x][3].(string) if !ok { - return resp, errors.New("type assertion failed for low") + return nil, errors.New("type assertion failed for low") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Low = floatData strData, ok = data[x][4].(string) if !ok { - return resp, errors.New("type assertion failed for close") + return nil, errors.New("type assertion failed for close") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Close = floatData strData, ok = data[x][5].(string) if !ok { - return resp, errors.New("type assertion failed for volume") + return nil, errors.New("type assertion failed for volume") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Volume = floatData floatData, ok = data[x][6].(float64) if !ok { - return resp, errors.New("type assertion failed for close time") + return nil, errors.New("type assertion failed for close time") } tempData.CloseTime = time.Unix(int64(floatData), 0) strData, ok = data[x][7].(string) if !ok { - return resp, errors.New("type assertion failed for base asset volume") + return nil, errors.New("type assertion failed for base asset volume") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.BaseAssetVolume = floatData floatData, ok = data[x][8].(float64) if !ok { - return resp, errors.New("type assertion failed for taker buy volume") + return nil, errors.New("type assertion failed for taker buy volume") } tempData.TakerBuyVolume = floatData strData, ok = data[x][9].(string) if !ok { - return resp, errors.New("type assertion failed for taker buy base asset volume") + return nil, errors.New("type assertion failed for taker buy base asset volume") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.TakerBuyBaseAssetVolume = floatData - resp = append(resp, tempData) + resp[x] = tempData } return resp, nil } // GetIndexPriceKlines gets continuous kline data func (b *Binance) GetIndexPriceKlines(ctx context.Context, pair, interval string, limit int64, startTime, endTime time.Time) ([]FuturesCandleStick, error) { - var data [][10]interface{} - var resp []FuturesCandleStick params := url.Values{} params.Set("pair", pair) if limit > 0 && limit <= 1500 { params.Set("limit", strconv.FormatInt(limit, 10)) } if !common.StringDataCompare(validFuturesIntervals, interval) { - return resp, errors.New("invalid interval parsed") + return nil, errors.New("invalid interval parsed") } params.Set("interval", interval) if !startTime.IsZero() && !endTime.IsZero() { if startTime.After(endTime) { - return resp, errors.New("startTime cannot be after endTime") + return nil, errors.New("startTime cannot be after endTime") } params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10)) params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10)) } + rateBudget := getKlineRateBudget(limit) + var data [][10]interface{} err := b.SendHTTPRequest(ctx, exchange.RestCoinMargined, cfuturesIndexKline+params.Encode(), rateBudget, &data) if err != nil { - return resp, err + return nil, err } - var floatData float64 - var strData string - var ok bool - var tempData FuturesCandleStick + + resp := make([]FuturesCandleStick, len(data)) for x := range data { + var floatData float64 + var strData string + var ok bool + var tempData FuturesCandleStick floatData, ok = data[x][0].(float64) if !ok { - return resp, errors.New("type assertion failed for open time") + return nil, errors.New("type assertion failed for open time") } tempData.OpenTime = time.Unix(int64(floatData), 0) strData, ok = data[x][1].(string) if !ok { - return resp, errors.New("type assertion failed for open") + return nil, errors.New("type assertion failed for open") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Open = floatData strData, ok = data[x][2].(string) if !ok { - return resp, errors.New("type assertion failed for high") + return nil, errors.New("type assertion failed for high") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.High = floatData strData, ok = data[x][3].(string) if !ok { - return resp, errors.New("type assertion failed for low") + return nil, errors.New("type assertion failed for low") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Low = floatData strData, ok = data[x][4].(string) if !ok { - return resp, errors.New("type assertion failed for close") + return nil, errors.New("type assertion failed for close") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Close = floatData strData, ok = data[x][5].(string) if !ok { - return resp, errors.New("type assertion failed for volume") + return nil, errors.New("type assertion failed for volume") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Volume = floatData floatData, ok = data[x][6].(float64) if !ok { - return resp, errors.New("type assertion failed for close time") + return nil, errors.New("type assertion failed for close time") } tempData.CloseTime = time.Unix(int64(floatData), 0) strData, ok = data[x][7].(string) if !ok { - return resp, errors.New("type assertion failed for base asset volume") + return nil, errors.New("type assertion failed for base asset volume") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.BaseAssetVolume = floatData floatData, ok = data[x][8].(float64) if !ok { - return resp, errors.New("type assertion failed for taker buy volume") + return nil, errors.New("type assertion failed for taker buy volume") } tempData.TakerBuyVolume = floatData strData, ok = data[x][9].(string) if !ok { - return resp, errors.New("type assertion failed for taker buy base asset volume") + return nil, errors.New("type assertion failed for taker buy base asset volume") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.TakerBuyBaseAssetVolume = floatData - resp = append(resp, tempData) + resp[x] = tempData } return resp, nil } // GetMarkPriceKline gets mark price kline data func (b *Binance) GetMarkPriceKline(ctx context.Context, symbol currency.Pair, interval string, limit int64, startTime, endTime time.Time) ([]FuturesCandleStick, error) { - var data [][10]interface{} - var resp []FuturesCandleStick - params := url.Values{} symbolValue, err := b.FormatSymbol(symbol, asset.CoinMarginedFutures) if err != nil { - return resp, err + return nil, err } + params := url.Values{} params.Set("symbol", symbolValue) if limit > 0 && limit <= 1500 { params.Set("limit", strconv.FormatInt(limit, 10)) } if !common.StringDataCompare(validFuturesIntervals, interval) { - return resp, errors.New("invalid interval parsed") + return nil, errors.New("invalid interval parsed") } params.Set("interval", interval) if !startTime.IsZero() && !endTime.IsZero() { if startTime.After(endTime) { - return resp, errors.New("startTime cannot be after endTime") + return nil, errors.New("startTime cannot be after endTime") } params.Set("start_time", strconv.FormatInt(startTime.Unix(), 10)) params.Set("end_time", strconv.FormatInt(endTime.Unix(), 10)) } + + var data [][10]interface{} rateBudget := getKlineRateBudget(limit) err = b.SendHTTPRequest(ctx, exchange.RestCoinMargined, cfuturesMarkPriceKline+params.Encode(), rateBudget, &data) if err != nil { - return resp, err + return nil, err } - var floatData float64 - var strData string - var ok bool - var tempData FuturesCandleStick + + resp := make([]FuturesCandleStick, len(data)) for x := range data { + var floatData float64 + var strData string + var ok bool + var tempData FuturesCandleStick floatData, ok = data[x][0].(float64) if !ok { - return resp, errors.New("type assertion failed for open time") + return nil, errors.New("type assertion failed for open time") } tempData.OpenTime = time.Unix(int64(floatData), 0) strData, ok = data[x][1].(string) if !ok { - return resp, errors.New("type assertion failed for open") + return nil, errors.New("type assertion failed for open") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Open = floatData strData, ok = data[x][2].(string) if !ok { - return resp, errors.New("type assertion failed for high") + return nil, errors.New("type assertion failed for high") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.High = floatData strData, ok = data[x][3].(string) if !ok { - return resp, errors.New("type assertion failed for low") + return nil, errors.New("type assertion failed for low") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Low = floatData strData, ok = data[x][4].(string) if !ok { - return resp, errors.New("type assertion failed for close") + return nil, errors.New("type assertion failed for close") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Close = floatData strData, ok = data[x][5].(string) if !ok { - return resp, errors.New("type assertion failed for volume") + return nil, errors.New("type assertion failed for volume") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Volume = floatData floatData, ok = data[x][6].(float64) if !ok { - return resp, errors.New("type assertion failed for close time") + return nil, errors.New("type assertion failed for close time") } tempData.CloseTime = time.Unix(int64(floatData), 0) strData, ok = data[x][7].(string) if !ok { - return resp, errors.New("type assertion failed for base asset volume") + return nil, errors.New("type assertion failed for base asset volume") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.BaseAssetVolume = floatData floatData, ok = data[x][8].(float64) if !ok { - return resp, errors.New("type assertion failed for taker buy volume") + return nil, errors.New("type assertion failed for taker buy volume") } tempData.TakerBuyVolume = floatData strData, ok = data[x][9].(string) if !ok { - return resp, errors.New("type assertion failed for taker buy base asset volume") + return nil, errors.New("type assertion failed for taker buy base asset volume") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.TakerBuyBaseAssetVolume = floatData - resp = append(resp, tempData) + resp[x] = tempData } return resp, nil } @@ -1466,12 +1480,12 @@ func (b *Binance) FuturesPositionsADLEstimate(ctx context.Context, symbol curren // FetchCoinMarginExchangeLimits fetches coin margined order execution limits func (b *Binance) FetchCoinMarginExchangeLimits(ctx context.Context) ([]order.MinMaxLevel, error) { - var limits []order.MinMaxLevel coinFutures, err := b.FuturesExchangeInfo(ctx) if err != nil { return nil, err } + limits := make([]order.MinMaxLevel, 0, len(coinFutures.Symbols)) for x := range coinFutures.Symbols { symbol := strings.Split(coinFutures.Symbols[x].Symbol, currency.UnderscoreDelimiter) var cp currency.Pair diff --git a/exchanges/binance/binance_live_test.go b/exchanges/binance/binance_live_test.go index 843210bd..5ea6bb72 100644 --- a/exchanges/binance/binance_live_test.go +++ b/exchanges/binance/binance_live_test.go @@ -36,6 +36,7 @@ func TestMain(m *testing.M) { log.Fatal("Binance setup error", err) } b.setupOrderbookManager() + request.MaxRequestJobs = 100 b.Websocket.DataHandler = sharedtestvalues.GetWebsocketInterfaceChannelOverride() log.Printf(sharedtestvalues.LiveTesting, b.Name) os.Exit(m.Run()) diff --git a/exchanges/binance/binance_mock_test.go b/exchanges/binance/binance_mock_test.go index 2e54e6bc..695423ed 100644 --- a/exchanges/binance/binance_mock_test.go +++ b/exchanges/binance/binance_mock_test.go @@ -12,6 +12,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/exchanges/mock" + "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" ) @@ -57,6 +58,7 @@ func TestMain(m *testing.M) { log.Fatal(err) } } + request.MaxRequestJobs = 100 log.Printf(sharedtestvalues.MockTesting, b.Name) os.Exit(m.Run()) } diff --git a/exchanges/binance/binance_ufutures.go b/exchanges/binance/binance_ufutures.go index ad4879c3..e812f974 100644 --- a/exchanges/binance/binance_ufutures.go +++ b/exchanges/binance/binance_ufutures.go @@ -84,22 +84,22 @@ func (b *Binance) UExchangeInfo(ctx context.Context) (UFuturesExchangeInfo, erro } // UFuturesOrderbook gets orderbook data for usdt margined futures -func (b *Binance) UFuturesOrderbook(ctx context.Context, symbol currency.Pair, limit int64) (OrderBook, error) { - var resp OrderBook - var data OrderbookData - params := url.Values{} +func (b *Binance) UFuturesOrderbook(ctx context.Context, symbol currency.Pair, limit int64) (*OrderBook, error) { symbolValue, err := b.FormatSymbol(symbol, asset.USDTMarginedFutures) if err != nil { - return resp, err + return nil, err } + + params := url.Values{} params.Set("symbol", symbolValue) strLimit := strconv.FormatInt(limit, 10) if strLimit != "" { if !common.StringDataCompare(uValidOBLimits, strLimit) { - return resp, fmt.Errorf("invalid limit: %v", limit) + return nil, fmt.Errorf("invalid limit: %v", limit) } params.Set("limit", strLimit) } + rateBudget := uFuturesDefaultRate switch { case limit == 5, limit == 10, limit == 20, limit == 50: @@ -111,42 +111,50 @@ func (b *Binance) UFuturesOrderbook(ctx context.Context, symbol currency.Pair, l case limit == 1000: rateBudget = uFuturesOrderbook1000Rate } + + var data OrderbookData err = b.SendHTTPRequest(ctx, exchange.RestUSDTMargined, ufuturesOrderbook+params.Encode(), rateBudget, &data) if err != nil { - return resp, err + return nil, err } - resp.Symbol = symbolValue - resp.LastUpdateID = data.LastUpdateID + + resp := OrderBook{ + Symbol: symbolValue, + LastUpdateID: data.LastUpdateID, + Bids: make([]OrderbookItem, len(data.Bids)), + Asks: make([]OrderbookItem, len(data.Asks)), + } + var price, quantity float64 for x := range data.Asks { price, err = strconv.ParseFloat(data.Asks[x][0], 64) if err != nil { - return resp, err + return nil, err } quantity, err = strconv.ParseFloat(data.Asks[x][1], 64) if err != nil { - return resp, err + return nil, err } - resp.Asks = append(resp.Asks, OrderbookItem{ + resp.Asks[x] = OrderbookItem{ Price: price, Quantity: quantity, - }) + } } for y := range data.Bids { price, err = strconv.ParseFloat(data.Bids[y][0], 64) if err != nil { - return resp, err + return nil, err } quantity, err = strconv.ParseFloat(data.Bids[y][1], 64) if err != nil { - return resp, err + return nil, err } - resp.Bids = append(resp.Bids, OrderbookItem{ + resp.Bids[y] = OrderbookItem{ Price: price, Quantity: quantity, - }) + } } - return resp, nil + return &resp, nil } // URecentTrades gets recent trades for usdt margined futures @@ -212,16 +220,14 @@ func (b *Binance) UCompressedTrades(ctx context.Context, symbol currency.Pair, f // UKlineData gets kline data for usdt margined futures func (b *Binance) UKlineData(ctx context.Context, symbol currency.Pair, interval string, limit int64, startTime, endTime time.Time) ([]FuturesCandleStick, error) { - var data [][10]interface{} - var resp []FuturesCandleStick params := url.Values{} symbolValue, err := b.FormatSymbol(symbol, asset.USDTMarginedFutures) if err != nil { - return resp, err + return nil, err } params.Set("symbol", symbolValue) if !common.StringDataCompare(validFuturesIntervals, interval) { - return resp, errors.New("invalid interval") + return nil, errors.New("invalid interval") } params.Set("interval", interval) if limit > 0 && limit <= 1500 { @@ -229,7 +235,7 @@ func (b *Binance) UKlineData(ctx context.Context, symbol currency.Pair, interval } if !startTime.IsZero() && !endTime.IsZero() { if startTime.After(endTime) { - return resp, errors.New("startTime cannot be after endTime") + return nil, errors.New("startTime cannot be after endTime") } params.Set("startTime", timeString(startTime)) params.Set("endTime", timeString(endTime)) @@ -245,71 +251,76 @@ func (b *Binance) UKlineData(ctx context.Context, symbol currency.Pair, interval case limit > 1000: rateBudget = uFuturesKlineMaxRate } + + var data [][10]interface{} err = b.SendHTTPRequest(ctx, exchange.RestUSDTMargined, ufuturesKlineData+params.Encode(), rateBudget, &data) if err != nil { - return resp, err + return nil, err } - var tempData FuturesCandleStick - var floatData float64 - var strData string - var ok bool + + resp := make([]FuturesCandleStick, len(data)) for x := range data { + var tempData FuturesCandleStick + var floatData float64 + var strData string + var ok bool + floatData, ok = data[x][0].(float64) if !ok { - return resp, errors.New("type assertion failed for opentime") + return nil, errors.New("type assertion failed for opentime") } tempData.OpenTime, err = convert.TimeFromUnixTimestampFloat(floatData) if err != nil { - return resp, err + return nil, err } strData, ok = data[x][1].(string) if !ok { - return resp, errors.New("type assertion failed for open") + return nil, errors.New("type assertion failed for open") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Open = floatData strData, ok = data[x][2].(string) if !ok { - return resp, errors.New("type assertion failed for high") + return nil, errors.New("type assertion failed for high") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.High = floatData strData, ok = data[x][3].(string) if !ok { - return resp, errors.New("type assertion failed for low") + return nil, errors.New("type assertion failed for low") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Low = floatData strData, ok = data[x][4].(string) if !ok { - return resp, errors.New("type assertion failed for close") + return nil, errors.New("type assertion failed for close") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Close = floatData strData, ok = data[x][5].(string) if !ok { - return resp, errors.New("type assertion failed for volume") + return nil, errors.New("type assertion failed for volume") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.Volume = floatData floatData, ok = data[x][6].(float64) if !ok { - return resp, errors.New("type assertion failed for close time") + return nil, errors.New("type assertion failed for close time") } tempData.CloseTime, err = convert.TimeFromUnixTimestampFloat(floatData) if err != nil { @@ -317,28 +328,28 @@ func (b *Binance) UKlineData(ctx context.Context, symbol currency.Pair, interval } strData, ok = data[x][7].(string) if !ok { - return resp, errors.New("type assertion failed base asset volume") + return nil, errors.New("type assertion failed base asset volume") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.BaseAssetVolume = floatData floatData, ok = data[x][8].(float64) if !ok { - return resp, errors.New("type assertion failed for taker buy volume") + return nil, errors.New("type assertion failed for taker buy volume") } tempData.TakerBuyVolume = floatData strData, ok = data[x][9].(string) if !ok { - return resp, errors.New("type assertion failed for taker buy base asset volume") + return nil, errors.New("type assertion failed for taker buy base asset volume") } floatData, err = strconv.ParseFloat(strData, 64) if err != nil { - return resp, err + return nil, err } tempData.TakerBuyBaseAssetVolume = floatData - resp = append(resp, tempData) + resp[x] = tempData } return resp, nil } @@ -1139,12 +1150,12 @@ func (b *Binance) GetFundingRates(ctx context.Context, symbol currency.Pair, lim // FetchUSDTMarginExchangeLimits fetches USDT margined order execution limits func (b *Binance) FetchUSDTMarginExchangeLimits(ctx context.Context) ([]order.MinMaxLevel, error) { - var limits []order.MinMaxLevel usdtFutures, err := b.UExchangeInfo(ctx) if err != nil { return nil, err } + limits := make([]order.MinMaxLevel, 0, len(usdtFutures.Symbols)) for x := range usdtFutures.Symbols { var cp currency.Pair cp, err = currency.NewPairFromStrings(usdtFutures.Symbols[x].BaseAsset, diff --git a/exchanges/binance/binance_websocket.go b/exchanges/binance/binance_websocket.go index a2212aef..b6b71231 100644 --- a/exchanges/binance/binance_websocket.go +++ b/exchanges/binance/binance_websocket.go @@ -482,31 +482,32 @@ func (b *Binance) SeedLocalCache(ctx context.Context, p currency.Pair) error { if err != nil { return err } - return b.SeedLocalCacheWithBook(p, &ob) + return b.SeedLocalCacheWithBook(p, ob) } // SeedLocalCacheWithBook seeds the local orderbook cache func (b *Binance) SeedLocalCacheWithBook(p currency.Pair, orderbookNew *OrderBook) error { - var newOrderBook orderbook.Base + newOrderBook := orderbook.Base{ + Pair: p, + Asset: asset.Spot, + Exchange: b.Name, + LastUpdateID: orderbookNew.LastUpdateID, + VerifyOrderbook: b.CanVerifyOrderbook, + Bids: make(orderbook.Items, len(orderbookNew.Bids)), + Asks: make(orderbook.Items, len(orderbookNew.Asks)), + } for i := range orderbookNew.Bids { - newOrderBook.Bids = append(newOrderBook.Bids, orderbook.Item{ + newOrderBook.Bids[i] = orderbook.Item{ Amount: orderbookNew.Bids[i].Quantity, Price: orderbookNew.Bids[i].Price, - }) + } } for i := range orderbookNew.Asks { - newOrderBook.Asks = append(newOrderBook.Asks, orderbook.Item{ + newOrderBook.Asks[i] = orderbook.Item{ Amount: orderbookNew.Asks[i].Quantity, Price: orderbookNew.Asks[i].Price, - }) + } } - - newOrderBook.Pair = p - newOrderBook.Asset = asset.Spot - newOrderBook.Exchange = b.Name - newOrderBook.LastUpdateID = orderbookNew.LastUpdateID - newOrderBook.VerifyOrderbook = b.CanVerifyOrderbook - return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } @@ -626,7 +627,7 @@ func (b *Binance) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription // ProcessUpdate processes the websocket orderbook update func (b *Binance) ProcessUpdate(cp currency.Pair, a asset.Item, ws *WebsocketDepthStream) error { - var updateBid []orderbook.Item + updateBid := make([]orderbook.Item, len(ws.UpdateBids)) for i := range ws.UpdateBids { price, ok := ws.UpdateBids[i][0].(string) if !ok { @@ -644,10 +645,10 @@ func (b *Binance) ProcessUpdate(cp currency.Pair, a asset.Item, ws *WebsocketDep if err != nil { return err } - updateBid = append(updateBid, orderbook.Item{Price: p, Amount: a}) + updateBid[i] = orderbook.Item{Price: p, Amount: a} } - var updateAsk []orderbook.Item + updateAsk := make([]orderbook.Item, len(ws.UpdateAsks)) for i := range ws.UpdateAsks { price, ok := ws.UpdateAsks[i][0].(string) if !ok { @@ -665,7 +666,7 @@ func (b *Binance) ProcessUpdate(cp currency.Pair, a asset.Item, ws *WebsocketDep if err != nil { return err } - updateAsk = append(updateAsk, orderbook.Item{Price: p, Amount: a}) + updateAsk[i] = orderbook.Item{Price: p, Amount: a} } return b.Websocket.Orderbook.Update(&buffer.Update{ diff --git a/exchanges/binance/binance_wrapper.go b/exchanges/binance/binance_wrapper.go index aed5e228..e155f7f4 100644 --- a/exchanges/binance/binance_wrapper.go +++ b/exchanges/binance/binance_wrapper.go @@ -657,7 +657,7 @@ func (b *Binance) UpdateOrderbook(ctx context.Context, p currency.Pair, assetTyp Asset: assetType, VerifyOrderbook: b.CanVerifyOrderbook, } - var orderbookNew OrderBook + var orderbookNew *OrderBook var err error switch assetType { case asset.Spot, asset.Margin: @@ -673,17 +673,20 @@ func (b *Binance) UpdateOrderbook(ctx context.Context, p currency.Pair, assetTyp if err != nil { return book, err } + + book.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[x] = orderbook.Item{ Amount: orderbookNew.Bids[x].Quantity, Price: orderbookNew.Bids[x].Price, - }) + } } + book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[x] = orderbook.Item{ Amount: orderbookNew.Asks[x].Quantity, Price: orderbookNew.Asks[x].Price, - }) + } } err = book.Process() @@ -834,15 +837,16 @@ func (b *Binance) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (r // GetRecentTrades returns the most recent trades for a currency and asset func (b *Binance) GetRecentTrades(ctx context.Context, p currency.Pair, assetType asset.Item) ([]trade.Data, error) { - var resp []trade.Data - limit := 1000 + const limit = 1000 tradeData, err := b.GetMostRecentTrades(ctx, RecentTradeRequestParams{p, limit}) if err != nil { return nil, err } + + resp := make([]trade.Data, len(tradeData)) for i := range tradeData { - resp = append(resp, trade.Data{ + resp[i] = trade.Data{ TID: strconv.FormatInt(tradeData[i].ID, 10), Exchange: b.Name, CurrencyPair: p, @@ -850,7 +854,7 @@ func (b *Binance) GetRecentTrades(ctx context.Context, p currency.Pair, assetTyp Price: tradeData[i].Price, Amount: tradeData[i].Quantity, Timestamp: tradeData[i].Time, - }) + } } if b.IsSaveTradeDataEnabled() { err := trade.AddTradesToBuffer(b.Name, resp...) @@ -874,11 +878,11 @@ func (b *Binance) GetHistoricTrades(ctx context.Context, p currency.Pair, a asse if err != nil { return nil, err } - var result []trade.Data + result := make([]trade.Data, len(trades)) exName := b.GetName() for i := range trades { t := trades[i].toTradeData(p, exName, a) - result = append(result, *t) + result[i] = *t } return result, nil } diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index f7a071ba..dd052c6f 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -148,7 +148,7 @@ func baseMarginInfo(data []interface{}) (MarginInfoV2, error) { } func symbolMarginInfo(data []interface{}) ([]MarginInfoV2, error) { - var resp []MarginInfoV2 + resp := make([]MarginInfoV2, len(data)) for x := range data { var tempResp MarginInfoV2 tempData, ok := data[x].([]interface{}) @@ -183,7 +183,7 @@ func symbolMarginInfo(data []interface{}) ([]MarginInfoV2, error) { if !ok { return nil, fmt.Errorf("%w for BestBidAmount", errTypeAssert) } - resp = append(resp, tempResp) + resp[x] = tempResp } return resp, nil } @@ -381,7 +381,6 @@ func (b *Bitfinex) GetAccountInfoV2(ctx context.Context) (AccountV2Data, error) // GetV2Balances gets v2 balances func (b *Bitfinex) GetV2Balances(ctx context.Context) ([]WalletDataV2, error) { - var resp []WalletDataV2 var data [][4]interface{} err := b.SendAuthenticatedHTTPRequestV2(ctx, exchange.RestSpot, http.MethodPost, @@ -390,8 +389,9 @@ func (b *Bitfinex) GetV2Balances(ctx context.Context) ([]WalletDataV2, error) { &data, getAccountFees) if err != nil { - return resp, err + return nil, err } + resp := make([]WalletDataV2, len(data)) for x := range data { wType, ok := data[x][0].(string) if !ok { @@ -409,12 +409,12 @@ func (b *Bitfinex) GetV2Balances(ctx context.Context) ([]WalletDataV2, error) { if !ok { return resp, fmt.Errorf("%v GetV2Balances: %w for unsettledInterest", b.Name, errTypeAssert) } - resp = append(resp, WalletDataV2{ + resp[x] = WalletDataV2{ WalletType: wType, Currency: curr, Balance: bal, UnsettledInterest: unsettledInterest, - }) + } } return resp, nil } @@ -435,9 +435,6 @@ func (b *Bitfinex) GetMarginPairs(ctx context.Context) ([]string, error) { // GetDerivativeStatusInfo gets status data for the queried derivative func (b *Bitfinex) GetDerivativeStatusInfo(ctx context.Context, keys, startTime, endTime string, sort, limit int64) ([]DerivativeDataResponse, error) { - var result [][]interface{} - var finalResp []DerivativeDataResponse - params := url.Values{} params.Set("keys", keys) if startTime != "" { @@ -452,12 +449,15 @@ func (b *Bitfinex) GetDerivativeStatusInfo(ctx context.Context, keys, startTime, if limit != 0 { params.Set("limit", strconv.FormatInt(limit, 10)) } + + var result [][]interface{} path := bitfinexAPIVersion2 + bitfinexDerivativeData + params.Encode() err := b.SendHTTPRequest(ctx, exchange.RestSpot, path, &result, status) if err != nil { - return finalResp, err + return nil, err } + finalResp := make([]DerivativeDataResponse, len(result)) for z := range result { if len(result[z]) < 19 { return finalResp, fmt.Errorf("%v GetDerivativeStatusInfo: invalid response, array length too small, check api docs for updates", b.Name) @@ -507,7 +507,7 @@ func (b *Bitfinex) GetDerivativeStatusInfo(ctx context.Context, keys, startTime, t, ) } - finalResp = append(finalResp, response) + finalResp[z] = response } return finalResp, nil } @@ -526,82 +526,198 @@ func (b *Bitfinex) GetTickerBatch(ctx context.Context) (map[string]Ticker, error var tickers = make(map[string]Ticker) for x := range response { + symbol, ok := response[x][0].(string) + if !ok { + return nil, errors.New("unable to type assert symbol") + } + + var t Ticker if len(response[x]) > 11 { - tickers[response[x][0].(string)] = Ticker{ - FlashReturnRate: response[x][1].(float64), - Bid: response[x][2].(float64), - BidPeriod: int64(response[x][3].(float64)), - BidSize: response[x][4].(float64), - Ask: response[x][5].(float64), - AskPeriod: int64(response[x][6].(float64)), - AskSize: response[x][7].(float64), - DailyChange: response[x][8].(float64), - DailyChangePerc: response[x][9].(float64), - Last: response[x][10].(float64), - Volume: response[x][11].(float64), - High: response[x][12].(float64), - Low: response[x][13].(float64), - FFRAmountAvailable: response[x][16].(float64), + if t.FlashReturnRate, ok = response[x][1].(float64); !ok { + return nil, errors.New("unable to type assert flashReturnRate") } + if t.Bid, ok = response[x][2].(float64); !ok { + return nil, errors.New("unable to type assert bid") + } + var bidPeriod float64 + bidPeriod, ok = response[x][3].(float64) + if !ok { + return nil, errors.New("unable to type assert bidPeriod") + } + t.BidPeriod = int64(bidPeriod) + if t.BidSize, ok = response[x][4].(float64); !ok { + return nil, errors.New("unable to type assert bidSize") + } + if t.Ask, ok = response[x][5].(float64); !ok { + return nil, errors.New("unable to type assert ask") + } + var askPeriod float64 + askPeriod, ok = response[x][6].(float64) + if !ok { + return nil, errors.New("unable to type assert askPeriod") + } + t.AskPeriod = int64(askPeriod) + if t.AskSize, ok = response[x][7].(float64); !ok { + return nil, errors.New("unable to type assert askSize") + } + if t.DailyChange, ok = response[x][8].(float64); !ok { + return nil, errors.New("unable to type assert dailyChange") + } + if t.DailyChangePerc, ok = response[x][9].(float64); !ok { + return nil, errors.New("unable to type assert dailyChangePerc") + } + if t.Last, ok = response[x][10].(float64); !ok { + return nil, errors.New("unable to type assert last") + } + if t.Volume, ok = response[x][11].(float64); !ok { + return nil, errors.New("unable to type assert volume") + } + if t.High, ok = response[x][12].(float64); !ok { + return nil, errors.New("unable to type assert high") + } + if t.Low, ok = response[x][13].(float64); !ok { + return nil, errors.New("unable to type assert low") + } + if t.FFRAmountAvailable, ok = response[x][16].(float64); !ok { + return nil, errors.New("unable to type assert FFRAmountAvailable") + } + + tickers[symbol] = t continue } - tickers[response[x][0].(string)] = Ticker{ - Bid: response[x][1].(float64), - BidSize: response[x][2].(float64), - Ask: response[x][3].(float64), - AskSize: response[x][4].(float64), - DailyChange: response[x][5].(float64), - DailyChangePerc: response[x][6].(float64), - Last: response[x][7].(float64), - Volume: response[x][8].(float64), - High: response[x][9].(float64), - Low: response[x][10].(float64), + + if t.Bid, ok = response[x][1].(float64); !ok { + return nil, errors.New("unable to type assert bid") } + if t.BidSize, ok = response[x][2].(float64); !ok { + return nil, errors.New("unable to type assert bid size") + } + if t.Ask, ok = response[x][3].(float64); !ok { + return nil, errors.New("unable to type assert ask") + } + if t.AskSize, ok = response[x][4].(float64); !ok { + return nil, errors.New("unable to type assert ask size") + } + if t.DailyChange, ok = response[x][5].(float64); !ok { + return nil, errors.New("unable to type assert daily change") + } + if t.DailyChangePerc, ok = response[x][6].(float64); !ok { + return nil, errors.New("unable to type assert daily change perc") + } + if t.Last, ok = response[x][7].(float64); !ok { + return nil, errors.New("unable to type assert last") + } + if t.Volume, ok = response[x][8].(float64); !ok { + return nil, errors.New("unable to type assert volume") + } + if t.High, ok = response[x][9].(float64); !ok { + return nil, errors.New("unable to type assert high") + } + if t.Low, ok = response[x][10].(float64); !ok { + return nil, errors.New("unable to type assert low") + } + tickers[symbol] = t } return tickers, nil } // GetTicker returns ticker information for one symbol -func (b *Bitfinex) GetTicker(ctx context.Context, symbol string) (Ticker, error) { +func (b *Bitfinex) GetTicker(ctx context.Context, symbol string) (*Ticker, error) { var response []interface{} path := bitfinexAPIVersion2 + bitfinexTicker + symbol err := b.SendHTTPRequest(ctx, exchange.RestSpot, path, &response, tickerFunction) if err != nil { - return Ticker{}, err + return nil, err } + var t Ticker if len(response) > 10 { - return Ticker{ - FlashReturnRate: response[0].(float64), - Bid: response[1].(float64), - BidPeriod: int64(response[2].(float64)), - BidSize: response[3].(float64), - Ask: response[4].(float64), - AskPeriod: int64(response[5].(float64)), - AskSize: response[6].(float64), - DailyChange: response[7].(float64), - DailyChangePerc: response[8].(float64), - Last: response[9].(float64), - Volume: response[10].(float64), - High: response[11].(float64), - Low: response[12].(float64), - FFRAmountAvailable: response[15].(float64), - }, nil + var ok bool + if t.FlashReturnRate, ok = response[0].(float64); !ok { + return nil, errors.New("unable to type assert flashReturnRate") + } + if t.Bid, ok = response[1].(float64); !ok { + return nil, errors.New("unable to type assert bid") + } + var bidPeriod float64 + bidPeriod, ok = response[2].(float64) + if !ok { + return nil, errors.New("unable to type assert bidPeriod") + } + t.BidPeriod = int64(bidPeriod) + if t.BidSize, ok = response[3].(float64); !ok { + return nil, errors.New("unable to type assert bidSize") + } + if t.Ask, ok = response[4].(float64); !ok { + return nil, errors.New("unable to type assert ask") + } + var askPeriod float64 + askPeriod, ok = response[5].(float64) + if !ok { + return nil, errors.New("unable to type assert askPeriod") + } + t.AskPeriod = int64(askPeriod) + if t.AskSize, ok = response[6].(float64); !ok { + return nil, errors.New("unable to type assert askSize") + } + if t.DailyChange, ok = response[7].(float64); !ok { + return nil, errors.New("unable to type assert dailyChange") + } + if t.DailyChangePerc, ok = response[8].(float64); !ok { + return nil, errors.New("unable to type assert dailyChangePerc") + } + if t.Last, ok = response[9].(float64); !ok { + return nil, errors.New("unable to type assert last") + } + if t.Volume, ok = response[10].(float64); !ok { + return nil, errors.New("unable to type assert volume") + } + if t.High, ok = response[11].(float64); !ok { + return nil, errors.New("unable to type assert high") + } + if t.Low, ok = response[12].(float64); !ok { + return nil, errors.New("unable to type assert low") + } + if t.FFRAmountAvailable, ok = response[15].(float64); !ok { + return nil, errors.New("unable to type assert FFRAmountAvailable") + } + return &t, nil } - return Ticker{ - Bid: response[0].(float64), - BidSize: response[1].(float64), - Ask: response[2].(float64), - AskSize: response[3].(float64), - DailyChange: response[4].(float64), - DailyChangePerc: response[5].(float64), - Last: response[6].(float64), - Volume: response[7].(float64), - High: response[8].(float64), - Low: response[9].(float64), - }, nil + + var ok bool + if t.Bid, ok = response[0].(float64); !ok { + return nil, errors.New("unable to type assert bid") + } + if t.BidSize, ok = response[1].(float64); !ok { + return nil, errors.New("unable to type assert bidSize") + } + if t.Ask, ok = response[2].(float64); !ok { + return nil, errors.New("unable to type assert ask") + } + if t.AskSize, ok = response[3].(float64); !ok { + return nil, errors.New("unable to type assert askSize") + } + if t.DailyChange, ok = response[4].(float64); !ok { + return nil, errors.New("unable to type assert dailyChange") + } + if t.DailyChangePerc, ok = response[5].(float64); !ok { + return nil, errors.New("unable to type assert dailyChangePerc") + } + if t.Last, ok = response[6].(float64); !ok { + return nil, errors.New("unable to type assert last") + } + if t.Volume, ok = response[7].(float64); !ok { + return nil, errors.New("unable to type assert volume") + } + if t.High, ok = response[8].(float64); !ok { + return nil, errors.New("unable to type assert high") + } + if t.Low, ok = response[9].(float64); !ok { + return nil, errors.New("unable to type assert low") + } + return &t, nil } // GetTrades gets historic trades that occurred on the exchange @@ -637,7 +753,7 @@ func (b *Bitfinex) GetTrades(ctx context.Context, currencyPair string, limit, ti return nil, err } - var history []Trade + history := make([]Trade, len(resp)) for i := range resp { amount, ok := resp[i][2].(float64) if !ok { @@ -649,25 +765,49 @@ func (b *Bitfinex) GetTrades(ctx context.Context, currencyPair string, limit, ti amount *= -1 } - if len(resp[i]) > 4 { - history = append(history, Trade{ - TID: int64(resp[i][0].(float64)), - Timestamp: int64(resp[i][1].(float64)), - Amount: amount, - Rate: resp[i][3].(float64), - Period: int64(resp[i][4].(float64)), - Type: side, - }) - continue + tid, ok := resp[i][0].(float64) + if !ok { + return nil, errors.New("unable to type assert trade ID") + } + timestamp, ok := resp[i][1].(float64) + if !ok { + return nil, errors.New("unable to type assert timestamp") } - history = append(history, Trade{ - TID: int64(resp[i][0].(float64)), - Timestamp: int64(resp[i][1].(float64)), + if len(resp[i]) > 4 { + var rate float64 + rate, ok = resp[i][3].(float64) + if !ok { + return nil, errors.New("unable to type assert rate") + } + var period float64 + period, ok = resp[i][4].(float64) + if !ok { + return nil, errors.New("unable to type assert period") + } + + history[i] = Trade{ + TID: int64(tid), + Timestamp: int64(timestamp), + Amount: amount, + Rate: rate, + Period: int64(period), + Type: side, + } + continue + } + price, ok := resp[i][3].(float64) + if !ok { + return nil, errors.New("unable to type assert price") + } + + history[i] = Trade{ + TID: int64(tid), + Timestamp: int64(timestamp), Amount: amount, - Price: resp[i][3].(float64), + Price: price, Type: side, - }) + } } return history, nil @@ -873,19 +1013,33 @@ func (b *Bitfinex) GetCandles(ctx context.Context, symbol, timeFrame string, sta return nil, err } - var c []Candle + candles := make([]Candle, len(response)) for i := range response { - c = append(c, Candle{ - Timestamp: time.Unix(int64(response[i][0].(float64)/1000), 0), - Open: response[i][1].(float64), - Close: response[i][2].(float64), - High: response[i][3].(float64), - Low: response[i][4].(float64), - Volume: response[i][5].(float64), - }) + var c Candle + timestamp, ok := response[i][0].(float64) + if !ok { + return nil, errors.New("unable to type assert timestamp") + } + c.Timestamp = time.UnixMilli(int64(timestamp)) + if c.Open, ok = response[i][1].(float64); !ok { + return nil, errors.New("unable to type assert open") + } + if c.Close, ok = response[i][2].(float64); !ok { + return nil, errors.New("unable to type assert close") + } + if c.High, ok = response[i][3].(float64); !ok { + return nil, errors.New("unable to type assert high") + } + if c.Low, ok = response[i][4].(float64); !ok { + return nil, errors.New("unable to type assert low") + } + if c.Volume, ok = response[i][5].(float64); !ok { + return nil, errors.New("unable to type assert volume") + } + candles[i] = c } - return c, nil + return candles, nil } path += "/last" @@ -900,14 +1054,29 @@ func (b *Bitfinex) GetCandles(ctx context.Context, symbol, timeFrame string, sta return nil, errors.New("no data returned") } - return []Candle{{ - Timestamp: time.Unix(int64(response[0].(float64))/1000, 0), - Open: response[1].(float64), - Close: response[2].(float64), - High: response[3].(float64), - Low: response[4].(float64), - Volume: response[5].(float64), - }}, nil + var c Candle + timestamp, ok := response[0].(float64) + if !ok { + return nil, errors.New("unable to type assert timestamp") + } + c.Timestamp = time.UnixMilli(int64(timestamp)) + if c.Open, ok = response[1].(float64); !ok { + return nil, errors.New("unable to type assert open") + } + if c.Close, ok = response[2].(float64); !ok { + return nil, errors.New("unable to type assert close") + } + if c.High, ok = response[3].(float64); !ok { + return nil, errors.New("unable to type assert high") + } + if c.Low, ok = response[4].(float64); !ok { + return nil, errors.New("unable to type assert low") + } + if c.Volume, ok = response[5].(float64); !ok { + return nil, errors.New("unable to type assert volume") + } + + return []Candle{c}, nil } // GetConfigurations fetchs currency and symbol site configuration data. @@ -983,7 +1152,7 @@ func (b *Bitfinex) GetLeaderboard(ctx context.Context, key, timeframe, symbol st return r } - var result []LeaderboardEntry + result := make([]LeaderboardEntry, len(resp)) for x := range resp { r, ok := resp[x].([]interface{}) if !ok { @@ -1008,13 +1177,13 @@ func (b *Bitfinex) GetLeaderboard(ctx context.Context, key, timeframe, symbol st if !ok { return nil, errors.New("unable to type assert value") } - result = append(result, LeaderboardEntry{ + result[x] = LeaderboardEntry{ Timestamp: time.UnixMilli(int64(tm)), Username: username, Ranking: int(ranking), Value: value, TwitterHandle: parseTwitterHandle(r[9]), - }) + } } return result, nil } diff --git a/exchanges/bitfinex/bitfinex_test.go b/exchanges/bitfinex/bitfinex_test.go index 80209643..d8556906 100644 --- a/exchanges/bitfinex/bitfinex_test.go +++ b/exchanges/bitfinex/bitfinex_test.go @@ -1311,6 +1311,7 @@ func TestWsOrderSnapshot(t *testing.T) { } func TestWsNotifications(t *testing.T) { + b.WsAddSubscriptionChannel(0, "account", "N/A") pressXToJSON := `[0,"n",[1575282446099,"fon-req",null,null,[41238905,null,null,null,-1000,null,null,null,null,null,null,null,null,null,0.002,2,null,null,null,null,null],null,"SUCCESS","Submitting funding bid of 1000.0 USD at 0.2000 for 2 days."]]` err := b.wsHandleData([]byte(pressXToJSON)) if err != nil { @@ -1324,6 +1325,90 @@ func TestWsNotifications(t *testing.T) { } } +func TestWSFundingOfferSnapshotAndUpdate(t *testing.T) { + b.WsAddSubscriptionChannel(0, "account", "N/A") + pressXToJSON := `[0,"fos",[[41237920,"fETH",1573912039000,1573912039000,0.5,0.5,"LIMIT",null,null,0,"ACTIVE",null,null,null,0.0024,2,0,0,null,0,null]]]` + if err := b.wsHandleData([]byte(pressXToJSON)); err != nil { + t.Error(err) + } + + pressXToJSON = `[0,"fon",[41238747,"fUST",1575026670000,1575026670000,5000,5000,"LIMIT",null,null,0,"ACTIVE",null,null,null,0.006000000000000001,30,0,0,null,0,null]]` + if err := b.wsHandleData([]byte(pressXToJSON)); err != nil { + t.Error(err) + } +} + +func TestWSFundingCreditSnapshotAndUpdate(t *testing.T) { + b.WsAddSubscriptionChannel(0, "account", "N/A") + pressXToJSON := `[0,"fcs",[[26223578,"fUST",1,1575052261000,1575296187000,350,0,"ACTIVE",null,null,null,0,30,1575052261000,1575293487000,0,0,null,0,null,0,"tBTCUST"],[26223711,"fUSD",-1,1575291961000,1575296187000,180,0,"ACTIVE",null,null,null,0.002,7,1575282446000,1575295587000,0,0,null,0,null,0,"tETHUSD"]]]` + if err := b.wsHandleData([]byte(pressXToJSON)); err != nil { + t.Error(err) + } + + pressXToJSON = `[0,"fcu",[26223578,"fUST",1,1575052261000,1575296787000,350,0,"ACTIVE",null,null,null,0,30,1575052261000,1575293487000,0,0,null,0,null,0,"tBTCUST"]]` + if err := b.wsHandleData([]byte(pressXToJSON)); err != nil { + t.Error(err) + } +} + +func TestWSFundingLoanSnapshotAndUpdate(t *testing.T) { + b.WsAddSubscriptionChannel(0, "account", "N/A") + pressXToJSON := `[0,"fls",[[2995442,"fUSD",-1,1575291961000,1575295850000,820,0,"ACTIVE",null,null,null,0.002,7,1575282446000,1575295850000,0,0,null,0,null,0]]]` + if err := b.wsHandleData([]byte(pressXToJSON)); err != nil { + t.Error(err) + } + + pressXToJSON = `[0,"fln",[2995444,"fUSD",-1,1575298742000,1575298742000,1000,0,"ACTIVE",null,null,null,0.002,7,1575298742000,1575298742000,0,0,null,0,null,0]]` + if err := b.wsHandleData([]byte(pressXToJSON)); err != nil { + t.Error(err) + } +} + +func TestWSWalletSnapshot(t *testing.T) { + b.WsAddSubscriptionChannel(0, "account", "N/A") + pressXToJSON := `[0,"ws",[["exchange","SAN",19.76,0,null,null,null]]]` + if err := b.wsHandleData([]byte(pressXToJSON)); err != nil { + t.Error(err) + } +} + +func TestWSBalanceUpdate(t *testing.T) { + b.WsAddSubscriptionChannel(0, "account", "N/A") + const pressXToJSON = `[0,"bu",[4131.85,4131.85]]` + if err := b.wsHandleData([]byte(pressXToJSON)); err != nil { + t.Error(err) + } +} + +func TestWSMarginInfoUpdate(t *testing.T) { + b.WsAddSubscriptionChannel(0, "account", "N/A") + const pressXToJSON = `[0,"miu",["base",[-13.014640000000007,0,49331.70267297,49318.68803297,27]]]` + if err := b.wsHandleData([]byte(pressXToJSON)); err != nil { + t.Error(err) + } +} + +func TestWSFundingInfoUpdate(t *testing.T) { + b.WsAddSubscriptionChannel(0, "account", "N/A") + const pressXToJSON = `[0,"fiu",["sym","tETHUSD",[149361.09689202666,149639.26293509,830.0182168075556,895.0658432466332]]]` + if err := b.wsHandleData([]byte(pressXToJSON)); err != nil { + t.Error(err) + } +} + +func TestWSFundingTrade(t *testing.T) { + b.WsAddSubscriptionChannel(0, "account", "N/A") + pressXToJSON := `[0,"fte",[636854,"fUSD",1575282446000,41238905,-1000,0.002,7,null]]` + if err := b.wsHandleData([]byte(pressXToJSON)); err != nil { + t.Error(err) + } + + pressXToJSON = `[0,"ftu",[636854,"fUSD",1575282446000,41238905,-1000,0.002,7,null]]` + if err := b.wsHandleData([]byte(pressXToJSON)); err != nil { + t.Error(err) + } +} + func TestGetHistoricCandles(t *testing.T) { currencyPair, err := currency.NewPairFromString("BTCUSD") if err != nil { diff --git a/exchanges/bitfinex/bitfinex_types.go b/exchanges/bitfinex/bitfinex_types.go index 813d0d8f..921e0c2a 100644 --- a/exchanges/bitfinex/bitfinex_types.go +++ b/exchanges/bitfinex/bitfinex_types.go @@ -657,13 +657,13 @@ const ( wsOrderNewRequest = wsOrderNew + wsRequest wsOrderUpdateRequest = wsOrderUpdate + wsRequest wsOrderCancelRequest = wsOrderCancel + wsRequest - wsFundingOrderSnapshot = "fos" - wsFundingOrderNew = "fon" - wsFundingOrderUpdate = "fou" - wsFundingOrderCancel = "foc" - wsFundingOrderNewRequest = wsFundingOrderNew + wsRequest - wsFundingOrderUpdateRequest = wsFundingOrderUpdate + wsRequest - wsFundingOrderCancelRequest = wsFundingOrderCancel + wsRequest + wsFundingOfferSnapshot = "fos" + wsFundingOfferNew = "fon" + wsFundingOfferUpdate = "fou" + wsFundingOfferCancel = "foc" + wsFundingOfferNewRequest = wsFundingOfferNew + wsRequest + wsFundingOfferUpdateRequest = wsFundingOfferUpdate + wsRequest + wsFundingOfferCancelRequest = wsFundingOfferCancel + wsRequest wsCancelMultipleOrders = "oc_multi" wsBook = "book" wsCandles = "candles" @@ -686,8 +686,8 @@ type WsAuthRequest struct { type WsFundingOffer struct { ID int64 Symbol string - Created int64 - Updated int64 + Created time.Time + Updated time.Time Amount float64 OriginalAmount float64 Type string @@ -706,19 +706,18 @@ type WsFundingOffer struct { type WsCredit struct { ID int64 Symbol string - Side string - Created int64 - Updated int64 + Side int8 // 1 if you are the lender, 0 if you are both the lender and borrower, -1 if you're the borrower + Created time.Time + Updated time.Time Amount float64 - Flags interface{} + Flags interface{} // Future params object (stay tuned) Status string Rate float64 Period int64 - Opened int64 - LastPayout int64 + Opened time.Time + LastPayout time.Time Notify bool Hidden bool - Insure bool Renew bool RateReal float64 NoClose bool @@ -755,13 +754,14 @@ type WsMarginInfoBase struct { UserSwaps float64 MarginBalance float64 MarginNet float64 + MarginRequired float64 } // WsFundingTrade recent funding trades received via websocket type WsFundingTrade struct { ID int64 Symbol string - MTSCreated int64 + MTSCreated time.Time OfferID int64 Amount float64 Rate float64 diff --git a/exchanges/bitfinex/bitfinex_websocket.go b/exchanges/bitfinex/bitfinex_websocket.go index 9a84f19c..66545bdc 100644 --- a/exchanges/bitfinex/bitfinex_websocket.go +++ b/exchanges/bitfinex/bitfinex_websocket.go @@ -139,23 +139,30 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { event := d["event"] switch event { case "subscribed": + chanID, ok := d["chanId"].(float64) + if !ok { + return errors.New("unable to type assert chanId") + } + channel, ok := d["channel"].(string) + if !ok { + return errors.New("unable to type assert channel") + } if symbol, ok := d["symbol"].(string); ok { - b.WsAddSubscriptionChannel(int(d["chanId"].(float64)), - d["channel"].(string), + b.WsAddSubscriptionChannel(int(chanID), + channel, symbol, ) } else if key, ok := d["key"].(string); ok { // Capture trading subscriptions - contents := strings.Split(d["key"].(string), ":") - if len(contents) > 3 { + if contents := strings.Split(key, ":"); len(contents) > 3 { // Edge case to parse margin strings. // map[chanId:139136 channel:candles event:subscribed key:trade:1m:tXAUTF0:USTF0] if contents[2][0] == 't' { key = contents[2] + ":" + contents[3] } } - b.WsAddSubscriptionChannel(int(d["chanId"].(float64)), - d["channel"].(string), + b.WsAddSubscriptionChannel(int(chanID), + channel, key, ) } @@ -168,8 +175,11 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { b.Websocket.DataHandler <- d b.WsAddSubscriptionChannel(0, "account", "N/A") } else if status == "fail" { - return fmt.Errorf("websocket unable to AUTH. Error code: %s", - d["code"].(string)) + if code, ok := d["code"].(string); ok { + return fmt.Errorf("websocket unable to AUTH. Error code: %s", + code) + } + return errors.New("websocket unable to auth") } } case []interface{}: @@ -355,29 +365,29 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { var element []interface{} element, ok = candleBundle[i].([]interface{}) if !ok { - return errors.New("type assertion for element data") + return errors.New("candle type assertion for element data") } if len(element) < 6 { return errors.New("invalid candleBundle length") } var klineData stream.KlineData if klineData.Timestamp, err = convert.TimeFromUnixTimestampFloat(element[0]); err != nil { - return fmt.Errorf("unable to convert timestamp: %w", err) + return fmt.Errorf("unable to convert candle timestamp: %w", err) } if klineData.OpenPrice, ok = element[1].(float64); !ok { - return errors.New("unable to type assert OpenPrice") + return errors.New("unable to type assert candle open price") } if klineData.ClosePrice, ok = element[2].(float64); !ok { - return errors.New("unable to type assert ClosePrice") + return errors.New("unable to type assert candle close price") } if klineData.HighPrice, ok = element[3].(float64); !ok { - return errors.New("unable to type assert HighPrice") + return errors.New("unable to type assert candle high price") } if klineData.LowPrice, ok = element[4].(float64); !ok { - return errors.New("unable to type assert LowPrice") + return errors.New("unable to type assert candle low price") } if klineData.Volume, ok = element[5].(float64); !ok { - return errors.New("unable to type assert volume") + return errors.New("unable to type assert candle volume") } klineData.Exchange = b.Name klineData.AssetType = chanAsset @@ -390,22 +400,22 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { } var klineData stream.KlineData if klineData.Timestamp, err = convert.TimeFromUnixTimestampFloat(candleData); err != nil { - return fmt.Errorf("unable to convert timestamp: %w", err) + return fmt.Errorf("unable to convert candle timestamp: %w", err) } if klineData.OpenPrice, ok = candleBundle[1].(float64); !ok { - return errors.New("unable to type assert OpenPrice") + return errors.New("unable to type assert candle open price") } if klineData.ClosePrice, ok = candleBundle[2].(float64); !ok { - return errors.New("unable to type assert ClosePrice") + return errors.New("unable to type assert candle close price") } if klineData.HighPrice, ok = candleBundle[3].(float64); !ok { - return errors.New("unable to type assert HighPrice") + return errors.New("unable to type assert candle high price") } if klineData.LowPrice, ok = candleBundle[4].(float64); !ok { - return errors.New("unable to type assert LowPrice") + return errors.New("unable to type assert candle low price") } if klineData.Volume, ok = candleBundle[5].(float64); !ok { - return errors.New("unable to type assert volume") + return errors.New("unable to type assert candle volume") } klineData.Exchange = b.Name klineData.AssetType = chanAsset @@ -419,38 +429,71 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { if !ok { return errors.New("type assertion for tickerData") } - if len(tickerData) == 10 { - b.Websocket.DataHandler <- &ticker.Price{ - ExchangeName: b.Name, - Bid: tickerData[0].(float64), - Ask: tickerData[2].(float64), - Last: tickerData[6].(float64), - Volume: tickerData[7].(float64), - High: tickerData[8].(float64), - Low: tickerData[9].(float64), - AssetType: chanAsset, - Pair: pair, - } - } else { - b.Websocket.DataHandler <- &ticker.Price{ - ExchangeName: b.Name, - FlashReturnRate: tickerData[0].(float64), - Bid: tickerData[1].(float64), - BidPeriod: tickerData[2].(float64), - BidSize: tickerData[3].(float64), - Ask: tickerData[4].(float64), - AskPeriod: tickerData[5].(float64), - AskSize: tickerData[6].(float64), - Last: tickerData[9].(float64), - Volume: tickerData[10].(float64), - High: tickerData[11].(float64), - Low: tickerData[12].(float64), - FlashReturnRateAmount: tickerData[15].(float64), - AssetType: chanAsset, - Pair: pair, - } + + t := &ticker.Price{ + AssetType: chanAsset, + Pair: pair, + ExchangeName: b.Name, } + if len(tickerData) == 10 { + if t.Bid, ok = tickerData[0].(float64); !ok { + return errors.New("unable to type assert ticker bid") + } + if t.Ask, ok = tickerData[2].(float64); !ok { + return errors.New("unable to type assert ticker ask") + } + if t.Last, ok = tickerData[6].(float64); !ok { + return errors.New("unable to type assert ticker last") + } + if t.Volume, ok = tickerData[7].(float64); !ok { + return errors.New("unable to type assert ticker volume") + } + if t.High, ok = tickerData[8].(float64); !ok { + return errors.New("unable to type assert ticker high") + } + if t.Low, ok = tickerData[9].(float64); !ok { + return errors.New("unable to type assert ticker low") + } + } else { + if t.FlashReturnRate, ok = tickerData[0].(float64); !ok { + return errors.New("unable to type assert ticker flash return rate") + } + if t.Bid, ok = tickerData[1].(float64); !ok { + return errors.New("unable to type assert ticker bid") + } + if t.BidPeriod, ok = tickerData[2].(float64); !ok { + return errors.New("unable to type assert ticker bid period") + } + if t.BidSize, ok = tickerData[3].(float64); !ok { + return errors.New("unable to type assert ticker bid size") + } + if t.Ask, ok = tickerData[4].(float64); !ok { + return errors.New("unable to type assert ticker ask") + } + if t.AskPeriod, ok = tickerData[5].(float64); !ok { + return errors.New("unable to type assert ticker ask period") + } + if t.AskSize, ok = tickerData[6].(float64); !ok { + return errors.New("unable to type assert ticker ask size") + } + if t.Last, ok = tickerData[9].(float64); !ok { + return errors.New("unable to type assert ticker last") + } + if t.Volume, ok = tickerData[10].(float64); !ok { + return errors.New("unable to type assert ticker volume") + } + if t.High, ok = tickerData[11].(float64); !ok { + return errors.New("unable to type assert ticker high") + } + if t.Low, ok = tickerData[12].(float64); !ok { + return errors.New("unable to type assert ticker low") + } + if t.FlashReturnRateAmount, ok = tickerData[15].(float64); !ok { + return errors.New("unable to type assert ticker flash return rate") + } + } + b.Websocket.DataHandler <- t return nil case wsTrades: if !b.IsSaveTradeDataEnabled() { @@ -464,57 +507,102 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { case 2: snapshot, ok := d[1].([]interface{}) if !ok { - return errors.New("unable to type assert snapshot data") + return errors.New("unable to type assert trade snapshot data") } for i := range snapshot { elem, ok := snapshot[i].([]interface{}) if !ok { - return errors.New("unable to type assert snapshot element data") + return errors.New("unable to type assert trade snapshot element data") + } + tradeID, ok := elem[0].(float64) + if !ok { + return errors.New("unable to type assert trade ID") + } + timestamp, ok := elem[1].(float64) + if !ok { + return errors.New("unable to type assert trade timestamp") + } + amount, ok := elem[2].(float64) + if !ok { + return errors.New("unable to type assert trade amount") + } + wsTrade := WebsocketTrade{ + ID: int64(tradeID), + Timestamp: int64(timestamp), + Amount: amount, } if len(elem) == 5 { - tradeHolder = append(tradeHolder, WebsocketTrade{ - ID: int64(elem[0].(float64)), - Timestamp: int64(elem[1].(float64)), - Amount: elem[2].(float64), - Rate: elem[3].(float64), - Period: int64(elem[4].(float64)), - }) + rate, ok := elem[3].(float64) + if !ok { + return errors.New("unable to type assert trade rate") + } + wsTrade.Rate = rate + period, ok := elem[4].(float64) + if !ok { + return errors.New("unable to type assert trade period") + } + wsTrade.Period = int64(period) } else { - tradeHolder = append(tradeHolder, WebsocketTrade{ - ID: int64(elem[0].(float64)), - Timestamp: int64(elem[1].(float64)), - Amount: elem[2].(float64), - Price: elem[3].(float64), - }) + price, ok := elem[3].(float64) + if !ok { + return errors.New("unable to type assert trade price") + } + wsTrade.Rate = price } + tradeHolder = append(tradeHolder, wsTrade) } case 3: - if d[1].(string) != wsFundingTradeUpdate && - d[1].(string) != wsTradeExecutionUpdate { + event, ok := d[1].(string) + if !ok { + return errors.New("unable to type assert data event") + } + if event != wsFundingTradeUpdate && + event != wsTradeExecutionUpdate { return nil } data, ok := d[2].([]interface{}) if !ok { - return errors.New("data type assertion error") + return errors.New("trade data type assertion error") + } + + tradeID, ok := data[0].(float64) + if !ok { + return errors.New("unable to type assert trade ID") + } + timestamp, ok := data[1].(float64) + if !ok { + return errors.New("unable to type assert trade timestamp") + } + amount, ok := data[2].(float64) + if !ok { + return errors.New("unable to type assert trade amount") + } + wsTrade := WebsocketTrade{ + ID: int64(tradeID), + Timestamp: int64(timestamp), + Amount: amount, } if len(data) == 5 { - tradeHolder = append(tradeHolder, WebsocketTrade{ - ID: int64(data[0].(float64)), - Timestamp: int64(data[1].(float64)), - Amount: data[2].(float64), - Rate: data[3].(float64), - Period: int64(data[4].(float64)), - }) + rate, ok := data[3].(float64) + if !ok { + return errors.New("unable to type assert trade rate") + } + period, ok := data[4].(float64) + if !ok { + return errors.New("unable to type assert trade period") + } + wsTrade.Rate = rate + wsTrade.Period = int64(period) } else { - tradeHolder = append(tradeHolder, WebsocketTrade{ - ID: int64(data[0].(float64)), - Timestamp: int64(data[1].(float64)), - Amount: data[2].(float64), - Price: data[3].(float64), - }) + price, ok := data[3].(float64) + if !ok { + return errors.New("unable to type assert trade price") + } + wsTrade.Price = price } + tradeHolder = append(tradeHolder, wsTrade) } - var trades []trade.Data + trades := make([]trade.Data, len(tradeHolder)) for i := range tradeHolder { side := order.Buy newAmount := tradeHolder[i].Amount @@ -526,7 +614,7 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { if price == 0 && tradeHolder[i].Rate > 0 { price = tradeHolder[i].Rate } - trades = append(trades, trade.Data{ + trades[i] = trade.Data{ TID: strconv.FormatInt(tradeHolder[i].ID, 10), CurrencyPair: pair, Timestamp: time.UnixMilli(tradeHolder[i].Timestamp), @@ -535,7 +623,7 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { Exchange: b.Name, AssetType: chanAsset, Side: side, - }) + } } return b.AddTradesToBuffer(trades...) @@ -556,27 +644,32 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { return errors.New("unable to type assert channelName") } switch { - case strings.Contains(channelName, wsFundingOrderNewRequest), - strings.Contains(channelName, wsFundingOrderUpdateRequest), - strings.Contains(channelName, wsFundingOrderCancelRequest): - if data[0] != nil && data[0].(float64) > 0 { - id := int64(data[0].(float64)) - if b.Websocket.Match.IncomingWithData(id, respRaw) { - return nil + case strings.Contains(channelName, wsFundingOfferNewRequest), + strings.Contains(channelName, wsFundingOfferUpdateRequest), + strings.Contains(channelName, wsFundingOfferCancelRequest): + if data[0] != nil { + if id, ok := data[0].(float64); ok && id > 0 { + if b.Websocket.Match.IncomingWithData(int64(id), respRaw) { + return nil + } + offer, err := wsHandleFundingOffer(data, true /* include rate real */) + if err != nil { + return err + } + b.Websocket.DataHandler <- offer } - b.wsHandleFundingOffer(data) } case strings.Contains(channelName, wsOrderNewRequest), strings.Contains(channelName, wsOrderUpdateRequest), strings.Contains(channelName, wsOrderCancelRequest): - if data[2] != nil && data[2].(float64) > 0 { - id := int64(data[2].(float64)) - if b.Websocket.Match.IncomingWithData(id, respRaw) { - return nil + if data[2] != nil { + if id, ok := data[2].(float64); ok && id > 0 { + if b.Websocket.Match.IncomingWithData(int64(id), respRaw) { + return nil + } + b.wsHandleOrder(data) } - b.wsHandleOrder(data) } - default: return fmt.Errorf("%s - Unexpected data returned %s", b.Name, @@ -611,305 +704,364 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { b.wsHandleOrder(oData) } case wsPositionSnapshot: - var snapshot []WebsocketPosition if snapBundle, ok := d[2].([]interface{}); ok && len(snapBundle) > 0 { if _, ok := snapBundle[0].([]interface{}); ok { + snapshot := make([]WebsocketPosition, len(snapBundle)) for i := range snapBundle { positionData, ok := snapBundle[i].([]interface{}) if !ok { return errors.New("unable to type assert wsPositionSnapshot positionData") } - position := WebsocketPosition{ - Pair: positionData[0].(string), - Status: positionData[1].(string), - Amount: positionData[2].(float64), - Price: positionData[3].(float64), - MarginFunding: positionData[4].(float64), - MarginFundingType: int64(positionData[5].(float64)), - ProfitLoss: positionData[6].(float64), - ProfitLossPercent: positionData[7].(float64), - LiquidationPrice: positionData[8].(float64), - Leverage: positionData[9].(float64), + var position WebsocketPosition + if position.Pair, ok = positionData[0].(string); !ok { + return errors.New("unable to type assert position snapshot pair") } - snapshot = append(snapshot, position) + if position.Status, ok = positionData[1].(string); !ok { + return errors.New("unable to type assert position snapshot status") + } + if position.Amount, ok = positionData[2].(float64); !ok { + return errors.New("unable to type assert position snapshot amount") + } + if position.Price, ok = positionData[3].(float64); !ok { + return errors.New("unable to type assert position snapshot price") + } + if position.MarginFunding, ok = positionData[4].(float64); !ok { + return errors.New("unable to type assert position snapshot margin funding") + } + marginFundingType, ok := positionData[5].(float64) + if !ok { + return errors.New("unable to type assert position snapshot margin funding type") + } + position.MarginFundingType = int64(marginFundingType) + if position.ProfitLoss, ok = positionData[6].(float64); !ok { + return errors.New("unable to type assert position snapshot profit loss") + } + if position.ProfitLossPercent, ok = positionData[7].(float64); !ok { + return errors.New("unable to type assert position snapshot profit loss percent") + } + if position.LiquidationPrice, ok = positionData[8].(float64); !ok { + return errors.New("unable to type assert position snapshot liquidation price") + } + if position.Leverage, ok = positionData[9].(float64); !ok { + return errors.New("unable to type assert position snapshot leverage") + } + snapshot[i] = position } b.Websocket.DataHandler <- snapshot } } case wsPositionNew, wsPositionUpdate, wsPositionClose: if positionData, ok := d[2].([]interface{}); ok && len(positionData) > 0 { - position := WebsocketPosition{ - Pair: positionData[0].(string), - Status: positionData[1].(string), - Amount: positionData[2].(float64), - Price: positionData[3].(float64), - MarginFunding: positionData[4].(float64), - MarginFundingType: int64(positionData[5].(float64)), - ProfitLoss: positionData[6].(float64), - ProfitLossPercent: positionData[7].(float64), - LiquidationPrice: positionData[8].(float64), - Leverage: positionData[9].(float64), + var position WebsocketPosition + if position.Pair, ok = positionData[0].(string); !ok { + return errors.New("unable to type assert position pair") + } + if position.Status, ok = positionData[1].(string); !ok { + return errors.New("unable to type assert position status") + } + if position.Amount, ok = positionData[2].(float64); !ok { + return errors.New("unable to type assert position amount") + } + if position.Price, ok = positionData[3].(float64); !ok { + return errors.New("unable to type assert position price") + } + if position.MarginFunding, ok = positionData[4].(float64); !ok { + return errors.New("unable to type assert margin position funding") + } + marginFundingType, ok := positionData[5].(float64) + if !ok { + return errors.New("unable to type assert position margin funding type") + } + position.MarginFundingType = int64(marginFundingType) + if position.ProfitLoss, ok = positionData[6].(float64); !ok { + return errors.New("unable to type assert position profit loss") + } + if position.ProfitLossPercent, ok = positionData[7].(float64); !ok { + return errors.New("unable to type assert position profit loss percent") + } + if position.LiquidationPrice, ok = positionData[8].(float64); !ok { + return errors.New("unable to type assert position liquidation price") + } + if position.Leverage, ok = positionData[9].(float64); !ok { + return errors.New("unable to type assert position leverage") } b.Websocket.DataHandler <- position } case wsTradeExecuted, wsTradeExecutionUpdate: if tradeData, ok := d[2].([]interface{}); ok && len(tradeData) > 4 { - b.Websocket.DataHandler <- WebsocketTradeData{ - TradeID: int64(tradeData[0].(float64)), - Pair: tradeData[1].(string), - Timestamp: int64(tradeData[2].(float64)), - OrderID: int64(tradeData[3].(float64)), - AmountExecuted: tradeData[4].(float64), - PriceExecuted: tradeData[5].(float64), - OrderType: tradeData[6].(string), - OrderPrice: tradeData[7].(float64), - Maker: tradeData[8].(float64) == 1, - Fee: tradeData[9].(float64), - FeeCurrency: tradeData[10].(string), + var tData WebsocketTradeData + var tradeID float64 + if tradeID, ok = tradeData[0].(float64); !ok { + return errors.New("unable to type assert trade ID") } + tData.TradeID = int64(tradeID) + if tData.Pair, ok = tradeData[1].(string); !ok { + return errors.New("unable to type assert trade pair") + } + var timestamp float64 + if timestamp, ok = tradeData[2].(float64); !ok { + return errors.New("unable to type assert trade timestamp") + } + tData.Timestamp = int64(timestamp) + var orderID float64 + if orderID, ok = tradeData[3].(float64); !ok { + return errors.New("unable to type assert trade order ID") + } + tData.OrderID = int64(orderID) + if tData.AmountExecuted, ok = tradeData[4].(float64); !ok { + return errors.New("unable to type assert trade amount executed") + } + if tData.PriceExecuted, ok = tradeData[5].(float64); !ok { + return errors.New("unable to type assert trade price executed") + } + if tData.OrderType, ok = tradeData[6].(string); !ok { + return errors.New("unable to type assert trade order type") + } + if tData.OrderPrice, ok = tradeData[7].(float64); !ok { + return errors.New("unable to type assert trade order type") + } + var maker float64 + if maker, ok = tradeData[8].(float64); !ok { + return errors.New("unable to type assert trade maker") + } + tData.Maker = maker == 1 + if tData.Fee, ok = tradeData[9].(float64); !ok { + return errors.New("unable to type assert trade fee") + } + if tData.FeeCurrency, ok = tradeData[10].(string); !ok { + return errors.New("unable to type assert trade fee currency") + } + b.Websocket.DataHandler <- tData } - case wsFundingOrderSnapshot: - var snapshot []WsFundingOffer + case wsFundingOfferSnapshot: if snapBundle, ok := d[2].([]interface{}); ok && len(snapBundle) > 0 { if _, ok := snapBundle[0].([]interface{}); ok { + snapshot := make([]*WsFundingOffer, len(snapBundle)) for i := range snapBundle { data, ok := snapBundle[i].([]interface{}) if !ok { return errors.New("unable to type assert wsFundingOrderSnapshot snapBundle data") } - offer := WsFundingOffer{ - ID: int64(data[0].(float64)), - Symbol: data[1].(string), - Created: int64(data[2].(float64)), - Updated: int64(data[3].(float64)), - Amount: data[4].(float64), - OriginalAmount: data[5].(float64), - Type: data[6].(string), - Flags: data[9].(float64), - Status: data[10].(string), - Rate: data[14].(float64), - Period: int64(data[15].(float64)), - Notify: data[16].(float64) == 1, - Hidden: data[17].(float64) == 1, - Insure: data[18].(float64) == 1, - Renew: data[19].(float64) == 1, - RateReal: data[20].(float64), + offer, err := wsHandleFundingOffer(data, false /* include rate real */) + if err != nil { + return err } - snapshot = append(snapshot, offer) + snapshot[i] = offer } b.Websocket.DataHandler <- snapshot } } - case wsFundingOrderNew, wsFundingOrderUpdate, wsFundingOrderCancel: + case wsFundingOfferNew, wsFundingOfferUpdate, wsFundingOfferCancel: if data, ok := d[2].([]interface{}); ok && len(data) > 0 { - b.wsHandleFundingOffer(data) + offer, err := wsHandleFundingOffer(data, true /* include rate real */) + if err != nil { + return err + } + b.Websocket.DataHandler <- offer } case wsFundingCreditSnapshot: - var snapshot []WsCredit if snapBundle, ok := d[2].([]interface{}); ok && len(snapBundle) > 0 { if _, ok := snapBundle[0].([]interface{}); ok { + snapshot := make([]*WsCredit, len(snapBundle)) for i := range snapBundle { data, ok := snapBundle[i].([]interface{}) if !ok { return errors.New("unable to type assert wsFundingCreditSnapshot snapBundle data") } - credit := WsCredit{ - ID: int64(data[0].(float64)), - Symbol: data[1].(string), - Side: data[2].(string), - Created: int64(data[3].(float64)), - Updated: int64(data[4].(float64)), - Amount: data[5].(float64), - Flags: data[6].(string), - Status: data[7].(string), - Rate: data[11].(float64), - Period: int64(data[12].(float64)), - Opened: int64(data[13].(float64)), - LastPayout: int64(data[14].(float64)), - Notify: data[15].(float64) == 1, - Hidden: data[16].(float64) == 1, - Insure: data[17].(float64) == 1, - Renew: data[18].(float64) == 1, - RateReal: data[19].(float64), - NoClose: data[20].(float64) == 1, - PositionPair: data[21].(string), + fundingCredit, err := wsHandleFundingCreditLoanData(data, true /* include position pair */) + if err != nil { + return err } - snapshot = append(snapshot, credit) + snapshot[i] = fundingCredit } b.Websocket.DataHandler <- snapshot } } case wsFundingCreditNew, wsFundingCreditUpdate, wsFundingCreditCancel: if data, ok := d[2].([]interface{}); ok && len(data) > 0 { - b.Websocket.DataHandler <- WsCredit{ - ID: int64(data[0].(float64)), - Symbol: data[1].(string), - Side: data[2].(string), - Created: int64(data[3].(float64)), - Updated: int64(data[4].(float64)), - Amount: data[5].(float64), - Flags: data[6].(string), - Status: data[7].(string), - Rate: data[11].(float64), - Period: int64(data[12].(float64)), - Opened: int64(data[13].(float64)), - LastPayout: int64(data[14].(float64)), - Notify: data[15].(float64) == 1, - Hidden: data[16].(float64) == 1, - Insure: data[17].(float64) == 1, - Renew: data[18].(float64) == 1, - RateReal: data[19].(float64), - NoClose: data[20].(float64) == 1, - PositionPair: data[21].(string), + fundingCredit, err := wsHandleFundingCreditLoanData(data, true /* include position pair */) + if err != nil { + return err } + b.Websocket.DataHandler <- fundingCredit } case wsFundingLoanSnapshot: - var snapshot []WsCredit if snapBundle, ok := d[2].([]interface{}); ok && len(snapBundle) > 0 { if _, ok := snapBundle[0].([]interface{}); ok { + snapshot := make([]*WsCredit, len(snapBundle)) for i := range snapBundle { data, ok := snapBundle[i].([]interface{}) if !ok { return errors.New("unable to type assert wsFundingLoanSnapshot snapBundle data") } - credit := WsCredit{ - ID: int64(data[0].(float64)), - Symbol: data[1].(string), - Side: data[2].(string), - Created: int64(data[3].(float64)), - Updated: int64(data[4].(float64)), - Amount: data[5].(float64), - Flags: data[6].(string), - Status: data[7].(string), - Rate: data[11].(float64), - Period: int64(data[12].(float64)), - Opened: int64(data[13].(float64)), - LastPayout: int64(data[14].(float64)), - Notify: data[15].(float64) == 1, - Hidden: data[16].(float64) == 1, - Insure: data[17].(float64) == 1, - Renew: data[18].(float64) == 1, - RateReal: data[19].(float64), - NoClose: data[20].(float64) == 1, + fundingLoanSnapshot, err := wsHandleFundingCreditLoanData(data, false /* include position pair */) + if err != nil { + return err } - snapshot = append(snapshot, credit) + snapshot[i] = fundingLoanSnapshot } b.Websocket.DataHandler <- snapshot } } case wsFundingLoanNew, wsFundingLoanUpdate, wsFundingLoanCancel: if data, ok := d[2].([]interface{}); ok && len(data) > 0 { - b.Websocket.DataHandler <- WsCredit{ - ID: int64(data[0].(float64)), - Symbol: data[1].(string), - Side: data[2].(string), - Created: int64(data[3].(float64)), - Updated: int64(data[4].(float64)), - Amount: data[5].(float64), - Flags: data[6].(string), - Status: data[7].(string), - Rate: data[11].(float64), - Period: int64(data[12].(float64)), - Opened: int64(data[13].(float64)), - LastPayout: int64(data[14].(float64)), - Notify: data[15].(float64) == 1, - Hidden: data[16].(float64) == 1, - Insure: data[17].(float64) == 1, - Renew: data[18].(float64) == 1, - RateReal: data[19].(float64), - NoClose: data[20].(float64) == 1, + fundingData, err := wsHandleFundingCreditLoanData(data, false /* include position pair */) + if err != nil { + return err } + b.Websocket.DataHandler <- fundingData } case wsWalletSnapshot: - var snapshot []WsWallet if snapBundle, ok := d[2].([]interface{}); ok && len(snapBundle) > 0 { if _, ok := snapBundle[0].([]interface{}); ok { + snapshot := make([]WsWallet, len(snapBundle)) for i := range snapBundle { data, ok := snapBundle[i].([]interface{}) if !ok { return errors.New("unable to type assert wsWalletSnapshot snapBundle data") } - var balanceAvailable float64 - if v, ok := data[4].(float64); ok { - balanceAvailable = v + var wallet WsWallet + if wallet.Type, ok = data[0].(string); !ok { + return errors.New("unable to type assert wallet snapshot type") } - wallet := WsWallet{ - Type: data[0].(string), - Currency: data[1].(string), - Balance: data[2].(float64), - UnsettledInterest: data[3].(float64), - BalanceAvailable: balanceAvailable, + if wallet.Currency, ok = data[1].(string); !ok { + return errors.New("unable to type assert wallet snapshot currency") } - snapshot = append(snapshot, wallet) + if wallet.Balance, ok = data[2].(float64); !ok { + return errors.New("unable to type assert wallet snapshot balance") + } + if wallet.UnsettledInterest, ok = data[3].(float64); !ok { + return errors.New("unable to type assert wallet snapshot unsettled interest") + } + if data[4] != nil { + if wallet.BalanceAvailable, ok = data[4].(float64); !ok { + return errors.New("unable to type assert wallet snapshot balance available") + } + } + snapshot[i] = wallet } b.Websocket.DataHandler <- snapshot } } case wsWalletUpdate: if data, ok := d[2].([]interface{}); ok && len(data) > 0 { - var balanceAvailable float64 - if v, ok := data[4].(float64); ok { - balanceAvailable = v + var wallet WsWallet + if wallet.Type, ok = data[0].(string); !ok { + return errors.New("unable to type assert wallet snapshot type") } - b.Websocket.DataHandler <- WsWallet{ - Type: data[0].(string), - Currency: data[1].(string), - Balance: data[2].(float64), - UnsettledInterest: data[3].(float64), - BalanceAvailable: balanceAvailable, + if wallet.Currency, ok = data[1].(string); !ok { + return errors.New("unable to type assert wallet snapshot currency") } + if wallet.Balance, ok = data[2].(float64); !ok { + return errors.New("unable to type assert wallet snapshot balance") + } + if wallet.UnsettledInterest, ok = data[3].(float64); !ok { + return errors.New("unable to type assert wallet snapshot unsettled interest") + } + if data[4] != nil { + if wallet.BalanceAvailable, ok = data[4].(float64); !ok { + return errors.New("unable to type assert wallet snapshot balance available") + } + } + b.Websocket.DataHandler <- wallet } case wsBalanceUpdate: if data, ok := d[2].([]interface{}); ok && len(data) > 0 { - b.Websocket.DataHandler <- WsBalanceInfo{ - TotalAssetsUnderManagement: data[0].(float64), - NetAssetsUnderManagement: data[1].(float64), + var balance WsBalanceInfo + if balance.TotalAssetsUnderManagement, ok = data[0].(float64); !ok { + return errors.New("unable to type assert balance total assets under management") } + if balance.NetAssetsUnderManagement, ok = data[1].(float64); !ok { + return errors.New("unable to type assert balance net assets under management") + } + b.Websocket.DataHandler <- balance } case wsMarginInfoUpdate: if data, ok := d[2].([]interface{}); ok && len(data) > 0 { - if data[0].(string) == "base" { - if infoBase, ok := d[2].([]interface{}); ok && len(infoBase) > 0 { - baseData, ok := data[1].([]interface{}) - if !ok { - return errors.New("unable to type assert wsMarginInfoUpdate baseData") - } - b.Websocket.DataHandler <- WsMarginInfoBase{ - UserProfitLoss: baseData[0].(float64), - UserSwaps: baseData[1].(float64), - MarginBalance: baseData[2].(float64), - MarginNet: baseData[3].(float64), - } + if eventType, ok := data[0].(string); ok && eventType == "base" { + baseData, ok := data[1].([]interface{}) + if !ok { + return errors.New("unable to type assert wsMarginInfoUpdate baseData") } + var marginInfoBase WsMarginInfoBase + if marginInfoBase.UserProfitLoss, ok = baseData[0].(float64); !ok { + return errors.New("unable to type assert margin info user profit loss") + } + if marginInfoBase.UserSwaps, ok = baseData[1].(float64); !ok { + return errors.New("unable to type assert margin info user swaps") + } + if marginInfoBase.MarginBalance, ok = baseData[2].(float64); !ok { + return errors.New("unable to type assert margin info balance") + } + if marginInfoBase.MarginNet, ok = baseData[3].(float64); !ok { + return errors.New("unable to type assert margin info net") + } + if marginInfoBase.MarginRequired, ok = baseData[4].(float64); !ok { + return errors.New("unable to type assert margin info required") + } + b.Websocket.DataHandler <- marginInfoBase } } case wsFundingInfoUpdate: if data, ok := d[2].([]interface{}); ok && len(data) > 0 { - if data[0].(string) == "sym" { - symbolData, ok := data[1].([]interface{}) + if fundingType, ok := data[0].(string); ok && fundingType == "sym" { + symbolData, ok := data[2].([]interface{}) if !ok { return errors.New("unable to type assert wsFundingInfoUpdate symbolData") } - b.Websocket.DataHandler <- WsFundingInfo{ - YieldLoan: symbolData[0].(float64), - YieldLend: symbolData[1].(float64), - DurationLoan: symbolData[2].(float64), - DurationLend: symbolData[3].(float64), + var fundingInfo WsFundingInfo + if fundingInfo.Symbol, ok = data[1].(string); !ok { + return errors.New("unable to type assert symbol") } + if fundingInfo.YieldLoan, ok = symbolData[0].(float64); !ok { + return errors.New("unable to type assert funding info update yield loan") + } + if fundingInfo.YieldLend, ok = symbolData[1].(float64); !ok { + return errors.New("unable to type assert funding info update yield lend") + } + if fundingInfo.DurationLoan, ok = symbolData[2].(float64); !ok { + return errors.New("unable to type assert funding info update duration loan") + } + if fundingInfo.DurationLend, ok = symbolData[3].(float64); !ok { + return errors.New("unable to type assert funding info update duration lend") + } + b.Websocket.DataHandler <- fundingInfo } } case wsFundingTradeExecuted, wsFundingTradeUpdate: if data, ok := d[2].([]interface{}); ok && len(data) > 0 { - b.Websocket.DataHandler <- WsFundingTrade{ - ID: int64(data[0].(float64)), - Symbol: data[1].(string), - MTSCreated: int64(data[2].(float64)), - OfferID: int64(data[3].(float64)), - Amount: data[4].(float64), - Rate: data[5].(float64), - Period: int64(data[6].(float64)), - Maker: data[7].(float64) == 1, + var wsFundingTrade WsFundingTrade + tradeID, ok := data[0].(float64) + if !ok { + return errors.New("unable to type assert funding trade ID") } + wsFundingTrade.ID = int64(tradeID) + if wsFundingTrade.Symbol, ok = data[1].(string); !ok { + return errors.New("unable to type assert funding trade symbol") + } + created, ok := data[2].(float64) + if !ok { + return errors.New("unable to type assert funding trade created") + } + wsFundingTrade.MTSCreated = time.UnixMilli(int64(created)) + offerID, ok := data[3].(float64) + if !ok { + return errors.New("unable to type assert funding trade offer ID") + } + wsFundingTrade.OfferID = int64(offerID) + if wsFundingTrade.Amount, ok = data[4].(float64); !ok { + return errors.New("unable to type assert funding trade amount") + } + if wsFundingTrade.Rate, ok = data[5].(float64); !ok { + return errors.New("unable to type assert funding trade rate") + } + period, ok := data[6].(float64) + if !ok { + return errors.New("unable to type assert funding trade period") + } + wsFundingTrade.Period = int64(period) + wsFundingTrade.Maker = data[7] != nil + b.Websocket.DataHandler <- wsFundingTrade } default: b.Websocket.DataHandler <- stream.UnhandledMessageWarning{ @@ -922,91 +1074,217 @@ func (b *Bitfinex) wsHandleData(respRaw []byte) error { return nil } -func (b *Bitfinex) wsHandleFundingOffer(data []interface{}) { - var fo WsFundingOffer - +func wsHandleFundingOffer(data []interface{}, includeRateReal bool) (*WsFundingOffer, error) { + var offer WsFundingOffer + var ok bool if data[0] != nil { - if id, ok := data[0].(float64); ok { - fo.ID = int64(id) + var offerID float64 + if offerID, ok = data[0].(float64); !ok { + return nil, errors.New("unable to type assert funding offer ID") } + offer.ID = int64(offerID) } if data[1] != nil { - if sym, ok := data[1].(string); ok { - fo.Symbol = sym[1:] + if offer.Symbol, ok = data[1].(string); !ok { + return nil, errors.New("unable to type assert funding offer symbol") } } if data[2] != nil { - if created, ok := data[2].(float64); ok { - fo.Created = int64(created) + var created float64 + if created, ok = data[2].(float64); !ok { + return nil, errors.New("unable to type assert funding offer created") } + offer.Created = time.UnixMilli(int64(created)) } if data[3] != nil { - if updated, ok := data[3].(float64); ok { - fo.Updated = int64(updated) - } - } - if data[15] != nil { - if period, ok := data[15].(float64); ok { - fo.Period = int64(period) + var updated float64 + if updated, ok = data[3].(float64); !ok { + return nil, errors.New("unable to type assert funding offer updated") } + offer.Updated = time.UnixMilli(int64(updated)) } if data[4] != nil { - if amount, ok := data[4].(float64); ok { - fo.Amount = amount + if offer.Amount, ok = data[4].(float64); !ok { + return nil, errors.New("unable to type assert funding offer amount") } } if data[5] != nil { - if origAmount, ok := data[5].(float64); ok { - fo.OriginalAmount = origAmount + if offer.OriginalAmount, ok = data[5].(float64); !ok { + return nil, errors.New("unable to type assert funding offer original amount") } } if data[6] != nil { - if fType, ok := data[6].(string); ok { - fo.Type = fType + if offer.Type, ok = data[6].(string); !ok { + return nil, errors.New("unable to type assert funding offer type") } } if data[9] != nil { - if flags, ok := data[9].(float64); ok { - fo.Flags = flags + if offer.Flags, ok = data[9].(float64); !ok { + return nil, errors.New("unable to type assert funding offer flags") } } - if data[9] != nil && data[10] != nil { - if status, ok := data[10].(string); ok { - fo.Status = status + if data[10] != nil { + if offer.Status, ok = data[10].(string); !ok { + return nil, errors.New("unable to type assert funding offer status") } } - if data[9] != nil && data[14] != nil { - if rate, ok := data[14].(float64); ok { - fo.Rate = rate + if data[14] != nil { + if offer.Rate, ok = data[14].(float64); !ok { + return nil, errors.New("unable to type assert funding offer rate") } } + if data[15] != nil { + var period float64 + if period, ok = data[15].(float64); !ok { + return nil, errors.New("unable to type assert funding offer period") + } + offer.Period = int64(period) + } if data[16] != nil { - if notify, ok := data[16].(float64); ok { - fo.Notify = notify == 1 + var notify float64 + if notify, ok = data[16].(float64); !ok { + return nil, errors.New("unable to type assert funding offer notify") } + offer.Notify = notify == 1 } if data[17] != nil { - if hidden, ok := data[17].(float64); ok { - fo.Hidden = hidden == 1 - } - } - if data[18] != nil { - if insure, ok := data[18].(float64); ok { - fo.Insure = insure == 1 + var hidden float64 + if hidden, ok = data[17].(float64); !ok { + return nil, errors.New("unable to type assert funding offer hidden") } + offer.Hidden = hidden == 1 } if data[19] != nil { - if renew, ok := data[19].(float64); ok { - fo.Renew = renew == 1 + var renew float64 + if renew, ok = data[19].(float64); !ok { + return nil, errors.New("unable to type assert funding offer renew") + } + offer.Renew = renew == 1 + } + if includeRateReal && data[20] != nil { + if offer.RateReal, ok = data[20].(float64); !ok { + return nil, errors.New("unable to type assert funding offer rate real") + } + } + return &offer, nil +} + +func wsHandleFundingCreditLoanData(data []interface{}, includePositionPair bool) (*WsCredit, error) { + var credit WsCredit + var ok bool + if data[0] != nil { + var id float64 + if id, ok = data[0].(float64); !ok { + return nil, errors.New("unable to type assert funding credit ID") + } + credit.ID = int64(id) + } + if data[1] != nil { + if credit.Symbol, ok = data[1].(string); !ok { + return nil, errors.New("unable to type assert funding credit symbol") + } + } + if data[2] != nil { + var side float64 + if side, ok = data[2].(float64); !ok { + return nil, errors.New("unable to type assert funding credit side") + } + credit.Side = int8(side) + } + if data[3] != nil { + var created float64 + if created, ok = data[3].(float64); !ok { + return nil, errors.New("unable to type assert funding credit created") + } + credit.Created = time.UnixMilli(int64(created)) + } + if data[4] != nil { + var updated float64 + if updated, ok = data[4].(float64); !ok { + return nil, errors.New("unable to type assert funding credit updated") + } + credit.Updated = time.UnixMilli(int64(updated)) + } + if data[5] != nil { + if credit.Amount, ok = data[5].(float64); !ok { + return nil, errors.New("unable to type assert funding credit amount") + } + } + if data[6] != nil { + credit.Flags = data[6] + } + if data[7] != nil { + if credit.Status, ok = data[7].(string); !ok { + return nil, errors.New("unable to type assert funding credit status") + } + } + if data[11] != nil { + if credit.Rate, ok = data[11].(float64); !ok { + return nil, errors.New("unable to type assert funding credit rate") + } + } + if data[12] != nil { + var period float64 + if period, ok = data[12].(float64); !ok { + return nil, errors.New("unable to type assert funding credit period") + } + credit.Period = int64(period) + } + if data[13] != nil { + var opened float64 + if opened, ok = data[13].(float64); !ok { + return nil, errors.New("unable to type assert funding credit opened") + } + credit.Opened = time.UnixMilli(int64(opened)) + } + if data[14] != nil { + var lastPayout float64 + if lastPayout, ok = data[14].(float64); !ok { + return nil, errors.New("unable to type assert last funding credit payout") + } + credit.LastPayout = time.UnixMilli(int64(lastPayout)) + } + if data[15] != nil { + var notify float64 + if notify, ok = data[15].(float64); !ok { + return nil, errors.New("unable to type assert funding credit notify") + } + credit.Notify = notify == 1 + } + if data[16] != nil { + var hidden float64 + if hidden, ok = data[16].(float64); !ok { + return nil, errors.New("unable to type assert funding credit hidden") + } + credit.Hidden = hidden == 1 + } + if data[18] != nil { + var renew float64 + if renew, ok = data[18].(float64); !ok { + return nil, errors.New("unable to type assert funding credit renew") + } + credit.Renew = renew == 1 + } + if data[19] != nil { + if credit.RateReal, ok = data[19].(float64); !ok { + return nil, errors.New("unable to type assert rate funding credit real") } } if data[20] != nil { - if rateReal, ok := data[20].(float64); ok { - fo.RateReal = rateReal + var noClose float64 + if noClose, ok = data[20].(float64); !ok { + return nil, errors.New("unable to type assert no funding credit close") + } + credit.NoClose = noClose == 1 + } + if includePositionPair { + if data[21] != nil { + if credit.PositionPair, ok = data[21].(string); !ok { + return nil, errors.New("unable to type assert funding credit position pair") + } } } - - b.Websocket.DataHandler <- fo + return &credit, nil } func (b *Bitfinex) wsHandleOrder(data []interface{}) { @@ -1093,6 +1371,8 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books return errors.New("no orderbooks submitted") } var book orderbook.Base + book.Bids = make(orderbook.Items, 0, len(books)) + book.Asks = make(orderbook.Items, 0, len(books)) for i := range books { item := orderbook.Item{ ID: books[i].ID, @@ -1129,7 +1409,12 @@ func (b *Bitfinex) WsInsertSnapshot(p currency.Pair, assetType asset.Item, books // WsUpdateOrderbook updates the orderbook list, removing and adding to the // orderbook sides func (b *Bitfinex) WsUpdateOrderbook(p currency.Pair, assetType asset.Item, book []WebsocketBook, channelID int, sequenceNo int64, fundingRate bool) error { - orderbookUpdate := buffer.Update{Asset: assetType, Pair: p} + orderbookUpdate := buffer.Update{ + Asset: assetType, + Pair: p, + Bids: make([]orderbook.Item, 0, len(book)), + Asks: make([]orderbook.Item, 0, len(book)), + } for i := range book { item := orderbook.Item{ @@ -1526,7 +1811,7 @@ func (b *Bitfinex) WsCancelAllOrders() error { // WsNewOffer authenticated new offer request func (b *Bitfinex) WsNewOffer(data *WsNewOfferRequest) error { - request := makeRequestInterface(wsFundingOrderNew, data) + request := makeRequestInterface(wsFundingOfferNew, data) return b.Websocket.AuthConn.SendJSONMessage(request) } @@ -1535,7 +1820,7 @@ func (b *Bitfinex) WsCancelOffer(orderID int64) error { cancel := WsCancelOrderRequest{ OrderID: orderID, } - request := makeRequestInterface(wsFundingOrderCancel, cancel) + request := makeRequestInterface(wsFundingOfferCancel, cancel) resp, err := b.Websocket.AuthConn.SendMessageReturnResponse(orderID, request) if err != nil { return err diff --git a/exchanges/bitfinex/bitfinex_wrapper.go b/exchanges/bitfinex/bitfinex_wrapper.go index 5db6935c..17a8ec4d 100644 --- a/exchanges/bitfinex/bitfinex_wrapper.go +++ b/exchanges/bitfinex/bitfinex_wrapper.go @@ -438,40 +438,44 @@ func (b *Bitfinex) UpdateOrderbook(ctx context.Context, p currency.Pair, assetTy var orderbookNew Orderbook orderbookNew, err = b.GetOrderbook(ctx, prefix+fPair.String(), "R0", 100) if err != nil { - return nil, err + return o, err } if assetType == asset.MarginFunding { o.IsFundingRate = true + o.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { - o.Asks = append(o.Asks, orderbook.Item{ + o.Asks[x] = orderbook.Item{ ID: orderbookNew.Asks[x].OrderID, Price: orderbookNew.Asks[x].Rate, Amount: orderbookNew.Asks[x].Amount, Period: int64(orderbookNew.Asks[x].Period), - }) + } } + o.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { - o.Bids = append(o.Bids, orderbook.Item{ + o.Bids[x] = orderbook.Item{ ID: orderbookNew.Bids[x].OrderID, Price: orderbookNew.Bids[x].Rate, Amount: orderbookNew.Bids[x].Amount, Period: int64(orderbookNew.Bids[x].Period), - }) + } } } else { + o.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { - o.Asks = append(o.Asks, orderbook.Item{ + o.Asks[x] = orderbook.Item{ ID: orderbookNew.Asks[x].OrderID, Price: orderbookNew.Asks[x].Price, Amount: orderbookNew.Asks[x].Amount, - }) + } } + o.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { - o.Bids = append(o.Bids, orderbook.Item{ + o.Bids[x] = orderbook.Item{ ID: orderbookNew.Bids[x].OrderID, Price: orderbookNew.Bids[x].Price, Amount: orderbookNew.Bids[x].Amount, - }) + } } } err = o.Process() @@ -885,12 +889,12 @@ func (b *Bitfinex) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequ return nil, err } - var orders []order.Detail resp, err := b.GetOpenOrders(ctx) if err != nil { return nil, err } + orders := make([]order.Detail, len(resp)) for i := range resp { orderSide := order.Side(strings.ToUpper(resp[i].Side)) timestamp, err := strconv.ParseFloat(resp[i].Timestamp, 64) @@ -937,7 +941,7 @@ func (b *Bitfinex) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequ orderDetail.Type = order.Type(strings.ToUpper(orderType)) } - orders = append(orders, orderDetail) + orders[i] = orderDetail } order.FilterOrdersBySide(&orders, req.Side) @@ -954,12 +958,12 @@ func (b *Bitfinex) GetOrderHistory(ctx context.Context, req *order.GetOrdersRequ return nil, err } - var orders []order.Detail resp, err := b.GetInactiveOrders(ctx) if err != nil { return nil, err } + orders := make([]order.Detail, len(resp)) for i := range resp { orderSide := order.Side(strings.ToUpper(resp[i].Side)) timestamp, err := strconv.ParseInt(resp[i].Timestamp, 10, 64) @@ -1007,7 +1011,7 @@ func (b *Bitfinex) GetOrderHistory(ctx context.Context, req *order.GetOrdersRequ orderDetail.Type = order.Type(strings.ToUpper(orderType)) } - orders = append(orders, orderDetail) + orders[i] = orderDetail } order.FilterOrdersBySide(&orders, req.Side) diff --git a/exchanges/bitflyer/bitflyer_wrapper.go b/exchanges/bitflyer/bitflyer_wrapper.go index 0d41f566..1a6022d7 100644 --- a/exchanges/bitflyer/bitflyer_wrapper.go +++ b/exchanges/bitflyer/bitflyer_wrapper.go @@ -288,16 +288,20 @@ func (b *Bitflyer) UpdateOrderbook(ctx context.Context, p currency.Pair, assetTy return book, err } + book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[x] = orderbook.Item{ Price: orderbookNew.Asks[x].Price, - Amount: orderbookNew.Asks[x].Size}) + Amount: orderbookNew.Asks[x].Size, + } } + book.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[x] = orderbook.Item{ Price: orderbookNew.Bids[x].Price, - Amount: orderbookNew.Bids[x].Size}) + Amount: orderbookNew.Bids[x].Size, + } } err = book.Process() @@ -346,7 +350,7 @@ func (b *Bitflyer) GetRecentTrades(ctx context.Context, p currency.Pair, assetTy if err != nil { return nil, err } - var resp []trade.Data + resp := make([]trade.Data, len(tradeData)) for i := range tradeData { var timestamp time.Time timestamp, err = time.Parse("2006-01-02T15:04:05.999999999", tradeData[i].ExecDate) @@ -358,7 +362,7 @@ func (b *Bitflyer) GetRecentTrades(ctx context.Context, p currency.Pair, assetTy if err != nil { return nil, err } - resp = append(resp, trade.Data{ + resp[i] = trade.Data{ TID: strconv.FormatInt(tradeData[i].ID, 10), Exchange: b.Name, CurrencyPair: p, @@ -367,7 +371,7 @@ func (b *Bitflyer) GetRecentTrades(ctx context.Context, p currency.Pair, assetTy Price: tradeData[i].Price, Amount: tradeData[i].Size, Timestamp: timestamp, - }) + } } err = b.AddTradesToBuffer(resp...) diff --git a/exchanges/bithumb/bithumb.go b/exchanges/bithumb/bithumb.go index 397d0262..365c5716 100644 --- a/exchanges/bithumb/bithumb.go +++ b/exchanges/bithumb/bithumb.go @@ -64,7 +64,7 @@ func (b *Bithumb) GetTradablePairs(ctx context.Context) ([]string, error) { return nil, err } - var currencies []string + currencies := make([]string, 0, len(result)) for x := range result { currencies = append(currencies, x) } @@ -703,7 +703,7 @@ func (b *Bithumb) FetchExchangeLimits(ctx context.Context) ([]order.MinMaxLevel, return nil, err } - var limits []order.MinMaxLevel + limits := make([]order.MinMaxLevel, 0, len(ticks)) for code, data := range ticks { c := currency.NewCode(code) cp := currency.NewPair(c, currency.KRW) diff --git a/exchanges/bithumb/bithumb_websocket_test.go b/exchanges/bithumb/bithumb_websocket_test.go index b885aae9..d0f41165 100644 --- a/exchanges/bithumb/bithumb_websocket_test.go +++ b/exchanges/bithumb/bithumb_websocket_test.go @@ -73,8 +73,7 @@ func TestWsHandleData(t *testing.T) { } handled := <-dummy.Websocket.DataHandler - _, ok := handled.(*ticker.Price) - if !ok { + if _, ok := handled.(*ticker.Price); !ok { t.Fatal("unexpected value") } diff --git a/exchanges/bithumb/bithumb_wrapper.go b/exchanges/bithumb/bithumb_wrapper.go index 56705ed7..6dc6ea1c 100644 --- a/exchanges/bithumb/bithumb_wrapper.go +++ b/exchanges/bithumb/bithumb_wrapper.go @@ -336,20 +336,20 @@ func (b *Bithumb) UpdateOrderbook(ctx context.Context, p currency.Pair, assetTyp return book, err } + book.Bids = make(orderbook.Items, len(orderbookNew.Data.Bids)) for i := range orderbookNew.Data.Bids { - book.Bids = append(book.Bids, - orderbook.Item{ - Amount: orderbookNew.Data.Bids[i].Quantity, - Price: orderbookNew.Data.Bids[i].Price, - }) + book.Bids[i] = orderbook.Item{ + Amount: orderbookNew.Data.Bids[i].Quantity, + Price: orderbookNew.Data.Bids[i].Price, + } } + book.Asks = make(orderbook.Items, len(orderbookNew.Data.Asks)) for i := range orderbookNew.Data.Asks { - book.Asks = append(book.Asks, - orderbook.Item{ - Amount: orderbookNew.Data.Asks[i].Quantity, - Price: orderbookNew.Data.Asks[i].Price, - }) + book.Asks[i] = orderbook.Item{ + Amount: orderbookNew.Data.Asks[i].Quantity, + Price: orderbookNew.Data.Asks[i].Price, + } } err = book.Process() @@ -368,7 +368,7 @@ func (b *Bithumb) UpdateAccountInfo(ctx context.Context, assetType asset.Item) ( return info, err } - var exchangeBalances []account.Balance + exchangeBalances := make([]account.Balance, 0, len(bal.Total)) for key, totalAmount := range bal.Total { hold, ok := bal.InUse[key] if !ok { @@ -435,7 +435,7 @@ func (b *Bithumb) GetRecentTrades(ctx context.Context, p currency.Pair, assetTyp if err != nil { return nil, err } - var resp []trade.Data + resp := make([]trade.Data, len(tradeData.Data)) for i := range tradeData.Data { var side order.Side side, err = order.StringToOrderSide(tradeData.Data[i].Type) @@ -447,7 +447,7 @@ func (b *Bithumb) GetRecentTrades(ctx context.Context, p currency.Pair, assetTyp if err != nil { return nil, err } - resp = append(resp, trade.Data{ + resp[i] = trade.Data{ Exchange: b.Name, CurrencyPair: p, AssetType: assetType, @@ -455,7 +455,7 @@ func (b *Bithumb) GetRecentTrades(ctx context.Context, p currency.Pair, assetTyp Price: tradeData.Data[i].Price, Amount: tradeData.Data[i].UnitsTraded, Timestamp: t, - }) + } } err = b.AddTradesToBuffer(resp...) diff --git a/exchanges/bithumb/bithumb_ws_orderbook.go b/exchanges/bithumb/bithumb_ws_orderbook.go index 4486b767..0f5af066 100644 --- a/exchanges/bithumb/bithumb_ws_orderbook.go +++ b/exchanges/bithumb/bithumb_ws_orderbook.go @@ -26,7 +26,8 @@ const ( ) func (b *Bithumb) processBooks(updates *WsOrderbooks) error { - var bids, asks []orderbook.Item + bids := make([]orderbook.Item, 0, len(updates.List)) + asks := make([]orderbook.Item, 0, len(updates.List)) for x := range updates.List { i := orderbook.Item{Price: updates.List[x].Price, Amount: updates.List[x].Quantity} if updates.List[x].OrderSide == "bid" { @@ -426,17 +427,19 @@ func (b *Bithumb) SeedLocalCache(ctx context.Context, p currency.Pair) error { // SeedLocalCacheWithBook seeds the local orderbook cache func (b *Bithumb) SeedLocalCacheWithBook(p currency.Pair, o *Orderbook) error { var newOrderBook orderbook.Base + newOrderBook.Bids = make(orderbook.Items, len(o.Data.Bids)) for i := range o.Data.Bids { - newOrderBook.Bids = append(newOrderBook.Bids, orderbook.Item{ + newOrderBook.Bids[i] = orderbook.Item{ Amount: o.Data.Bids[i].Quantity, Price: o.Data.Bids[i].Price, - }) + } } + newOrderBook.Asks = make(orderbook.Items, len(o.Data.Asks)) for i := range o.Data.Asks { - newOrderBook.Asks = append(newOrderBook.Asks, orderbook.Item{ + newOrderBook.Asks[i] = orderbook.Item{ Amount: o.Data.Asks[i].Quantity, Price: o.Data.Asks[i].Price, - }) + } } newOrderBook.Pair = p diff --git a/exchanges/bitmex/bitmex.go b/exchanges/bitmex/bitmex.go index a0db6e1d..9672b5ec 100644 --- a/exchanges/bitmex/bitmex.go +++ b/exchanges/bitmex/bitmex.go @@ -58,8 +58,8 @@ const ( // Authenticated endpoints bitmexEndpointAPIkeys = "/apiKey" - bitmexEndpointDisableAPIkey = "/apiKey/disable" - bitmexEndpointEnableAPIkey = "/apiKey/enable" + bitmexEndpointDisableAPIkey = "/apiKey/disable" // nolint:gosec // false positive + bitmexEndpointEnableAPIkey = "/apiKey/enable" // nolint:gosec // false positive bitmexEndpointTrollboxSend = "/chat" bitmexEndpointExecution = "/execution" bitmexEndpointExecutionTradeHistory = "/execution/tradeHistory" diff --git a/exchanges/bitmex/bitmex_test.go b/exchanges/bitmex/bitmex_test.go index ab1362bb..4e09136f 100644 --- a/exchanges/bitmex/bitmex_test.go +++ b/exchanges/bitmex/bitmex_test.go @@ -781,7 +781,7 @@ func TestWithdraw(t *testing.T) { Amount: -1, Currency: currency.BTC, Description: "WITHDRAW IT ALL", - OneTimePassword: 000000, + OneTimePassword: 000000, // nolint // gocritic false positive } if areTestAPIKeysSet() && !canManipulateRealOrders { @@ -860,7 +860,11 @@ func TestWsAuth(t *testing.T) { timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout) select { case resp := <-b.Websocket.DataHandler: - if !resp.(WebsocketSubscribeResp).Success { + sub, ok := resp.(WebsocketSubscribeResp) + if !ok { + t.Fatal("unable to type assert WebsocketSubscribeResp") + } + if !sub.Success { t.Error("Expected successful subscription") } case <-timer.C: diff --git a/exchanges/bitmex/bitmex_websocket.go b/exchanges/bitmex/bitmex_websocket.go index 69d420a8..016682b9 100644 --- a/exchanges/bitmex/bitmex_websocket.go +++ b/exchanges/bitmex/bitmex_websocket.go @@ -97,18 +97,20 @@ func (b *Bitmex) WsConnect() error { b.Websocket.Wg.Add(1) go b.wsReadData() - err = b.websocketSendAuth(context.TODO()) - if err != nil { - log.Errorf(log.ExchangeSys, - "%v - authentication failed: %v\n", - b.Name, - err) - } else { - authsubs, err := b.GenerateAuthenticatedSubscriptions() + if b.Websocket.CanUseAuthenticatedEndpoints() { + err = b.websocketSendAuth(context.TODO()) if err != nil { - return err + log.Errorf(log.ExchangeSys, + "%v - authentication failed: %v\n", + b.Name, + err) + } else { + authsubs, err := b.GenerateAuthenticatedSubscriptions() + if err != nil { + return err + } + return b.Websocket.SubscribeToChannels(authsubs) } - return b.Websocket.SubscribeToChannels(authsubs) } return nil } @@ -491,7 +493,11 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, p currency. switch action { case bitmexActionInitialData: - var book orderbook.Base + book := orderbook.Base{ + Asks: make(orderbook.Items, 0, len(data)), + Bids: make(orderbook.Items, 0, len(data)), + } + for i := range data { item := orderbook.Item{ Price: data[i].Price, @@ -520,7 +526,8 @@ func (b *Bitmex) processOrderbook(data []OrderBookL2, action string, p currency. err) } default: - var asks, bids []orderbook.Item + asks := make([]orderbook.Item, 0, len(data)) + bids := make([]orderbook.Item, 0, len(data)) for i := range data { nItem := orderbook.Item{ Price: data[i].Price, diff --git a/exchanges/bitmex/bitmex_wrapper.go b/exchanges/bitmex/bitmex_wrapper.go index 26a78293..b385a8f0 100644 --- a/exchanges/bitmex/bitmex_wrapper.go +++ b/exchanges/bitmex/bitmex_wrapper.go @@ -233,9 +233,9 @@ func (b *Bitmex) FetchTradablePairs(ctx context.Context, asset asset.Item) ([]st return nil, err } - var products []string + products := make([]string, len(marketInfo)) for x := range marketInfo { - products = append(products, marketInfo[x].Symbol.String()) + products[x] = marketInfo[x].Symbol.String() } return products, nil @@ -394,16 +394,20 @@ func (b *Bitmex) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType return book, err } + book.Asks = make(orderbook.Items, 0, len(orderbookNew)) + book.Bids = make(orderbook.Items, 0, len(orderbookNew)) for i := range orderbookNew { switch { case strings.EqualFold(orderbookNew[i].Side, order.Sell.String()): book.Asks = append(book.Asks, orderbook.Item{ Amount: float64(orderbookNew[i].Size), - Price: orderbookNew[i].Price}) + Price: orderbookNew[i].Price, + }) case strings.EqualFold(orderbookNew[i].Side, order.Buy.String()): book.Bids = append(book.Bids, orderbook.Item{ Amount: float64(orderbookNew[i].Size), - Price: orderbookNew[i].Price}) + Price: orderbookNew[i].Price, + }) default: return book, fmt.Errorf("could not process orderbook, order side [%s] could not be matched", @@ -748,10 +752,9 @@ func (b *Bitmex) GetActiveOrders(ctx context.Context, req *order.GetOrdersReques return nil, err } - var orders []order.Detail - params := OrdersRequest{} - params.Filter = "{\"open\":true}" - + params := OrdersRequest{ + Filter: "{\"open\":true}", + } resp, err := b.GetOrders(ctx, ¶ms) if err != nil { return nil, err @@ -762,6 +765,7 @@ func (b *Bitmex) GetActiveOrders(ctx context.Context, req *order.GetOrdersReques return nil, err } + orders := make([]order.Detail, len(resp)) for i := range resp { orderSide := orderSideMap[resp[i].Side] orderStatus, err := order.StringToOrderStatus(resp[i].OrdStatus) @@ -789,7 +793,7 @@ func (b *Bitmex) GetActiveOrders(ctx context.Context, req *order.GetOrdersReques format.Delimiter), } - orders = append(orders, orderDetail) + orders[i] = orderDetail } order.FilterOrdersBySide(&orders, req.Side) @@ -807,7 +811,6 @@ func (b *Bitmex) GetOrderHistory(ctx context.Context, req *order.GetOrdersReques return nil, err } - var orders []order.Detail params := OrdersRequest{} resp, err := b.GetOrders(ctx, ¶ms) if err != nil { @@ -819,6 +822,7 @@ func (b *Bitmex) GetOrderHistory(ctx context.Context, req *order.GetOrdersReques return nil, err } + orders := make([]order.Detail, len(resp)) for i := range resp { orderSide := orderSideMap[resp[i].Side] orderStatus, err := order.StringToOrderStatus(resp[i].OrdStatus) @@ -848,7 +852,7 @@ func (b *Bitmex) GetOrderHistory(ctx context.Context, req *order.GetOrdersReques } orderDetail.InferCostsAndTimes() - orders = append(orders, orderDetail) + orders[i] = orderDetail } order.FilterOrdersBySide(&orders, req.Side) diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index 119a4f40..ad4d07dc 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -18,7 +18,6 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" - "github.com/thrasher-corp/gocryptotrader/log" ) const ( @@ -144,51 +143,51 @@ func (b *Bitstamp) GetTicker(ctx context.Context, currency string, hourly bool) // GetOrderbook Returns a JSON dictionary with "bids" and "asks". Each is a list // of open orders and each order is represented as a list holding the price and // the amount. -func (b *Bitstamp) GetOrderbook(ctx context.Context, currency string) (Orderbook, error) { +func (b *Bitstamp) GetOrderbook(ctx context.Context, currency string) (*Orderbook, error) { type response struct { - Timestamp int64 `json:"timestamp,string"` - Bids [][]string `json:"bids"` - Asks [][]string `json:"asks"` + Timestamp int64 `json:"timestamp,string"` + Bids [][2]string `json:"bids"` + Asks [][2]string `json:"asks"` } - resp := response{} + path := "/v" + bitstampAPIVersion + "/" + bitstampAPIOrderbook + "/" + strings.ToLower(currency) + "/" + var resp response err := b.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) if err != nil { - return Orderbook{}, err + return nil, err } - orderbook := Orderbook{} - orderbook.Timestamp = resp.Timestamp - - for _, x := range resp.Bids { - price, err := strconv.ParseFloat(x[0], 64) - if err != nil { - log.Error(log.ExchangeSys, err) - continue - } - amount, err := strconv.ParseFloat(x[1], 64) - if err != nil { - log.Error(log.ExchangeSys, err) - continue - } - orderbook.Bids = append(orderbook.Bids, OrderbookBase{price, amount}) + orderbook := Orderbook{ + Timestamp: resp.Timestamp, + Bids: make([]OrderbookBase, len(resp.Bids)), + Asks: make([]OrderbookBase, len(resp.Asks)), } - for _, x := range resp.Asks { - price, err := strconv.ParseFloat(x[0], 64) + for x := range resp.Bids { + price, err := strconv.ParseFloat(resp.Bids[x][0], 64) if err != nil { - log.Error(log.ExchangeSys, err) - continue + return nil, err } - amount, err := strconv.ParseFloat(x[1], 64) + amount, err := strconv.ParseFloat(resp.Bids[x][1], 64) if err != nil { - log.Error(log.ExchangeSys, err) - continue + return nil, err } - orderbook.Asks = append(orderbook.Asks, OrderbookBase{price, amount}) + orderbook.Bids[x] = OrderbookBase{price, amount} } - return orderbook, nil + for x := range resp.Asks { + price, err := strconv.ParseFloat(resp.Asks[x][0], 64) + if err != nil { + return nil, err + } + amount, err := strconv.ParseFloat(resp.Asks[x][1], 64) + if err != nil { + return nil, err + } + orderbook.Asks[x] = OrderbookBase{price, amount} + } + + return &orderbook, nil } // GetTradingPairs returns a list of trading pairs which Bitstamp @@ -305,7 +304,7 @@ func (b *Bitstamp) GetUserTransactions(ctx context.Context, currencyPair string) } } - var transactions []UserTransactions + transactions := make([]UserTransactions, len(response)) for x := range response { tx := UserTransactions{} tx.Date = response[x].Date @@ -318,7 +317,7 @@ func (b *Bitstamp) GetUserTransactions(ctx context.Context, currencyPair string) tx.BTCUSD = processNumber(response[x].BTCUSD) tx.Fee = response[x].Fee tx.OrderID = response[x].OrderID - transactions = append(transactions, tx) + transactions[x] = tx } return transactions, nil diff --git a/exchanges/bitstamp/bitstamp_types.go b/exchanges/bitstamp/bitstamp_types.go index ce4cee26..2742820c 100644 --- a/exchanges/bitstamp/bitstamp_types.go +++ b/exchanges/bitstamp/bitstamp_types.go @@ -221,10 +221,10 @@ type websocketOrderBookResponse struct { } type websocketOrderBook struct { - Asks [][]string `json:"asks"` - Bids [][]string `json:"bids"` - Timestamp int64 `json:"timestamp,string"` - Microtimestamp string `json:"microtimestamp"` + Asks [][2]string `json:"asks"` + Bids [][2]string `json:"bids"` + Timestamp int64 `json:"timestamp,string"` + Microtimestamp string `json:"microtimestamp"` } // OHLCResponse holds returned candle data diff --git a/exchanges/bitstamp/bitstamp_websocket.go b/exchanges/bitstamp/bitstamp_websocket.go index 14daad25..a15ec6d5 100644 --- a/exchanges/bitstamp/bitstamp_websocket.go +++ b/exchanges/bitstamp/bitstamp_websocket.go @@ -21,7 +21,7 @@ import ( ) const ( - bitstampWSURL = "wss://ws.bitstamp.net" + bitstampWSURL = "wss://ws.bitstamp.net" // nolint // gosec false positive ) // WsConnect connects to a websocket feed @@ -255,33 +255,29 @@ func (b *Bitstamp) wsUpdateOrderbook(update websocketOrderBook, p currency.Pair, if len(update.Asks) == 0 && len(update.Bids) == 0 { return errors.New("no orderbook data") } - var asks, bids []orderbook.Item + asks := make([]orderbook.Item, len(update.Asks)) + bids := make([]orderbook.Item, len(update.Bids)) for i := range update.Asks { target, err := strconv.ParseFloat(update.Asks[i][0], 64) if err != nil { - b.Websocket.DataHandler <- err - continue + return err } amount, err := strconv.ParseFloat(update.Asks[i][1], 64) if err != nil { - b.Websocket.DataHandler <- err - continue + return err } - asks = append(asks, orderbook.Item{Price: target, Amount: amount}) + asks[i] = orderbook.Item{Price: target, Amount: amount} } for i := range update.Bids { target, err := strconv.ParseFloat(update.Bids[i][0], 64) if err != nil { - b.Websocket.DataHandler <- err - continue + return err } amount, err := strconv.ParseFloat(update.Bids[i][1], 64) if err != nil { - b.Websocket.DataHandler <- err - continue + return err } - - bids = append(bids, orderbook.Item{Price: target, Amount: amount}) + bids[i] = orderbook.Item{Price: target, Amount: amount} } return b.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{ Bids: bids, @@ -311,17 +307,19 @@ func (b *Bitstamp) seedOrderBook(ctx context.Context) error { } var newOrderBook orderbook.Base + newOrderBook.Asks = make(orderbook.Items, len(orderbookSeed.Asks)) for i := range orderbookSeed.Asks { - newOrderBook.Asks = append(newOrderBook.Asks, orderbook.Item{ + newOrderBook.Asks[i] = orderbook.Item{ Price: orderbookSeed.Asks[i].Price, Amount: orderbookSeed.Asks[i].Amount, - }) + } } + newOrderBook.Bids = make(orderbook.Items, len(orderbookSeed.Bids)) for i := range orderbookSeed.Bids { - newOrderBook.Bids = append(newOrderBook.Bids, orderbook.Item{ + newOrderBook.Bids[i] = orderbook.Item{ Price: orderbookSeed.Bids[i].Price, Amount: orderbookSeed.Bids[i].Amount, - }) + } } newOrderBook.Pair = p[x] newOrderBook.Asset = asset.Spot diff --git a/exchanges/bitstamp/bitstamp_wrapper.go b/exchanges/bitstamp/bitstamp_wrapper.go index 5b068a0e..bc3a623d 100644 --- a/exchanges/bitstamp/bitstamp_wrapper.go +++ b/exchanges/bitstamp/bitstamp_wrapper.go @@ -285,7 +285,7 @@ func (b *Bitstamp) FetchTradablePairs(ctx context.Context, asset asset.Item) ([] return nil, err } - var products []string + products := make([]string, 0, len(pairs)) for x := range pairs { if pairs[x].Trading != "Enabled" { continue @@ -406,18 +406,20 @@ func (b *Bitstamp) UpdateOrderbook(ctx context.Context, p currency.Pair, assetTy return book, err } + book.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[x] = orderbook.Item{ Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price, - }) + } } + book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[x] = orderbook.Item{ Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price, - }) + } } err = book.Process() if err != nil { @@ -436,7 +438,7 @@ func (b *Bitstamp) UpdateAccountInfo(ctx context.Context, assetType asset.Item) return response, err } - var currencies []account.Balance + currencies := make([]account.Balance, 0, len(accountBalance)) for k, v := range accountBalance { currencies = append(currencies, account.Balance{ CurrencyName: currency.NewCode(k), @@ -480,23 +482,23 @@ func (b *Bitstamp) GetWithdrawalsHistory(ctx context.Context, c currency.Code) ( // GetRecentTrades returns the most recent trades for a currency and asset func (b *Bitstamp) GetRecentTrades(ctx context.Context, p currency.Pair, assetType asset.Item) ([]trade.Data, error) { - var err error - p, err = b.FormatExchangeCurrency(p, assetType) + p, err := b.FormatExchangeCurrency(p, assetType) if err != nil { return nil, err } - var tradeData []Transactions - tradeData, err = b.GetTransactions(ctx, p.String(), "") + + tradeData, err := b.GetTransactions(ctx, p.String(), "") if err != nil { return nil, err } - var resp []trade.Data + + resp := make([]trade.Data, len(tradeData)) for i := range tradeData { s := order.Buy if tradeData[i].Type == 1 { s = order.Sell } - resp = append(resp, trade.Data{ + resp[i] = trade.Data{ Exchange: b.Name, TID: strconv.FormatInt(tradeData[i].TradeID, 10), CurrencyPair: p, @@ -505,7 +507,7 @@ func (b *Bitstamp) GetRecentTrades(ctx context.Context, p currency.Pair, assetTy Price: tradeData[i].Price, Amount: tradeData[i].Amount, Timestamp: time.Unix(tradeData[i].Date, 0), - }) + } } err = b.AddTradesToBuffer(resp...) @@ -720,7 +722,7 @@ func (b *Bitstamp) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequ return nil, err } - var orders []order.Detail + orders := make([]order.Detail, len(resp)) for i := range resp { orderSide := order.Buy if resp[i].Type == SellOrder { @@ -745,7 +747,7 @@ func (b *Bitstamp) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequ p = req.Pairs[0] } - orders = append(orders, order.Detail{ + orders[i] = order.Detail{ Amount: resp[i].Amount, ID: strconv.FormatInt(resp[i].ID, 10), Price: resp[i].Price, @@ -754,7 +756,7 @@ func (b *Bitstamp) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequ Date: tm, Pair: p, Exchange: b.Name, - }) + } } order.FilterOrdersByTimeRange(&orders, req.StartTime, req.EndTime) @@ -788,7 +790,7 @@ func (b *Bitstamp) GetOrderHistory(ctx context.Context, req *order.GetOrdersRequ return nil, err } - var orders []order.Detail + orders := make([]order.Detail, 0, len(resp)) for i := range resp { if resp[i].Type != MarketTrade { continue diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go index c8a74899..ca20c3b5 100644 --- a/exchanges/bittrex/bittrex.go +++ b/exchanges/bittrex/bittrex.go @@ -103,7 +103,7 @@ func (b *Bittrex) GetMarketSummary(ctx context.Context, marketName string) (Mark // GetOrderbook method returns current order book information by currency and depth. // "marketSymbol" ie ltc-btc // "depth" is either 1, 25 or 500. Server side, the depth defaults to 25. -func (b *Bittrex) GetOrderbook(ctx context.Context, marketName string, depth int64) (OrderbookData, int64, error) { +func (b *Bittrex) GetOrderbook(ctx context.Context, marketName string, depth int64) (*OrderbookData, int64, error) { strDepth := strconv.FormatInt(depth, 10) var resp OrderbookData @@ -111,14 +111,14 @@ func (b *Bittrex) GetOrderbook(ctx context.Context, marketName string, depth int resultHeader := http.Header{} err := b.SendHTTPRequest(ctx, exchange.RestSpot, fmt.Sprintf(getOrderbook, marketName, strDepth), &resp, &resultHeader) if err != nil { - return OrderbookData{}, 0, err + return nil, 0, err } sequence, err = strconv.ParseInt(resultHeader.Get("sequence"), 10, 64) if err != nil { - return OrderbookData{}, 0, err + return nil, 0, err } - return resp, sequence, nil + return &resp, sequence, nil } // GetMarketHistory retrieves the latest trades that have occurred for a specific market diff --git a/exchanges/bittrex/bittrex_websocket.go b/exchanges/bittrex/bittrex_websocket.go index 575f2a60..17f368f2 100644 --- a/exchanges/bittrex/bittrex_websocket.go +++ b/exchanges/bittrex/bittrex_websocket.go @@ -7,7 +7,7 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "net/http" "net/url" "strconv" @@ -61,7 +61,7 @@ var defaultSpotSubscribedChannelsAuth = []string{ type TickerCache struct { MarketSummaries map[string]*MarketSummaryData Tickers map[string]*TickerData - sync.RWMutex + mu sync.RWMutex } // WsConnect connects to a websocket feed @@ -277,9 +277,9 @@ func (b *Bittrex) subscribeSlice(channelsToSubscribe []stream.ChannelSubscriptio InvocationID: b.Websocket.Conn.GenerateMessageID(false), } - var channels []string + channels := make([]string, len(channelsToSubscribe)) for i := range channelsToSubscribe { - channels = append(channels, channelsToSubscribe[i].Channel) + channels[i] = channelsToSubscribe[i].Channel } arguments := make([][]string, 0) arguments = append(arguments, channels) @@ -342,9 +342,9 @@ func (b *Bittrex) unsubscribeSlice(channelsToUnsubscribe []stream.ChannelSubscri InvocationID: b.Websocket.Conn.GenerateMessageID(false), } - var channels []string + channels := make([]string, len(channelsToUnsubscribe)) for i := range channelsToUnsubscribe { - channels = append(channels, channelsToUnsubscribe[i].Channel) + channels[i] = channelsToUnsubscribe[i].Channel } arguments := make([][]string, 0) arguments = append(arguments, channels) @@ -409,10 +409,16 @@ func (b *Bittrex) wsDecodeMessage(encodedMessage string, v interface{}) error { return err } reader := flate.NewReader(bytes.NewBuffer(raw)) - message, err := ioutil.ReadAll(reader) + message, err := io.ReadAll(reader) if err != nil { return err } + if err = reader.Close(); err != nil { + log.Warnf(log.WebsocketMgr, "%s wsDecodeMessage: unable to close reader: %s", + b.Name, + err, + ) + } return json.Unmarshal(message, v) } @@ -520,8 +526,8 @@ func (b *Bittrex) WsProcessUpdateTicker(tickerData TickerData) error { tickerPrice, err := ticker.GetTicker(b.Name, pair, asset.Spot) if err != nil { - b.tickerCache.Lock() - defer b.tickerCache.Unlock() + b.tickerCache.mu.Lock() + defer b.tickerCache.mu.Unlock() if b.tickerCache.MarketSummaries[tickerData.Symbol] != nil { marketSummaryData := b.tickerCache.MarketSummaries[tickerData.Symbol] tickerPrice = b.constructTicker(tickerData, marketSummaryData, pair, asset.Spot) @@ -550,8 +556,8 @@ func (b *Bittrex) WsProcessUpdateMarketSummary(marketSummaryData *MarketSummaryD tickerPrice, err := ticker.GetTicker(b.Name, pair, asset.Spot) if err != nil { - b.tickerCache.Lock() - defer b.tickerCache.Unlock() + b.tickerCache.mu.Lock() + defer b.tickerCache.mu.Unlock() if b.tickerCache.Tickers[marketSummaryData.Symbol] != nil { tickerData := b.tickerCache.Tickers[marketSummaryData.Symbol] tickerPrice = b.constructTicker(*tickerData, marketSummaryData, pair, asset.Spot) diff --git a/exchanges/bittrex/bittrex_wrapper.go b/exchanges/bittrex/bittrex_wrapper.go index f0fe590a..8fae9e58 100644 --- a/exchanges/bittrex/bittrex_wrapper.go +++ b/exchanges/bittrex/bittrex_wrapper.go @@ -257,7 +257,7 @@ func (b *Bittrex) FetchTradablePairs(ctx context.Context, asset asset.Item) ([]s return nil, err } - var resp []string + resp := make([]string, 0, len(markets)) for x := range markets { if markets[x].Status != "ONLINE" { continue @@ -358,42 +358,41 @@ func (b *Bittrex) FetchOrderbook(ctx context.Context, c currency.Pair, assetType // UpdateOrderbook updates and returns the orderbook for a currency pair func (b *Bittrex) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { + book := &orderbook.Base{ + Exchange: b.Name, + Pair: p, + Asset: assetType, + VerifyOrderbook: b.CanVerifyOrderbook, + } + formattedPair, err := b.FormatExchangeCurrency(p, assetType) if err != nil { - return nil, err + return book, err } // Valid order book depths are 1, 25 and 500 orderbookData, sequence, err := b.GetOrderbook(ctx, formattedPair.String(), orderbookDepth) if err != nil { - return nil, err + return book, err } - book := &orderbook.Base{ - Exchange: b.Name, - Pair: p, - Asset: assetType, - VerifyOrderbook: b.CanVerifyOrderbook, - LastUpdateID: sequence, - } + book.LastUpdateID = sequence + book.Bids = make(orderbook.Items, len(orderbookData.Bid)) + book.Asks = make(orderbook.Items, len(orderbookData.Ask)) for x := range orderbookData.Bid { - book.Bids = append(book.Bids, - orderbook.Item{ - Amount: orderbookData.Bid[x].Quantity, - Price: orderbookData.Bid[x].Rate, - }, - ) + book.Bids[x] = orderbook.Item{ + Amount: orderbookData.Bid[x].Quantity, + Price: orderbookData.Bid[x].Rate, + } } for x := range orderbookData.Ask { - book.Asks = append(book.Asks, - orderbook.Item{ - Amount: orderbookData.Ask[x].Quantity, - Price: orderbookData.Ask[x].Rate, - }, - ) + book.Asks[x] = orderbook.Item{ + Amount: orderbookData.Ask[x].Quantity, + Price: orderbookData.Ask[x].Rate, + } } err = book.Process() if err != nil { @@ -411,14 +410,14 @@ func (b *Bittrex) UpdateAccountInfo(ctx context.Context, assetType asset.Item) ( return resp, err } - var currencies []account.Balance + currencies := make([]account.Balance, len(balanceData)) for i := range balanceData { - currencies = append(currencies, account.Balance{ + currencies[i] = account.Balance{ CurrencyName: currency.NewCode(balanceData[i].CurrencySymbol), Total: balanceData[i].Total, Hold: balanceData[i].Total - balanceData[i].Available, Free: balanceData[i].Available, - }) + } } resp.Accounts = append(resp.Accounts, account.SubAccount{ @@ -441,18 +440,32 @@ func (b *Bittrex) FetchAccountInfo(ctx context.Context, assetType asset.Item) (a // GetFundingHistory returns funding history, deposits and // withdrawals func (b *Bittrex) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory, error) { - var resp []exchange.FundHistory closedDepositData, err := b.GetClosedDeposits(ctx) if err != nil { - return resp, err + return nil, err } openDepositData, err := b.GetOpenDeposits(ctx) if err != nil { - return resp, err + return nil, err + } + closedWithdrawalData, err := b.GetClosedWithdrawals(ctx) + if err != nil { + return nil, err + } + openWithdrawalData, err := b.GetOpenWithdrawals(ctx) + if err != nil { + return nil, err } - // nolint: gocritic - depositData := append(closedDepositData, openDepositData...) + depositData := make([]DepositData, 0, len(closedDepositData)+len(openDepositData)) + depositData = append(depositData, closedDepositData...) + depositData = append(depositData, openDepositData...) + + withdrawalData := make([]WithdrawalData, 0, len(closedWithdrawalData)+len(openWithdrawalData)) + withdrawalData = append(withdrawalData, closedWithdrawalData...) + withdrawalData = append(withdrawalData, openWithdrawalData...) + + resp := make([]exchange.FundHistory, 0, len(depositData)+len(withdrawalData)) for x := range depositData { resp = append(resp, exchange.FundHistory{ ExchangeName: b.Name, @@ -466,17 +479,6 @@ func (b *Bittrex) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory CryptoTxID: depositData[x].TxID, }) } - closedWithdrawalData, err := b.GetClosedWithdrawals(ctx) - if err != nil { - return resp, err - } - openWithdrawalData, err := b.GetOpenWithdrawals(ctx) - if err != nil { - return resp, err - } - // nolint: gocritic - withdrawalData := append(closedWithdrawalData, openWithdrawalData...) - for x := range withdrawalData { resp = append(resp, exchange.FundHistory{ ExchangeName: b.Name, @@ -502,23 +504,24 @@ func (b *Bittrex) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (r // GetRecentTrades returns the most recent trades for a currency and asset func (b *Bittrex) GetRecentTrades(ctx context.Context, p currency.Pair, assetType asset.Item) ([]trade.Data, error) { - var err error formattedPair, err := b.FormatExchangeCurrency(p, assetType) if err != nil { return nil, err } + tradeData, err := b.GetMarketHistory(ctx, formattedPair.String()) if err != nil { return nil, err } - var resp []trade.Data + + resp := make([]trade.Data, len(tradeData)) for i := range tradeData { var side order.Side side, err = order.StringToOrderSide(tradeData[i].TakerSide) if err != nil { return nil, err } - resp = append(resp, trade.Data{ + resp[i] = trade.Data{ Exchange: b.Name, TID: tradeData[i].ID, CurrencyPair: formattedPair, @@ -527,7 +530,7 @@ func (b *Bittrex) GetRecentTrades(ctx context.Context, p currency.Pair, assetTyp Price: tradeData[i].Rate, Amount: tradeData[i].Quantity, Timestamp: tradeData[i].ExecutedAt, - }) + } } err = b.AddTradesToBuffer(resp...) @@ -772,7 +775,7 @@ func (b *Bittrex) GetActiveOrders(ctx context.Context, req *order.GetOrdersReque return nil, err } - var resp []order.Detail + resp := make([]order.Detail, 0, len(orderData)) for i := range orderData { pair, err := currency.NewPairDelimiter(orderData[i].MarketSymbol, format.Delimiter) diff --git a/exchanges/bittrex/bittrex_ws_orderbook.go b/exchanges/bittrex/bittrex_ws_orderbook.go index 15dd5138..8cd9bae3 100644 --- a/exchanges/bittrex/bittrex_ws_orderbook.go +++ b/exchanges/bittrex/bittrex_ws_orderbook.go @@ -49,19 +49,19 @@ func (b *Bittrex) setupOrderbookManager() { // ProcessUpdateOB processes the websocket orderbook update func (b *Bittrex) ProcessUpdateOB(pair currency.Pair, message *OrderbookUpdateMessage) error { - var updateBids []orderbook.Item + updateBids := make([]orderbook.Item, len(message.BidDeltas)) for x := range message.BidDeltas { - updateBids = append(updateBids, orderbook.Item{ + updateBids[x] = orderbook.Item{ Price: message.BidDeltas[x].Rate, Amount: message.BidDeltas[x].Quantity, - }) + } } - var updateAsks []orderbook.Item + updateAsks := make([]orderbook.Item, len(message.AskDeltas)) for x := range message.AskDeltas { - updateAsks = append(updateAsks, orderbook.Item{ + updateAsks[x] = orderbook.Item{ Price: message.AskDeltas[x].Rate, Amount: message.AskDeltas[x].Quantity, - }) + } } return b.Websocket.Orderbook.Update(&buffer.Update{ @@ -116,30 +116,33 @@ func (b *Bittrex) SeedLocalOBCache(ctx context.Context, p currency.Pair) error { if err != nil { return err } - return b.SeedLocalCacheWithOrderBook(p, sequence, &ob) + return b.SeedLocalCacheWithOrderBook(p, sequence, ob) } // SeedLocalCacheWithOrderBook seeds the local orderbook cache func (b *Bittrex) SeedLocalCacheWithOrderBook(p currency.Pair, sequence int64, orderbookNew *OrderbookData) error { - var newOrderBook orderbook.Base - for i := range orderbookNew.Bid { - newOrderBook.Bids = append(newOrderBook.Bids, orderbook.Item{ - Amount: orderbookNew.Bid[i].Quantity, - Price: orderbookNew.Bid[i].Rate, - }) - } - for i := range orderbookNew.Ask { - newOrderBook.Asks = append(newOrderBook.Asks, orderbook.Item{ - Amount: orderbookNew.Ask[i].Quantity, - Price: orderbookNew.Ask[i].Rate, - }) + newOrderBook := orderbook.Base{ + Pair: p, + Asset: asset.Spot, + Exchange: b.Name, + LastUpdateID: sequence, + VerifyOrderbook: b.CanVerifyOrderbook, + Bids: make(orderbook.Items, len(orderbookNew.Bid)), + Asks: make(orderbook.Items, len(orderbookNew.Ask)), } - newOrderBook.Pair = p - newOrderBook.Asset = asset.Spot - newOrderBook.Exchange = b.Name - newOrderBook.LastUpdateID = sequence - newOrderBook.VerifyOrderbook = b.CanVerifyOrderbook + for i := range orderbookNew.Bid { + newOrderBook.Bids[i] = orderbook.Item{ + Amount: orderbookNew.Bid[i].Quantity, + Price: orderbookNew.Bid[i].Rate, + } + } + for i := range orderbookNew.Ask { + newOrderBook.Asks[i] = orderbook.Item{ + Amount: orderbookNew.Ask[i].Quantity, + Price: orderbookNew.Ask[i].Rate, + } + } return b.Websocket.Orderbook.LoadSnapshot(&newOrderBook) } diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 0ab80ca6..66f0ddb2 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -128,50 +128,54 @@ func (b *BTCMarkets) GetTrades(ctx context.Context, marketID string, before, aft // 0 - Returns the top bids and ask orders only. // 1 - Returns top 50 bids and asks. // 2 - Returns full orderbook. WARNING: This is cached every 10 seconds. -func (b *BTCMarkets) GetOrderbook(ctx context.Context, marketID string, level int64) (Orderbook, error) { - var orderbook Orderbook - var temp tempOrderbook +func (b *BTCMarkets) GetOrderbook(ctx context.Context, marketID string, level int64) (*Orderbook, error) { params := url.Values{} if level != 0 { params.Set("level", strconv.FormatInt(level, 10)) } + var temp tempOrderbook err := b.SendHTTPRequest(ctx, btcMarketsUnauthPath+marketID+btcMarketOrderBook+params.Encode(), &temp) if err != nil { - return orderbook, err + return nil, err + } + + orderbook := Orderbook{ + MarketID: temp.MarketID, + SnapshotID: temp.SnapshotID, + Bids: make([]OBData, len(temp.Bids)), + Asks: make([]OBData, len(temp.Asks)), } - orderbook.MarketID = temp.MarketID - orderbook.SnapshotID = temp.SnapshotID for x := range temp.Asks { price, err := strconv.ParseFloat(temp.Asks[x][0], 64) if err != nil { - return orderbook, err + return nil, err } amount, err := strconv.ParseFloat(temp.Asks[x][1], 64) if err != nil { - return orderbook, err + return nil, err } - orderbook.Asks = append(orderbook.Asks, OBData{ + orderbook.Asks[x] = OBData{ Price: price, Volume: amount, - }) + } } for a := range temp.Bids { price, err := strconv.ParseFloat(temp.Bids[a][0], 64) if err != nil { - return orderbook, err + return nil, err } amount, err := strconv.ParseFloat(temp.Bids[a][1], 64) if err != nil { - return orderbook, err + return nil, err } - orderbook.Bids = append(orderbook.Bids, OBData{ + orderbook.Bids[a] = OBData{ Price: price, Volume: amount, - }) + } } - return orderbook, nil + return &orderbook, nil } // GetMarketCandles gets candles for specified currency pair @@ -216,9 +220,7 @@ func (b *BTCMarkets) GetTickers(ctx context.Context, marketIDs currency.Pairs) ( // GetMultipleOrderbooks gets orderbooks func (b *BTCMarkets) GetMultipleOrderbooks(ctx context.Context, marketIDs []string) ([]Orderbook, error) { - var orderbooks []Orderbook var temp []tempOrderbook - var tempOB Orderbook params := url.Values{} for x := range marketIDs { params.Add("marketId", marketIDs[x]) @@ -226,12 +228,16 @@ func (b *BTCMarkets) GetMultipleOrderbooks(ctx context.Context, marketIDs []stri err := b.SendHTTPRequest(ctx, btcMarketsUnauthPath+btcMarketsMultipleOrderbooks+params.Encode(), &temp) if err != nil { - return orderbooks, err + return nil, err } + orderbooks := make([]Orderbook, 0, len(marketIDs)) for i := range temp { + var tempOB Orderbook var price, volume float64 tempOB.MarketID = temp[i].MarketID tempOB.SnapshotID = temp[i].SnapshotID + tempOB.Asks = make([]OBData, len(temp[i].Asks)) + tempOB.Bids = make([]OBData, len(temp[i].Bids)) for a := range temp[i].Asks { volume, err = strconv.ParseFloat(temp[i].Asks[a][1], 64) if err != nil { @@ -241,7 +247,7 @@ func (b *BTCMarkets) GetMultipleOrderbooks(ctx context.Context, marketIDs []stri if err != nil { return orderbooks, err } - tempOB.Asks = append(tempOB.Asks, OBData{Price: price, Volume: volume}) + tempOB.Asks[a] = OBData{Price: price, Volume: volume} } for y := range temp[i].Bids { volume, err = strconv.ParseFloat(temp[i].Bids[y][1], 64) @@ -252,7 +258,7 @@ func (b *BTCMarkets) GetMultipleOrderbooks(ctx context.Context, marketIDs []stri if err != nil { return orderbooks, err } - tempOB.Bids = append(tempOB.Bids, OBData{Price: price, Volume: volume}) + tempOB.Bids[y] = OBData{Price: price, Volume: volume} } orderbooks = append(orderbooks, tempOB) } @@ -708,22 +714,24 @@ func (b *BTCMarkets) RequestWithdraw(ctx context.Context, assetName string, amou } // BatchPlaceCancelOrders places and cancels batch orders -func (b *BTCMarkets) BatchPlaceCancelOrders(ctx context.Context, cancelOrders []CancelBatch, placeOrders []PlaceBatch) (BatchPlaceCancelResponse, error) { - var resp BatchPlaceCancelResponse - var orderRequests []interface{} - if len(cancelOrders)+len(placeOrders) > 4 { - return resp, errors.New("BTCMarkets can only handle 4 orders at a time") +func (b *BTCMarkets) BatchPlaceCancelOrders(ctx context.Context, cancelOrders []CancelBatch, placeOrders []PlaceBatch) (*BatchPlaceCancelResponse, error) { + numActions := len(cancelOrders) + len(placeOrders) + if numActions > 4 { + return nil, errors.New("BTCMarkets can only handle 4 orders at a time") } + + orderRequests := make([]interface{}, numActions) for x := range cancelOrders { - orderRequests = append(orderRequests, CancelOrderMethod{CancelOrder: cancelOrders[x]}) + orderRequests[x] = CancelOrderMethod{CancelOrder: cancelOrders[x]} } for y := range placeOrders { if placeOrders[y].ClientOrderID == "" { - return resp, errors.New("placeorders must have clientorderids filled") + return nil, errors.New("placeorders must have ClientOrderID filled") } - orderRequests = append(orderRequests, PlaceOrderMethod{PlaceOrder: placeOrders[y]}) + orderRequests[y] = PlaceOrderMethod{PlaceOrder: placeOrders[y]} } - return resp, b.SendAuthenticatedRequest(ctx, http.MethodPost, + var resp BatchPlaceCancelResponse + return &resp, b.SendAuthenticatedRequest(ctx, http.MethodPost, btcMarketsBatchOrders, orderRequests, &resp, diff --git a/exchanges/btcmarkets/btcmarkets_test.go b/exchanges/btcmarkets/btcmarkets_test.go index f937c12a..7a476d47 100644 --- a/exchanges/btcmarkets/btcmarkets_test.go +++ b/exchanges/btcmarkets/btcmarkets_test.go @@ -893,8 +893,8 @@ func TestChecksum(t *testing.T) { }, } - expecting := 3802968298 - err := checksum(b, uint32(expecting)) + expecting := uint32(3802968298) + err := checksum(b, expecting) if err != nil { t.Fatal(err) } diff --git a/exchanges/btcmarkets/btcmarkets_websocket.go b/exchanges/btcmarkets/btcmarkets_websocket.go index b7058601..4e89f346 100644 --- a/exchanges/btcmarkets/btcmarkets_websocket.go +++ b/exchanges/btcmarkets/btcmarkets_websocket.go @@ -333,8 +333,8 @@ func (b *BTCMarkets) generateDefaultSubscriptions() ([]stream.ChannelSubscriptio } } - var authChannels = []string{fundChange, heartbeat, orderChange} if b.Websocket.CanUseAuthenticatedEndpoints() { + var authChannels = []string{fundChange, heartbeat, orderChange} for i := range authChannels { subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: authChannels[i], @@ -346,12 +346,6 @@ func (b *BTCMarkets) generateDefaultSubscriptions() ([]stream.ChannelSubscriptio // Subscribe sends a websocket message to receive data from the channel func (b *BTCMarkets) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { - creds, err := b.GetCredentials(context.TODO()) - if err != nil { - return err - } - var authChannels = []string{fundChange, heartbeat, orderChange} - var payload WsSubscribe payload.MessageType = subscribe @@ -368,28 +362,34 @@ func (b *BTCMarkets) Subscribe(channelsToSubscribe []stream.ChannelSubscription) } } - for i := range authChannels { - if !common.StringDataCompare(payload.Channels, authChannels[i]) { - continue - } - signTime := strconv.FormatInt(time.Now().UnixMilli(), 10) - strToSign := "/users/self/subscribe" + "\n" + signTime - var tempSign []byte - tempSign, err = crypto.GetHMAC(crypto.HashSHA512, - []byte(strToSign), - []byte(creds.Secret)) + if b.Websocket.CanUseAuthenticatedEndpoints() { + var authChannels = []string{fundChange, heartbeat, orderChange} + creds, err := b.GetCredentials(context.TODO()) if err != nil { return err } - sign := crypto.Base64Encode(tempSign) - payload.Key = creds.Key - payload.Signature = sign - payload.Timestamp = signTime - break + + for i := range authChannels { + if !common.StringDataCompare(payload.Channels, authChannels[i]) { + continue + } + signTime := strconv.FormatInt(time.Now().UnixMilli(), 10) + strToSign := "/users/self/subscribe" + "\n" + signTime + tempSign, err := crypto.GetHMAC(crypto.HashSHA512, + []byte(strToSign), + []byte(creds.Secret)) + if err != nil { + return err + } + sign := crypto.Base64Encode(tempSign) + payload.Key = creds.Key + payload.Signature = sign + payload.Timestamp = signTime + break + } } - err = b.Websocket.Conn.SendJSONMessage(payload) - if err != nil { + if err := b.Websocket.Conn.SendJSONMessage(payload); err != nil { return err } b.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) diff --git a/exchanges/btcmarkets/btcmarkets_wrapper.go b/exchanges/btcmarkets/btcmarkets_wrapper.go index b6140f2d..ba3b7d2a 100644 --- a/exchanges/btcmarkets/btcmarkets_wrapper.go +++ b/exchanges/btcmarkets/btcmarkets_wrapper.go @@ -276,9 +276,9 @@ func (b *BTCMarkets) FetchTradablePairs(ctx context.Context, a asset.Item) ([]st return nil, err } - var pairs []string + pairs := make([]string, len(markets)) for x := range markets { - pairs = append(pairs, markets[x].MarketID) + pairs[x] = markets[x].MarketID } return pairs, nil } @@ -398,15 +398,20 @@ func (b *BTCMarkets) UpdateOrderbook(ctx context.Context, p currency.Pair, asset return book, err } + book.Bids = make(orderbook.Items, len(tempResp.Bids)) for x := range tempResp.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[x] = orderbook.Item{ Amount: tempResp.Bids[x].Volume, - Price: tempResp.Bids[x].Price}) + Price: tempResp.Bids[x].Price, + } } + + book.Asks = make(orderbook.Items, len(tempResp.Asks)) for y := range tempResp.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[y] = orderbook.Item{ Amount: tempResp.Asks[y].Volume, - Price: tempResp.Asks[y].Price}) + Price: tempResp.Asks[y].Price, + } } err = book.Process() if err != nil { @@ -471,12 +476,14 @@ func (b *BTCMarkets) GetRecentTrades(ctx context.Context, p currency.Pair, asset if err != nil { return nil, err } - var resp []trade.Data + var tradeData []Trade tradeData, err = b.GetTrades(ctx, p.String(), 0, 0, 200) if err != nil { return nil, err } + + resp := make([]trade.Data, len(tradeData)) for i := range tradeData { side := order.Side("") if tradeData[i].Side != "" { @@ -485,7 +492,7 @@ func (b *BTCMarkets) GetRecentTrades(ctx context.Context, p currency.Pair, asset return nil, err } } - resp = append(resp, trade.Data{ + resp[i] = trade.Data{ Exchange: b.Name, TID: tradeData[i].TradeID, CurrencyPair: p, @@ -494,7 +501,7 @@ func (b *BTCMarkets) GetRecentTrades(ctx context.Context, p currency.Pair, asset Price: tradeData[i].Price, Amount: tradeData[i].Amount, Timestamp: tradeData[i].Timestamp, - }) + } } err = b.AddTradesToBuffer(resp...) @@ -584,16 +591,17 @@ func (b *BTCMarkets) CancelBatchOrders(ctx context.Context, o []order.Cancel) (o // CancelAllOrders cancels all orders associated with a currency pair func (b *BTCMarkets) CancelAllOrders(ctx context.Context, _ *order.Cancel) (order.CancelAllResponse, error) { var resp order.CancelAllResponse - tempMap := make(map[string]string) - var orderIDs []string orders, err := b.GetOrders(ctx, "", -1, -1, -1, true) if err != nil { return resp, err } + + orderIDs := make([]string, len(orders)) for x := range orders { - orderIDs = append(orderIDs, orders[x].OrderID) + orderIDs[x] = orders[x].OrderID } splitOrders := common.SplitStringSliceByLimit(orderIDs, 20) + tempMap := make(map[string]string) for z := range splitOrders { tempResp, err := b.CancelBatch(ctx, splitOrders[z]) if err != nil { diff --git a/exchanges/btse/btse_websocket.go b/exchanges/btse/btse_websocket.go index 9dac4300..aec8f318 100644 --- a/exchanges/btse/btse_websocket.go +++ b/exchanges/btse/btse_websocket.go @@ -272,7 +272,10 @@ func (b *BTSE) wsHandleData(respRaw []byte) error { if err != nil { return err } - var newOB orderbook.Base + newOB := orderbook.Base{ + Bids: make(orderbook.Items, 0, len(t.Data.BuyQuote)), + Asks: make(orderbook.Items, 0, len(t.Data.SellQuote)), + } var price, amount float64 for i := range t.Data.SellQuote { p := strings.Replace(t.Data.SellQuote[i].Price, ",", "", -1) diff --git a/exchanges/btse/btse_wrapper.go b/exchanges/btse/btse_wrapper.go index d992a724..28599f5a 100644 --- a/exchanges/btse/btse_wrapper.go +++ b/exchanges/btse/btse_wrapper.go @@ -247,12 +247,12 @@ func (b *BTSE) Run() { // FetchTradablePairs returns a list of the exchanges tradable pairs func (b *BTSE) FetchTradablePairs(ctx context.Context, a asset.Item) ([]string, error) { - var currencies []string m, err := b.GetMarketSummary(ctx, "", a == asset.Spot) if err != nil { return nil, err } + currencies := make([]string, 0, len(m)) for x := range m { if !m[x].Active { continue @@ -359,21 +359,25 @@ func (b *BTSE) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType a return book, err } + book.Bids = make(orderbook.Items, 0, len(a.BuyQuote)) for x := range a.BuyQuote { if b.orderbookFilter(a.BuyQuote[x].Price, a.BuyQuote[x].Size) { continue } book.Bids = append(book.Bids, orderbook.Item{ Price: a.BuyQuote[x].Price, - Amount: a.BuyQuote[x].Size}) + Amount: a.BuyQuote[x].Size, + }) } + book.Asks = make(orderbook.Items, 0, len(a.SellQuote)) for x := range a.SellQuote { if b.orderbookFilter(a.SellQuote[x].Price, a.SellQuote[x].Size) { continue } book.Asks = append(book.Asks, orderbook.Item{ Price: a.SellQuote[x].Price, - Amount: a.SellQuote[x].Size}) + Amount: a.SellQuote[x].Size, + }) } book.Asks.Reverse() // Reverse asks for correct alignment book.Pair = p @@ -395,16 +399,14 @@ func (b *BTSE) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (acc return a, err } - var currencies []account.Balance + currencies := make([]account.Balance, len(balance)) for b := range balance { - currencies = append(currencies, - account.Balance{ - CurrencyName: currency.NewCode(balance[b].Currency), - Total: balance[b].Total, - Hold: balance[b].Total - balance[b].Available, - Free: balance[b].Available, - }, - ) + currencies[b] = account.Balance{ + CurrencyName: currency.NewCode(balance[b].Currency), + Total: balance[b].Total, + Hold: balance[b].Total - balance[b].Available, + Free: balance[b].Available, + } } a.Exchange = b.Name a.Accounts = []account.SubAccount{ @@ -459,9 +461,8 @@ func (b *BTSE) GetRecentTrades(ctx context.Context, p currency.Pair, assetType a if err != nil { return nil, err } - var resp []trade.Data - limit := 500 + const limit = 500 var tradeData []Trade tradeData, err = b.GetTrades(ctx, p.String(), @@ -472,14 +473,16 @@ func (b *BTSE) GetRecentTrades(ctx context.Context, p currency.Pair, assetType a if err != nil { return nil, err } + + resp := make([]trade.Data, len(tradeData)) for i := range tradeData { - tradeTimestamp := time.Unix(tradeData[i].Time/1000, 0) + tradeTimestamp := time.UnixMilli(tradeData[i].Time) var side order.Side side, err = order.StringToOrderSide(tradeData[i].Side) if err != nil { return nil, err } - resp = append(resp, trade.Data{ + resp[i] = trade.Data{ Exchange: b.Name, TID: strconv.FormatInt(tradeData[i].SerialID, 10), CurrencyPair: p, @@ -488,7 +491,7 @@ func (b *BTSE) GetRecentTrades(ctx context.Context, p currency.Pair, assetType a Price: tradeData[i].Price, Amount: tradeData[i].Amount, Timestamp: tradeTimestamp, - }) + } } err = b.AddTradesToBuffer(resp...) if err != nil { diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 54f0757c..fa9befb7 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -80,59 +80,110 @@ func (c *CoinbasePro) GetOrderbook(ctx context.Context, symbol string, level int } if level == 3 { - ob := OrderbookL3{} - ob.Sequence = orderbook.Sequence - for _, x := range orderbook.Asks { - price, err := strconv.ParseFloat((x[0].(string)), 64) - if err != nil { - continue - } - amount, err := strconv.ParseFloat((x[1].(string)), 64) - if err != nil { - continue - } - - ob.Asks = append(ob.Asks, OrderL3{Price: price, Amount: amount, OrderID: x[2].(string)}) + ob := OrderbookL3{ + Sequence: orderbook.Sequence, + Bids: make([]OrderL3, len(orderbook.Bids)), + Asks: make([]OrderL3, len(orderbook.Asks)), } - for _, x := range orderbook.Bids { - price, err := strconv.ParseFloat((x[0].(string)), 64) - if err != nil { - continue + ob.Sequence = orderbook.Sequence + for x := range orderbook.Asks { + priceConv, ok := orderbook.Asks[x][0].(string) + if !ok { + return nil, errors.New("unable to type assert price") } - amount, err := strconv.ParseFloat((x[1].(string)), 64) + price, err := strconv.ParseFloat(priceConv, 64) if err != nil { - continue + return nil, err } - - ob.Bids = append(ob.Bids, OrderL3{Price: price, Amount: amount, OrderID: x[2].(string)}) + amountConv, ok := orderbook.Asks[x][1].(string) + if !ok { + return nil, errors.New("unable to type assert amount") + } + amount, err := strconv.ParseFloat(amountConv, 64) + if err != nil { + return nil, err + } + ordID, ok := orderbook.Asks[x][2].(string) + if !ok { + return nil, errors.New("unable to type assert order ID") + } + ob.Asks[x] = OrderL3{Price: price, Amount: amount, OrderID: ordID} + } + for x := range orderbook.Bids { + priceConv, ok := orderbook.Bids[x][0].(string) + if !ok { + return nil, errors.New("unable to type assert price") + } + price, err := strconv.ParseFloat(priceConv, 64) + if err != nil { + return nil, err + } + amountConv, ok := orderbook.Bids[x][1].(string) + if !ok { + return nil, errors.New("unable to type assert amount") + } + amount, err := strconv.ParseFloat(amountConv, 64) + if err != nil { + return nil, err + } + ordID, ok := orderbook.Bids[x][2].(string) + if !ok { + return nil, errors.New("unable to type assert order ID") + } + ob.Bids[x] = OrderL3{Price: price, Amount: amount, OrderID: ordID} } return ob, nil } - ob := OrderbookL1L2{} - ob.Sequence = orderbook.Sequence - for _, x := range orderbook.Asks { - price, err := strconv.ParseFloat((x[0].(string)), 64) - if err != nil { - continue - } - amount, err := strconv.ParseFloat((x[1].(string)), 64) - if err != nil { - continue - } - - ob.Asks = append(ob.Asks, OrderL1L2{Price: price, Amount: amount, NumOrders: x[2].(float64)}) + ob := OrderbookL1L2{ + Sequence: orderbook.Sequence, + Bids: make([]OrderL1L2, len(orderbook.Bids)), + Asks: make([]OrderL1L2, len(orderbook.Asks)), } - for _, x := range orderbook.Bids { - price, err := strconv.ParseFloat((x[0].(string)), 64) - if err != nil { - continue + for x := range orderbook.Asks { + priceConv, ok := orderbook.Asks[x][0].(string) + if !ok { + return nil, errors.New("unable to type assert price") } - amount, err := strconv.ParseFloat((x[1].(string)), 64) + price, err := strconv.ParseFloat(priceConv, 64) if err != nil { - continue + return nil, err } - - ob.Bids = append(ob.Bids, OrderL1L2{Price: price, Amount: amount, NumOrders: x[2].(float64)}) + amountConv, ok := orderbook.Asks[x][1].(string) + if !ok { + return nil, errors.New("unable to type assert amount") + } + amount, err := strconv.ParseFloat(amountConv, 64) + if err != nil { + return nil, err + } + numOrders, ok := orderbook.Asks[x][2].(float64) + if !ok { + return nil, errors.New("unable to type assert number of orders") + } + ob.Asks[x] = OrderL1L2{Price: price, Amount: amount, NumOrders: numOrders} + } + for x := range orderbook.Bids { + priceConv, ok := orderbook.Bids[x][0].(string) + if !ok { + return nil, errors.New("unable to type assert price") + } + price, err := strconv.ParseFloat(priceConv, 64) + if err != nil { + return nil, err + } + amountConv, ok := orderbook.Bids[x][1].(string) + if !ok { + return nil, errors.New("unable to type assert amount") + } + amount, err := strconv.ParseFloat(amountConv, 64) + if err != nil { + return nil, err + } + numOrders, ok := orderbook.Bids[x][2].(float64) + if !ok { + return nil, errors.New("unable to type assert number of orders") + } + ob.Bids[x] = OrderL1L2{Price: price, Amount: amount, NumOrders: numOrders} } return ob, nil } @@ -158,8 +209,6 @@ func (c *CoinbasePro) GetTrades(ctx context.Context, currencyPair string) ([]Tra // GetHistoricRates returns historic rates for a product. Rates are returned in // grouped buckets based on requested granularity. func (c *CoinbasePro) GetHistoricRates(ctx context.Context, currencyPair, start, end string, granularity int64) ([]History, error) { - var resp [][]interface{} - var history []History values := url.Values{} if len(start) > 0 { @@ -183,29 +232,24 @@ func (c *CoinbasePro) GetHistoricRates(ctx context.Context, currencyPair, start, values.Set("granularity", strconv.FormatInt(granularity, 10)) } + var resp [][6]float64 path := common.EncodeURLValues( fmt.Sprintf("%s/%s/%s", coinbaseproProducts, currencyPair, coinbaseproHistory), values) - if err := c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp); err != nil { - return history, err + return nil, err } - for _, single := range resp { - var s History - a, _ := single[0].(float64) - s.Time = int64(a) - b, _ := single[1].(float64) - s.Low = b - c, _ := single[2].(float64) - s.High = c - d, _ := single[3].(float64) - s.Open = d - e, _ := single[4].(float64) - s.Close = e - f, _ := single[5].(float64) - s.Volume = f - history = append(history, s) + history := make([]History, len(resp)) + for x := range resp { + history[x] = History{ + Time: time.Unix(int64(resp[x][0]), 0), + Low: resp[x][1], + High: resp[x][2], + Open: resp[x][3], + Close: resp[x][4], + Volume: resp[x][5], + } } return history, nil diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 65b34de7..35d7de8c 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -84,6 +84,17 @@ func TestGetProducts(t *testing.T) { } } +func TestGetOrderbook(t *testing.T) { + _, err := c.GetOrderbook(context.Background(), testPair.String(), 2) + if err != nil { + t.Error(err) + } + _, err = c.GetOrderbook(context.Background(), testPair.String(), 3) + if err != nil { + t.Error(err) + } +} + func TestGetTicker(t *testing.T) { _, err := c.GetTicker(context.Background(), testPair.String()) if err != nil { diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index b4d72422..17e4b473 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -39,12 +39,12 @@ type Trade struct { // History holds historic rate information type History struct { - Time int64 `json:"time"` - Low float64 `json:"low"` - High float64 `json:"high"` - Open float64 `json:"open"` - Close float64 `json:"close"` - Volume float64 `json:"volume"` + Time time.Time + Low float64 + High float64 + Open float64 + Close float64 + Volume float64 } // Stats holds last 24 hr data for coinbasepro @@ -331,9 +331,9 @@ type OrderbookL3 struct { // OrderbookResponse is a generalized response for order books type OrderbookResponse struct { - Sequence int64 `json:"sequence"` - Bids [][]interface{} `json:"bids"` - Asks [][]interface{} `json:"asks"` + Sequence int64 `json:"sequence"` + Bids [][3]interface{} `json:"bids"` + Asks [][3]interface{} `json:"asks"` } // FillResponse contains fill information from the exchange @@ -427,18 +427,18 @@ type WebsocketTicker struct { // WebsocketOrderbookSnapshot defines a snapshot response type WebsocketOrderbookSnapshot struct { - ProductID string `json:"product_id"` - Type string `json:"type"` - Bids [][]interface{} `json:"bids"` - Asks [][]interface{} `json:"asks"` + ProductID string `json:"product_id"` + Type string `json:"type"` + Bids [][2]string `json:"bids"` + Asks [][2]string `json:"asks"` } // WebsocketL2Update defines an update on the L2 orderbooks type WebsocketL2Update struct { - Type string `json:"type"` - ProductID string `json:"product_id"` - Time string `json:"time"` - Changes [][]interface{} `json:"changes"` + Type string `json:"type"` + ProductID string `json:"product_id"` + Time string `json:"time"` + Changes [][3]string `json:"changes"` } type wsMsgType struct { diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index e448ff6a..0d9401b9 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -121,7 +121,7 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error { return err } - err = c.ProcessUpdate(update) + err = c.ProcessUpdate(&update) if err != nil { return err } @@ -280,42 +280,45 @@ func statusToStandardStatus(stat string) (order.Status, error) { // ProcessSnapshot processes the initial orderbook snap shot func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) error { - var base orderbook.Base - for i := range snapshot.Bids { - price, err := strconv.ParseFloat(snapshot.Bids[i][0].(string), 64) - if err != nil { - return err - } - - amount, err := strconv.ParseFloat(snapshot.Bids[i][1].(string), 64) - if err != nil { - return err - } - - base.Bids = append(base.Bids, - orderbook.Item{Price: price, Amount: amount}) - } - - for i := range snapshot.Asks { - price, err := strconv.ParseFloat(snapshot.Asks[i][0].(string), 64) - if err != nil { - return err - } - - amount, err := strconv.ParseFloat(snapshot.Asks[i][1].(string), 64) - if err != nil { - return err - } - - base.Asks = append(base.Asks, - orderbook.Item{Price: price, Amount: amount}) - } - pair, err := currency.NewPairFromString(snapshot.ProductID) if err != nil { return err } + base := orderbook.Base{ + Pair: pair, + Bids: make(orderbook.Items, len(snapshot.Bids)), + Asks: make(orderbook.Items, len(snapshot.Asks)), + } + + for i := range snapshot.Bids { + price, err := strconv.ParseFloat(snapshot.Bids[i][0], 64) + if err != nil { + return err + } + + amount, err := strconv.ParseFloat(snapshot.Bids[i][1], 64) + if err != nil { + return err + } + + base.Bids[i] = orderbook.Item{Price: price, Amount: amount} + } + + for i := range snapshot.Asks { + price, err := strconv.ParseFloat(snapshot.Asks[i][0], 64) + if err != nil { + return err + } + + amount, err := strconv.ParseFloat(snapshot.Asks[i][1], 64) + if err != nil { + return err + } + + base.Asks[i] = orderbook.Item{Price: price, Amount: amount} + } + base.Asset = asset.Spot base.Pair = pair base.Exchange = c.Name @@ -325,27 +328,8 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) erro } // ProcessUpdate updates the orderbook local cache -func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error { - var asks, bids []orderbook.Item - - for i := range update.Changes { - price, err := strconv.ParseFloat(update.Changes[i][1].(string), 64) - if err != nil { - return err - } - volume, err := strconv.ParseFloat(update.Changes[i][2].(string), 64) - if err != nil { - return err - } - - if update.Changes[i][0].(string) == order.Buy.Lower() { - bids = append(bids, orderbook.Item{Price: price, Amount: volume}) - } else { - asks = append(asks, orderbook.Item{Price: price, Amount: volume}) - } - } - - if len(asks) == 0 && len(bids) == 0 { +func (c *CoinbasePro) ProcessUpdate(update *WebsocketL2Update) error { + if len(update.Changes) == 0 { return errors.New("no data in websocket update") } @@ -358,6 +342,26 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketL2Update) error { if err != nil { return err } + + asks := make(orderbook.Items, 0, len(update.Changes)) + bids := make(orderbook.Items, 0, len(update.Changes)) + + for i := range update.Changes { + price, err := strconv.ParseFloat(update.Changes[i][1], 64) + if err != nil { + return err + } + volume, err := strconv.ParseFloat(update.Changes[i][2], 64) + if err != nil { + return err + } + if update.Changes[i][0] == order.Buy.Lower() { + bids = append(bids, orderbook.Item{Price: price, Amount: volume}) + } else { + asks = append(asks, orderbook.Item{Price: price, Amount: volume}) + } + } + return c.Websocket.Orderbook.Update(&buffer.Update{ Bids: bids, Asks: asks, @@ -398,10 +402,15 @@ func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]stream.ChannelSubscripti // Subscribe sends a websocket message to receive data from the channel func (c *CoinbasePro) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { - creds, err := c.GetCredentials(context.TODO()) - if err != nil { - return err + var creds *exchange.Credentials + var err error + if c.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + creds, err = c.GetCredentials(context.TODO()) + if err != nil { + return err + } } + subscribe := WebsocketSubscribe{ Type: "subscribe", } @@ -423,8 +432,8 @@ subscriptions: Name: channelsToSubscribe[i].Channel, }) - if channelsToSubscribe[i].Channel == "user" || - channelsToSubscribe[i].Channel == "full" { + if (channelsToSubscribe[i].Channel == "user" || + channelsToSubscribe[i].Channel == "full") && creds != nil { n := strconv.FormatInt(time.Now().Unix(), 10) message := n + http.MethodGet + "/users/self/verify" var hmac []byte diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index d74ba0b7..e93b407f 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -295,11 +295,9 @@ func (c *CoinbasePro) FetchTradablePairs(ctx context.Context, asset asset.Item) return nil, err } - var products []string + products := make([]string, len(pairs)) for x := range pairs { - products = append(products, pairs[x].BaseCurrency+ - format.Delimiter+ - pairs[x].QuoteCurrency) + products[x] = pairs[x].BaseCurrency + format.Delimiter + pairs[x].QuoteCurrency } return products, nil @@ -449,16 +447,21 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse if !ok { return book, errors.New("unable to type assert orderbook data") } + + book.Bids = make(orderbook.Items, len(obNew.Bids)) for x := range obNew.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[x] = orderbook.Item{ Amount: obNew.Bids[x].Amount, - Price: obNew.Bids[x].Price}) + Price: obNew.Bids[x].Price, + } } + book.Asks = make(orderbook.Items, len(obNew.Asks)) for x := range obNew.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[x] = orderbook.Item{ Amount: obNew.Asks[x].Amount, - Price: obNew.Asks[x].Price}) + Price: obNew.Asks[x].Price, + } } err = book.Process() if err != nil { @@ -490,14 +493,14 @@ func (c *CoinbasePro) GetRecentTrades(ctx context.Context, p currency.Pair, asse if err != nil { return nil, err } - var resp []trade.Data + resp := make([]trade.Data, len(tradeData)) for i := range tradeData { var side order.Side side, err = order.StringToOrderSide(tradeData[i].Side) if err != nil { return nil, err } - resp = append(resp, trade.Data{ + resp[i] = trade.Data{ Exchange: c.Name, TID: strconv.FormatInt(tradeData[i].TradeID, 10), CurrencyPair: p, @@ -506,7 +509,7 @@ func (c *CoinbasePro) GetRecentTrades(ctx context.Context, p currency.Pair, asse Price: tradeData[i].Price, Amount: tradeData[i].Size, Timestamp: tradeData[i].Time, - }) + } } err = c.AddTradesToBuffer(resp...) @@ -772,7 +775,7 @@ func (c *CoinbasePro) GetActiveOrders(ctx context.Context, req *order.GetOrdersR return nil, err } - var orders []order.Detail + orders := make([]order.Detail, len(respOrders)) for i := range respOrders { var curr currency.Pair curr, err = currency.NewPairDelimiter(respOrders[i].ProductID, @@ -782,7 +785,7 @@ func (c *CoinbasePro) GetActiveOrders(ctx context.Context, req *order.GetOrdersR } orderSide := order.Side(strings.ToUpper(respOrders[i].Side)) orderType := order.Type(strings.ToUpper(respOrders[i].Type)) - orders = append(orders, order.Detail{ + orders[i] = order.Detail{ ID: respOrders[i].ID, Amount: respOrders[i].Size, ExecutedAmount: respOrders[i].FilledSize, @@ -791,7 +794,7 @@ func (c *CoinbasePro) GetActiveOrders(ctx context.Context, req *order.GetOrdersR Side: orderSide, Pair: curr, Exchange: c.Name, - }) + } } order.FilterOrdersByType(&orders, req.Type) @@ -836,7 +839,7 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.GetOrdersR return nil, err } - var orders []order.Detail + orders := make([]order.Detail, len(respOrders)) for i := range respOrders { var curr currency.Pair curr, err = currency.NewPairDelimiter(respOrders[i].ProductID, @@ -869,7 +872,7 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.GetOrdersR Exchange: c.Name, } detail.InferCostsAndTimes() - orders = append(orders, detail) + orders[i] = detail } order.FilterOrdersByType(&orders, req.Type) @@ -908,13 +911,6 @@ func (c *CoinbasePro) GetHistoricCandles(ctx context.Context, p currency.Pair, a return kline.Item{}, errors.New(kline.ErrRequestExceedsExchangeLimits) } - candles := kline.Item{ - Exchange: c.Name, - Pair: p, - Asset: a, - Interval: interval, - } - gran, err := strconv.ParseInt(c.FormatExchangeKlineInterval(interval), 10, 64) if err != nil { return kline.Item{}, err @@ -934,15 +930,23 @@ func (c *CoinbasePro) GetHistoricCandles(ctx context.Context, p currency.Pair, a return kline.Item{}, err } + candles := kline.Item{ + Exchange: c.Name, + Pair: p, + Asset: a, + Interval: interval, + Candles: make([]kline.Candle, len(history)), + } + for x := range history { - candles.Candles = append(candles.Candles, kline.Candle{ - Time: time.Unix(history[x].Time, 0), + candles.Candles[x] = kline.Candle{ + Time: history[x].Time, Low: history[x].Low, High: history[x].High, Open: history[x].Open, Close: history[x].Close, Volume: history[x].Volume, - }) + } } candles.SortCandlesByTimestamp(false) @@ -955,13 +959,6 @@ func (c *CoinbasePro) GetHistoricCandlesExtended(ctx context.Context, p currency return kline.Item{}, err } - ret := kline.Item{ - Exchange: c.Name, - Pair: p, - Asset: a, - Interval: interval, - } - gran, err := strconv.ParseInt(c.FormatExchangeKlineInterval(interval), 10, 64) if err != nil { return kline.Item{}, err @@ -976,6 +973,13 @@ func (c *CoinbasePro) GetHistoricCandlesExtended(ctx context.Context, p currency return kline.Item{}, err } + ret := kline.Item{ + Exchange: c.Name, + Pair: p, + Asset: a, + Interval: interval, + } + for x := range dates.Ranges { var history []History history, err = c.GetHistoricRates(ctx, @@ -989,7 +993,7 @@ func (c *CoinbasePro) GetHistoricCandlesExtended(ctx context.Context, p currency for i := range history { ret.Candles = append(ret.Candles, kline.Candle{ - Time: time.Unix(history[i].Time, 0), + Time: history[i].Time, Low: history[i].Low, High: history[i].High, Open: history[i].Open, diff --git a/exchanges/coinut/coinut.go b/exchanges/coinut/coinut.go index 03ec6a20..1dd0388b 100644 --- a/exchanges/coinut/coinut.go +++ b/exchanges/coinut/coinut.go @@ -82,7 +82,7 @@ func (c *COINUT) GetInstrumentTicker(ctx context.Context, instrumentID int64) (T } // GetInstrumentOrderbook returns the orderbooks for a specific instrument -func (c *COINUT) GetInstrumentOrderbook(ctx context.Context, instrumentID, limit int64) (Orderbook, error) { +func (c *COINUT) GetInstrumentOrderbook(ctx context.Context, instrumentID, limit int64) (*Orderbook, error) { var result Orderbook params := make(map[string]interface{}) params["inst_id"] = instrumentID @@ -90,7 +90,7 @@ func (c *COINUT) GetInstrumentOrderbook(ctx context.Context, instrumentID, limit params["top_n"] = limit } - return result, c.SendHTTPRequest(ctx, exchange.RestSpot, coinutOrderbook, params, false, &result) + return &result, c.SendHTTPRequest(ctx, exchange.RestSpot, coinutOrderbook, params, false, &result) } // GetTrades returns trade information @@ -488,7 +488,7 @@ func (i *instrumentMap) GetInstrumentIDs() []int64 { return nil } - var instruments []int64 + instruments := make([]int64, 0, len(i.Instruments)) for _, x := range i.Instruments { instruments = append(instruments, x) } diff --git a/exchanges/coinut/coinut_websocket.go b/exchanges/coinut/coinut_websocket.go index ec43a342..7d3eab8d 100644 --- a/exchanges/coinut/coinut_websocket.go +++ b/exchanges/coinut/coinut_websocket.go @@ -517,20 +517,20 @@ func (c *COINUT) WsGetInstruments() (Instruments, error) { // WsProcessOrderbookSnapshot processes the orderbook snapshot func (c *COINUT) WsProcessOrderbookSnapshot(ob *WsOrderbookSnapshot) error { - var bids []orderbook.Item + bids := make([]orderbook.Item, len(ob.Buy)) for i := range ob.Buy { - bids = append(bids, orderbook.Item{ + bids[i] = orderbook.Item{ Amount: ob.Buy[i].Volume, Price: ob.Buy[i].Price, - }) + } } - var asks []orderbook.Item + asks := make([]orderbook.Item, len(ob.Sell)) for i := range ob.Sell { - asks = append(asks, orderbook.Item{ + asks[i] = orderbook.Item{ Amount: ob.Sell[i].Volume, Price: ob.Sell[i].Price, - }) + } } var newOrderBook orderbook.Base @@ -672,7 +672,12 @@ func (c *COINUT) Unsubscribe(channelToUnsubscribe []stream.ChannelSubscription) errs = append(errs, err) continue } - if response["status"].([]interface{})[0] != "OK" { + + val, ok := response["status"].([]interface{}) + if !ok { + errs = append(errs, errors.New("unable to type assert response status")) + } + if val[0] != "OK" { errs = append(errs, fmt.Errorf("%v unsubscribe failed for channel %v", c.Name, channelToUnsubscribe[i].Channel)) @@ -796,7 +801,6 @@ func (c *COINUT) wsSubmitOrder(o *WsSubmitOrderParameters) (*order.Detail, error func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]order.Detail, []error) { var errs []error - var ordersResponse []order.Detail if !c.Websocket.CanUseAuthenticatedEndpoints() { errs = append(errs, fmt.Errorf("%v not authorised to submit orders", c.Name)) @@ -833,6 +837,8 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]order.Detai errs = append(errs, err) return nil, errs } + + ordersResponse := make([]order.Detail, 0, len(incoming)) for i := range incoming { o, err := c.parseOrderContainer(&incoming[i]) if err != nil { diff --git a/exchanges/coinut/coinut_wrapper.go b/exchanges/coinut/coinut_wrapper.go index 39e666bc..42ba0331 100644 --- a/exchanges/coinut/coinut_wrapper.go +++ b/exchanges/coinut/coinut_wrapper.go @@ -286,7 +286,7 @@ func (c *COINUT) FetchTradablePairs(ctx context.Context, asset asset.Item) ([]st } instruments = resp.Instruments - var pairs []string + pairs := make([]string, 0, len(instruments)) for i := range instruments { c.instrumentMap.Seed(instruments[i][0].Base+instruments[i][0].Quote, instruments[i][0].InstrumentID) p := instruments[i][0].Base + format.Delimiter + instruments[i][0].Quote @@ -503,16 +503,20 @@ func (c *COINUT) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType return book, err } + book.Bids = make(orderbook.Items, len(orderbookNew.Buy)) for x := range orderbookNew.Buy { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[x] = orderbook.Item{ Amount: orderbookNew.Buy[x].Quantity, - Price: orderbookNew.Buy[x].Price}) + Price: orderbookNew.Buy[x].Price, + } } + book.Asks = make(orderbook.Items, len(orderbookNew.Sell)) for x := range orderbookNew.Sell { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[x] = orderbook.Item{ Amount: orderbookNew.Sell[x].Quantity, - Price: orderbookNew.Sell[x].Price}) + Price: orderbookNew.Sell[x].Price, + } } err = book.Process() if err != nil { @@ -548,14 +552,14 @@ func (c *COINUT) GetRecentTrades(ctx context.Context, p currency.Pair, assetType if err != nil { return nil, err } - var resp []trade.Data + resp := make([]trade.Data, len(tradeData.Trades)) for i := range tradeData.Trades { var side order.Side side, err = order.StringToOrderSide(tradeData.Trades[i].Side) if err != nil { return nil, err } - resp = append(resp, trade.Data{ + resp[i] = trade.Data{ Exchange: c.Name, TID: strconv.FormatInt(tradeData.Trades[i].TransactionID, 10), CurrencyPair: p, @@ -564,7 +568,7 @@ func (c *COINUT) GetRecentTrades(ctx context.Context, p currency.Pair, assetType Price: tradeData.Trades[i].Price, Amount: tradeData.Trades[i].Quantity, Timestamp: time.Unix(0, tradeData.Trades[i].Timestamp*int64(time.Microsecond)), - }) + } } err = c.AddTradesToBuffer(resp...) diff --git a/exchanges/exchange.go b/exchanges/exchange.go index a0580d86..0bbc5dde 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -1106,8 +1106,8 @@ func (e *Endpoints) SetDefaultEndpoints(m map[URL]string) error { // SetRunning populates running URLs map func (e *Endpoints) SetRunning(key, val string) error { - e.Lock() - defer e.Unlock() + e.mu.Lock() + defer e.mu.Unlock() err := validateKey(key) if err != nil { return err @@ -1136,8 +1136,8 @@ func validateKey(keyVal string) error { // GetURL gets default url from URLs map func (e *Endpoints) GetURL(key URL) (string, error) { - e.RLock() - defer e.RUnlock() + e.mu.RLock() + defer e.mu.RUnlock() val, ok := e.defaults[key.String()] if !ok { return "", fmt.Errorf("no endpoint path found for the given key: %v", key) @@ -1147,12 +1147,12 @@ func (e *Endpoints) GetURL(key URL) (string, error) { // GetURLMap gets all urls for either running or default map based on the bool value supplied func (e *Endpoints) GetURLMap() map[string]string { - e.RLock() + e.mu.RLock() var urlMap = make(map[string]string) for k, v := range e.defaults { urlMap[k] = v } - e.RUnlock() + e.mu.RUnlock() return urlMap } diff --git a/exchanges/exchange_types.go b/exchanges/exchange_types.go index d4636ad6..7e514dfa 100644 --- a/exchanges/exchange_types.go +++ b/exchanges/exchange_types.go @@ -177,7 +177,7 @@ type FeaturesSupported struct { type Endpoints struct { Exchange string defaults map[string]string - sync.RWMutex + mu sync.RWMutex } // API stores the exchange API settings diff --git a/exchanges/exmo/exmo_wrapper.go b/exchanges/exmo/exmo_wrapper.go index a5a4e28b..b91840ac 100644 --- a/exchanges/exmo/exmo_wrapper.go +++ b/exchanges/exmo/exmo_wrapper.go @@ -173,7 +173,7 @@ func (e *EXMO) FetchTradablePairs(ctx context.Context, asset asset.Item) ([]stri return nil, err } - var currencies []string + currencies := make([]string, 0, len(pairs)) for x := range pairs { currencies = append(currencies, x) } @@ -298,6 +298,7 @@ func (e *EXMO) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType a continue } + book.Asks = make(orderbook.Items, len(data.Ask)) for y := range data.Ask { var price, amount float64 price, err = strconv.ParseFloat(data.Ask[y][0], 64) @@ -310,12 +311,13 @@ func (e *EXMO) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType a return book, err } - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[y] = orderbook.Item{ Price: price, Amount: amount, - }) + } } + book.Bids = make(orderbook.Items, len(data.Bid)) for y := range data.Bid { var price, amount float64 price, err = strconv.ParseFloat(data.Bid[y][0], 64) @@ -328,10 +330,10 @@ func (e *EXMO) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType a return book, err } - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[y] = orderbook.Item{ Price: price, Amount: amount, - }) + } } err = book.Process() @@ -352,7 +354,7 @@ func (e *EXMO) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (acc return response, err } - var currencies []account.Balance + currencies := make([]account.Balance, 0, len(result.Balances)) for x, y := range result.Balances { var exchangeCurrency account.Balance exchangeCurrency.CurrencyName = currency.NewCode(x) @@ -421,15 +423,16 @@ func (e *EXMO) GetRecentTrades(ctx context.Context, p currency.Pair, assetType a if err != nil { return nil, err } - var resp []trade.Data + mapData := tradeData[p.String()] + resp := make([]trade.Data, len(mapData)) for i := range mapData { var side order.Side side, err = order.StringToOrderSide(mapData[i].Type) if err != nil { return nil, err } - resp = append(resp, trade.Data{ + resp[i] = trade.Data{ Exchange: e.Name, TID: strconv.FormatInt(mapData[i].TradeID, 10), CurrencyPair: p, @@ -438,7 +441,7 @@ func (e *EXMO) GetRecentTrades(ctx context.Context, p currency.Pair, assetType a Price: mapData[i].Price, Amount: mapData[i].Quantity, Timestamp: time.Unix(mapData[i].Date, 0), - }) + } } err = e.AddTradesToBuffer(resp...) @@ -638,7 +641,7 @@ func (e *EXMO) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequest) return nil, err } - var orders []order.Detail + orders := make([]order.Detail, 0, len(resp)) for i := range resp { var symbol currency.Pair symbol, err = currency.NewPairDelimiter(resp[i].Pair, "_") @@ -690,7 +693,7 @@ func (e *EXMO) GetOrderHistory(ctx context.Context, req *order.GetOrdersRequest) } } - var orders []order.Detail + orders := make([]order.Detail, len(allTrades)) for i := range allTrades { pair, err := currency.NewPairDelimiter(allTrades[i].Pair, "_") if err != nil { @@ -711,7 +714,7 @@ func (e *EXMO) GetOrderHistory(ctx context.Context, req *order.GetOrdersRequest) Pair: pair, } detail.InferCostsAndTimes() - orders = append(orders, detail) + orders[i] = detail } order.FilterOrdersByTimeRange(&orders, req.StartTime, req.EndTime) diff --git a/exchanges/ftx/ftx.go b/exchanges/ftx/ftx.go index 1676890d..1f1f3a14 100644 --- a/exchanges/ftx/ftx.go +++ b/exchanges/ftx/ftx.go @@ -202,7 +202,7 @@ func (f *FTX) GetMarket(ctx context.Context, marketName string) (MarketData, err } // GetOrderbook gets orderbook for a given market with a given depth (default depth 20) -func (f *FTX) GetOrderbook(ctx context.Context, marketName string, depth int64) (OrderbookData, error) { +func (f *FTX) GetOrderbook(ctx context.Context, marketName string, depth int64) (*OrderbookData, error) { result := struct { Data TempOBData `json:"result"` }{} @@ -216,22 +216,24 @@ func (f *FTX) GetOrderbook(ctx context.Context, marketName string, depth int64) var resp OrderbookData err := f.SendHTTPRequest(ctx, exchange.RestSpot, fmt.Sprintf(getOrderbook, marketName, strDepth), &result) if err != nil { - return resp, err + return nil, err } resp.MarketName = marketName + resp.Asks = make([]OData, len(result.Data.Asks)) for x := range result.Data.Asks { - resp.Asks = append(resp.Asks, OData{ + resp.Asks[x] = OData{ Price: result.Data.Asks[x][0], Size: result.Data.Asks[x][1], - }) + } } + resp.Bids = make([]OData, len(result.Data.Bids)) for y := range result.Data.Bids { - resp.Bids = append(resp.Bids, OData{ + resp.Bids[y] = OData{ Price: result.Data.Bids[y][0], Size: result.Data.Bids[y][1], - }) + } } - return resp, nil + return &resp, nil } // GetTrades gets trades based on the conditions specified @@ -1506,7 +1508,7 @@ func (f *FTX) FetchExchangeLimits(ctx context.Context) ([]order.MinMaxLevel, err return nil, err } - var limits []order.MinMaxLevel + limits := make([]order.MinMaxLevel, 0, len(data)) for x := range data { if !data[x].Enabled { continue diff --git a/exchanges/ftx/ftx_test.go b/exchanges/ftx/ftx_test.go index ca11698d..4ef74f38 100644 --- a/exchanges/ftx/ftx_test.go +++ b/exchanges/ftx/ftx_test.go @@ -3,7 +3,6 @@ package ftx import ( "context" "errors" - "fmt" "log" "math" "os" @@ -1000,7 +999,7 @@ func TestGetPublicOptionsTrades(t *testing.T) { } tmNow := time.Now() result, err = f.GetPublicOptionsTrades(context.Background(), - tmNow.AddDate(0, -1, 0), tmNow, "5") + tmNow.AddDate(-1, 0, 0), tmNow, "5") if err != nil { t.Error(err) } @@ -2036,19 +2035,19 @@ func TestCalculatePNL(t *testing.T) { if err != nil { t.Error(err) } - var orders []order.Detail + orders := make([]order.Detail, len(positions)) for i := range positions { - orders = append(orders, order.Detail{ + orders[i] = order.Detail{ Side: positions[i].Side, Pair: pair, - ID: fmt.Sprintf("%v", positions[i].ID), + ID: positions[i].ID, Price: positions[i].Price, Amount: positions[i].Amount, AssetType: asset.Futures, Exchange: f.Name, Fee: positions[i].Fee, Date: positions[i].Date, - }) + } } exch := f.Name diff --git a/exchanges/ftx/ftx_websocket.go b/exchanges/ftx/ftx_websocket.go index 280a4e4e..fc09f9c4 100644 --- a/exchanges/ftx/ftx_websocket.go +++ b/exchanges/ftx/ftx_websocket.go @@ -480,24 +480,25 @@ func (f *FTX) WsProcessUpdateOB(data *WsOrderbookData, p currency.Pair, a asset. update := buffer.Update{ Asset: a, Pair: p, + Bids: make([]orderbook.Item, len(data.Bids)), + Asks: make([]orderbook.Item, len(data.Asks)), UpdateTime: timestampFromFloat64(data.Time), } - var err error for x := range data.Bids { - update.Bids = append(update.Bids, orderbook.Item{ + update.Bids[x] = orderbook.Item{ Price: data.Bids[x][0], Amount: data.Bids[x][1], - }) + } } for x := range data.Asks { - update.Asks = append(update.Asks, orderbook.Item{ + update.Asks[x] = orderbook.Item{ Price: data.Asks[x][0], Amount: data.Asks[x][1], - }) + } } - err = f.Websocket.Orderbook.Update(&update) + err := f.Websocket.Orderbook.Update(&update) if err != nil { return err } @@ -544,18 +545,19 @@ func (f *FTX) WsProcessPartialOB(data *WsOrderbookData, p currency.Pair, a asset a, p) } - var bids, asks []orderbook.Item + bids := make(orderbook.Items, len(data.Bids)) + asks := make(orderbook.Items, len(data.Asks)) for x := range data.Bids { - bids = append(bids, orderbook.Item{ + bids[x] = orderbook.Item{ Price: data.Bids[x][0], Amount: data.Bids[x][1], - }) + } } for x := range data.Asks { - asks = append(asks, orderbook.Item{ + asks[x] = orderbook.Item{ Price: data.Asks[x][0], Amount: data.Asks[x][1], - }) + } } newOrderBook := orderbook.Base{ diff --git a/exchanges/ftx/ftx_websocket_test.go b/exchanges/ftx/ftx_websocket_test.go index 5492ca19..b67afc6f 100644 --- a/exchanges/ftx/ftx_websocket_test.go +++ b/exchanges/ftx/ftx_websocket_test.go @@ -140,8 +140,11 @@ func TestFTX_wsHandleData_Details(t *testing.T) { "createdAt": "2021-08-08T10:35:02.649437+00:00" } }` - if status := parseRaw(t, inputFilled).(*order.Detail).Status; status != order.Filled { - t.Errorf("have %s, want %s", status, order.Filled) + orderDetail, ok := parseRaw(t, inputFilled).(*order.Detail) + if !ok { + t.Error("unable to type asset order detail") + } else if orderDetail.Status != order.Filled { + t.Errorf("have %s, want %s", orderDetail.Status, order.Filled) } const inputCancelled = `{ @@ -166,8 +169,12 @@ func TestFTX_wsHandleData_Details(t *testing.T) { "createdAt": "2021-08-08T10:35:02.649437+00:00" } }` - if status := parseRaw(t, inputCancelled).(*order.Detail).Status; status != order.Cancelled { - t.Errorf("have %s, want %s", status, order.Cancelled) + + orderDetail, ok = parseRaw(t, inputCancelled).(*order.Detail) + if !ok { + t.Error("unable to type asset order detail") + } else if orderDetail.Status != order.Cancelled { + t.Errorf("have %s, want %s", orderDetail.Status, order.Cancelled) } } diff --git a/exchanges/ftx/ftx_wrapper.go b/exchanges/ftx/ftx_wrapper.go index fc42c809..5b358318 100644 --- a/exchanges/ftx/ftx_wrapper.go +++ b/exchanges/ftx/ftx_wrapper.go @@ -431,6 +431,7 @@ func (f *FTX) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType as return book, err } + book.Bids = make(orderbook.Items, 0, len(tempResp.Bids)) for x := range tempResp.Bids { // Bear tokens have illiquid books and contain negative place holders. if tempResp.Bids[x].Size < 0 && strings.Contains(p.String(), "BEAR") { @@ -438,8 +439,10 @@ func (f *FTX) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType as } book.Bids = append(book.Bids, orderbook.Item{ Amount: tempResp.Bids[x].Size, - Price: tempResp.Bids[x].Price}) + Price: tempResp.Bids[x].Price, + }) } + book.Asks = make(orderbook.Items, 0, len(tempResp.Asks)) for y := range tempResp.Asks { // Bear tokens have illiquid books and contain negative place holders. if tempResp.Asks[y].Size < 0 && strings.Contains(p.String(), "BEAR") { @@ -447,7 +450,8 @@ func (f *FTX) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType as } book.Asks = append(book.Asks, orderbook.Item{ Amount: tempResp.Asks[y].Size, - Price: tempResp.Asks[y].Price}) + Price: tempResp.Asks[y].Price, + }) } err = book.Process() if err != nil { @@ -522,44 +526,44 @@ func (f *FTX) FetchAccountInfo(ctx context.Context, assetType asset.Item) (accou // GetFundingHistory returns funding history, deposits and // withdrawals func (f *FTX) GetFundingHistory(ctx context.Context) ([]exchange.FundHistory, error) { - var resp []exchange.FundHistory depositData, err := f.FetchDepositHistory(ctx) if err != nil { - return resp, err - } - for x := range depositData { - var tempData exchange.FundHistory - tempData.Fee = depositData[x].Fee - tempData.Timestamp = depositData[x].Time - tempData.ExchangeName = f.Name - tempData.CryptoToAddress = depositData[x].Address.Address - tempData.CryptoTxID = depositData[x].TxID - tempData.CryptoChain = depositData[x].Address.Method - tempData.Status = depositData[x].Status - tempData.Amount = depositData[x].Size - tempData.Currency = depositData[x].Coin - tempData.TransferID = strconv.FormatInt(depositData[x].ID, 10) - resp = append(resp, tempData) + return nil, err } withdrawalData, err := f.FetchWithdrawalHistory(ctx) if err != nil { - return resp, err + return nil, err + } + fundingData := make([]exchange.FundHistory, 0, len(depositData)+len(withdrawalData)) + for x := range depositData { + fundingData = append(fundingData, exchange.FundHistory{ + Fee: depositData[x].Fee, + Timestamp: depositData[x].Time, + ExchangeName: f.Name, + CryptoToAddress: depositData[x].Address.Address, + CryptoTxID: depositData[x].TxID, + CryptoChain: depositData[x].Address.Method, + Status: depositData[x].Status, + Amount: depositData[x].Size, + Currency: depositData[x].Coin, + TransferID: strconv.FormatInt(depositData[x].ID, 10), + }) } for y := range withdrawalData { - var tempData exchange.FundHistory - tempData.Fee = withdrawalData[y].Fee - tempData.Timestamp = withdrawalData[y].Time - tempData.ExchangeName = f.Name - tempData.CryptoToAddress = withdrawalData[y].Address - tempData.CryptoTxID = withdrawalData[y].TXID - tempData.CryptoChain = withdrawalData[y].Method - tempData.Status = withdrawalData[y].Status - tempData.Amount = withdrawalData[y].Size - tempData.Currency = withdrawalData[y].Coin - tempData.TransferID = strconv.FormatInt(withdrawalData[y].ID, 10) - resp = append(resp, tempData) + fundingData = append(fundingData, exchange.FundHistory{ + Fee: withdrawalData[y].Fee, + Timestamp: withdrawalData[y].Time, + ExchangeName: f.Name, + CryptoToAddress: withdrawalData[y].Address, + CryptoTxID: withdrawalData[y].TXID, + CryptoChain: withdrawalData[y].Method, + Status: withdrawalData[y].Status, + Amount: withdrawalData[y].Size, + Currency: withdrawalData[y].Coin, + TransferID: strconv.FormatInt(withdrawalData[y].ID, 10), + }) } - return resp, nil + return fundingData, nil } // GetWithdrawalsHistory returns previous withdrawals data @@ -1659,18 +1663,15 @@ func (f *FTX) GetFuturesPositions(ctx context.Context, a asset.Item, cp currency if err != nil { return nil, err } - sort.Slice(fills, func(i, j int) bool { - return fills[i].Time.Before(fills[j].Time) - }) - var resp []order.Detail - var side order.Side + + resp := make([]order.Detail, len(fills)) for i := range fills { price := fills[i].Price - side, err = order.StringToOrderSide(fills[i].Side) + side, err := order.StringToOrderSide(fills[i].Side) if err != nil { return nil, err } - resp = append(resp, order.Detail{ + resp[i] = order.Detail{ Side: side, Pair: cp, ID: strconv.FormatInt(fills[i].ID, 10), @@ -1680,8 +1681,12 @@ func (f *FTX) GetFuturesPositions(ctx context.Context, a asset.Item, cp currency Exchange: f.Name, Fee: fills[i].Fee, Date: fills[i].Time, - }) + } } + sort.Slice(resp, func(i, j int) bool { + return resp[i].Date.Before(resp[j].Date) + }) + return resp, nil } diff --git a/exchanges/gateio/gateio.go b/exchanges/gateio/gateio.go index 557ab211..ab68c4e8 100644 --- a/exchanges/gateio/gateio.go +++ b/exchanges/gateio/gateio.go @@ -83,11 +83,23 @@ func (g *Gateio) GetMarketInfo(ctx context.Context) (MarketInfoResponse, error) if !ok { return result, errors.New("unable to type assert pairv") } + decimalPlaces, ok := pairv["decimal_places"].(float64) + if !ok { + return result, errors.New("unable to type assert decimal_places") + } + minAmount, ok := pairv["min_amount"].(float64) + if !ok { + return result, errors.New("unable to type assert min_amount") + } + fee, ok := pairv["fee"].(float64) + if !ok { + return result, errors.New("unable to type assert fee") + } result.Pairs = append(result.Pairs, MarketInfoPairsResponse{ Symbol: itemk, - DecimalPlaces: pairv["decimal_places"].(float64), - MinAmount: pairv["min_amount"].(float64), - Fee: pairv["fee"].(float64), + DecimalPlaces: decimalPlaces, + MinAmount: minAmount, + Fee: fee, }) } } @@ -138,64 +150,59 @@ func (g *Gateio) GetTrades(ctx context.Context, symbol string) (TradeHistory, er } // GetOrderbook returns the orderbook data for a suppled symbol -func (g *Gateio) GetOrderbook(ctx context.Context, symbol string) (Orderbook, error) { +func (g *Gateio) GetOrderbook(ctx context.Context, symbol string) (*Orderbook, error) { urlPath := fmt.Sprintf("/%s/%s/%s", gateioAPIVersion, gateioOrderbook, symbol) var resp OrderbookResponse err := g.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, urlPath, &resp) if err != nil { - return Orderbook{}, err + return nil, err } - if resp.Result != "true" { - return Orderbook{}, errors.New("result was not true") - } - - var ob Orderbook - - if len(resp.Asks) == 0 { - return ob, errors.New("asks are empty") + switch { + case resp.Result != "true": + return nil, errors.New("result was not true") + case len(resp.Asks) == 0: + return nil, errors.New("asks are empty") + case len(resp.Bids) == 0: + return nil, errors.New("bids are empty") } // Asks are in reverse order - for x := len(resp.Asks) - 1; x != 0; x-- { - data := resp.Asks[x] + ob := Orderbook{ + Result: resp.Result, + Elapsed: resp.Elapsed, + Bids: make([]OrderbookItem, len(resp.Bids)), + Asks: make([]OrderbookItem, 0, len(resp.Asks)), + } - price, err := strconv.ParseFloat(data[0], 64) + for x := len(resp.Asks) - 1; x != 0; x-- { + price, err := strconv.ParseFloat(resp.Asks[x][0], 64) if err != nil { - continue + return nil, err } - amount, err := strconv.ParseFloat(data[1], 64) + amount, err := strconv.ParseFloat(resp.Asks[x][1], 64) if err != nil { - continue + return nil, err } ob.Asks = append(ob.Asks, OrderbookItem{Price: price, Amount: amount}) } - if len(resp.Bids) == 0 { - return ob, errors.New("bids are empty") - } - for x := range resp.Bids { - data := resp.Bids[x] - - price, err := strconv.ParseFloat(data[0], 64) + price, err := strconv.ParseFloat(resp.Bids[x][0], 64) if err != nil { - continue + return nil, err } - amount, err := strconv.ParseFloat(data[1], 64) + amount, err := strconv.ParseFloat(resp.Bids[x][1], 64) if err != nil { - continue + return nil, err } - ob.Bids = append(ob.Bids, OrderbookItem{Price: price, Amount: amount}) + ob.Bids[x] = OrderbookItem{Price: price, Amount: amount} } - - ob.Result = resp.Result - ob.Elapsed = resp.Elapsed - return ob, nil + return &ob, nil } // GetSpotKline returns kline data for the most recent time period diff --git a/exchanges/gateio/gateio_websocket.go b/exchanges/gateio/gateio_websocket.go index 0ef07b2e..95eb1abc 100644 --- a/exchanges/gateio/gateio_websocket.go +++ b/exchanges/gateio/gateio_websocket.go @@ -313,6 +313,22 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { if err != nil { return err } + + orderID, ok := invalidJSON["id"].(float64) + if !ok { + return errors.New("unable to type assert order id") + } + + ctime, ok := invalidJSON["ctime"].(float64) + if !ok { + return errors.New("unable to type assert ctime") + } + + mtime, ok := invalidJSON["mtime"].(float64) + if !ok { + return errors.New("unable to type assert mtime") + } + g.Websocket.DataHandler <- &order.Detail{ Price: price, Amount: amount, @@ -320,13 +336,13 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { RemainingAmount: left, Fee: fee, Exchange: g.Name, - ID: strconv.FormatFloat(invalidJSON["id"].(float64), 'f', -1, 64), + ID: strconv.FormatFloat(orderID, 'f', -1, 64), Type: oType, Side: oSide, Status: oStatus, AssetType: a, - Date: convert.TimeFromUnixTimestampDecimal(invalidJSON["ctime"].(float64)), - LastUpdated: convert.TimeFromUnixTimestampDecimal(invalidJSON["mtime"].(float64)), + Date: convert.TimeFromUnixTimestampDecimal(ctime), + LastUpdated: convert.TimeFromUnixTimestampDecimal(mtime), Pair: p, } case strings.Contains(result.Method, "depth"): @@ -349,7 +365,7 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { return err } - var asks, bids []orderbook.Item + asks := make([]orderbook.Item, len(data.Asks)) var amount, price float64 for i := range data.Asks { amount, err = strconv.ParseFloat(data.Asks[i][1], 64) @@ -360,9 +376,10 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { if err != nil { return err } - asks = append(asks, orderbook.Item{Amount: amount, Price: price}) + asks[i] = orderbook.Item{Amount: amount, Price: price} } + bids := make([]orderbook.Item, len(data.Bids)) for i := range data.Bids { amount, err = strconv.ParseFloat(data.Bids[i][1], 64) if err != nil { @@ -372,7 +389,7 @@ func (g *Gateio) wsHandleData(respRaw []byte) error { if err != nil { return err } - bids = append(bids, orderbook.Item{Amount: amount, Price: price}) + bids[i] = orderbook.Item{Amount: amount, Price: price} } var p currency.Pair @@ -616,7 +633,7 @@ func (g *Gateio) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) // & LTC_USDT this function will unsubscribe both. This function will be // kept unlinked to the websocket subsystem and a full connection flush will // occur when currency items are disabled. - var channelsThusFar []string + channelsThusFar := make([]string, 0, len(channelsToUnsubscribe)) for i := range channelsToUnsubscribe { if common.StringDataCompare(channelsThusFar, channelsToUnsubscribe[i].Channel) { diff --git a/exchanges/gateio/gateio_wrapper.go b/exchanges/gateio/gateio_wrapper.go index 98ca3697..42029325 100644 --- a/exchanges/gateio/gateio_wrapper.go +++ b/exchanges/gateio/gateio_wrapper.go @@ -319,18 +319,20 @@ func (g *Gateio) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType return book, err } + book.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[x] = orderbook.Item{ Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price, - }) + } } + book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[x] = orderbook.Item{ Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price, - }) + } } err = book.Process() if err != nil { @@ -463,14 +465,14 @@ func (g *Gateio) GetRecentTrades(ctx context.Context, p currency.Pair, assetType if err != nil { return nil, err } - var resp []trade.Data + resp := make([]trade.Data, len(tradeData.Data)) for i := range tradeData.Data { var side order.Side side, err = order.StringToOrderSide(tradeData.Data[i].Type) if err != nil { return nil, err } - resp = append(resp, trade.Data{ + resp[i] = trade.Data{ Exchange: g.Name, TID: tradeData.Data[i].TradeID, CurrencyPair: p, @@ -479,7 +481,7 @@ func (g *Gateio) GetRecentTrades(ctx context.Context, p currency.Pair, assetType Price: tradeData.Data[i].Rate, Amount: tradeData.Data[i].Amount, Timestamp: time.Unix(tradeData.Data[i].Timestamp, 0), - }) + } } err = g.AddTradesToBuffer(resp...) @@ -829,7 +831,7 @@ func (g *Gateio) GetOrderHistory(ctx context.Context, req *order.GetOrdersReques return nil, err } - var orders []order.Detail + orders := make([]order.Detail, len(trades)) for i := range trades { var pair currency.Pair pair, err = currency.NewPairDelimiter(trades[i].Pair, format.Delimiter) @@ -850,7 +852,7 @@ func (g *Gateio) GetOrderHistory(ctx context.Context, req *order.GetOrdersReques Pair: pair, } detail.InferCostsAndTimes() - orders = append(orders, detail) + orders[i] = detail } order.FilterOrdersByTimeRange(&orders, req.StartTime, req.EndTime) @@ -919,9 +921,9 @@ func (g *Gateio) GetAvailableTransferChains(ctx context.Context, cryptocurrency return nil, err } - var availableChains []string + availableChains := make([]string, len(chains.MultichainAddresses)) for x := range chains.MultichainAddresses { - availableChains = append(availableChains, chains.MultichainAddresses[x].Chain) + availableChains[x] = chains.MultichainAddresses[x].Chain } return availableChains, nil } diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index d9d27507..6053dac0 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -82,7 +82,7 @@ func (g *Gemini) GetTicker(ctx context.Context, currencyPair string) (TickerV2, // // params - limit_bids or limit_asks [OPTIONAL] default 50, 0 returns all Values // Type is an integer ie "params.Set("limit_asks", 30)" -func (g *Gemini) GetOrderbook(ctx context.Context, currencyPair string, params url.Values) (Orderbook, error) { +func (g *Gemini) GetOrderbook(ctx context.Context, currencyPair string, params url.Values) (*Orderbook, error) { path := common.EncodeURLValues( fmt.Sprintf("/v%s/%s/%s", geminiAPIVersion, @@ -91,7 +91,7 @@ func (g *Gemini) GetOrderbook(ctx context.Context, currencyPair string, params u params) var orderbook Orderbook - return orderbook, g.SendHTTPRequest(ctx, exchange.RestSpot, path, &orderbook) + return &orderbook, g.SendHTTPRequest(ctx, exchange.RestSpot, path, &orderbook) } // GetTrades return the trades that have executed since the specified timestamp. diff --git a/exchanges/gemini/gemini_test.go b/exchanges/gemini/gemini_test.go index f4cdd5f5..485bf4f9 100644 --- a/exchanges/gemini/gemini_test.go +++ b/exchanges/gemini/gemini_test.go @@ -587,7 +587,11 @@ func TestWsAuth(t *testing.T) { timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout) select { case resp := <-g.Websocket.DataHandler: - if resp.(WsSubscriptionAcknowledgementResponse).Type != "subscription_ack" { + subAck, ok := resp.(WsSubscriptionAcknowledgementResponse) + if !ok { + t.Error("unable to type assert WsSubscriptionAcknowledgementResponse") + } + if subAck.Type != "subscription_ack" { t.Error("Login failed") } case <-timer.C: diff --git a/exchanges/gemini/gemini_websocket.go b/exchanges/gemini/gemini_websocket.go index deb59d08..2f1d1492 100644 --- a/exchanges/gemini/gemini_websocket.go +++ b/exchanges/gemini/gemini_websocket.go @@ -90,7 +90,7 @@ func (g *Gemini) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, e // Subscribe sends a websocket message to receive data from the channel func (g *Gemini) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { - var channels []string + channels := make([]string, 0, len(channelsToSubscribe)) for x := range channelsToSubscribe { if common.StringDataCompareInsensitive(channels, channelsToSubscribe[x].Channel) { continue @@ -111,12 +111,12 @@ func (g *Gemini) Subscribe(channelsToSubscribe []stream.ChannelSubscription) err return err } - var subs []wsSubscriptions + subs := make([]wsSubscriptions, len(channels)) for x := range channels { - subs = append(subs, wsSubscriptions{ + subs[x] = wsSubscriptions{ Name: channels[x], Symbols: strings.Split(fmtPairs, ","), - }) + } } wsSub := wsSubscribeRequest{ @@ -134,7 +134,7 @@ func (g *Gemini) Subscribe(channelsToSubscribe []stream.ChannelSubscription) err // Unsubscribe sends a websocket message to stop receiving data from the channel func (g *Gemini) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { - var channels []string + channels := make([]string, 0, len(channelsToUnsubscribe)) for x := range channelsToUnsubscribe { if common.StringDataCompareInsensitive(channels, channelsToUnsubscribe[x].Channel) { continue @@ -155,12 +155,12 @@ func (g *Gemini) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) return err } - var subs []wsSubscriptions + subs := make([]wsSubscriptions, len(channels)) for x := range channels { - subs = append(subs, wsSubscriptions{ + subs[x] = wsSubscriptions{ Name: channels[x], Symbols: strings.Split(fmtPairs, ","), - }) + } } wsSub := wsSubscribeRequest{ @@ -384,7 +384,7 @@ func (g *Gemini) wsHandleData(respRaw []byte) error { } tradeEvent := trade.Data{ - Timestamp: time.Unix(result.Timestamp/1000, 0), + Timestamp: time.UnixMilli(result.Timestamp), CurrencyPair: pair, AssetType: asset.Spot, Exchange: g.Name, @@ -442,12 +442,16 @@ func (g *Gemini) wsHandleData(respRaw []byte) error { if len(candle.Changes[i]) != 6 { continue } + interval, ok := result["type"].(string) + if !ok { + return errors.New("unable to type assert interval") + } g.Websocket.DataHandler <- stream.KlineData{ - Timestamp: time.Unix(int64(candle.Changes[i][0])/1000, 0), + Timestamp: time.UnixMilli(int64(candle.Changes[i][0])), Pair: pair, AssetType: asset.Spot, Exchange: g.Name, - Interval: result["type"].(string), + Interval: interval, OpenPrice: candle.Changes[i][1], HighPrice: candle.Changes[i][2], LowPrice: candle.Changes[i][3], @@ -526,7 +530,9 @@ func (g *Gemini) wsProcessUpdate(result *wsL2MarketData) error { return err } - var bids, asks []orderbook.Item + bids := make([]orderbook.Item, 0, len(result.Changes)) + asks := make([]orderbook.Item, 0, len(result.Changes)) + for x := range result.Changes { price, err := strconv.ParseFloat(result.Changes[x][1], 64) if err != nil { @@ -582,7 +588,7 @@ func (g *Gemini) wsProcessUpdate(result *wsL2MarketData) error { return nil } - var trades []trade.Data + trades := make([]trade.Data, len(result.Trades)) for x := range result.Trades { tSide, err := order.StringToOrderSide(result.Trades[x].Side) if err != nil { @@ -591,8 +597,8 @@ func (g *Gemini) wsProcessUpdate(result *wsL2MarketData) error { Err: err, } } - trades = append(trades, trade.Data{ - Timestamp: time.Unix(result.Trades[x].Timestamp/1000, 0), + trades[x] = trade.Data{ + Timestamp: time.UnixMilli(result.Trades[x].Timestamp), CurrencyPair: pair, AssetType: asset.Spot, Exchange: g.Name, @@ -600,7 +606,7 @@ func (g *Gemini) wsProcessUpdate(result *wsL2MarketData) error { Amount: result.Trades[x].Quantity, Side: tSide, TID: strconv.FormatInt(result.Trades[x].EventID, 10), - }) + } } return trade.AddTradesToBuffer(g.Name, trades...) diff --git a/exchanges/gemini/gemini_wrapper.go b/exchanges/gemini/gemini_wrapper.go index 7382dd02..61c0ab3b 100644 --- a/exchanges/gemini/gemini_wrapper.go +++ b/exchanges/gemini/gemini_wrapper.go @@ -319,14 +319,14 @@ func (g *Gemini) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a return response, err } - var currencies []account.Balance + currencies := make([]account.Balance, len(accountBalance)) for i := range accountBalance { - currencies = append(currencies, account.Balance{ + currencies[i] = account.Balance{ CurrencyName: currency.NewCode(accountBalance[i].Currency), Total: accountBalance[i].Amount, Hold: accountBalance[i].Amount - accountBalance[i].Available, Free: accountBalance[i].Available, - }) + } } response.Accounts = append(response.Accounts, account.SubAccount{ @@ -431,16 +431,20 @@ func (g *Gemini) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType return book, err } + book.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[x] = orderbook.Item{ Amount: orderbookNew.Bids[x].Amount, - Price: orderbookNew.Bids[x].Price}) + Price: orderbookNew.Bids[x].Price, + } } + book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[x] = orderbook.Item{ Amount: orderbookNew.Asks[x].Amount, - Price: orderbookNew.Asks[x].Price}) + Price: orderbookNew.Asks[x].Price, + } } err = book.Process() if err != nil { @@ -692,7 +696,7 @@ func (g *Gemini) GetActiveOrders(ctx context.Context, req *order.GetOrdersReques return nil, err } - var orders []order.Detail + orders := make([]order.Detail, len(resp)) for i := range resp { var symbol currency.Pair symbol, err = currency.NewPairFromFormattedPairs(resp[i].Symbol, availPairs, format) @@ -709,7 +713,7 @@ func (g *Gemini) GetActiveOrders(ctx context.Context, req *order.GetOrdersReques side := order.Side(strings.ToUpper(resp[i].Type)) orderDate := time.Unix(resp[i].Timestamp, 0) - orders = append(orders, order.Detail{ + orders[i] = order.Detail{ Amount: resp[i].OriginalAmount, RemainingAmount: resp[i].RemainingAmount, ID: strconv.FormatInt(resp[i].OrderID, 10), @@ -720,7 +724,7 @@ func (g *Gemini) GetActiveOrders(ctx context.Context, req *order.GetOrdersReques Price: resp[i].Price, Pair: symbol, Date: orderDate, - }) + } } order.FilterOrdersByTimeRange(&orders, req.StartTime, req.EndTime) @@ -767,7 +771,7 @@ func (g *Gemini) GetOrderHistory(ctx context.Context, req *order.GetOrdersReques return nil, err } - var orders []order.Detail + orders := make([]order.Detail, len(trades)) for i := range trades { side := order.Side(strings.ToUpper(trades[i].Type)) orderDate := time.Unix(trades[i].Timestamp, 0) @@ -789,7 +793,7 @@ func (g *Gemini) GetOrderHistory(ctx context.Context, req *order.GetOrdersReques ), } detail.InferCostsAndTimes() - orders = append(orders, detail) + orders[i] = detail } order.FilterOrdersByTimeRange(&orders, req.StartTime, req.EndTime) diff --git a/exchanges/hitbtc/hitbtc.go b/exchanges/hitbtc/hitbtc.go index e801b244..37af72da 100644 --- a/exchanges/hitbtc/hitbtc.go +++ b/exchanges/hitbtc/hitbtc.go @@ -159,7 +159,7 @@ func (h *HitBTC) GetTrades(ctx context.Context, currencyPair, by, sort string, f // GetOrderbook an order book is an electronic list of buy and sell orders for a // specific symbol, organized by price level. -func (h *HitBTC) GetOrderbook(ctx context.Context, currencyPair string, limit int) (Orderbook, error) { +func (h *HitBTC) GetOrderbook(ctx context.Context, currencyPair string, limit int) (*Orderbook, error) { // limit Limit of orderbook levels, default 100. Set 0 to view full orderbook levels vals := url.Values{} @@ -167,21 +167,16 @@ func (h *HitBTC) GetOrderbook(ctx context.Context, currencyPair string, limit in vals.Set("limit", strconv.Itoa(limit)) } - resp := OrderbookResponse{} + var resp Orderbook path := fmt.Sprintf("/%s/%s?%s", apiV2Orderbook, currencyPair, vals.Encode()) - err := h.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) if err != nil { - return Orderbook{}, err + return nil, err } - - ob := Orderbook{} - ob.Asks = append(ob.Asks, resp.Asks...) - ob.Bids = append(ob.Bids, resp.Bids...) - return ob, nil + return &resp, nil } // GetCandles returns candles which is used for OHLC a specific currency. diff --git a/exchanges/hitbtc/hitbtc_types.go b/exchanges/hitbtc/hitbtc_types.go index df7bcd27..74c32430 100644 --- a/exchanges/hitbtc/hitbtc_types.go +++ b/exchanges/hitbtc/hitbtc_types.go @@ -32,12 +32,6 @@ type Symbol struct { FeeCurrency string `json:"feeCurrency"` // Default fee rate for market making trades } -// OrderbookResponse is the full orderbook response -type OrderbookResponse struct { - Asks []OrderbookItem `json:"ask"` // Ask side array of levels - Bids []OrderbookItem `json:"bid"` // Bid side array of levels -} - // OrderbookItem is a sub type for orderbook response type OrderbookItem struct { Price float64 `json:"price,string"` // Price level @@ -46,8 +40,8 @@ type OrderbookItem struct { // Orderbook contains orderbook data type Orderbook struct { - Asks []OrderbookItem `json:"asks"` - Bids []OrderbookItem `json:"bids"` + Asks []OrderbookItem `json:"ask"` + Bids []OrderbookItem `json:"bid"` } // TradeHistory contains trade history data diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index f88fecc9..4a84da4d 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -46,9 +46,11 @@ func (h *HitBTC) WsConnect() error { h.Websocket.Wg.Add(1) go h.wsReadData() - err = h.wsLogin(context.TODO()) - if err != nil { - log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", h.Name, err) + if h.Websocket.CanUseAuthenticatedEndpoints() { + err = h.wsLogin(context.TODO()) + if err != nil { + log.Errorf(log.ExchangeSys, "%v - authentication failed: %v\n", h.Name, err) + } } return nil @@ -180,7 +182,7 @@ func (h *HitBTC) wsHandleData(respRaw []byte) error { if err != nil { return err } - err = h.WsProcessOrderbookSnapshot(obSnapshot) + err = h.WsProcessOrderbookSnapshot(&obSnapshot) if err != nil { return err } @@ -190,7 +192,7 @@ func (h *HitBTC) wsHandleData(respRaw []byte) error { if err != nil { return err } - err = h.WsProcessOrderbookUpdate(obUpdate) + err = h.WsProcessOrderbookUpdate(&obUpdate) if err != nil { return err } @@ -293,24 +295,26 @@ func (h *HitBTC) wsHandleData(respRaw []byte) error { } // WsProcessOrderbookSnapshot processes a full orderbook snapshot to a local cache -func (h *HitBTC) WsProcessOrderbookSnapshot(ob WsOrderbook) error { +func (h *HitBTC) WsProcessOrderbookSnapshot(ob *WsOrderbook) error { if len(ob.Params.Bid) == 0 || len(ob.Params.Ask) == 0 { return errors.New("no orderbooks to process") } - var newOrderBook orderbook.Base + newOrderBook := orderbook.Base{ + Bids: make(orderbook.Items, len(ob.Params.Bid)), + Asks: make(orderbook.Items, len(ob.Params.Ask)), + } for i := range ob.Params.Bid { - newOrderBook.Bids = append(newOrderBook.Bids, orderbook.Item{ + newOrderBook.Bids[i] = orderbook.Item{ Amount: ob.Params.Bid[i].Size, Price: ob.Params.Bid[i].Price, - }) + } } - for i := range ob.Params.Ask { - newOrderBook.Asks = append(newOrderBook.Asks, orderbook.Item{ + newOrderBook.Asks[i] = orderbook.Item{ Amount: ob.Params.Ask[i].Size, Price: ob.Params.Ask[i].Price, - }) + } } pairs, err := h.GetEnabledPairs(asset.Spot) @@ -410,26 +414,27 @@ func (h *HitBTC) wsHandleOrderData(o *wsOrderData) error { } // WsProcessOrderbookUpdate updates a local cache -func (h *HitBTC) WsProcessOrderbookUpdate(update WsOrderbook) error { +func (h *HitBTC) WsProcessOrderbookUpdate(update *WsOrderbook) error { if len(update.Params.Bid) == 0 && len(update.Params.Ask) == 0 { // Periodically HitBTC sends empty updates which includes a sequence // can return this as nil. return nil } - var bids, asks []orderbook.Item + bids := make(orderbook.Items, len(update.Params.Bid)) for i := range update.Params.Bid { - bids = append(bids, orderbook.Item{ + bids[i] = orderbook.Item{ Price: update.Params.Bid[i].Price, Amount: update.Params.Bid[i].Size, - }) + } } + asks := make(orderbook.Items, len(update.Params.Ask)) for i := range update.Params.Ask { - asks = append(asks, orderbook.Item{ + asks[i] = orderbook.Item{ Price: update.Params.Ask[i].Price, Amount: update.Params.Ask[i].Size, - }) + } } pairs, err := h.GetEnabledPairs(asset.Spot) diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index d181282c..44a1b661 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -291,11 +291,9 @@ func (h *HitBTC) FetchTradablePairs(ctx context.Context, asset asset.Item) ([]st return nil, err } - var pairs []string + pairs := make([]string, len(symbols)) for x := range symbols { - pairs = append(pairs, symbols[x].BaseCurrency+ - format.Delimiter+ - symbols[x].QuoteCurrency) + pairs[x] = symbols[x].BaseCurrency + format.Delimiter + symbols[x].QuoteCurrency } return pairs, nil } @@ -409,18 +407,19 @@ func (h *HitBTC) UpdateOrderbook(ctx context.Context, c currency.Pair, assetType return book, err } + book.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[x] = orderbook.Item{ Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price, - }) + } } - + book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[x] = orderbook.Item{ Amount: orderbookNew.Asks[x].Amount, Price: orderbookNew.Asks[x].Price, - }) + } } err = book.Process() if err != nil { @@ -439,7 +438,7 @@ func (h *HitBTC) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (a return response, err } - var currencies []account.Balance + currencies := make([]account.Balance, 0, len(accountBalance)) for i := range accountBalance { currencies = append(currencies, account.Balance{ CurrencyName: currency.NewCode(accountBalance[i].Currency), @@ -734,7 +733,7 @@ func (h *HitBTC) GetActiveOrders(ctx context.Context, req *order.GetOrdersReques return nil, err } - var orders []order.Detail + orders := make([]order.Detail, len(allOrders)) for i := range allOrders { var symbol currency.Pair symbol, err = currency.NewPairDelimiter(allOrders[i].Symbol, @@ -743,7 +742,7 @@ func (h *HitBTC) GetActiveOrders(ctx context.Context, req *order.GetOrdersReques return nil, err } side := order.Side(strings.ToUpper(allOrders[i].Side)) - orders = append(orders, order.Detail{ + orders[i] = order.Detail{ ID: allOrders[i].ID, Amount: allOrders[i].Quantity, Exchange: h.Name, @@ -751,7 +750,7 @@ func (h *HitBTC) GetActiveOrders(ctx context.Context, req *order.GetOrdersReques Date: allOrders[i].CreatedAt, Side: side, Pair: symbol, - }) + } } order.FilterOrdersByTimeRange(&orders, req.StartTime, req.EndTime) @@ -784,7 +783,7 @@ func (h *HitBTC) GetOrderHistory(ctx context.Context, req *order.GetOrdersReques return nil, err } - var orders []order.Detail + orders := make([]order.Detail, len(allOrders)) for i := range allOrders { var pair currency.Pair pair, err = currency.NewPairDelimiter(allOrders[i].Symbol, @@ -812,7 +811,7 @@ func (h *HitBTC) GetOrderHistory(ctx context.Context, req *order.GetOrdersReques Pair: pair, } detail.InferCostsAndTimes() - orders = append(orders, detail) + orders[i] = detail } order.FilterOrdersByTimeRange(&orders, req.StartTime, req.EndTime) diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index 7f009b00..ff305b22 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -158,12 +158,12 @@ func (h *HUOBI) GetMarketDetailMerged(ctx context.Context, symbol currency.Pair) } // GetDepth returns the depth for the specified symbol -func (h *HUOBI) GetDepth(ctx context.Context, obd OrderBookDataRequestParams) (Orderbook, error) { - vals := url.Values{} +func (h *HUOBI) GetDepth(ctx context.Context, obd *OrderBookDataRequestParams) (*Orderbook, error) { symbolValue, err := h.FormatSymbol(obd.Symbol, asset.Spot) if err != nil { - return Orderbook{}, err + return nil, err } + vals := url.Values{} vals.Set("symbol", symbolValue) if obd.Type != OrderBookDataRequestParamsTypeNone { @@ -176,12 +176,11 @@ func (h *HUOBI) GetDepth(ctx context.Context, obd OrderBookDataRequestParams) (O } var result response - err = h.SendHTTPRequest(ctx, exchange.RestSpot, common.EncodeURLValues(huobiMarketDepth, vals), &result) if result.ErrorMessage != "" { - return result.Depth, errors.New(result.ErrorMessage) + return nil, errors.New(result.ErrorMessage) } - return result.Depth, err + return &result.Depth, err } // GetTrades returns the trades for the specified symbol diff --git a/exchanges/huobi/huobi_futures.go b/exchanges/huobi/huobi_futures.go index de3b52b0..2711751a 100644 --- a/exchanges/huobi/huobi_futures.go +++ b/exchanges/huobi/huobi_futures.go @@ -178,35 +178,41 @@ func (h *HUOBI) FGetEstimatedDeliveryPrice(ctx context.Context, symbol currency. } // FGetMarketDepth gets market depth data for futures contracts -func (h *HUOBI) FGetMarketDepth(ctx context.Context, symbol currency.Pair, dataType string) (OBData, error) { - var resp OBData - var tempData FMarketDepth - params := url.Values{} +func (h *HUOBI) FGetMarketDepth(ctx context.Context, symbol currency.Pair, dataType string) (*OBData, error) { symbolValue, err := h.formatFuturesPair(symbol) if err != nil { - return resp, err + return nil, err } + params := url.Values{} params.Set("symbol", symbolValue) params.Set("type", dataType) path := common.EncodeURLValues(fContractMarketDepth, params) + + var tempData FMarketDepth err = h.SendHTTPRequest(ctx, exchange.RestFutures, path, &tempData) if err != nil { - return resp, err + return nil, err + } + + resp := OBData{ + Symbol: symbolValue, + Bids: make([]obItem, len(tempData.Tick.Bids)), + Asks: make([]obItem, len(tempData.Tick.Asks)), } resp.Symbol = symbolValue for x := range tempData.Tick.Asks { - resp.Asks = append(resp.Asks, obItem{ + resp.Asks[x] = obItem{ Price: tempData.Tick.Asks[x][0], Quantity: tempData.Tick.Asks[x][1], - }) + } } for y := range tempData.Tick.Bids { - resp.Bids = append(resp.Bids, obItem{ + resp.Bids[y] = obItem{ Price: tempData.Tick.Bids[y][0], Quantity: tempData.Tick.Bids[y][1], - }) + } } - return resp, nil + return &resp, nil } // FGetKlineData gets kline data for futures diff --git a/exchanges/huobi/huobi_test.go b/exchanges/huobi/huobi_test.go index c7a25e13..b8a65c35 100644 --- a/exchanges/huobi/huobi_test.go +++ b/exchanges/huobi/huobi_test.go @@ -20,6 +20,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" + "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" @@ -55,6 +56,7 @@ func TestMain(m *testing.M) { hConfig.API.Credentials.Key = apiKey hConfig.API.Credentials.Secret = apiSecret h.Websocket = sharedtestvalues.NewTestWebsocket() + request.MaxRequestJobs = 100 err = h.Setup(hConfig) if err != nil { log.Fatal("Huobi setup error", err) @@ -1657,7 +1659,7 @@ func TestGetDepth(t *testing.T) { t.Error(err) } _, err = h.GetDepth(context.Background(), - OrderBookDataRequestParams{ + &OrderBookDataRequestParams{ Symbol: cp, Type: OrderBookDataRequestParamsTypeStep1, }) diff --git a/exchanges/huobi/huobi_types.go b/exchanges/huobi/huobi_types.go index ba835386..a62fc97d 100644 --- a/exchanges/huobi/huobi_types.go +++ b/exchanges/huobi/huobi_types.go @@ -553,10 +553,10 @@ type OrderBookDataRequestParams struct { // Orderbook stores the orderbook data type Orderbook struct { - ID int64 `json:"id"` - Timetstamp int64 `json:"ts"` - Bids [][]float64 `json:"bids"` - Asks [][]float64 `json:"asks"` + ID int64 `json:"id"` + Timetstamp int64 `json:"ts"` + Bids [][2]float64 `json:"bids"` + Asks [][2]float64 `json:"asks"` } // Trade stores the trade data diff --git a/exchanges/huobi/huobi_websocket.go b/exchanges/huobi/huobi_websocket.go index 5887a8a8..12d3000f 100644 --- a/exchanges/huobi/huobi_websocket.go +++ b/exchanges/huobi/huobi_websocket.go @@ -68,20 +68,23 @@ func (h *HUOBI) WsConnect() error { if err != nil { return err } - err = h.wsAuthenticatedDial(&dialer) - if err != nil { - log.Errorf(log.ExchangeSys, - "%v - authenticated dial failed: %v\n", - h.Name, - err) - } - err = h.wsLogin(context.TODO()) - if err != nil { - log.Errorf(log.ExchangeSys, - "%v - authentication failed: %v\n", - h.Name, - err) - h.Websocket.SetCanUseAuthenticatedEndpoints(false) + + if h.Websocket.CanUseAuthenticatedEndpoints() { + err = h.wsAuthenticatedDial(&dialer) + if err != nil { + log.Errorf(log.ExchangeSys, + "%v - authenticated dial failed: %v\n", + h.Name, + err) + } + err = h.wsLogin(context.TODO()) + if err != nil { + log.Errorf(log.ExchangeSys, + "%v - authentication failed: %v\n", + h.Name, + err) + h.Websocket.SetCanUseAuthenticatedEndpoints(false) + } } h.Websocket.Wg.Add(1) @@ -466,19 +469,36 @@ func (h *HUOBI) WsProcessOrderbook(update *WsDepth, symbol string) error { return err } - var bids, asks []orderbook.Item + bids := make(orderbook.Items, len(update.Tick.Bids)) for i := range update.Tick.Bids { - bids = append(bids, orderbook.Item{ - Price: update.Tick.Bids[i][0].(float64), - Amount: update.Tick.Bids[i][1].(float64), - }) + price, ok := update.Tick.Bids[i][0].(float64) + if !ok { + return errors.New("unable to type assert bid price") + } + amount, ok := update.Tick.Bids[i][1].(float64) + if !ok { + return errors.New("unable to type assert bid amount") + } + bids[i] = orderbook.Item{ + Price: price, + Amount: amount, + } } + asks := make(orderbook.Items, len(update.Tick.Asks)) for i := range update.Tick.Asks { - asks = append(asks, orderbook.Item{ - Price: update.Tick.Asks[i][0].(float64), - Amount: update.Tick.Asks[i][1].(float64), - }) + price, ok := update.Tick.Asks[i][0].(float64) + if !ok { + return errors.New("unable to type assert ask price") + } + amount, ok := update.Tick.Asks[i][1].(float64) + if !ok { + return errors.New("unable to type assert ask amount") + } + asks[i] = orderbook.Item{ + Price: price, + Amount: amount, + } } var newOrderBook orderbook.Base @@ -525,14 +545,18 @@ func (h *HUOBI) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, er // Subscribe sends a websocket message to receive data from the channel func (h *HUOBI) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { - creds, err := h.GetCredentials(context.TODO()) - if err != nil { - return err + var creds *exchange.Credentials + if h.Websocket.CanUseAuthenticatedEndpoints() { + var err error + creds, err = h.GetCredentials(context.TODO()) + if err != nil { + return err + } } var errs common.Errors for i := range channelsToSubscribe { - if strings.Contains(channelsToSubscribe[i].Channel, "orders.") || - strings.Contains(channelsToSubscribe[i].Channel, "accounts") { + if (strings.Contains(channelsToSubscribe[i].Channel, "orders.") || + strings.Contains(channelsToSubscribe[i].Channel, "accounts")) && creds != nil { err := h.wsAuthenticatedSubscribe(creds, "sub", wsAccountsOrdersEndPoint+channelsToSubscribe[i].Channel, @@ -561,14 +585,18 @@ func (h *HUOBI) Subscribe(channelsToSubscribe []stream.ChannelSubscription) erro // Unsubscribe sends a websocket message to stop receiving data from the channel func (h *HUOBI) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { - creds, err := h.GetCredentials(context.TODO()) - if err != nil { - return err + var creds *exchange.Credentials + if h.Websocket.CanUseAuthenticatedEndpoints() { + var err error + creds, err = h.GetCredentials(context.TODO()) + if err != nil { + return err + } } var errs common.Errors for i := range channelsToUnsubscribe { - if strings.Contains(channelsToUnsubscribe[i].Channel, "orders.") || - strings.Contains(channelsToUnsubscribe[i].Channel, "accounts") { + if (strings.Contains(channelsToUnsubscribe[i].Channel, "orders.") || + strings.Contains(channelsToUnsubscribe[i].Channel, "accounts")) && creds != nil { err := h.wsAuthenticatedSubscribe(creds, "unsub", wsAccountsOrdersEndPoint+channelsToUnsubscribe[i].Channel, diff --git a/exchanges/huobi/huobi_wrapper.go b/exchanges/huobi/huobi_wrapper.go index 97a8a66a..f69ce910 100644 --- a/exchanges/huobi/huobi_wrapper.go +++ b/exchanges/huobi/huobi_wrapper.go @@ -547,68 +547,74 @@ func (h *HUOBI) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType var err error switch assetType { case asset.Spot: - var orderbookNew Orderbook + var orderbookNew *Orderbook orderbookNew, err = h.GetDepth(ctx, - OrderBookDataRequestParams{ + &OrderBookDataRequestParams{ Symbol: p, Type: OrderBookDataRequestParamsTypeStep0, }) if err != nil { - return nil, err + return book, err } + book.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[x] = orderbook.Item{ Amount: orderbookNew.Bids[x][1], Price: orderbookNew.Bids[x][0], - }) + } } - + book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[x] = orderbook.Item{ Amount: orderbookNew.Asks[x][1], Price: orderbookNew.Asks[x][0], - }) + } } case asset.Futures: - var orderbookNew OBData + var orderbookNew *OBData orderbookNew, err = h.FGetMarketDepth(ctx, p, "step0") if err != nil { - return nil, err + return book, err } + book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[x] = orderbook.Item{ Amount: orderbookNew.Asks[x].Quantity, Price: orderbookNew.Asks[x].Price, - }) + } } + book.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for y := range orderbookNew.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[y] = orderbook.Item{ Amount: orderbookNew.Bids[y].Quantity, Price: orderbookNew.Bids[y].Price, - }) + } } case asset.CoinMarginedFutures: var orderbookNew SwapMarketDepthData orderbookNew, err = h.GetSwapMarketDepth(ctx, p, "step0") if err != nil { - return nil, err + return book, err } + book.Asks = make(orderbook.Items, len(orderbookNew.Tick.Asks)) for x := range orderbookNew.Tick.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[x] = orderbook.Item{ Amount: orderbookNew.Tick.Asks[x][1], Price: orderbookNew.Tick.Asks[x][0], - }) + } } + + book.Bids = make(orderbook.Items, len(orderbookNew.Tick.Bids)) for y := range orderbookNew.Tick.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[y] = orderbook.Item{ Amount: orderbookNew.Tick.Bids[y][1], Price: orderbookNew.Tick.Bids[y][0], - }) + } } } err = book.Process() @@ -1831,9 +1837,9 @@ func (h *HUOBI) GetAvailableTransferChains(ctx context.Context, cryptocurrency c return nil, errors.New("chain data isn't populated") } - var availableChains []string + availableChains := make([]string, len(chains[0].ChainData)) for x := range chains[0].ChainData { - availableChains = append(availableChains, chains[0].ChainData[x].Chain) + availableChains[x] = chains[0].ChainData[x].Chain } return availableChains, nil } diff --git a/exchanges/itbit/itbit.go b/exchanges/itbit/itbit.go index 4d2100f7..204e9928 100644 --- a/exchanges/itbit/itbit.go +++ b/exchanges/itbit/itbit.go @@ -49,11 +49,11 @@ func (i *ItBit) GetTicker(ctx context.Context, currencyPair string) (Ticker, err // GetOrderbook returns full order book for the specified market. // currencyPair - example "XBTUSD" "XBTSGD" "XBTEUR" -func (i *ItBit) GetOrderbook(ctx context.Context, currencyPair string) (OrderbookResponse, error) { +func (i *ItBit) GetOrderbook(ctx context.Context, currencyPair string) (*OrderbookResponse, error) { response := OrderbookResponse{} path := fmt.Sprintf("/%s/%s/%s", itbitMarkets, currencyPair, itbitOrderbook) - return response, i.SendHTTPRequest(ctx, exchange.RestSpot, path, &response) + return &response, i.SendHTTPRequest(ctx, exchange.RestSpot, path, &response) } // GetTradeHistory returns recent trades for a specified market. diff --git a/exchanges/itbit/itbit_wrapper.go b/exchanges/itbit/itbit_wrapper.go index c81bd463..e93efa1e 100644 --- a/exchanges/itbit/itbit_wrapper.go +++ b/exchanges/itbit/itbit_wrapper.go @@ -221,9 +221,10 @@ func (i *ItBit) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType orderbookNew, err := i.GetOrderbook(ctx, fpair.String()) if err != nil { - return nil, err + return book, err } + book.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { var price, amount float64 price, err = strconv.ParseFloat(orderbookNew.Bids[x][0], 64) @@ -234,13 +235,13 @@ func (i *ItBit) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType if err != nil { return book, err } - book.Bids = append(book.Bids, - orderbook.Item{ - Amount: amount, - Price: price, - }) + book.Bids[x] = orderbook.Item{ + Amount: amount, + Price: price, + } } + book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { var price, amount float64 price, err = strconv.ParseFloat(orderbookNew.Asks[x][0], 64) @@ -251,11 +252,10 @@ func (i *ItBit) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType if err != nil { return book, err } - book.Asks = append(book.Asks, - orderbook.Item{ - Amount: amount, - Price: price, - }) + book.Asks[x] = orderbook.Item{ + Amount: amount, + Price: price, + } } err = book.Process() if err != nil { @@ -288,7 +288,7 @@ func (i *ItBit) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac } } - var fullBalance []account.Balance + fullBalance := make([]account.Balance, 0, len(amounts)) for key := range amounts { fullBalance = append(fullBalance, account.Balance{ CurrencyName: currency.NewCode(key), @@ -343,9 +343,9 @@ func (i *ItBit) GetRecentTrades(ctx context.Context, p currency.Pair, assetType if err != nil { return nil, err } - var resp []trade.Data + resp := make([]trade.Data, len(tradeData.RecentTrades)) for x := range tradeData.RecentTrades { - resp = append(resp, trade.Data{ + resp[x] = trade.Data{ Exchange: i.Name, TID: tradeData.RecentTrades[x].MatchNumber, CurrencyPair: p, @@ -353,7 +353,7 @@ func (i *ItBit) GetRecentTrades(ctx context.Context, p currency.Pair, assetType Price: tradeData.RecentTrades[x].Price, Amount: tradeData.RecentTrades[x].Amount, Timestamp: tradeData.RecentTrades[x].Timestamp, - }) + } } err = i.AddTradesToBuffer(resp...) @@ -547,7 +547,7 @@ func (i *ItBit) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequest return nil, err } - var orders []order.Detail + orders := make([]order.Detail, 0, len(allOrders)) for j := range allOrders { var symbol currency.Pair symbol, err := currency.NewPairDelimiter(allOrders[j].Instrument, @@ -611,7 +611,7 @@ func (i *ItBit) GetOrderHistory(ctx context.Context, req *order.GetOrdersRequest return nil, err } - var orders []order.Detail + orders := make([]order.Detail, 0, len(allOrders)) for j := range allOrders { if allOrders[j].Type == "open" { continue diff --git a/exchanges/kline/kline.go b/exchanges/kline/kline.go index d8e9c509..23ac8141 100644 --- a/exchanges/kline/kline.go +++ b/exchanges/kline/kline.go @@ -353,13 +353,13 @@ func ConvertToNewInterval(item *Item, newInterval Interval) (*Item, error) { oldIntervalsPerNewCandle := int64(newInterval / item.Interval) var candleBundles [][]Candle - var candles []Candle + candles := make([]Candle, 0, oldIntervalsPerNewCandle) for i := range item.Candles { candles = append(candles, item.Candles[i]) intervalCount := int64(i + 1) if oldIntervalsPerNewCandle == intervalCount { candleBundles = append(candleBundles, candles) - candles = []Candle{} + candles = candles[:0] } } responseCandle := &Item{ diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index ec355efc..ba495013 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -234,7 +234,7 @@ func (k *Kraken) GetOHLC(ctx context.Context, symbol currency.Pair, interval str return nil, errors.New("invalid data returned") } - var OHLC []OpenHighLowClose + OHLC := make([]OpenHighLowClose, len(ohlcData)) for x := range ohlcData { subData, ok := ohlcData[x].([]interface{}) if !ok { @@ -270,56 +270,55 @@ func (k *Kraken) GetOHLC(ctx context.Context, symbol currency.Pair, interval str if o.Count, ok = subData[7].(float64); !ok { return nil, errors.New("unable to type assert count") } - OHLC = append(OHLC, o) + OHLC[x] = o } return OHLC, nil } // GetDepth returns the orderbook for a particular currency -func (k *Kraken) GetDepth(ctx context.Context, symbol currency.Pair) (Orderbook, error) { - var result interface{} - var orderBook Orderbook - values := url.Values{} +func (k *Kraken) GetDepth(ctx context.Context, symbol currency.Pair) (*Orderbook, error) { symbolValue, err := k.FormatSymbol(symbol, asset.Spot) if err != nil { - return orderBook, err + return nil, err } + values := url.Values{} values.Set("pair", symbolValue) path := fmt.Sprintf("/%s/public/%s?%s", krakenAPIVersion, krakenDepth, values.Encode()) + var result interface{} err = k.SendHTTPRequest(ctx, exchange.RestSpot, path, &result) if err != nil { - return orderBook, err + return nil, err } if result == nil { - return orderBook, fmt.Errorf("%s GetDepth result is nil", k.Name) + return nil, fmt.Errorf("%s GetDepth result is nil", k.Name) } data, ok := result.(map[string]interface{}) if !ok { - return orderBook, errors.New("unable to type assert data") + return nil, errors.New("unable to type assert data") } orderbookData, ok := data["result"].(map[string]interface{}) if !ok { - return orderBook, fmt.Errorf("%s GetDepth data[result] is nil", k.Name) + return nil, fmt.Errorf("%s GetDepth data[result] is nil", k.Name) } var bidsData []interface{} var asksData []interface{} for _, y := range orderbookData { yData, ok := y.(map[string]interface{}) if !ok { - return orderBook, errors.New("unable to type assert yData") + return nil, errors.New("unable to type assert yData") } if bidsData, ok = yData["bids"].([]interface{}); !ok { - return orderBook, errors.New("unable to type assert bidsData") + return nil, errors.New("unable to type assert bidsData") } if asksData, ok = yData["asks"].([]interface{}); !ok { - return orderBook, errors.New("unable to type assert asksData") + return nil, errors.New("unable to type assert asksData") } } processOrderbook := func(data []interface{}) ([]OrderbookBase, error) { - var result []OrderbookBase + result := make([]OrderbookBase, len(data)) for x := range data { entry, ok := data[x].([]interface{}) if !ok { @@ -340,18 +339,19 @@ func (k *Kraken) GetDepth(ctx context.Context, symbol currency.Pair) (Orderbook, return nil, amountErr } - result = append(result, OrderbookBase{Price: price, Amount: amount}) + result[x] = OrderbookBase{Price: price, Amount: amount} } return result, nil } + var orderBook Orderbook orderBook.Bids, err = processOrderbook(bidsData) if err != nil { - return orderBook, err + return nil, err } orderBook.Asks, err = processOrderbook(asksData) - return orderBook, err + return &orderBook, err } // GetTrades returns current trades on Kraken @@ -364,7 +364,6 @@ func (k *Kraken) GetTrades(ctx context.Context, symbol currency.Pair) ([]RecentT translatedAsset := assetTranslator.LookupCurrency(symbolValue) values.Set("pair", translatedAsset) - var recentTrades []RecentTrades var result interface{} path := fmt.Sprintf("/%s/public/%s?%s", krakenAPIVersion, krakenTrades, values.Encode()) @@ -422,10 +421,11 @@ func (k *Kraken) GetTrades(ctx context.Context, symbol currency.Pair) ([]RecentT return nil, fmt.Errorf("no trades returned for symbol %v", symbol) } - for _, x := range trades { + recentTrades := make([]RecentTrades, len(trades)) + for x := range trades { r := RecentTrades{} var individualTrade []interface{} - individualTrade, ok = x.([]interface{}) + individualTrade, ok = trades[x].([]interface{}) if !ok { return nil, errors.New("unable to parse individual trade data") } @@ -456,7 +456,7 @@ func (k *Kraken) GetTrades(ctx context.Context, symbol currency.Pair) ([]RecentT if !ok { return nil, errors.New("unable to parse misc field for individual trade data") } - recentTrades = append(recentTrades, r) + recentTrades[x] = r } return recentTrades, nil } @@ -489,7 +489,7 @@ func (k *Kraken) GetSpread(ctx context.Context, symbol currency.Pair) ([]Spread, return nil, errors.New("unable to type assert spreadData") } - var peanutButter []Spread + peanutButter := make([]Spread, len(spreadData)) for x := range spreadData { subData, ok := spreadData[x].([]interface{}) if !ok { @@ -513,7 +513,7 @@ func (k *Kraken) GetSpread(ctx context.Context, symbol currency.Pair) ([]Spread, if s.Ask, err = convert.FloatFromString(subData[2]); err != nil { return nil, err } - peanutButter = append(peanutButter, s) + peanutButter[x] = s } return peanutButter, nil } @@ -866,19 +866,19 @@ func (k *Kraken) QueryLedgers(ctx context.Context, id string, ids ...string) (ma } // GetTradeVolume returns your trade volume by currency -func (k *Kraken) GetTradeVolume(ctx context.Context, feeinfo bool, symbol ...currency.Pair) (TradeVolumeResponse, error) { +func (k *Kraken) GetTradeVolume(ctx context.Context, feeinfo bool, symbol ...currency.Pair) (*TradeVolumeResponse, error) { var response struct { - Error []string `json:"error"` - Result TradeVolumeResponse `json:"result"` + Error []string `json:"error"` + Result *TradeVolumeResponse `json:"result"` } params := url.Values{} - var formattedPairs []string + formattedPairs := make([]string, len(symbol)) for x := range symbol { symbolValue, err := k.FormatSymbol(symbol[x], asset.Spot) if err != nil { - return response.Result, err + return nil, err } - formattedPairs = append(formattedPairs, symbolValue) + formattedPairs[x] = symbolValue } if symbol != nil { params.Set("pair", strings.Join(formattedPairs, ",")) @@ -889,7 +889,7 @@ func (k *Kraken) GetTradeVolume(ctx context.Context, feeinfo bool, symbol ...cur } if err := k.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, krakenTradeVolume, params, &response); err != nil { - return response.Result, err + return nil, err } return response.Result, GetError(response.Error) diff --git a/exchanges/kraken/kraken_futures.go b/exchanges/kraken/kraken_futures.go index e83f660a..09f2d26d 100644 --- a/exchanges/kraken/kraken_futures.go +++ b/exchanges/kraken/kraken_futures.go @@ -23,15 +23,15 @@ import ( var errInvalidBatchOrderType = errors.New("invalid batch order type") // GetFuturesOrderbook gets orderbook data for futures -func (k *Kraken) GetFuturesOrderbook(ctx context.Context, symbol currency.Pair) (FuturesOrderbookData, error) { - var resp FuturesOrderbookData - params := url.Values{} +func (k *Kraken) GetFuturesOrderbook(ctx context.Context, symbol currency.Pair) (*FuturesOrderbookData, error) { symbolValue, err := k.FormatSymbol(symbol, asset.Futures) if err != nil { - return resp, err + return nil, err } + params := url.Values{} params.Set("symbol", symbolValue) - return resp, k.SendHTTPRequest(ctx, exchange.RestFutures, futuresOrderbook+"?"+params.Encode(), &resp) + var resp FuturesOrderbookData + return &resp, k.SendHTTPRequest(ctx, exchange.RestFutures, futuresOrderbook+"?"+params.Encode(), &resp) } // GetFuturesMarkets gets a list of futures markets and their data diff --git a/exchanges/kraken/kraken_websocket.go b/exchanges/kraken/kraken_websocket.go index e9865f3a..6b3e3671 100644 --- a/exchanges/kraken/kraken_websocket.go +++ b/exchanges/kraken/kraken_websocket.go @@ -767,7 +767,7 @@ func (k *Kraken) wsProcessTrades(channelData *WebsocketChannelData, data []inter if !k.IsSaveTradeDataEnabled() { return nil } - var trades []trade.Data + trades := make([]trade.Data, len(data)) for i := range data { t, ok := data[i].([]interface{}) if !ok { @@ -788,11 +788,15 @@ func (k *Kraken) wsProcessTrades(channelData *WebsocketChannelData, data []inter return err } var tSide = order.Buy - if t[3].(string) == "s" { + s, ok := t[3].(string) + if !ok { + return errors.New("unable to type assert side") + } + if s == "s" { tSide = order.Sell } - trades = append(trades, trade.Data{ + trades[i] = trade.Data{ AssetType: asset.Spot, CurrencyPair: channelData.Pair, Exchange: k.Name, @@ -800,7 +804,7 @@ func (k *Kraken) wsProcessTrades(channelData *WebsocketChannelData, data []inter Amount: amount, Timestamp: convert.TimeFromUnixTimestampDecimal(timeData), Side: tSide, - }) + } } return trade.AddTradesToBuffer(k.Name, trades...) } @@ -855,6 +859,8 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, as Pair: channelData.Pair, Asset: asset.Spot, VerifyOrderbook: k.CanVerifyOrderbook, + Bids: make(orderbook.Items, len(bidData)), + Asks: make(orderbook.Items, len(askData)), } // Kraken ob data is timestamped per price, GCT orderbook data is // timestamped per entry using the highest last update time, we can attempt @@ -876,14 +882,14 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, as if err != nil { return err } - base.Asks = append(base.Asks, orderbook.Item{ - Amount: amount, - Price: price, - }) timeData, err := strconv.ParseFloat(asks[2].(string), 64) if err != nil { return err } + base.Asks[i] = orderbook.Item{ + Amount: amount, + Price: price, + } askUpdatedTime := convert.TimeFromUnixTimestampDecimal(timeData) if highestLastUpdate.Before(askUpdatedTime) { highestLastUpdate = askUpdatedTime @@ -906,14 +912,16 @@ func (k *Kraken) wsProcessOrderBookPartial(channelData *WebsocketChannelData, as if err != nil { return err } - base.Bids = append(base.Bids, orderbook.Item{ - Amount: amount, - Price: price, - }) timeData, err := strconv.ParseFloat(bids[2].(string), 64) if err != nil { return err } + + base.Bids[i] = orderbook.Item{ + Amount: amount, + Price: price, + } + bidUpdateTime := convert.TimeFromUnixTimestampDecimal(timeData) if highestLastUpdate.Before(bidUpdateTime) { highestLastUpdate = bidUpdateTime @@ -930,6 +938,8 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, ask Asset: asset.Spot, Pair: channelData.Pair, MaxDepth: krakenWsOrderbookDepth, + Bids: make([]orderbook.Item, len(bidData)), + Asks: make([]orderbook.Item, len(askData)), } // Calculating checksum requires incoming decimal place checks for both @@ -965,11 +975,6 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, ask return err } - update.Asks = append(update.Asks, orderbook.Item{ - Amount: amount, - Price: price, - }) - timeStr, ok := asks[2].(string) if !ok { return errors.New("time type assertion failure") @@ -980,6 +985,11 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, ask return err } + update.Asks[i] = orderbook.Item{ + Amount: amount, + Price: price, + } + askUpdatedTime := convert.TimeFromUnixTimestampDecimal(timeData) if highestLastUpdate.Before(askUpdatedTime) { highestLastUpdate = askUpdatedTime @@ -1028,11 +1038,6 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, ask return err } - update.Bids = append(update.Bids, orderbook.Item{ - Amount: amount, - Price: price, - }) - timeStr, ok := bids[2].(string) if !ok { return errors.New("time type assertion failure") @@ -1043,6 +1048,11 @@ func (k *Kraken) wsProcessOrderBookUpdate(channelData *WebsocketChannelData, ask return err } + update.Bids[i] = orderbook.Item{ + Amount: amount, + Price: price, + } + bidUpdatedTime := convert.TimeFromUnixTimestampDecimal(timeData) if highestLastUpdate.Before(bidUpdatedTime) { highestLastUpdate = bidUpdatedTime @@ -1372,7 +1382,7 @@ func (k *Kraken) wsAddOrder(request *WsAddOrderRequest) (string, error) { return "", err } if resp.ErrorMessage != "" { - return "", fmt.Errorf(k.Name + " - " + resp.ErrorMessage) + return "", errors.New(k.Name + " - " + resp.ErrorMessage) } return resp.TransactionID, nil } @@ -1436,7 +1446,7 @@ func (k *Kraken) wsCancelAllOrders() (*WsCancelOrderResponse, error) { return &WsCancelOrderResponse{}, err } if resp.ErrorMessage != "" { - return &WsCancelOrderResponse{}, fmt.Errorf(k.Name + " - " + resp.ErrorMessage) + return &WsCancelOrderResponse{}, errors.New(k.Name + " - " + resp.ErrorMessage) } return &resp, nil } diff --git a/exchanges/kraken/kraken_wrapper.go b/exchanges/kraken/kraken_wrapper.go index dcffed5e..03f97fd3 100644 --- a/exchanges/kraken/kraken_wrapper.go +++ b/exchanges/kraken/kraken_wrapper.go @@ -532,40 +532,44 @@ func (k *Kraken) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType var err error switch assetType { case asset.Spot: - var orderbookNew Orderbook + var orderbookNew *Orderbook orderbookNew, err = k.GetDepth(ctx, p) if err != nil { - return nil, err + return book, err } + book.Bids = make([]orderbook.Item, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[x] = orderbook.Item{ Amount: orderbookNew.Bids[x].Amount, Price: orderbookNew.Bids[x].Price, - }) + } } + book.Asks = make([]orderbook.Item, len(orderbookNew.Asks)) for y := range orderbookNew.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[y] = orderbook.Item{ Amount: orderbookNew.Asks[y].Amount, Price: orderbookNew.Asks[y].Price, - }) + } } case asset.Futures: - var futuresOB FuturesOrderbookData + var futuresOB *FuturesOrderbookData futuresOB, err = k.GetFuturesOrderbook(ctx, p) if err != nil { - return nil, err + return book, err } + book.Asks = make([]orderbook.Item, len(futuresOB.Orderbook.Asks)) for x := range futuresOB.Orderbook.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[x] = orderbook.Item{ Price: futuresOB.Orderbook.Asks[x][0], Amount: futuresOB.Orderbook.Asks[x][1], - }) + } } + book.Bids = make([]orderbook.Item, len(futuresOB.Orderbook.Bids)) for y := range futuresOB.Orderbook.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[y] = orderbook.Item{ Price: futuresOB.Orderbook.Bids[y][0], Amount: futuresOB.Orderbook.Bids[y][1], - }) + } } default: return book, fmt.Errorf("invalid assetType: %v", assetType) @@ -666,19 +670,17 @@ func (k *Kraken) GetWithdrawalsHistory(ctx context.Context, c currency.Code) (re // GetRecentTrades returns the most recent trades for a currency and asset func (k *Kraken) GetRecentTrades(ctx context.Context, p currency.Pair, assetType asset.Item) ([]trade.Data, error) { - var err error - var tradeData []RecentTrades - tradeData, err = k.GetTrades(ctx, p) + tradeData, err := k.GetTrades(ctx, p) if err != nil { return nil, err } - var resp []trade.Data + resp := make([]trade.Data, len(tradeData)) for i := range tradeData { side := order.Buy if tradeData[i].BuyOrSell == "s" { side = order.Sell } - resp = append(resp, trade.Data{ + resp[i] = trade.Data{ Exchange: k.Name, CurrencyPair: p, AssetType: assetType, @@ -686,7 +688,7 @@ func (k *Kraken) GetRecentTrades(ctx context.Context, p currency.Pair, assetType Price: tradeData[i].Price, Amount: tradeData[i].Volume, Timestamp: convert.TimeFromUnixTimestampDecimal(tradeData[i].Time), - }) + } } err = k.AddTradesToBuffer(resp...) @@ -809,20 +811,20 @@ func (k *Kraken) CancelOrder(ctx context.Context, o *order.Cancel) error { // CancelBatchOrders cancels an orders by their corresponding ID numbers func (k *Kraken) CancelBatchOrders(ctx context.Context, orders []order.Cancel) (order.CancelBatchResponse, error) { - var ordersList []string + if !k.Websocket.CanUseAuthenticatedWebsocketForWrapper() { + return order.CancelBatchResponse{}, common.ErrFunctionNotSupported + } + + ordersList := make([]string, len(orders)) for i := range orders { if err := orders[i].Validate(orders[i].StandardCancel()); err != nil { return order.CancelBatchResponse{}, err } - ordersList = append(ordersList, orders[i].ID) + ordersList[i] = orders[i].ID } - if k.Websocket.CanUseAuthenticatedWebsocketForWrapper() { - err := k.wsCancelOrders(ordersList) - return order.CancelBatchResponse{}, err - } - - return order.CancelBatchResponse{}, common.ErrFunctionNotSupported + err := k.wsCancelOrders(ordersList) + return order.CancelBatchResponse{}, err } // CancelAllOrders cancels all orders associated with a currency pair @@ -1555,9 +1557,9 @@ func (k *Kraken) GetAvailableTransferChains(ctx context.Context, cryptocurrency return nil, err } - var availableChains []string + availableChains := make([]string, len(methods)) for x := range methods { - availableChains = append(availableChains, methods[x].Method) + availableChains[x] = methods[x].Method } return availableChains, nil } diff --git a/exchanges/lbank/lbank.go b/exchanges/lbank/lbank.go index f33c2d29..e59d732a 100644 --- a/exchanges/lbank/lbank.go +++ b/exchanges/lbank/lbank.go @@ -89,14 +89,14 @@ func (l *Lbank) GetCurrencyPairs(ctx context.Context) ([]string, error) { } // GetMarketDepths returns arrays of asks, bids and timestamp -func (l *Lbank) GetMarketDepths(ctx context.Context, symbol, size, merge string) (MarketDepthResponse, error) { +func (l *Lbank) GetMarketDepths(ctx context.Context, symbol, size, merge string) (*MarketDepthResponse, error) { var m MarketDepthResponse params := url.Values{} params.Set("symbol", symbol) params.Set("size", size) params.Set("merge", merge) path := fmt.Sprintf("/v%s/%s?%s", lbankAPIVersion2, lbankMarketDepths, params.Encode()) - return m, l.SendHTTPRequest(ctx, exchange.RestSpot, path, &m) + return &m, l.SendHTTPRequest(ctx, exchange.RestSpot, path, &m) } // GetTrades returns an array of available trades regarding a particular exchange @@ -128,19 +128,19 @@ func (l *Lbank) GetKlines(ctx context.Context, symbol, size, klineType, tm strin return nil, err } - var k []KlineResponse + k := make([]KlineResponse, len(klineTemp)) for x := range klineTemp { if len(klineTemp[x]) < 6 { return nil, errors.New("unexpected kline data length") } - k = append(k, KlineResponse{ + k[x] = KlineResponse{ TimeStamp: time.Unix(int64(klineTemp[x][0]), 0).UTC(), OpenPrice: klineTemp[x][1], HigestPrice: klineTemp[x][2], LowestPrice: klineTemp[x][3], ClosePrice: klineTemp[x][4], TradingVolume: klineTemp[x][5], - }) + } } return k, nil } diff --git a/exchanges/lbank/lbank_wrapper.go b/exchanges/lbank/lbank_wrapper.go index 20fc593e..ac7fc54e 100644 --- a/exchanges/lbank/lbank_wrapper.go +++ b/exchanges/lbank/lbank_wrapper.go @@ -281,6 +281,8 @@ func (l *Lbank) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType if err != nil { return book, err } + + book.Asks = make(orderbook.Items, len(a.Data.Asks)) for i := range a.Data.Asks { price, convErr := strconv.ParseFloat(a.Data.Asks[i][0], 64) if convErr != nil { @@ -290,10 +292,12 @@ func (l *Lbank) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType if convErr != nil { return book, convErr } - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[i] = orderbook.Item{ Price: price, - Amount: amount}) + Amount: amount, + } } + book.Bids = make(orderbook.Items, len(a.Data.Bids)) for i := range a.Data.Bids { price, convErr := strconv.ParseFloat(a.Data.Bids[i][0], 64) if convErr != nil { @@ -303,9 +307,10 @@ func (l *Lbank) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType if convErr != nil { return book, convErr } - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[i] = orderbook.Item{ Price: price, - Amount: amount}) + Amount: amount, + } } err = book.Process() if err != nil { diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index 2e92a705..56c73fc6 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -665,7 +665,7 @@ func (l *LocalBitcoins) GetTradableCurrencies(ctx context.Context) ([]string, er return nil, err } - var currencies []string + currencies := make([]string, 0, len(resp)) for x := range resp { currencies = append(currencies, x) } @@ -686,44 +686,47 @@ func (l *LocalBitcoins) GetTrades(ctx context.Context, currency string, values u // the maximum amount available for the trade request. Price is the hourly // updated price. The price is based on the price equation and commission % // entered by the ad author. -func (l *LocalBitcoins) GetOrderbook(ctx context.Context, currency string) (Orderbook, error) { +func (l *LocalBitcoins) GetOrderbook(ctx context.Context, curr string) (*Orderbook, error) { type response struct { Bids [][2]string `json:"bids"` Asks [][2]string `json:"asks"` } - path := localbitcoinsAPIBitcoincharts + currency + localbitcoinsAPIOrderbook + path := localbitcoinsAPIBitcoincharts + curr + localbitcoinsAPIOrderbook resp := response{} - var ob Orderbook if err := l.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp, orderBookLimiter); err != nil { - return ob, err + return nil, err } + ob := Orderbook{ + Bids: make([]Price, len(resp.Bids)), + Asks: make([]Price, len(resp.Asks)), + } for x := range resp.Bids { price, err := strconv.ParseFloat(resp.Bids[x][0], 64) if err != nil { - return ob, err + return nil, err } amount, err := strconv.ParseFloat(resp.Bids[x][1], 64) if err != nil { - return ob, err + return nil, err } - ob.Bids = append(ob.Bids, Price{price, amount}) + ob.Bids[x] = Price{price, amount} } for x := range resp.Asks { price, err := strconv.ParseFloat(resp.Asks[x][0], 64) if err != nil { - return ob, err + return nil, err } amount, err := strconv.ParseFloat(resp.Asks[x][1], 64) if err != nil { - return ob, err + return nil, err } - ob.Asks = append(ob.Asks, Price{price, amount}) + ob.Asks[x] = Price{price, amount} } - return ob, nil + return &ob, nil } // SendHTTPRequest sends an unauthenticated HTTP request diff --git a/exchanges/localbitcoins/localbitcoins_wrapper.go b/exchanges/localbitcoins/localbitcoins_wrapper.go index 83e6c3a2..241ff261 100644 --- a/exchanges/localbitcoins/localbitcoins_wrapper.go +++ b/exchanges/localbitcoins/localbitcoins_wrapper.go @@ -155,9 +155,9 @@ func (l *LocalBitcoins) FetchTradablePairs(ctx context.Context, asset asset.Item return nil, err } - var pairs []string + pairs := make([]string, len(currencies)) for x := range currencies { - pairs = append(pairs, "BTC"+currencies[x]) + pairs[x] = "BTC" + currencies[x] } return pairs, nil @@ -248,18 +248,26 @@ func (l *LocalBitcoins) UpdateOrderbook(ctx context.Context, p currency.Pair, as return book, err } + book.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + if orderbookNew.Bids[x].Price == 0 { + return book, errors.New("orderbook bid price is 0") + } + book.Bids[x] = orderbook.Item{ Amount: orderbookNew.Bids[x].Amount / orderbookNew.Bids[x].Price, Price: orderbookNew.Bids[x].Price, - }) + } } + book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + if orderbookNew.Asks[x].Price == 0 { + return book, errors.New("orderbook ask price is 0") + } + book.Asks[x] = orderbook.Item{ Amount: orderbookNew.Asks[x].Amount / orderbookNew.Asks[x].Price, Price: orderbookNew.Asks[x].Price, - }) + } } book.PriceDuplication = true @@ -332,9 +340,9 @@ func (l *LocalBitcoins) GetRecentTrades(ctx context.Context, p currency.Pair, as if err != nil { return nil, err } - var resp []trade.Data + resp := make([]trade.Data, len(tradeData)) for i := range tradeData { - resp = append(resp, trade.Data{ + resp[i] = trade.Data{ Exchange: l.Name, TID: strconv.FormatInt(tradeData[i].TID, 10), CurrencyPair: p, @@ -342,7 +350,7 @@ func (l *LocalBitcoins) GetRecentTrades(ctx context.Context, p currency.Pair, as Price: tradeData[i].Price, Amount: tradeData[i].Amount, Timestamp: time.Unix(tradeData[i].Date, 0), - }) + } } err = l.AddTradesToBuffer(resp...) @@ -551,7 +559,7 @@ func (l *LocalBitcoins) GetActiveOrders(ctx context.Context, getOrdersRequest *o return nil, err } - var orders []order.Detail + orders := make([]order.Detail, len(resp)) for i := range resp { orderDate, err := time.Parse(time.RFC3339, resp[i].Data.CreatedAt) if err != nil { @@ -562,14 +570,12 @@ func (l *LocalBitcoins) GetActiveOrders(ctx context.Context, getOrdersRequest *o resp[i].Data.CreatedAt) } - var side order.Side - if resp[i].Data.IsBuying { - side = order.Buy - } else if resp[i].Data.IsSelling { + side := order.Buy + if resp[i].Data.IsSelling { side = order.Sell } - orders = append(orders, order.Detail{ + orders[i] = order.Detail{ Amount: resp[i].Data.AmountBTC, Price: resp[i].Data.Amount, ID: strconv.FormatInt(int64(resp[i].Data.Advertisement.ID), 10), @@ -580,7 +586,7 @@ func (l *LocalBitcoins) GetActiveOrders(ctx context.Context, getOrdersRequest *o resp[i].Data.Currency, format.Delimiter), Exchange: l.Name, - }) + } } order.FilterOrdersByTimeRange(&orders, getOrdersRequest.StartTime, @@ -621,7 +627,7 @@ func (l *LocalBitcoins) GetOrderHistory(ctx context.Context, getOrdersRequest *o return nil, err } - var orders []order.Detail + orders := make([]order.Detail, len(allTrades)) for i := range allTrades { orderDate, err := time.Parse(time.RFC3339, allTrades[i].Data.CreatedAt) if err != nil { @@ -659,7 +665,7 @@ func (l *LocalBitcoins) GetOrderHistory(ctx context.Context, getOrdersRequest *o log.Errorf(log.ExchangeSys, "%s %v", l.Name, err) } - orders = append(orders, order.Detail{ + orders[i] = order.Detail{ Amount: allTrades[i].Data.AmountBTC, Price: allTrades[i].Data.Amount, ID: strconv.FormatInt(int64(allTrades[i].Data.Advertisement.ID), 10), @@ -671,7 +677,7 @@ func (l *LocalBitcoins) GetOrderHistory(ctx context.Context, getOrdersRequest *o allTrades[i].Data.Currency, format.Delimiter), Exchange: l.Name, - }) + } } order.FilterOrdersByTimeRange(&orders, getOrdersRequest.StartTime, diff --git a/exchanges/mock/common.go b/exchanges/mock/common.go index e738bf8c..0f77a193 100644 --- a/exchanges/mock/common.go +++ b/exchanges/mock/common.go @@ -42,7 +42,7 @@ func MatchURLVals(v1, v2 url.Values) bool { // DeriveURLValsFromJSONMap gets url vals from a map[string]string encoded JSON body func DeriveURLValsFromJSONMap(payload []byte) (url.Values, error) { var vals = url.Values{} - if string(payload) == "" { + if len(payload) == 0 { return vals, nil } intermediary := make(map[string]interface{}) diff --git a/exchanges/mock/recording.go b/exchanges/mock/recording.go index 149ebff8..c24efec2 100644 --- a/exchanges/mock/recording.go +++ b/exchanges/mock/recording.go @@ -4,7 +4,7 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "net/http" "net/url" "os" @@ -48,7 +48,7 @@ func HTTPRecord(res *http.Response, service string, respContents []byte) error { fileout := filepath.Join(DefaultDirectory, service, service+".json") - contents, err := ioutil.ReadFile(fileout) + contents, err := os.ReadFile(fileout) if err != nil { return err } @@ -80,7 +80,7 @@ func HTTPRecord(res *http.Response, service string, respContents []byte) error { if bodyErr != nil { return bodyErr } - payload, bodyErr := ioutil.ReadAll(bodycopy) + payload, bodyErr := io.ReadAll(bodycopy) if bodyErr != nil { return bodyErr } @@ -298,11 +298,10 @@ const ( // CheckJSON recursively parses json data to retract keywords, quite intensive. func CheckJSON(data interface{}, excluded *Exclusion) (interface{}, error) { - var context map[string]interface{} - if reflect.TypeOf(data).String() == "[]interface {}" { + if d, ok := data.([]interface{}); ok { var sData []interface{} - for i := range data.([]interface{}) { - v := data.([]interface{})[i] + for i := range d { + v := d[i] switch v.(type) { case map[string]interface{}, []interface{}: checkedData, err := CheckJSON(v, excluded) @@ -324,6 +323,7 @@ func CheckJSON(data interface{}, excluded *Exclusion) (interface{}, error) { return nil, err } + var context map[string]interface{} err = json.Unmarshal(conv, &context) if err != nil { return nil, err @@ -426,7 +426,7 @@ func GetExcludedItems() (Exclusion, error) { m.Lock() defer m.Unlock() if !set { - file, err := ioutil.ReadFile(exclusionFile) + file, err := os.ReadFile(exclusionFile) if err != nil { if !strings.Contains(err.Error(), "no such file or directory") { return excludedList, err @@ -440,7 +440,7 @@ func GetExcludedItems() (Exclusion, error) { return excludedList, mErr } - mErr = ioutil.WriteFile(exclusionFile, data, os.ModePerm) + mErr = os.WriteFile(exclusionFile, data, os.ModePerm) if mErr != nil { return excludedList, mErr } diff --git a/exchanges/mock/server.go b/exchanges/mock/server.go index dd7df16b..2ff294b4 100644 --- a/exchanges/mock/server.go +++ b/exchanges/mock/server.go @@ -4,11 +4,12 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "io" "log" "net/http" "net/http/httptest" "net/url" + "os" "reflect" "strconv" "strings" @@ -42,7 +43,7 @@ func NewVCRServer(path string) (string, *http.Client, error) { var mockFile VCRMock - contents, err := ioutil.ReadFile(path) + contents, err := os.ReadFile(path) if err != nil { pathing := strings.Split(path, "/") dirPathing := pathing[:len(pathing)-1] @@ -126,7 +127,7 @@ func RegisterHandler(pattern string, mock map[string][]HTTPResponse, mux *http.S case http.MethodPost, http.MethodPut: switch r.Header.Get(contentType) { case applicationURLEncoded: - readBody, err := ioutil.ReadAll(r.Body) + readBody, err := io.ReadAll(r.Body) if err != nil { log.Fatal("Mock Test Failure - ReadAll error", err) } @@ -154,7 +155,7 @@ func RegisterHandler(pattern string, mock map[string][]HTTPResponse, mux *http.S return case applicationJSON: - readBody, err := ioutil.ReadAll(r.Body) + readBody, err := io.ReadAll(r.Body) if err != nil { log.Fatalf("Mock Test Failure - %v", err) } diff --git a/exchanges/mock/server_test.go b/exchanges/mock/server_test.go index 8cc8ebf4..5b9cb8cf 100644 --- a/exchanges/mock/server_test.go +++ b/exchanges/mock/server_test.go @@ -4,7 +4,6 @@ import ( "bytes" "context" "encoding/json" - "io/ioutil" "net/http" "os" "strings" @@ -48,7 +47,7 @@ func TestNewVCRServer(t *testing.T) { t.Fatal("marshal error", err) } - err = ioutil.WriteFile(testFile, payload, os.ModePerm) + err = os.WriteFile(testFile, payload, os.ModePerm) if err != nil { t.Fatal("marshal error", err) } diff --git a/exchanges/okcoin/okcoin_test.go b/exchanges/okcoin/okcoin_test.go index 8eec3145..dba24a89 100644 --- a/exchanges/okcoin/okcoin_test.go +++ b/exchanges/okcoin/okcoin_test.go @@ -788,7 +788,7 @@ func TestSendWsMessages(t *testing.T) { } response := <-o.Websocket.DataHandler if err, ok = response.(error); ok && err != nil { - if !strings.Contains(response.(error).Error(), subscriptions[0].Channel) { + if !strings.Contains(err.Error(), subscriptions[0].Channel) { t.Error("Expecting OKEX error - 30040 message: Channel badChannel doesn't exist") } } @@ -875,7 +875,10 @@ func TestOrderBookPartialChecksumCalculator(t *testing.T) { t.Error(err) } - calculatedChecksum := o.CalculatePartialOrderbookChecksum(&dataResponse) + calculatedChecksum, err := o.CalculatePartialOrderbookChecksum(&dataResponse) + if err != nil { + t.Fatal(err) + } if calculatedChecksum != dataResponse.Checksum { t.Errorf("Expected %v, received %v", dataResponse.Checksum, calculatedChecksum) } @@ -1089,21 +1092,21 @@ func TestWithdrawInternationalBank(t *testing.T) { func TestGetOrderbook(t *testing.T) { t.Parallel() _, err := o.GetOrderBook(context.Background(), - okgroup.GetOrderBookRequest{InstrumentID: "BTC-USDT"}, + &okgroup.GetOrderBookRequest{InstrumentID: "BTC-USDT"}, asset.Spot) if err != nil { t.Error(err) } _, err = o.GetOrderBook(context.Background(), - okgroup.GetOrderBookRequest{InstrumentID: "Payload"}, + &okgroup.GetOrderBookRequest{InstrumentID: "Payload"}, asset.Futures) if err == nil { t.Error("error cannot be nil") } _, err = o.GetOrderBook(context.Background(), - okgroup.GetOrderBookRequest{InstrumentID: "BTC-USD-SWAP"}, + &okgroup.GetOrderBookRequest{InstrumentID: "BTC-USD-SWAP"}, asset.PerpetualSwap) if err == nil { t.Error("error cannot be nil") diff --git a/exchanges/okcoin/okcoin_wrapper.go b/exchanges/okcoin/okcoin_wrapper.go index f689de5c..ee8e8384 100644 --- a/exchanges/okcoin/okcoin_wrapper.go +++ b/exchanges/okcoin/okcoin_wrapper.go @@ -258,11 +258,9 @@ func (o *OKCoin) FetchTradablePairs(ctx context.Context, asset asset.Item) ([]st return nil, err } - var pairs []string + pairs := make([]string, len(prods)) for x := range prods { - pairs = append(pairs, prods[x].BaseCurrency+ - format.Delimiter+ - prods[x].QuoteCurrency) + pairs[x] = prods[x].BaseCurrency + format.Delimiter + prods[x].QuoteCurrency } return pairs, nil diff --git a/exchanges/okex/okex_test.go b/exchanges/okex/okex_test.go index 7084abb7..5812d59b 100644 --- a/exchanges/okex/okex_test.go +++ b/exchanges/okex/okex_test.go @@ -1657,7 +1657,7 @@ func TestSendWsMessages(t *testing.T) { } response := <-o.Websocket.DataHandler if err, ok = response.(error); ok && err != nil { - if !strings.Contains(response.(error).Error(), subscriptions[0].Channel) { + if !strings.Contains(err.Error(), subscriptions[0].Channel) { t.Error("Expecting OKEX error - 30040 message: Channel badChannel doesn't exist") } } @@ -1739,7 +1739,10 @@ func TestOrderBookPartialChecksumCalculator(t *testing.T) { t.Error(err) } - calculatedChecksum := o.CalculatePartialOrderbookChecksum(&dataResponse) + calculatedChecksum, err := o.CalculatePartialOrderbookChecksum(&dataResponse) + if err != nil { + t.Fatal(err) + } if calculatedChecksum != dataResponse.Checksum { t.Errorf("Expected %v, received %v", dataResponse.Checksum, calculatedChecksum) } @@ -1967,21 +1970,21 @@ func TestWithdrawInternationalBank(t *testing.T) { func TestGetOrderbook(t *testing.T) { t.Parallel() _, err := o.GetOrderBook(context.Background(), - okgroup.GetOrderBookRequest{InstrumentID: "BTC-USDT"}, + &okgroup.GetOrderBookRequest{InstrumentID: "BTC-USDT"}, asset.Spot) if err != nil { t.Error(err) } contract := getFutureInstrumentID() _, err = o.GetOrderBook(context.Background(), - okgroup.GetOrderBookRequest{InstrumentID: contract}, + &okgroup.GetOrderBookRequest{InstrumentID: contract}, asset.Futures) if err != nil { t.Error(err) } _, err = o.GetOrderBook(context.Background(), - okgroup.GetOrderBookRequest{InstrumentID: "BTC-USD-SWAP"}, + &okgroup.GetOrderBookRequest{InstrumentID: "BTC-USD-SWAP"}, asset.PerpetualSwap) if err != nil { t.Error(err) diff --git a/exchanges/okgroup/okgroup.go b/exchanges/okgroup/okgroup.go index 337dd163..57459bdc 100644 --- a/exchanges/okgroup/okgroup.go +++ b/exchanges/okgroup/okgroup.go @@ -314,7 +314,7 @@ func (o *OKGroup) GetSpotTokenPairDetails(ctx context.Context) (resp []GetSpotTo // GetOrderBook Getting the order book of a trading pair. Pagination is not // supported here. The whole book will be returned for one request. Websocket is // recommended here. -func (o *OKGroup) GetOrderBook(ctx context.Context, request GetOrderBookRequest, a asset.Item) (resp GetOrderBookResponse, _ error) { +func (o *OKGroup) GetOrderBook(ctx context.Context, request *GetOrderBookRequest, a asset.Item) (resp *GetOrderBookResponse, _ error) { var requestType, endpoint string switch a { case asset.Spot: @@ -535,8 +535,7 @@ func FormatParameters(request interface{}) (parameters string) { log.Errorf(log.ExchangeSys, "Could not parse %v to URL values. Check that the type has url fields", reflect.TypeOf(request).Name()) return } - urlEncodedValues := v.Encode() - if len(urlEncodedValues) > 0 { + if urlEncodedValues := v.Encode(); len(urlEncodedValues) > 0 { parameters = fmt.Sprintf("?%v", urlEncodedValues) } return diff --git a/exchanges/okgroup/okgroup_websocket.go b/exchanges/okgroup/okgroup_websocket.go index 238b9ebd..bb0e908b 100644 --- a/exchanges/okgroup/okgroup_websocket.go +++ b/exchanges/okgroup/okgroup_websocket.go @@ -461,7 +461,7 @@ func (o *OKGroup) wsProcessTrades(respRaw []byte) error { } a := o.GetAssetTypeFromTableName(response.Table) - var trades []trade.Data + trades := make([]trade.Data, len(response.Data)) for i := range response.Data { f := strings.Split(response.Data[i].InstrumentID, delimiterDash) @@ -487,7 +487,7 @@ func (o *OKGroup) wsProcessTrades(respRaw []byte) error { if response.Data[i].Quantity != 0 { amount = response.Data[i].Quantity } - trades = append(trades, trade.Data{ + trades[i] = trade.Data{ Amount: amount, AssetType: o.GetAssetTypeFromTableName(response.Table), CurrencyPair: c, @@ -496,7 +496,7 @@ func (o *OKGroup) wsProcessTrades(respRaw []byte) error { Side: tSide, Timestamp: response.Data[i].Timestamp, TID: response.Data[i].TradeID, - }) + } } return trade.AddTradesToBuffer(o.Name, trades...) } @@ -644,7 +644,7 @@ func (o *OKGroup) wsResubscribeToOrderbook(response *WebsocketOrderBooksData) er // AppendWsOrderbookItems adds websocket orderbook data bid/asks into an // orderbook item array func (o *OKGroup) AppendWsOrderbookItems(entries [][]interface{}) ([]orderbook.Item, error) { - var items []orderbook.Item + items := make([]orderbook.Item, len(entries)) for j := range entries { amount, err := strconv.ParseFloat(entries[j][1].(string), 64) if err != nil { @@ -654,7 +654,7 @@ func (o *OKGroup) AppendWsOrderbookItems(entries [][]interface{}) ([]orderbook.I if err != nil { return nil, err } - items = append(items, orderbook.Item{Amount: amount, Price: price}) + items[j] = orderbook.Item{Amount: amount, Price: price} } return items, nil } @@ -662,7 +662,13 @@ func (o *OKGroup) AppendWsOrderbookItems(entries [][]interface{}) ([]orderbook.I // WsProcessPartialOrderBook takes websocket orderbook data and creates an // orderbook Calculates checksum to ensure it is valid func (o *OKGroup) WsProcessPartialOrderBook(wsEventData *WebsocketOrderBook, instrument currency.Pair, a asset.Item) error { - signedChecksum := o.CalculatePartialOrderbookChecksum(wsEventData) + signedChecksum, err := o.CalculatePartialOrderbookChecksum(wsEventData) + if err != nil { + return fmt.Errorf("%s channel: %s. Orderbook unable to calculate partial orderbook checksum: %s", + o.Name, + a, + err) + } if signedChecksum != wsEventData.Checksum { return fmt.Errorf("%s channel: %s. Orderbook partial for %v checksum invalid", o.Name, @@ -744,24 +750,40 @@ func (o *OKGroup) WsProcessUpdateOrderbook(wsEventData *WebsocketOrderBook, inst // quantity with a semicolon (:) deliminating them. This will also work when // there are less than 25 entries (for whatever reason) // eg Bid:Ask:Bid:Ask:Ask:Ask -func (o *OKGroup) CalculatePartialOrderbookChecksum(orderbookData *WebsocketOrderBook) int32 { +func (o *OKGroup) CalculatePartialOrderbookChecksum(orderbookData *WebsocketOrderBook) (int32, error) { var checksum strings.Builder for i := 0; i < allowableIterations; i++ { if len(orderbookData.Bids)-1 >= i { - checksum.WriteString(orderbookData.Bids[i][0].(string) + + bidPrice, ok := orderbookData.Bids[i][0].(string) + if !ok { + return 0, fmt.Errorf("unable to type assert bidPrice") + } + bidAmount, ok := orderbookData.Bids[i][1].(string) + if !ok { + return 0, fmt.Errorf("unable to type assert bidAmount") + } + checksum.WriteString(bidPrice + delimiterColon + - orderbookData.Bids[i][1].(string) + + bidAmount + delimiterColon) } if len(orderbookData.Asks)-1 >= i { - checksum.WriteString(orderbookData.Asks[i][0].(string) + + askPrice, ok := orderbookData.Asks[i][0].(string) + if !ok { + return 0, fmt.Errorf("unable to type assert askPrice") + } + askAmount, ok := orderbookData.Asks[i][1].(string) + if !ok { + return 0, fmt.Errorf("unable to type assert askAmount") + } + checksum.WriteString(askPrice + delimiterColon + - orderbookData.Asks[i][1].(string) + + askAmount + delimiterColon) } } checksumStr := strings.TrimSuffix(checksum.String(), delimiterColon) - return int32(crc32.ChecksumIEEE([]byte(checksumStr))) + return int32(crc32.ChecksumIEEE([]byte(checksumStr))), nil } // CalculateUpdateOrderbookChecksum alternates over the first 25 bid and ask diff --git a/exchanges/okgroup/okgroup_wrapper.go b/exchanges/okgroup/okgroup_wrapper.go index bfbfb6e5..3b82f199 100644 --- a/exchanges/okgroup/okgroup_wrapper.go +++ b/exchanges/okgroup/okgroup_wrapper.go @@ -99,11 +99,11 @@ func (o *OKGroup) UpdateOrderbook(ctx context.Context, p currency.Pair, a asset. fPair, err := o.FormatExchangeCurrency(p, a) if err != nil { - return nil, err + return book, err } orderbookNew, err := o.GetOrderBook(ctx, - GetOrderBookRequest{ + &GetOrderBookRequest{ InstrumentID: fPair.String(), Size: 200, }, a) @@ -111,6 +111,7 @@ func (o *OKGroup) UpdateOrderbook(ctx context.Context, p currency.Pair, a asset. return book, err } + book.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { amount, convErr := strconv.ParseFloat(orderbookNew.Bids[x][1], 64) if convErr != nil { @@ -135,14 +136,15 @@ func (o *OKGroup) UpdateOrderbook(ctx context.Context, p currency.Pair, a asset. } } - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[x] = orderbook.Item{ Amount: amount, Price: price, LiquidationOrders: liquidationOrders, OrderCount: orderCount, - }) + } } + book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { amount, convErr := strconv.ParseFloat(orderbookNew.Asks[x][1], 64) if convErr != nil { @@ -167,12 +169,12 @@ func (o *OKGroup) UpdateOrderbook(ctx context.Context, p currency.Pair, a asset. } } - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[x] = orderbook.Item{ Amount: amount, Price: price, LiquidationOrders: liquidationOrders, OrderCount: orderCount, - }) + } } err = book.Process() @@ -359,7 +361,7 @@ func (o *OKGroup) CancelAllOrders(ctx context.Context, orderCancellation *order. orderIDs := strings.Split(orderCancellation.ID, ",") resp := order.CancelAllResponse{} resp.Status = make(map[string]string) - var orderIDNumbers []int64 + orderIDNumbers := make([]int64, 0, len(orderIDs)) for i := range orderIDs { orderIDNumber, err := strconv.ParseInt(orderIDs[i], 10, 64) if err != nil { diff --git a/exchanges/order/futures.go b/exchanges/order/futures.go index d7b7589a..b150ce5f 100644 --- a/exchanges/order/futures.go +++ b/exchanges/order/futures.go @@ -214,9 +214,9 @@ func (e *MultiPositionTracker) GetPositions() []PositionStats { } e.m.Lock() defer e.m.Unlock() - var resp []PositionStats + resp := make([]PositionStats, len(e.positions)) for i := range e.positions { - resp = append(resp, e.positions[i].GetStats()) + resp[i] = e.positions[i].GetStats() } return resp } diff --git a/exchanges/order/limits.go b/exchanges/order/limits.go index 4c35203d..74573a6a 100644 --- a/exchanges/order/limits.go +++ b/exchanges/order/limits.go @@ -404,7 +404,5 @@ func (l *Limits) ConformToAmount(amount float64) float64 { // derive modulus mod := dAmount.Mod(dStep) // subtract modulus to get the floor - rVal := dAmount.Sub(mod) - fVal, _ := rVal.Float64() - return fVal + return dAmount.Sub(mod).InexactFloat64() } diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index faae18ea..2fd1420d 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -483,7 +483,7 @@ func (t Type) Lower() string { // Title returns the type titleized, eg "Limit" func (t Type) Title() string { - return strings.Title(strings.ToLower(string(t))) + return strings.Title(strings.ToLower(string(t))) // nolint:staticcheck // Ignore Title usage warning } // String implements the stringer interface @@ -498,7 +498,7 @@ func (s Side) Lower() string { // Title returns the side titleized, eg "Buy" func (s Side) Title() string { - return strings.Title(strings.ToLower(string(s))) + return strings.Title(strings.ToLower(string(s))) // nolint:staticcheck // Ignore Title usage warning } // IsShort returns if the side is short diff --git a/exchanges/orderbook/calculator.go b/exchanges/orderbook/calculator.go index e763d31b..b04b9cda 100644 --- a/exchanges/orderbook/calculator.go +++ b/exchanges/orderbook/calculator.go @@ -139,7 +139,7 @@ func sortOrdersByPrice(o *orderSummary, reverse bool) { } func (b *Base) findAmount(price float64, buy bool) (float64, orderSummary) { - var orders orderSummary + orders := make(orderSummary, 0) var amt float64 if buy { diff --git a/exchanges/orderbook/depth_test.go b/exchanges/orderbook/depth_test.go index 6650929a..0db7d199 100644 --- a/exchanges/orderbook/depth_test.go +++ b/exchanges/orderbook/depth_test.go @@ -46,7 +46,7 @@ func TestRetrieve(t *testing.T) { pair: currency.NewPair(currency.THETA, currency.USD), asset: "Silly asset", lastUpdated: time.Now(), - lastUpdateID: 007, + lastUpdateID: 1337, priceDuplication: true, isFundingRate: true, VerifyOrderbook: true, diff --git a/exchanges/orderbook/linked_list_test.go b/exchanges/orderbook/linked_list_test.go index a632cd61..ad3eb160 100644 --- a/exchanges/orderbook/linked_list_test.go +++ b/exchanges/orderbook/linked_list_test.go @@ -1434,7 +1434,7 @@ func TestShiftBookmark(t *testing.T) { t.Fatal("nilBookmark not reassigned") } - head := bookmarkedNode // nolint // ifshort false positive + head := bookmarkedNode bookmarkedNode.Prev = nil bookmarkedNode.Next = originalBookmarkNext tip.Next = nil diff --git a/exchanges/orderbook/orderbook.go b/exchanges/orderbook/orderbook.go index 79eac8f7..3eede4e2 100644 --- a/exchanges/orderbook/orderbook.go +++ b/exchanges/orderbook/orderbook.go @@ -33,8 +33,8 @@ func DeployDepth(exchange string, p currency.Pair, a asset.Item) (*Depth, error) // SubscribeToExchangeOrderbooks returns a pipe to an exchange feed func SubscribeToExchangeOrderbooks(exchange string) (dispatch.Pipe, error) { - service.Lock() - defer service.Unlock() + service.mu.Lock() + defer service.mu.Unlock() exch, ok := service.books[strings.ToLower(exchange)] if !ok { return dispatch.Pipe{}, fmt.Errorf("%w for %s exchange", @@ -46,12 +46,12 @@ func SubscribeToExchangeOrderbooks(exchange string) (dispatch.Pipe, error) { // Update stores orderbook data func (s *Service) Update(b *Base) error { name := strings.ToLower(b.Exchange) - s.Lock() + s.mu.Lock() m1, ok := s.books[name] if !ok { id, err := s.Mux.GetID() if err != nil { - s.Unlock() + s.mu.Unlock() return err } m1 = Exchange{ @@ -80,7 +80,7 @@ func (s *Service) Update(b *Base) error { m3[b.Pair.Quote.Item] = book } book.LoadSnapshot(b.Bids, b.Asks, b.LastUpdateID, b.LastUpdated, true) - s.Unlock() + s.mu.Unlock() return s.Mux.Publish([]uuid.UUID{m1.ID}, book.Retrieve()) } @@ -96,8 +96,8 @@ func (s *Service) DeployDepth(exchange string, p currency.Pair, a asset.Item) (* if !a.IsValid() { return nil, errAssetTypeNotSet } - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() m1, ok := s.books[strings.ToLower(exchange)] if !ok { id, err := s.Mux.GetID() @@ -134,8 +134,8 @@ func (s *Service) DeployDepth(exchange string, p currency.Pair, a asset.Item) (* // GetDepth returns the actual depth struct for potential subsystems and // strategies to interact with func (s *Service) GetDepth(exchange string, p currency.Pair, a asset.Item) (*Depth, error) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() m1, ok := s.books[strings.ToLower(exchange)] if !ok { return nil, fmt.Errorf("%w for %s exchange", @@ -168,8 +168,8 @@ func (s *Service) GetDepth(exchange string, p currency.Pair, a asset.Item) (*Dep // Retrieve gets orderbook depth data from the associated linked list and // returns the base equivalent copy func (s *Service) Retrieve(exchange string, p currency.Pair, a asset.Item) (*Base, error) { - s.Lock() - defer s.Unlock() + s.mu.Lock() + defer s.mu.Unlock() m1, ok := s.books[strings.ToLower(exchange)] if !ok { return nil, fmt.Errorf("%w for %s exchange", diff --git a/exchanges/orderbook/orderbook_types.go b/exchanges/orderbook/orderbook_types.go index ee80a28c..639b09a4 100644 --- a/exchanges/orderbook/orderbook_types.go +++ b/exchanges/orderbook/orderbook_types.go @@ -43,7 +43,7 @@ var service = Service{ type Service struct { books map[string]Exchange *dispatch.Mux - sync.Mutex + mu sync.Mutex } // Exchange defines a holder for the exchange specific depth items with a diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index 6fc92e0b..d5c2f947 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -99,16 +99,23 @@ func (p *Poloniex) GetOrderbook(ctx context.Context, currencyPair string, depth if resp.Error != "" { return oba, fmt.Errorf("%s GetOrderbook() error: %s", p.Name, resp.Error) } - var ob Orderbook + ob := Orderbook{ + Bids: make([]OrderbookItem, len(resp.Bids)), + Asks: make([]OrderbookItem, len(resp.Asks)), + } for x := range resp.Asks { price, err := strconv.ParseFloat(resp.Asks[x][0].(string), 64) if err != nil { return oba, err } - ob.Asks = append(ob.Asks, OrderbookItem{ + amt, ok := resp.Asks[x][1].(float64) + if !ok { + return oba, errors.New("unable to type assert amount") + } + ob.Asks[x] = OrderbookItem{ Price: price, - Amount: resp.Asks[x][1].(float64), - }) + Amount: amt, + } } for x := range resp.Bids { @@ -116,10 +123,14 @@ func (p *Poloniex) GetOrderbook(ctx context.Context, currencyPair string, depth if err != nil { return oba, err } - ob.Bids = append(ob.Bids, OrderbookItem{ + amt, ok := resp.Bids[x][1].(float64) + if !ok { + return oba, errors.New("unable to type assert amount") + } + ob.Bids[x] = OrderbookItem{ Price: price, - Amount: resp.Bids[x][1].(float64), - }) + Amount: amt, + } } oba.Data[currencyPair] = ob } else { @@ -131,27 +142,37 @@ func (p *Poloniex) GetOrderbook(ctx context.Context, currencyPair string, depth return oba, err } for currency, orderbook := range resp.Data { - var ob Orderbook + ob := Orderbook{ + Bids: make([]OrderbookItem, len(orderbook.Bids)), + Asks: make([]OrderbookItem, len(orderbook.Asks)), + } for x := range orderbook.Asks { price, err := strconv.ParseFloat(orderbook.Asks[x][0].(string), 64) if err != nil { return oba, err } - ob.Asks = append(ob.Asks, OrderbookItem{ + amt, ok := orderbook.Asks[x][1].(float64) + if !ok { + return oba, errors.New("unable to type assert amount") + } + ob.Asks[x] = OrderbookItem{ Price: price, - Amount: orderbook.Asks[x][1].(float64), - }) + Amount: amt, + } } - for x := range orderbook.Bids { price, err := strconv.ParseFloat(orderbook.Bids[x][0].(string), 64) if err != nil { return oba, err } - ob.Bids = append(ob.Bids, OrderbookItem{ + amt, ok := orderbook.Bids[x][1].(float64) + if !ok { + return oba, errors.New("unable to type assert amount") + } + ob.Bids[x] = OrderbookItem{ Price: price, - Amount: orderbook.Bids[x][1].(float64), - }) + Amount: amt, + } } oba.Data[currency] = ob } @@ -440,7 +461,7 @@ func (p *Poloniex) GetAuthenticatedOrderStatus(ctx context.Context, orderID stri if err != nil { return o, err } - return o, fmt.Errorf(errMsg.Error) + return o, errors.New(errMsg.Error) case 1: // success var status map[string]OrderStatusData err = json.Unmarshal(rawOrderStatus.Result, &status) @@ -483,7 +504,7 @@ func (p *Poloniex) GetAuthenticatedOrderTrades(ctx context.Context, orderID stri return nil, err } if resp.Error != "" { - err = fmt.Errorf(resp.Error) + err = errors.New(resp.Error) } case '[': // data received err = json.Unmarshal(result, &o) diff --git a/exchanges/poloniex/poloniex_websocket.go b/exchanges/poloniex/poloniex_websocket.go index c96f123d..693b1ced 100644 --- a/exchanges/poloniex/poloniex_websocket.go +++ b/exchanges/poloniex/poloniex_websocket.go @@ -421,6 +421,7 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(data []interface{}) error { } var book orderbook.Base + book.Asks = make(orderbook.Items, 0, len(askData)) for price, volume := range askData { p, err := strconv.ParseFloat(price, 64) if err != nil { @@ -438,6 +439,7 @@ func (p *Poloniex) WsProcessOrderbookSnapshot(data []interface{}) error { book.Asks = append(book.Asks, orderbook.Item{Price: p, Amount: a}) } + book.Bids = make(orderbook.Items, 0, len(bidData)) for price, volume := range bidData { p, err := strconv.ParseFloat(price, 64) if err != nil { @@ -512,7 +514,12 @@ func (p *Poloniex) WsProcessOrderbookUpdate(sequenceNumber float64, data []inter // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (p *Poloniex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { - var subscriptions []stream.ChannelSubscription + enabledCurrencies, err := p.GetEnabledPairs(asset.Spot) + if err != nil { + return nil, err + } + + subscriptions := make([]stream.ChannelSubscription, 0, len(enabledCurrencies)) subscriptions = append(subscriptions, stream.ChannelSubscription{ Channel: strconv.FormatInt(wsTickerDataID, 10), }) @@ -523,10 +530,6 @@ func (p *Poloniex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, }) } - enabledCurrencies, err := p.GetEnabledPairs(asset.Spot) - if err != nil { - return nil, err - } for j := range enabledCurrencies { enabledCurrencies[j].Delimiter = currency.UnderscoreDelimiter subscriptions = append(subscriptions, stream.ChannelSubscription{ @@ -540,9 +543,13 @@ func (p *Poloniex) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, // Subscribe sends a websocket message to receive data from the channel func (p *Poloniex) Subscribe(sub []stream.ChannelSubscription) error { - creds, err := p.GetCredentials(context.TODO()) - if err != nil { - return err + var creds *exchange.Credentials + if p.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + var err error + creds, err = p.GetCredentials(context.TODO()) + if err != nil { + return err + } } var errs common.Errors channels: @@ -552,7 +559,7 @@ channels: } switch { case strings.EqualFold(strconv.FormatInt(wsAccountNotificationID, 10), - sub[i].Channel): + sub[i].Channel) && creds != nil: err := p.wsSendAuthorisedCommand(creds.Secret, creds.Key, "subscribe") if err != nil { errs = append(errs, err) @@ -583,9 +590,13 @@ channels: // Unsubscribe sends a websocket message to stop receiving data from the channel func (p *Poloniex) Unsubscribe(unsub []stream.ChannelSubscription) error { - creds, err := p.GetCredentials(context.TODO()) - if err != nil { - return err + var creds *exchange.Credentials + if p.GetAuthenticatedAPISupport(exchange.WebsocketAuthentication) { + var err error + creds, err = p.GetCredentials(context.TODO()) + if err != nil { + return err + } } var errs common.Errors channels: @@ -595,7 +606,7 @@ channels: } switch { case strings.EqualFold(strconv.FormatInt(wsAccountNotificationID, 10), - unsub[i].Channel): + unsub[i].Channel) && creds != nil: err := p.wsSendAuthorisedCommand(creds.Secret, creds.Key, "unsubscribe") if err != nil { errs = append(errs, err) diff --git a/exchanges/poloniex/poloniex_wrapper.go b/exchanges/poloniex/poloniex_wrapper.go index 91dc8bf8..3dd65b6d 100644 --- a/exchanges/poloniex/poloniex_wrapper.go +++ b/exchanges/poloniex/poloniex_wrapper.go @@ -260,7 +260,7 @@ func (p *Poloniex) FetchTradablePairs(ctx context.Context, asset asset.Item) ([] return nil, err } - var currencies []string + currencies := make([]string, 0, len(resp)) for x := range resp { currencies = append(currencies, x) } @@ -381,18 +381,20 @@ func (p *Poloniex) UpdateOrderbook(ctx context.Context, c currency.Pair, assetTy continue } + book.Bids = make(orderbook.Items, len(data.Bids)) for y := range data.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[y] = orderbook.Item{ Amount: data.Bids[y].Amount, Price: data.Bids[y].Price, - }) + } } + book.Asks = make(orderbook.Items, len(data.Asks)) for y := range data.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[y] = orderbook.Item{ Amount: data.Asks[y].Amount, Price: data.Asks[y].Price, - }) + } } err = book.Process() if err != nil { @@ -412,7 +414,7 @@ func (p *Poloniex) UpdateAccountInfo(ctx context.Context, assetType asset.Item) return response, err } - var currencies []account.Balance + currencies := make([]account.Balance, 0, len(accountBalance.Currency)) for x, y := range accountBalance.Currency { currencies = append(currencies, account.Balance{ CurrencyName: currency.NewCode(x), diff --git a/exchanges/request/request.go b/exchanges/request/request.go index 99b678cb..be16850a 100644 --- a/exchanges/request/request.go +++ b/exchanges/request/request.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "io/ioutil" "net/http" "net/http/httputil" "net/url" @@ -207,7 +206,7 @@ func (r *Requester) doRequest(ctx context.Context, endpoint EndpointLimit, newRe continue } - contents, err := ioutil.ReadAll(resp.Body) + contents, err := io.ReadAll(resp.Body) if err != nil { return err } @@ -273,7 +272,7 @@ func (r *Requester) doRequest(ctx context.Context, endpoint EndpointLimit, newRe } func (r *Requester) drainBody(body io.ReadCloser) { - if _, err := io.Copy(ioutil.Discard, io.LimitReader(body, drainBodyLimit)); err != nil { + if _, err := io.Copy(io.Discard, io.LimitReader(body, drainBodyLimit)); err != nil { log.Errorf(log.RequestSys, "%s failed to drain request body %s", r.name, @@ -335,7 +334,7 @@ func (r *Requester) SetHTTPClient(newClient *http.Client) error { return nil } -// SetClientTimeout sets the timeout value for the exchanges HTTP Client and +// SetHTTPClientTimeout sets the timeout value for the exchanges HTTP Client and // also the underlying transports idle connection timeout func (r *Requester) SetHTTPClientTimeout(timeout time.Duration) error { if r == nil { diff --git a/exchanges/stream/stream_match.go b/exchanges/stream/stream_match.go index f5cb57ae..431bdba9 100644 --- a/exchanges/stream/stream_match.go +++ b/exchanges/stream/stream_match.go @@ -17,8 +17,8 @@ func NewMatch() *Match { // connections. Stream systems fan in all incoming payloads to one routine for // processing. type Match struct { - m map[interface{}]chan []byte - sync.Mutex + m map[interface{}]chan []byte + mu sync.Mutex } // Incoming matches with request, disregarding the returned payload @@ -29,8 +29,8 @@ func (m *Match) Incoming(signature interface{}) bool { // IncomingWithData matches with requests and takes in the returned payload, to // be processed outside of a stream processing routine func (m *Match) IncomingWithData(signature interface{}, data []byte) bool { - m.Lock() - defer m.Unlock() + m.mu.Lock() + defer m.mu.Unlock() ch, ok := m.m[signature] if ok { select { @@ -47,15 +47,15 @@ func (m *Match) IncomingWithData(signature interface{}, data []byte) bool { // Sets the signature response channel for incoming data func (m *Match) set(signature interface{}) (matcher, error) { var ch chan []byte - m.Lock() + m.mu.Lock() if _, ok := m.m[signature]; ok { - m.Unlock() + m.mu.Unlock() return matcher{}, errors.New("signature collision") } // This is buffered so we don't need to wait for receiver. ch = make(chan []byte, 1) m.m[signature] = ch - m.Unlock() + m.mu.Unlock() return matcher{ C: ch, @@ -73,8 +73,8 @@ type matcher struct { // Cleanup closes underlying channel and deletes signature from map func (m *matcher) Cleanup() { - m.m.Lock() + m.m.mu.Lock() close(m.C) delete(m.m.m, m.sig) - m.m.Unlock() + m.m.mu.Unlock() } diff --git a/exchanges/stream/websocket_connection.go b/exchanges/stream/websocket_connection.go index a720cbb7..65ae597a 100644 --- a/exchanges/stream/websocket_connection.go +++ b/exchanges/stream/websocket_connection.go @@ -6,7 +6,7 @@ import ( "compress/gzip" "crypto/rand" "fmt" - "io/ioutil" + "io" "math/big" "net" "net/http" @@ -156,7 +156,7 @@ func (w *WebsocketConnection) SetupPingHandler(handler PingHandler) { time.Now().Add(handler.Delay)) if err == websocket.ErrCloseSent { return nil - } else if e, ok := err.(net.Error); ok && e.Temporary() { + } else if e, ok := err.(net.Error); ok && e.Timeout() { return nil } return err @@ -260,7 +260,7 @@ func (w *WebsocketConnection) parseBinaryResponse(resp []byte) ([]byte, error) { if err != nil { return standardMessage, err } - standardMessage, err = ioutil.ReadAll(gReader) + standardMessage, err = io.ReadAll(gReader) if err != nil { return standardMessage, err } @@ -270,7 +270,7 @@ func (w *WebsocketConnection) parseBinaryResponse(resp []byte) ([]byte, error) { } } else { reader := flate.NewReader(bytes.NewReader(resp)) - standardMessage, err = ioutil.ReadAll(reader) + standardMessage, err = io.ReadAll(reader) if err != nil { return standardMessage, err } diff --git a/exchanges/stream/websocket_test.go b/exchanges/stream/websocket_test.go index dd0d0e70..89cb96ab 100644 --- a/exchanges/stream/websocket_test.go +++ b/exchanges/stream/websocket_test.go @@ -305,7 +305,10 @@ func TestConnectionMessageErrors(t *testing.T) { ws.ReadMessageErrors <- errors.New("errorText") select { case err := <-ws.ToRoutine: - if err.(error).Error() != "errorText" { + errText, ok := err.(error) + if !ok { + t.Error("unable to type assert error") + } else if errText.Error() != "errorText" { t.Errorf("Expected 'errorText', received %v", err) } case <-timer.C: @@ -1045,12 +1048,12 @@ type GenSubs struct { // generateSubs default subs created from the enabled pairs list func (g *GenSubs) generateSubs() ([]ChannelSubscription, error) { - var superduperchannelsubs []ChannelSubscription + superduperchannelsubs := make([]ChannelSubscription, len(g.EnabledPairs)) for i := range g.EnabledPairs { - superduperchannelsubs = append(superduperchannelsubs, ChannelSubscription{ + superduperchannelsubs[i] = ChannelSubscription{ Channel: "TEST:" + strconv.FormatInt(int64(i), 10), Currency: g.EnabledPairs[i], - }) + } } return superduperchannelsubs, nil } diff --git a/exchanges/ticker/ticker.go b/exchanges/ticker/ticker.go index 0a437707..7057f5b0 100644 --- a/exchanges/ticker/ticker.go +++ b/exchanges/ticker/ticker.go @@ -29,8 +29,8 @@ func init() { // stream new ticker updates func SubscribeTicker(exchange string, p currency.Pair, a asset.Item) (dispatch.Pipe, error) { exchange = strings.ToLower(exchange) - service.Lock() - defer service.Unlock() + service.mu.Lock() + defer service.mu.Unlock() tick, ok := service.Tickers[exchange][p.Base.Item][p.Quote.Item][a] if !ok { @@ -45,8 +45,8 @@ func SubscribeTicker(exchange string, p currency.Pair, a asset.Item) (dispatch.P // SubscribeToExchangeTickers subcribes to all tickers on an exchange func SubscribeToExchangeTickers(exchange string) (dispatch.Pipe, error) { exchange = strings.ToLower(exchange) - service.Lock() - defer service.Unlock() + service.mu.Lock() + defer service.mu.Unlock() id, ok := service.Exchange[exchange] if !ok { return dispatch.Pipe{}, fmt.Errorf("%s exchange tickers not found", @@ -59,8 +59,8 @@ func SubscribeToExchangeTickers(exchange string) (dispatch.Pipe, error) { // GetTicker checks and returns a requested ticker if it exists func GetTicker(exchange string, p currency.Pair, a asset.Item) (*Price, error) { exchange = strings.ToLower(exchange) - service.Lock() - defer service.Unlock() + service.mu.Lock() + defer service.mu.Unlock() m1, ok := service.Tickers[exchange] if !ok { return nil, fmt.Errorf("no tickers for %s exchange", exchange) @@ -90,8 +90,8 @@ func GetTicker(exchange string, p currency.Pair, a asset.Item) (*Price, error) { // FindLast searches for a currency pair and returns the first available func FindLast(p currency.Pair, a asset.Item) (float64, error) { - service.Lock() - defer service.Unlock() + service.mu.Lock() + defer service.mu.Unlock() for _, m1 := range service.Tickers { m2, ok := m1[p.Base.Item] if !ok { @@ -146,7 +146,7 @@ func ProcessTicker(p *Price) error { // update updates ticker price func (s *Service) update(p *Price) error { name := strings.ToLower(p.ExchangeName) - s.Lock() + s.mu.Lock() m1, ok := service.Tickers[name] if !ok { @@ -171,18 +171,18 @@ func (s *Service) update(p *Price) error { newTicker := &Ticker{} err := s.setItemID(newTicker, p, name) if err != nil { - s.Unlock() + s.mu.Unlock() return err } m3[p.AssetType] = newTicker - s.Unlock() + s.mu.Unlock() return nil } t.Price = *p // nolint: gocritic ids := append(t.Assoc, t.Main) - s.Unlock() + s.mu.Unlock() return s.mux.Publish(ids, p) } diff --git a/exchanges/ticker/ticker_types.go b/exchanges/ticker/ticker_types.go index 78354727..4d094824 100644 --- a/exchanges/ticker/ticker_types.go +++ b/exchanges/ticker/ticker_types.go @@ -28,7 +28,7 @@ type Service struct { Tickers map[string]map[*currency.Item]map[*currency.Item]map[asset.Item]*Ticker Exchange map[string]uuid.UUID mux *dispatch.Mux - sync.Mutex + mu sync.Mutex } // Price struct stores the currency pair and pricing information diff --git a/exchanges/trade/trade.go b/exchanges/trade/trade.go index b5b4e049..86ed937a 100644 --- a/exchanges/trade/trade.go +++ b/exchanges/trade/trade.go @@ -72,7 +72,7 @@ func AddTradesToBuffer(exchangeName string, data ...Data) error { processor.setup(&wg) wg.Wait() } - var validDatas []Data + validDatas := make([]Data, 0, len(data)) for i := range data { if data[i].Price == 0 || data[i].Amount == 0 || @@ -179,13 +179,13 @@ func HasTradesInRanges(exchangeName, assetType, base, quote string, rangeHolder func tradeToSQLData(trades ...Data) ([]tradesql.Data, error) { sort.Sort(ByDate(trades)) - var results []tradesql.Data + results := make([]tradesql.Data, len(trades)) for i := range trades { tradeID, err := uuid.NewV4() if err != nil { return nil, err } - results = append(results, tradesql.Data{ + results[i] = tradesql.Data{ ID: tradeID.String(), Timestamp: trades[i].Timestamp, Exchange: trades[i].Exchange, @@ -196,7 +196,7 @@ func tradeToSQLData(trades ...Data) ([]tradesql.Data, error) { Amount: trades[i].Amount, Side: trades[i].Side.String(), TID: trades[i].TID, - }) + } } return results, nil } diff --git a/exchanges/yobit/yobit_wrapper.go b/exchanges/yobit/yobit_wrapper.go index f41c8f7a..63777e96 100644 --- a/exchanges/yobit/yobit_wrapper.go +++ b/exchanges/yobit/yobit_wrapper.go @@ -165,7 +165,7 @@ func (y *Yobit) FetchTradablePairs(ctx context.Context, asset asset.Item) ([]str return nil, err } - var currencies []string + currencies := make([]string, 0, len(info.Pairs)) for x := range info.Pairs { currencies = append(currencies, strings.ToUpper(x)) } @@ -307,7 +307,7 @@ func (y *Yobit) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (ac return response, err } - var currencies []account.Balance + currencies := make([]account.Balance, 0, len(accountBalance.FundsInclOrders)) for x, y := range accountBalance.FundsInclOrders { var exchangeCurrency account.Balance exchangeCurrency.CurrencyName = currency.NewCode(x) @@ -363,19 +363,21 @@ func (y *Yobit) GetRecentTrades(ctx context.Context, p currency.Pair, assetType if err != nil { return nil, err } - var resp []trade.Data + var tradeData []Trade tradeData, err = y.GetTrades(ctx, p.String()) if err != nil { return nil, err } + + resp := make([]trade.Data, len(tradeData)) for i := range tradeData { tradeTS := time.Unix(tradeData[i].Timestamp, 0) side := order.Buy if tradeData[i].Type == "ask" { side = order.Sell } - resp = append(resp, trade.Data{ + resp[i] = trade.Data{ Exchange: y.Name, TID: strconv.FormatInt(tradeData[i].TID, 10), CurrencyPair: p, @@ -384,7 +386,7 @@ func (y *Yobit) GetRecentTrades(ctx context.Context, p currency.Pair, assetType Price: tradeData[i].Price, Amount: tradeData[i].Amount, Timestamp: tradeTS, - }) + } } err = y.AddTradesToBuffer(resp...) @@ -465,11 +467,12 @@ func (y *Yobit) CancelAllOrders(ctx context.Context, _ *order.Cancel) (order.Can Status: make(map[string]string), } - var allActiveOrders []map[string]ActiveOrders enabledPairs, err := y.GetEnabledPairs(asset.Spot) if err != nil { return cancelAllOrdersResponse, err } + + allActiveOrders := make([]map[string]ActiveOrders, len(enabledPairs)) for i := range enabledPairs { fCurr, err := y.FormatExchangeCurrency(enabledPairs[i], asset.Spot) if err != nil { @@ -480,7 +483,7 @@ func (y *Yobit) CancelAllOrders(ctx context.Context, _ *order.Cancel) (order.Can return cancelAllOrdersResponse, err } - allActiveOrders = append(allActiveOrders, activeOrdersForPair) + allActiveOrders[i] = activeOrdersForPair } for i := range allActiveOrders { @@ -648,7 +651,7 @@ func (y *Yobit) GetOrderHistory(ctx context.Context, req *order.GetOrdersRequest return nil, err } - var orders []order.Detail + orders := make([]order.Detail, len(allOrders)) for i := range allOrders { var pair currency.Pair pair, err = currency.NewPairDelimiter(allOrders[i].Pair, format.Delimiter) @@ -670,7 +673,7 @@ func (y *Yobit) GetOrderHistory(ctx context.Context, req *order.GetOrdersRequest Exchange: y.Name, } detail.InferCostsAndTimes() - orders = append(orders, detail) + orders[i] = detail } order.FilterOrdersBySide(&orders, req.Side) diff --git a/exchanges/zb/zb.go b/exchanges/zb/zb.go index e35760ff..65acb752 100644 --- a/exchanges/zb/zb.go +++ b/exchanges/zb/zb.go @@ -209,31 +209,31 @@ func (z *ZB) GetTickers(ctx context.Context) (map[string]TickerChildResponse, er } // GetOrderbook returns the orderbook for a given symbol -func (z *ZB) GetOrderbook(ctx context.Context, symbol string) (OrderbookResponse, error) { +func (z *ZB) GetOrderbook(ctx context.Context, symbol string) (*OrderbookResponse, error) { urlPath := fmt.Sprintf("/%s/%s/%s?market=%s", zbData, zbAPIVersion, zbDepth, symbol) var res OrderbookResponse err := z.SendHTTPRequest(ctx, exchange.RestSpot, urlPath, &res, request.UnAuth) if err != nil { - return res, err + return nil, err } if len(res.Asks) == 0 { - return res, fmt.Errorf("ZB GetOrderbook asks is empty") + return nil, errors.New("ZB GetOrderbook asks is empty") } if len(res.Bids) == 0 { - return res, fmt.Errorf("ZB GetOrderbook bids is empty") + return nil, errors.New("ZB GetOrderbook bids is empty") } // reverse asks data - var data [][]float64 - for x := len(res.Asks); x > 0; x-- { - data = append(data, res.Asks[x-1]) + eLen := len(res.Asks) + var target int + for i := eLen/2 - 1; i >= 0; i-- { + target = eLen - 1 - i + (res.Asks)[i], (res.Asks)[target] = (res.Asks)[target], (res.Asks)[i] } - - res.Asks = data - return res, nil + return &res, nil } // GetSpotKline returns Kline data diff --git a/exchanges/zb/zb_test.go b/exchanges/zb/zb_test.go index 57be32b0..2314d5c6 100644 --- a/exchanges/zb/zb_test.go +++ b/exchanges/zb/zb_test.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "errors" - "fmt" "net/http" "strconv" "sync" @@ -288,8 +287,7 @@ func TestGetOrderHistory(t *testing.T) { func TestSubmitOrder(t *testing.T) { if areTestAPIKeysSet() && !canManipulateRealOrders { - t.Skip(fmt.Sprintf("Can place orders: %v", - canManipulateRealOrders)) + t.Skipf("Can place orders: %v", canManipulateRealOrders) } if mockTests { t.Skip("skipping authenticated function for mock testing") diff --git a/exchanges/zb/zb_types.go b/exchanges/zb/zb_types.go index a5c1a91a..11dc2590 100644 --- a/exchanges/zb/zb_types.go +++ b/exchanges/zb/zb_types.go @@ -9,9 +9,9 @@ import ( // OrderbookResponse holds the orderbook data for a symbol type OrderbookResponse struct { - Timestamp int64 `json:"timestamp"` - Asks [][]float64 `json:"asks"` - Bids [][]float64 `json:"bids"` + Timestamp int64 `json:"timestamp"` + Asks [][2]float64 `json:"asks"` + Bids [][2]float64 `json:"bids"` } // AccountsResponseCoin holds the accounts coin details diff --git a/exchanges/zb/zb_websocket.go b/exchanges/zb/zb_websocket.go index aeb3d8a2..98320638 100644 --- a/exchanges/zb/zb_websocket.go +++ b/exchanges/zb/zb_websocket.go @@ -121,32 +121,51 @@ func (z *ZB) wsHandleData(respRaw []byte) error { return err } - var book orderbook.Base - for i := range depth.Asks { - book.Asks = append(book.Asks, orderbook.Item{ - Amount: depth.Asks[i][1].(float64), - Price: depth.Asks[i][0].(float64), - }) - } - - for i := range depth.Bids { - book.Bids = append(book.Bids, orderbook.Item{ - Amount: depth.Bids[i][1].(float64), - Price: depth.Bids[i][0].(float64), - }) - } - channelInfo := strings.Split(result.Channel, currency.UnderscoreDelimiter) cPair, err := currency.NewPairFromString(channelInfo[0]) if err != nil { return err } + book := orderbook.Base{ + Bids: make(orderbook.Items, len(depth.Bids)), + Asks: make(orderbook.Items, len(depth.Asks)), + Asset: asset.Spot, + Pair: cPair, + Exchange: z.Name, + VerifyOrderbook: z.CanVerifyOrderbook, + } + + for i := range depth.Asks { + amt, ok := depth.Asks[i][1].(float64) + if !ok { + return errors.New("unable to type assert ask amount") + } + price, ok := depth.Asks[i][0].(float64) + if !ok { + return errors.New("unable to type assert ask price") + } + book.Asks[i] = orderbook.Item{ + Amount: amt, + Price: price, + } + } + for i := range depth.Bids { + amt, ok := depth.Bids[i][1].(float64) + if !ok { + return errors.New("unable to type assert bid amount") + } + price, ok := depth.Bids[i][0].(float64) + if !ok { + return errors.New("unable to type assert bid price") + } + book.Bids[i] = orderbook.Item{ + Amount: amt, + Price: price, + } + } + book.Asks.Reverse() // Reverse asks for correct alignment - book.Asset = asset.Spot - book.Pair = cPair - book.Exchange = z.Name - book.VerifyOrderbook = z.CanVerifyOrderbook err = z.Websocket.Orderbook.LoadSnapshot(&book) if err != nil { diff --git a/exchanges/zb/zb_wrapper.go b/exchanges/zb/zb_wrapper.go index 2325ab21..c7456a99 100644 --- a/exchanges/zb/zb_wrapper.go +++ b/exchanges/zb/zb_wrapper.go @@ -227,7 +227,7 @@ func (z *ZB) FetchTradablePairs(ctx context.Context, asset asset.Item) ([]string return nil, err } - var currencies []string + currencies := make([]string, 0, len(markets)) for x := range markets { currencies = append(currencies, x) } @@ -330,18 +330,20 @@ func (z *ZB) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType ass return book, err } + book.Bids = make(orderbook.Items, len(orderbookNew.Bids)) for x := range orderbookNew.Bids { - book.Bids = append(book.Bids, orderbook.Item{ + book.Bids[x] = orderbook.Item{ Amount: orderbookNew.Bids[x][1], Price: orderbookNew.Bids[x][0], - }) + } } + book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) for x := range orderbookNew.Asks { - book.Asks = append(book.Asks, orderbook.Item{ + book.Asks[x] = orderbook.Item{ Amount: orderbookNew.Asks[x][1], Price: orderbookNew.Asks[x][0], - }) + } } err = book.Process() if err != nil { @@ -354,7 +356,6 @@ func (z *ZB) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType ass // ZB exchange func (z *ZB) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) { var info account.Holdings - var balances []account.Balance var coins []AccountsResponseCoin if z.Websocket.CanUseAuthenticatedWebsocketForWrapper() { resp, err := z.wsGetAccountInfoRequest(ctx) @@ -370,6 +371,7 @@ func (z *ZB) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (accou coins = bal.Result.Coins } + balances := make([]account.Balance, len(coins)) for i := range coins { hold, err := strconv.ParseFloat(coins[i].Freeze, 64) if err != nil { @@ -381,12 +383,12 @@ func (z *ZB) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (accou return info, err } - balances = append(balances, account.Balance{ + balances[i] = account.Balance{ CurrencyName: currency.NewCode(coins[i].EnName), Total: hold + avail, Hold: hold, Free: avail, - }) + } } info.Exchange = z.Name @@ -434,7 +436,7 @@ func (z *ZB) GetRecentTrades(ctx context.Context, p currency.Pair, assetType ass if err != nil { return nil, err } - var resp []trade.Data + resp := make([]trade.Data, len(tradeData)) for i := range tradeData { var side order.Side side, err = order.StringToOrderSide(tradeData[i].Type) @@ -442,7 +444,7 @@ func (z *ZB) GetRecentTrades(ctx context.Context, p currency.Pair, assetType ass return nil, err } - resp = append(resp, trade.Data{ + resp[i] = trade.Data{ Exchange: z.Name, TID: strconv.FormatInt(tradeData[i].Tid, 10), CurrencyPair: p, @@ -451,7 +453,7 @@ func (z *ZB) GetRecentTrades(ctx context.Context, p currency.Pair, assetType ass Price: tradeData[i].Price, Amount: tradeData[i].Amount, Timestamp: time.Unix(tradeData[i].Date, 0), - }) + } } err = z.AddTradesToBuffer(resp...) @@ -741,7 +743,7 @@ func (z *ZB) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequest) ( return nil, err } - var orders []order.Detail + orders := make([]order.Detail, len(allOrders)) for i := range allOrders { var symbol currency.Pair symbol, err = currency.NewPairDelimiter(allOrders[i].Currency, @@ -751,7 +753,7 @@ func (z *ZB) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequest) ( } orderDate := time.Unix(int64(allOrders[i].TradeDate), 0) orderSide := orderSideMap[allOrders[i].Type] - orders = append(orders, order.Detail{ + orders[i] = order.Detail{ ID: strconv.FormatInt(allOrders[i].ID, 10), Amount: allOrders[i].TotalAmount, Exchange: z.Name, @@ -759,7 +761,7 @@ func (z *ZB) GetActiveOrders(ctx context.Context, req *order.GetOrdersRequest) ( Price: allOrders[i].Price, Side: orderSide, Pair: symbol, - }) + } } order.FilterOrdersByTimeRange(&orders, req.StartTime, req.EndTime) @@ -778,8 +780,8 @@ func (z *ZB) GetOrderHistory(ctx context.Context, req *order.GetOrdersRequest) ( if req.Side == order.AnySide || req.Side == "" { return nil, errors.New("specific order side is required") } + var allOrders []Order - var orders []order.Detail var side int64 if z.Websocket.CanUseAuthenticatedWebsocketForWrapper() { @@ -825,6 +827,7 @@ func (z *ZB) GetOrderHistory(ctx context.Context, req *order.GetOrdersRequest) ( return nil, err } + orders := make([]order.Detail, len(allOrders)) for i := range allOrders { var pair currency.Pair pair, err = currency.NewPairDelimiter(allOrders[i].Currency, @@ -847,7 +850,7 @@ func (z *ZB) GetOrderHistory(ctx context.Context, req *order.GetOrdersRequest) ( Pair: pair, } detail.InferCostsAndTimes() - orders = append(orders, detail) + orders[i] = detail } order.FilterOrdersByTimeRange(&orders, req.StartTime, req.EndTime) @@ -1008,9 +1011,9 @@ func (z *ZB) GetAvailableTransferChains(ctx context.Context, cryptocurrency curr return nil, err } - var availableChains []string + availableChains := make([]string, len(chains)) for x := range chains { - availableChains = append(availableChains, chains[x].Blockchain) + availableChains[x] = chains[x].Blockchain } return availableChains, nil } diff --git a/gctscript/modules/gct/gct.go b/gctscript/modules/gct/gct.go index 6a2cd5a5..ceabfced 100644 --- a/gctscript/modules/gct/gct.go +++ b/gctscript/modules/gct/gct.go @@ -2,7 +2,7 @@ package gct // AllModuleNames returns a list of all default module names. func AllModuleNames() []string { - var names []string + names := make([]string, 0, len(Modules)) for name := range Modules { names = append(names, name) } diff --git a/gctscript/modules/ta/indicators/ema.go b/gctscript/modules/ta/indicators/ema.go index 9c1bb252..73af7ac8 100644 --- a/gctscript/modules/ta/indicators/ema.go +++ b/gctscript/modules/ta/indicators/ema.go @@ -46,7 +46,7 @@ func ema(args ...objects.Object) (objects.Object, error) { return nil, fmt.Errorf(modules.ErrParameterConvertFailed, OHLCV) } - var ohlcvClose []float64 + ohlcvClose := make([]float64, len(ohlcvInputData)) var allErrors []string for x := range ohlcvInputData { t, ok := ohlcvInputData[x].([]interface{}) @@ -61,7 +61,7 @@ func ema(args ...objects.Object) (objects.Object, error) { if err != nil { allErrors = append(allErrors, err.Error()) } - ohlcvClose = append(ohlcvClose, value) + ohlcvClose[x] = value } inTimePeriod, ok := objects.ToInt(args[1]) diff --git a/gctscript/modules/ta/indicators/macd.go b/gctscript/modules/ta/indicators/macd.go index 9e9dc067..e8675414 100644 --- a/gctscript/modules/ta/indicators/macd.go +++ b/gctscript/modules/ta/indicators/macd.go @@ -47,7 +47,7 @@ func macd(args ...objects.Object) (objects.Object, error) { return nil, fmt.Errorf(modules.ErrParameterConvertFailed, OHLCV) } - var ohlcvClose []float64 + ohlcvClose := make([]float64, len(ohlcvInputData)) var allErrors []string for x := range ohlcvInputData { t, ok := ohlcvInputData[x].([]interface{}) @@ -61,7 +61,7 @@ func macd(args ...objects.Object) (objects.Object, error) { if err != nil { allErrors = append(allErrors, err.Error()) } - ohlcvClose = append(ohlcvClose, value) + ohlcvClose[x] = value } inFastPeriod, ok := objects.ToInt(args[1]) diff --git a/gctscript/modules/ta/indicators/rsi.go b/gctscript/modules/ta/indicators/rsi.go index 7588bda3..19b1f738 100644 --- a/gctscript/modules/ta/indicators/rsi.go +++ b/gctscript/modules/ta/indicators/rsi.go @@ -46,7 +46,7 @@ func rsi(args ...objects.Object) (objects.Object, error) { return nil, fmt.Errorf(modules.ErrParameterConvertFailed, OHLCV) } - var ohlcvClose []float64 + ohlcvClose := make([]float64, len(ohlcvInputData)) var allErrors []string for x := range ohlcvInputData { t, ok := ohlcvInputData[x].([]interface{}) @@ -61,7 +61,7 @@ func rsi(args ...objects.Object) (objects.Object, error) { if err != nil { allErrors = append(allErrors, err.Error()) } - ohlcvClose = append(ohlcvClose, value) + ohlcvClose[x] = value } inTimePeriod, ok := objects.ToInt(args[1]) diff --git a/gctscript/modules/ta/indicators/sma.go b/gctscript/modules/ta/indicators/sma.go index 3e1d674b..d6425796 100644 --- a/gctscript/modules/ta/indicators/sma.go +++ b/gctscript/modules/ta/indicators/sma.go @@ -46,7 +46,7 @@ func sma(args ...objects.Object) (objects.Object, error) { return nil, fmt.Errorf(modules.ErrParameterConvertFailed, OHLCV) } - var ohlcvClose []float64 + ohlcvClose := make([]float64, len(ohlcvInputData)) var allErrors []string for x := range ohlcvInputData { t, ok := ohlcvInputData[x].([]interface{}) @@ -60,7 +60,7 @@ func sma(args ...objects.Object) (objects.Object, error) { if err != nil { allErrors = append(allErrors, err.Error()) } - ohlcvClose = append(ohlcvClose, value) + ohlcvClose[x] = value } inTimePeriod, ok := objects.ToInt(args[1]) diff --git a/gctscript/modules/ta/ta.go b/gctscript/modules/ta/ta.go index 1a1b6f85..a745803d 100644 --- a/gctscript/modules/ta/ta.go +++ b/gctscript/modules/ta/ta.go @@ -2,9 +2,9 @@ package ta // AllModuleNames returns a list of all default module names. func AllModuleNames() []string { - var names []string - for name := range Modules { - names = append(names, name) + names := make([]string, 0, len(Modules)) + for x := range Modules { + names = append(names, x) } return names } diff --git a/gctscript/vm/gctscript.go b/gctscript/vm/gctscript.go index 5f388afe..2c611d9a 100644 --- a/gctscript/vm/gctscript.go +++ b/gctscript/vm/gctscript.go @@ -1,6 +1,7 @@ package vm import ( + "errors" "fmt" "github.com/gofrs/uuid" @@ -50,17 +51,21 @@ func (g *GctScriptManager) ShutdownAll() (err error) { log.Debugln(log.GCTScriptMgr, "Shutting down all Virtual Machines") } - var errors []error + var shutdownErrors []error AllVMSync.Range(func(k, v interface{}) bool { - errShutdown := v.(*VM).Shutdown() + vm, ok := v.(*VM) + if !ok { + shutdownErrors = append(shutdownErrors, errors.New("unable to type assert VM")) + } + errShutdown := vm.Shutdown() if err != nil { - errors = append(errors, errShutdown) + shutdownErrors = append(shutdownErrors, errShutdown) } return true }) - if len(errors) > 0 { - err = fmt.Errorf("failed to shutdown the following Virtual Machines: %v", errors) + if len(shutdownErrors) > 0 { + err = fmt.Errorf("failed to shutdown the following Virtual Machines: %v", shutdownErrors) } return err diff --git a/gctscript/vm/vm.go b/gctscript/vm/vm.go index f1904143..2da5f96c 100644 --- a/gctscript/vm/vm.go +++ b/gctscript/vm/vm.go @@ -5,7 +5,8 @@ import ( "bytes" "context" "encoding/hex" - "io/ioutil" + "errors" + "os" "path/filepath" "sync/atomic" "time" @@ -22,7 +23,7 @@ import ( ) // NewVM attempts to create a new Virtual Machine firstly from pool -func (g *GctScriptManager) NewVM() (vm *VM) { +func (g *GctScriptManager) NewVM() *VM { if !g.IsRunning() { log.Error(log.GCTScriptMgr, Error{ Action: "NewVM", @@ -43,13 +44,21 @@ func (g *GctScriptManager) NewVM() (vm *VM) { log.Debugln(log.GCTScriptMgr, "New GCTScript VM created") } - vm = &VM{ + s, ok := pool.Get().(*tengo.Script) + if !ok { + log.Error(log.GCTScriptMgr, Error{ + Action: "NewVM", + Cause: errors.New("unable to type assert tengo script"), + }) + return nil + } + + return &VM{ ID: newUUID, - Script: pool.Get().(*tengo.Script), + Script: s, config: g.config, unregister: func() error { return g.RemoveVM(newUUID) }, } - return } // SetDefaultScriptOutput sets default output file for scripts @@ -71,7 +80,7 @@ func (vm *VM) Load(file string) error { log.Debugf(log.GCTScriptMgr, "Loading script: %s ID: %v", vm.ShortName(), vm.ID) } - code, err := ioutil.ReadFile(file) + code, err := os.ReadFile(file) if err != nil { return &Error{ Action: "Load: ReadFile", @@ -213,7 +222,7 @@ func (vm *VM) read() ([]byte, error) { if vm.config.Verbose { log.Debugf(log.GCTScriptMgr, "Read script: %s ID: %v", vm.ShortName(), vm.ID) } - return ioutil.ReadFile(vm.File) + return os.ReadFile(vm.File) } // ShortName returns short (just filename.extension) of running script diff --git a/log/logger_rotate.go b/log/logger_rotate.go index 4197d44b..b47faa10 100644 --- a/log/logger_rotate.go +++ b/log/logger_rotate.go @@ -64,7 +64,7 @@ func (r *Rotate) openOrCreateFile(n int64) error { } } - file, err := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY, 0600) + file, err := os.OpenFile(logFile, os.O_APPEND|os.O_WRONLY, 0o600) if err != nil { return r.openNew() } @@ -92,7 +92,7 @@ func (r *Rotate) openNew() error { } } - file, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + file, err := os.OpenFile(name, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600) if err != nil { return fmt.Errorf("can't open new logfile: %s", err) } diff --git a/log/logger_setup.go b/log/logger_setup.go index 2185b2fb..d1a0e18f 100644 --- a/log/logger_setup.go +++ b/log/logger_setup.go @@ -19,8 +19,9 @@ func getWriters(s *SubLoggerConfig) (io.Writer, error) { if s == nil { return nil, errSubloggerConfigIsNil } - var writers []io.Writer + outputWriters := strings.Split(s.Output, "|") + writers := make([]io.Writer, 0, len(outputWriters)) for x := range outputWriters { var writer io.Writer switch strings.ToLower(outputWriters[x]) { @@ -33,7 +34,7 @@ func getWriters(s *SubLoggerConfig) (io.Writer, error) { writer = GlobalLogFile } default: - // Note: Do not want to add a ioutil.discard here as this adds + // Note: Do not want to add a io.Discard here as this adds // additional routines for every write for no reason. return nil, fmt.Errorf("%w: %s", errUnhandledOutputWriter, outputWriters[x]) } diff --git a/log/logger_test.go b/log/logger_test.go index 6e6d4fcc..bfa1c171 100644 --- a/log/logger_test.go +++ b/log/logger_test.go @@ -89,7 +89,7 @@ func BenchmarkInfo(b *testing.B) { func TestAddWriter(t *testing.T) { t.Parallel() - _, err := multiWriter(ioutil.Discard, ioutil.Discard) + _, err := multiWriter(io.Discard, io.Discard) if !errors.Is(err, errWriterAlreadyLoaded) { t.Fatalf("received: '%v' but expected: '%v'", err, errWriterAlreadyLoaded) } @@ -98,7 +98,7 @@ func TestAddWriter(t *testing.T) { if !errors.Is(err, nil) { t.Fatalf("received: '%v' but expected: '%v'", err, nil) } - err = mw.Add(ioutil.Discard) + err = mw.Add(io.Discard) if err != nil { t.Fatal(err) } @@ -122,7 +122,7 @@ func TestRemoveWriter(t *testing.T) { if err != nil { t.Fatal(err) } - err = mw.Add(ioutil.Discard) + err = mw.Add(io.Discard) if err != nil { t.Fatal(err) } @@ -169,7 +169,7 @@ var errWriteError = errors.New("write error") func TestMultiWriterWrite(t *testing.T) { t.Parallel() - mw, err := multiWriter(ioutil.Discard, &bytes.Buffer{}) + mw, err := multiWriter(io.Discard, &bytes.Buffer{}) if err != nil { t.Fatal(err) } @@ -183,7 +183,7 @@ func TestMultiWriterWrite(t *testing.T) { t.Fatal("unexpected return") } - mw, err = multiWriter(&WriteShorter{}, ioutil.Discard) + mw, err = multiWriter(&WriteShorter{}, io.Discard) if err != nil { t.Fatal(err) } @@ -192,7 +192,7 @@ func TestMultiWriterWrite(t *testing.T) { t.Fatalf("received: '%v' but expected: '%v'", err, io.ErrShortWrite) } - mw, err = multiWriter(&WriteError{}, ioutil.Discard) + mw, err = multiWriter(&WriteError{}, io.Discard) if err != nil { t.Fatal(err) } diff --git a/portfolio/withdraw/validate_test.go b/portfolio/withdraw/validate_test.go index 7fee4a62..12d98edd 100644 --- a/portfolio/withdraw/validate_test.go +++ b/portfolio/withdraw/validate_test.go @@ -241,8 +241,12 @@ func TestValidateFiat(t *testing.T) { } err := test.request.Validate(test.validate) if err != nil { - if test.output.(error).Error() != err.Error() { - t.Fatalf("Test Name %s expecting error [%s] but received [%s]", test.name, test.output.(error).Error(), err) + errOutput, ok := test.output.(error) + if !ok { + t.Fatalf("Test Name %s unable to type assert error", test.name) + } + if errOutput.Error() != err.Error() { + t.Fatalf("Test Name %s expecting error [%s] but received [%s]", test.name, errOutput.Error(), err) } } })