package engine import ( "errors" "strings" "testing" "time" "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/backtester/common" "github.com/thrasher-corp/gocryptotrader/backtester/config" "github.com/thrasher-corp/gocryptotrader/backtester/data" "github.com/thrasher-corp/gocryptotrader/backtester/data/kline" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/eventholder" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/exchange" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/risk" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/portfolio/size" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/statistics" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/base" "github.com/thrasher-corp/gocryptotrader/backtester/eventhandlers/strategies/dollarcostaverage" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/event" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/fill" evkline "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/kline" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/order" "github.com/thrasher-corp/gocryptotrader/backtester/eventtypes/signal" "github.com/thrasher-corp/gocryptotrader/backtester/funding" "github.com/thrasher-corp/gocryptotrader/backtester/report" gctcommon "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/database" "github.com/thrasher-corp/gocryptotrader/database/drivers" "github.com/thrasher-corp/gocryptotrader/engine" gctexchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/ftx" gctkline "github.com/thrasher-corp/gocryptotrader/exchanges/kline" gctorder "github.com/thrasher-corp/gocryptotrader/exchanges/order" ) const testExchange = "ftx" var leet = decimal.NewFromInt(1337) type portfolioOverride struct { Err error portfolio.Portfolio } func (p portfolioOverride) CreateLiquidationOrdersForExchange(ev common.DataEventHandler, _ funding.IFundingManager) ([]order.Event, error) { if p.Err != nil { return nil, p.Err } return []order.Event{ &order.Order{ Base: ev.GetBase(), ID: "1", Direction: gctorder.Short, }, }, nil } func TestNewFromConfig(t *testing.T) { t.Parallel() _, err := NewFromConfig(nil, "", "", false) if !errors.Is(err, errNilConfig) { t.Errorf("received %v, expected %v", err, errNilConfig) } cfg := &config.Config{} _, err = NewFromConfig(cfg, "", "", false) if !errors.Is(err, base.ErrStrategyNotFound) { t.Errorf("received: %v, expected: %v", err, base.ErrStrategyNotFound) } cfg.CurrencySettings = []config.CurrencySettings{ { ExchangeName: "test", Base: currency.NewCode("test"), Quote: currency.NewCode("test"), }, { ExchangeName: testExchange, Base: currency.BTC, Quote: currency.NewCode("0624"), Asset: asset.Futures, }, } _, err = NewFromConfig(cfg, "", "", false) if !errors.Is(err, engine.ErrExchangeNotFound) { t.Errorf("received: %v, expected: %v", err, engine.ErrExchangeNotFound) } cfg.CurrencySettings[0].ExchangeName = testExchange _, err = NewFromConfig(cfg, "", "", false) if !errors.Is(err, asset.ErrNotSupported) { t.Errorf("received: %v, expected: %v", err, asset.ErrNotSupported) } cfg.CurrencySettings[0].Asset = asset.Spot _, err = NewFromConfig(cfg, "", "", false) if !errors.Is(err, base.ErrStrategyNotFound) { t.Errorf("received: %v, expected: %v", err, base.ErrStrategyNotFound) } cfg.StrategySettings = config.StrategySettings{ Name: dollarcostaverage.Name, CustomSettings: map[string]interface{}{ "hello": "moto", }, } cfg.CurrencySettings[0].Base = currency.BTC cfg.CurrencySettings[0].Quote = currency.USD cfg.DataSettings.APIData = &config.APIData{ StartDate: time.Time{}, EndDate: time.Time{}, } _, err = NewFromConfig(cfg, "", "", false) if err != nil && !strings.Contains(err.Error(), "unrecognised dataType") { t.Error(err) } cfg.DataSettings.DataType = common.CandleStr _, err = NewFromConfig(cfg, "", "", false) if !errors.Is(err, errIntervalUnset) { t.Errorf("received: %v, expected: %v", err, errIntervalUnset) } cfg.DataSettings.Interval = gctkline.OneMin cfg.CurrencySettings[0].MakerFee = &decimal.Zero cfg.CurrencySettings[0].TakerFee = &decimal.Zero _, err = NewFromConfig(cfg, "", "", false) if !errors.Is(err, gctcommon.ErrDateUnset) { t.Errorf("received: %v, expected: %v", err, gctcommon.ErrDateUnset) } cfg.DataSettings.APIData.StartDate = time.Now().Add(-time.Minute) cfg.DataSettings.APIData.EndDate = time.Now() cfg.DataSettings.APIData.InclusiveEndDate = true _, err = NewFromConfig(cfg, "", "", false) if !errors.Is(err, nil) { t.Errorf("received: %v, expected: %v", err, nil) } cfg.FundingSettings.UseExchangeLevelFunding = true cfg.FundingSettings.ExchangeLevelFunding = []config.ExchangeLevelFunding{ { ExchangeName: testExchange, Asset: asset.Spot, Currency: currency.BTC, InitialFunds: leet, TransferFee: leet, }, { ExchangeName: testExchange, Asset: asset.Futures, Currency: currency.BTC, InitialFunds: leet, TransferFee: leet, }, } _, err = NewFromConfig(cfg, "", "", false) if !errors.Is(err, nil) { t.Errorf("received: %v, expected: %v", err, nil) } } func TestLoadDataAPI(t *testing.T) { t.Parallel() bt := BackTest{ Reports: &report.Data{}, } cp := currency.NewPair(currency.BTC, currency.USDT) cfg := &config.Config{ CurrencySettings: []config.CurrencySettings{ { ExchangeName: "Binance", Asset: asset.Spot, Base: cp.Base, Quote: cp.Quote, SpotDetails: &config.SpotDetails{ InitialQuoteFunds: &leet, }, BuySide: config.MinMax{}, SellSide: config.MinMax{}, MakerFee: &decimal.Zero, TakerFee: &decimal.Zero, }, }, DataSettings: config.DataSettings{ DataType: common.CandleStr, Interval: gctkline.OneMin, APIData: &config.APIData{ StartDate: time.Now().Add(-time.Minute), EndDate: time.Now(), }}, StrategySettings: config.StrategySettings{ Name: dollarcostaverage.Name, CustomSettings: map[string]interface{}{ "hello": "moto", }, }, } em := engine.ExchangeManager{} exch, err := em.NewExchangeByName("Binance") if err != nil { t.Fatal(err) } exch.SetDefaults() b := exch.GetBase() b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ Available: currency.Pairs{cp}, Enabled: currency.Pairs{cp}, AssetEnabled: convert.BoolPtr(true), ConfigFormat: ¤cy.PairFormat{Uppercase: true}, RequestFormat: ¤cy.PairFormat{Uppercase: true}} _, err = bt.loadData(cfg, exch, cp, asset.Spot, false) if err != nil { t.Error(err) } } func TestLoadDataDatabase(t *testing.T) { t.Parallel() bt := BackTest{ Reports: &report.Data{}, } cp := currency.NewPair(currency.BTC, currency.USDT) cfg := &config.Config{ CurrencySettings: []config.CurrencySettings{ { ExchangeName: "Binance", Asset: asset.Spot, Base: cp.Base, Quote: cp.Quote, SpotDetails: &config.SpotDetails{ InitialQuoteFunds: &leet, }, BuySide: config.MinMax{}, SellSide: config.MinMax{}, MakerFee: &decimal.Zero, TakerFee: &decimal.Zero, }, }, DataSettings: config.DataSettings{ DataType: common.CandleStr, Interval: gctkline.OneMin, DatabaseData: &config.DatabaseData{ Config: database.Config{ Enabled: true, Driver: "sqlite3", ConnectionDetails: drivers.ConnectionDetails{ Database: "gocryptotrader.db", }, }, StartDate: time.Now().Add(-time.Minute), EndDate: time.Now(), InclusiveEndDate: true, }}, StrategySettings: config.StrategySettings{ Name: dollarcostaverage.Name, CustomSettings: map[string]interface{}{ "hello": "moto", }, }, } em := engine.ExchangeManager{} exch, err := em.NewExchangeByName("Binance") if err != nil { t.Fatal(err) } exch.SetDefaults() b := exch.GetBase() b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ Available: currency.Pairs{cp}, Enabled: currency.Pairs{cp}, AssetEnabled: convert.BoolPtr(true), ConfigFormat: ¤cy.PairFormat{Uppercase: true}, RequestFormat: ¤cy.PairFormat{Uppercase: true}} bt.databaseManager, err = engine.SetupDatabaseConnectionManager(&cfg.DataSettings.DatabaseData.Config) if err != nil { t.Fatal(err) } _, err = bt.loadData(cfg, exch, cp, asset.Spot, false) if err != nil && !strings.Contains(err.Error(), "unable to retrieve data from GoCryptoTrader database") { t.Error(err) } } func TestLoadDataCSV(t *testing.T) { t.Parallel() bt := BackTest{ Reports: &report.Data{}, } cp := currency.NewPair(currency.BTC, currency.USDT) cfg := &config.Config{ CurrencySettings: []config.CurrencySettings{ { ExchangeName: "Binance", Asset: asset.Spot, Base: cp.Base, Quote: cp.Quote, SpotDetails: &config.SpotDetails{ InitialQuoteFunds: &leet, }, BuySide: config.MinMax{}, SellSide: config.MinMax{}, MakerFee: &decimal.Zero, TakerFee: &decimal.Zero, }, }, DataSettings: config.DataSettings{ DataType: common.CandleStr, Interval: gctkline.OneMin, CSVData: &config.CSVData{ FullPath: "test", }}, StrategySettings: config.StrategySettings{ Name: dollarcostaverage.Name, CustomSettings: map[string]interface{}{ "hello": "moto", }, }, } em := engine.ExchangeManager{} exch, err := em.NewExchangeByName("Binance") if err != nil { t.Fatal(err) } exch.SetDefaults() b := exch.GetBase() b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ Available: currency.Pairs{cp}, Enabled: currency.Pairs{cp}, AssetEnabled: convert.BoolPtr(true), ConfigFormat: ¤cy.PairFormat{Uppercase: true}, RequestFormat: ¤cy.PairFormat{Uppercase: true}} _, err = bt.loadData(cfg, exch, cp, asset.Spot, false) if err != nil && !strings.Contains(err.Error(), "The system cannot find the file specified.") && !strings.Contains(err.Error(), "no such file or directory") { t.Error(err) } } func TestLoadDataLive(t *testing.T) { t.Parallel() bt := BackTest{ Reports: &report.Data{}, shutdown: make(chan struct{}), } cp := currency.NewPair(currency.BTC, currency.USDT) cfg := &config.Config{ CurrencySettings: []config.CurrencySettings{ { ExchangeName: "Binance", Asset: asset.Spot, Base: cp.Base, Quote: cp.Quote, SpotDetails: &config.SpotDetails{ InitialQuoteFunds: &leet, }, BuySide: config.MinMax{}, SellSide: config.MinMax{}, MakerFee: &decimal.Zero, TakerFee: &decimal.Zero, }, }, DataSettings: config.DataSettings{ DataType: common.CandleStr, Interval: gctkline.OneMin, LiveData: &config.LiveData{ APIKeyOverride: "test", APISecretOverride: "test", APIClientIDOverride: "test", API2FAOverride: "test", RealOrders: true, }}, StrategySettings: config.StrategySettings{ Name: dollarcostaverage.Name, CustomSettings: map[string]interface{}{ "hello": "moto", }, }, } em := engine.ExchangeManager{} exch, err := em.NewExchangeByName("Binance") if err != nil { t.Fatal(err) } exch.SetDefaults() b := exch.GetBase() b.CurrencyPairs.Pairs = make(map[asset.Item]*currency.PairStore) b.CurrencyPairs.Pairs[asset.Spot] = ¤cy.PairStore{ Available: currency.Pairs{cp}, Enabled: currency.Pairs{cp}, AssetEnabled: convert.BoolPtr(true), ConfigFormat: ¤cy.PairFormat{Uppercase: true}, RequestFormat: ¤cy.PairFormat{Uppercase: true}} _, err = bt.loadData(cfg, exch, cp, asset.Spot, false) if err != nil { t.Error(err) } bt.Stop() } func TestLoadLiveData(t *testing.T) { t.Parallel() err := loadLiveData(nil, nil) if !errors.Is(err, common.ErrNilArguments) { t.Error(err) } cfg := &config.Config{} err = loadLiveData(cfg, nil) if !errors.Is(err, common.ErrNilArguments) { t.Error(err) } b := &gctexchange.Base{ Name: testExchange, API: gctexchange.API{ AuthenticatedSupport: false, AuthenticatedWebsocketSupport: false, PEMKeySupport: false, CredentialsValidator: struct { RequiresPEM bool RequiresKey bool RequiresSecret bool RequiresClientID bool RequiresBase64DecodeSecret bool }{ RequiresPEM: true, RequiresKey: true, RequiresSecret: true, RequiresClientID: true, RequiresBase64DecodeSecret: true, }, }, } err = loadLiveData(cfg, b) if !errors.Is(err, common.ErrNilArguments) { t.Error(err) } cfg.DataSettings.LiveData = &config.LiveData{ RealOrders: true, } cfg.DataSettings.Interval = gctkline.OneDay cfg.DataSettings.DataType = common.CandleStr err = loadLiveData(cfg, b) if err != nil { t.Error(err) } cfg.DataSettings.LiveData.APIKeyOverride = "1234" cfg.DataSettings.LiveData.APISecretOverride = "1234" cfg.DataSettings.LiveData.APIClientIDOverride = "1234" cfg.DataSettings.LiveData.API2FAOverride = "1234" cfg.DataSettings.LiveData.APISubAccountOverride = "1234" err = loadLiveData(cfg, b) if err != nil { t.Error(err) } } func TestReset(t *testing.T) { t.Parallel() f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, true, false) if err != nil { t.Error(err) } bt := BackTest{ shutdown: make(chan struct{}), Datas: &data.HandlerPerCurrency{}, Strategy: &dollarcostaverage.Strategy{}, Portfolio: &portfolio.Portfolio{}, Exchange: &exchange.Exchange{}, Statistic: &statistics.Statistic{}, EventQueue: &eventholder.Holder{}, Reports: &report.Data{}, Funding: f, } bt.Reset() if bt.Funding.IsUsingExchangeLevelFunding() { t.Error("expected false") } } func TestFullCycle(t *testing.T) { t.Parallel() ex := testExchange cp := currency.NewPair(currency.BTC, currency.USD) a := asset.Spot tt := time.Now() stats := &statistics.Statistic{} stats.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic) stats.ExchangeAssetPairStatistics[ex] = make(map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic) stats.ExchangeAssetPairStatistics[ex][a] = make(map[currency.Pair]*statistics.CurrencyPairStatistic) port, err := portfolio.Setup(&size.Size{ BuySide: exchange.MinMax{}, SellSide: exchange.MinMax{}, }, &risk.Risk{}, decimal.Zero) if err != nil { t.Error(err) } fx := &ftx.FTX{} fx.Name = testExchange err = port.SetupCurrencySettingsMap(&exchange.Settings{Exchange: fx, Asset: a, Pair: cp}) if err != nil { t.Error(err) } f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, true) if err != nil { t.Error(err) } b, err := funding.CreateItem(ex, a, cp.Base, decimal.Zero, decimal.Zero) if err != nil { t.Error(err) } quote, err := funding.CreateItem(ex, a, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) if err != nil { t.Error(err) } pair, err := funding.CreatePair(b, quote) if err != nil { t.Error(err) } err = f.AddPair(pair) if err != nil { t.Error(err) } bt := BackTest{ shutdown: nil, Datas: &data.HandlerPerCurrency{}, Strategy: &dollarcostaverage.Strategy{}, Portfolio: port, Exchange: &exchange.Exchange{}, Statistic: stats, EventQueue: &eventholder.Holder{}, Reports: &report.Data{}, Funding: f, } bt.Datas.Setup() k := kline.DataFromKline{ Item: gctkline.Item{ Exchange: ex, Pair: cp, Asset: a, Interval: gctkline.FifteenMin, Candles: []gctkline.Candle{{ Time: tt, Open: 1337, High: 1337, Low: 1337, Close: 1337, Volume: 1337, }}, }, Base: data.Base{}, RangeHolder: &gctkline.IntervalRangeHolder{ Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), Ranges: []gctkline.IntervalRange{ { Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), Intervals: []gctkline.IntervalData{ { Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), HasData: true, }, }, }, }, }, } err = k.Load() if err != nil { t.Error(err) } bt.Datas.SetDataForCurrency(ex, a, cp, &k) bt.Run() } func TestStop(t *testing.T) { t.Parallel() bt := BackTest{shutdown: make(chan struct{})} bt.Stop() } func TestFullCycleMulti(t *testing.T) { t.Parallel() ex := testExchange cp := currency.NewPair(currency.BTC, currency.USD) a := asset.Spot tt := time.Now() stats := &statistics.Statistic{} stats.ExchangeAssetPairStatistics = make(map[string]map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic) stats.ExchangeAssetPairStatistics[ex] = make(map[asset.Item]map[currency.Pair]*statistics.CurrencyPairStatistic) stats.ExchangeAssetPairStatistics[ex][a] = make(map[currency.Pair]*statistics.CurrencyPairStatistic) port, err := portfolio.Setup(&size.Size{ BuySide: exchange.MinMax{}, SellSide: exchange.MinMax{}, }, &risk.Risk{}, decimal.Zero) if err != nil { t.Error(err) } err = port.SetupCurrencySettingsMap(&exchange.Settings{Exchange: &ftx.FTX{}, Asset: a, Pair: cp}) if err != nil { t.Error(err) } f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, true) if err != nil { t.Error(err) } b, err := funding.CreateItem(ex, a, cp.Base, decimal.Zero, decimal.Zero) if err != nil { t.Error(err) } quote, err := funding.CreateItem(ex, a, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) if err != nil { t.Error(err) } pair, err := funding.CreatePair(b, quote) if err != nil { t.Error(err) } err = f.AddPair(pair) if err != nil { t.Error(err) } bt := BackTest{ shutdown: nil, Datas: &data.HandlerPerCurrency{}, Portfolio: port, Exchange: &exchange.Exchange{}, Statistic: stats, EventQueue: &eventholder.Holder{}, Reports: &report.Data{}, Funding: f, } bt.Strategy, err = strategies.LoadStrategyByName(dollarcostaverage.Name, true) if err != nil { t.Error(err) } bt.Datas.Setup() k := kline.DataFromKline{ Item: gctkline.Item{ Exchange: ex, Pair: cp, Asset: a, Interval: gctkline.FifteenMin, Candles: []gctkline.Candle{{ Time: tt, Open: 1337, High: 1337, Low: 1337, Close: 1337, Volume: 1337, }}, }, Base: data.Base{}, RangeHolder: &gctkline.IntervalRangeHolder{ Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), Ranges: []gctkline.IntervalRange{ { Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), Intervals: []gctkline.IntervalData{ { Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), HasData: true, }, }, }, }, }, } err = k.Load() if err != nil { t.Error(err) } bt.Datas.SetDataForCurrency(ex, a, cp, &k) bt.Run() } func TestTriggerLiquidationsForExchange(t *testing.T) { t.Parallel() bt := BackTest{} expectedError := common.ErrNilEvent err := bt.triggerLiquidationsForExchange(nil, nil) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } cp := currency.NewPair(currency.BTC, currency.USDT) a := asset.Futures expectedError = common.ErrNilArguments ev := &evkline.Kline{ Base: &event.Base{Exchange: testExchange, AssetType: a, CurrencyPair: cp}, } err = bt.triggerLiquidationsForExchange(ev, nil) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt.Portfolio = &portfolioOverride{} pnl := &portfolio.PNLSummary{} bt.Datas = &data.HandlerPerCurrency{} d := data.Base{} d.SetStream([]common.DataEventHandler{&evkline.Kline{ Base: &event.Base{ Exchange: testExchange, Time: time.Now(), Interval: gctkline.OneDay, CurrencyPair: cp, AssetType: a, }, Open: leet, Close: leet, Low: leet, High: leet, Volume: leet, }}) d.Next() da := &kline.DataFromKline{ Item: gctkline.Item{ Exchange: testExchange, Asset: a, Pair: cp, }, Base: d, RangeHolder: &gctkline.IntervalRangeHolder{}, } bt.Statistic = &statistics.Statistic{} expectedError = nil bt.EventQueue = &eventholder.Holder{} bt.Funding = &funding.FundManager{} bt.Datas.SetDataForCurrency(testExchange, a, cp, da) err = bt.Statistic.SetupEventForTime(ev) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } pnl.Exchange = ev.Exchange pnl.Item = ev.AssetType pnl.Pair = ev.CurrencyPair err = bt.triggerLiquidationsForExchange(ev, pnl) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } ev2 := bt.EventQueue.NextEvent() ev2o, ok := ev2.(order.Event) if !ok { t.Fatal("expected order event") } if ev2o.GetDirection() != gctorder.Short { t.Error("expected liquidation order") } } func TestUpdateStatsForDataEvent(t *testing.T) { t.Parallel() pt := &portfolio.Portfolio{} bt := &BackTest{ Statistic: &statistics.Statistic{}, Funding: &funding.FundManager{}, Portfolio: pt, } expectedError := common.ErrNilEvent err := bt.updateStatsForDataEvent(nil, nil) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } cp := currency.NewPair(currency.BTC, currency.USDT) a := asset.Futures ev := &evkline.Kline{ Base: &event.Base{Exchange: testExchange, AssetType: a, CurrencyPair: cp}, } expectedError = common.ErrNilArguments err = bt.updateStatsForDataEvent(ev, nil) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } expectedError = nil f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, true) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } b, err := funding.CreateItem(testExchange, a, cp.Base, decimal.Zero, decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } quote, err := funding.CreateItem(testExchange, a, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } pair, err := funding.CreateCollateral(b, quote) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt.Funding = f exch := &ftx.FTX{} exch.Name = testExchange err = pt.SetupCurrencySettingsMap(&exchange.Settings{ Exchange: exch, Pair: cp, Asset: a, }) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } ev.Time = time.Now() fl := &fill.Fill{ Base: ev.Base, Direction: gctorder.Short, Amount: decimal.NewFromInt(1), ClosePrice: decimal.NewFromInt(1), VolumeAdjustedPrice: decimal.NewFromInt(1), PurchasePrice: decimal.NewFromInt(1), Total: decimal.NewFromInt(1), Slippage: decimal.NewFromInt(1), Order: &gctorder.Detail{ Exchange: testExchange, AssetType: ev.AssetType, Pair: cp, Amount: 1, Price: 1, Side: gctorder.Short, OrderID: "1", Date: time.Now(), }, } _, err = pt.TrackFuturesOrder(fl, pair) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } err = bt.updateStatsForDataEvent(ev, pair) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } } func TestProcessSignalEvent(t *testing.T) { t.Parallel() var expectedError error pt, err := portfolio.Setup(&size.Size{}, &risk.Risk{}, decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt := &BackTest{ Statistic: &statistics.Statistic{}, Funding: &funding.FundManager{}, Portfolio: pt, Exchange: &exchange.Exchange{}, EventQueue: &eventholder.Holder{}, } cp := currency.NewPair(currency.BTC, currency.USDT) a := asset.Futures de := &evkline.Kline{ Base: &event.Base{Exchange: testExchange, AssetType: a, CurrencyPair: cp}, } err = bt.Statistic.SetupEventForTime(de) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } ev := &signal.Signal{ Base: de.Base, } f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, true) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } b, err := funding.CreateItem(testExchange, a, cp.Base, decimal.Zero, decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } quote, err := funding.CreateItem(testExchange, a, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } pair, err := funding.CreateCollateral(b, quote) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt.Funding = f exch := &ftx.FTX{} exch.Name = testExchange err = pt.SetupCurrencySettingsMap(&exchange.Settings{ Exchange: exch, Pair: cp, Asset: a, }) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt.Exchange.SetExchangeAssetCurrencySettings(a, cp, &exchange.Settings{ Exchange: exch, Pair: cp, Asset: a, }) ev.Direction = gctorder.Short err = bt.Statistic.SetEventForOffset(ev) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } err = bt.processSignalEvent(ev, pair) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } } func TestProcessOrderEvent(t *testing.T) { t.Parallel() var expectedError error pt, err := portfolio.Setup(&size.Size{}, &risk.Risk{}, decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt := &BackTest{ Statistic: &statistics.Statistic{}, Funding: &funding.FundManager{}, Portfolio: pt, Exchange: &exchange.Exchange{}, EventQueue: &eventholder.Holder{}, Datas: &data.HandlerPerCurrency{}, } cp := currency.NewPair(currency.BTC, currency.USDT) a := asset.Futures de := &evkline.Kline{ Base: &event.Base{Exchange: testExchange, AssetType: a, CurrencyPair: cp}, } err = bt.Statistic.SetupEventForTime(de) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } ev := &order.Order{ Base: de.Base, } f, err := funding.SetupFundingManager(&engine.ExchangeManager{}, false, true) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } b, err := funding.CreateItem(testExchange, a, cp.Base, decimal.Zero, decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } quote, err := funding.CreateItem(testExchange, a, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } pair, err := funding.CreateCollateral(b, quote) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt.Funding = f exch := &ftx.FTX{} exch.Name = testExchange err = pt.SetupCurrencySettingsMap(&exchange.Settings{ Exchange: exch, Pair: cp, Asset: a, }) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt.Exchange.SetExchangeAssetCurrencySettings(a, cp, &exchange.Settings{ Exchange: exch, Pair: cp, Asset: a, }) ev.Direction = gctorder.Short err = bt.Statistic.SetEventForOffset(ev) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } tt := time.Now() bt.Datas.Setup() k := kline.DataFromKline{ Item: gctkline.Item{ Exchange: testExchange, Pair: cp, Asset: a, Interval: gctkline.FifteenMin, Candles: []gctkline.Candle{{ Time: tt, Open: 1337, High: 1337, Low: 1337, Close: 1337, Volume: 1337, }}, }, Base: data.Base{}, RangeHolder: &gctkline.IntervalRangeHolder{ Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), Ranges: []gctkline.IntervalRange{ { Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), Intervals: []gctkline.IntervalData{ { Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), HasData: true, }, }, }, }, }, } err = k.Load() if err != nil { t.Error(err) } bt.Datas.SetDataForCurrency(testExchange, a, cp, &k) err = bt.processOrderEvent(ev, pair) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } ev2 := bt.EventQueue.NextEvent() if _, ok := ev2.(fill.Event); !ok { t.Fatal("expected fill event") } } func TestProcessFillEvent(t *testing.T) { t.Parallel() var expectedError error pt, err := portfolio.Setup(&size.Size{}, &risk.Risk{}, decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt := &BackTest{ Statistic: &statistics.Statistic{}, Funding: &funding.FundManager{}, Portfolio: pt, Exchange: &exchange.Exchange{}, EventQueue: &eventholder.Holder{}, Datas: &data.HandlerPerCurrency{}, } cp := currency.NewPair(currency.BTC, currency.USD) a := asset.Futures de := &evkline.Kline{ Base: &event.Base{Exchange: testExchange, AssetType: a, CurrencyPair: cp}, } err = bt.Statistic.SetupEventForTime(de) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } ev := &fill.Fill{ Base: de.Base, } em := engine.SetupExchangeManager() exch, err := em.NewExchangeByName(testExchange) if err != nil { t.Fatal(err) } exch.SetDefaults() cfg, err := exch.GetDefaultConfig() if err != nil { t.Fatal(err) } err = exch.Setup(cfg) if err != nil { t.Fatal(err) } em.Add(exch) f, err := funding.SetupFundingManager(em, false, true) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } b, err := funding.CreateItem(testExchange, a, cp.Base, decimal.Zero, decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } quote, err := funding.CreateItem(testExchange, a, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } pair, err := funding.CreateCollateral(b, quote) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } err = f.AddItem(b) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } err = f.AddItem(quote) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } spotBase, err := funding.CreateItem(testExchange, asset.Spot, cp.Base, decimal.Zero, decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } spotQuote, err := funding.CreateItem(testExchange, asset.Spot, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } spotPair, err := funding.CreatePair(spotBase, spotQuote) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } err = f.AddPair(spotPair) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt.Funding = f err = pt.SetupCurrencySettingsMap(&exchange.Settings{ Exchange: exch, Pair: cp, Asset: a, }) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt.Exchange.SetExchangeAssetCurrencySettings(a, cp, &exchange.Settings{ Exchange: exch, Pair: cp, Asset: a, }) ev.Direction = gctorder.Short err = bt.Statistic.SetEventForOffset(ev) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } tt := time.Now() bt.Datas.Setup() k := kline.DataFromKline{ Item: gctkline.Item{ Exchange: testExchange, Pair: cp, Asset: a, Interval: gctkline.FifteenMin, Candles: []gctkline.Candle{{ Time: tt, Open: 1337, High: 1337, Low: 1337, Close: 1337, Volume: 1337, }}, }, Base: data.Base{}, RangeHolder: &gctkline.IntervalRangeHolder{ Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), Ranges: []gctkline.IntervalRange{ { Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), Intervals: []gctkline.IntervalData{ { Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), HasData: true, }, }, }, }, }, } err = k.Load() if err != nil { t.Error(err) } bt.Datas.SetDataForCurrency(testExchange, a, cp, &k) err = bt.processFillEvent(ev, pair) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } } func TestProcessFuturesFillEvent(t *testing.T) { t.Parallel() var expectedError error pt, err := portfolio.Setup(&size.Size{}, &risk.Risk{}, decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt := &BackTest{ Statistic: &statistics.Statistic{}, Funding: &funding.FundManager{}, Portfolio: pt, Exchange: &exchange.Exchange{}, EventQueue: &eventholder.Holder{}, Datas: &data.HandlerPerCurrency{}, } cp := currency.NewPair(currency.BTC, currency.USD) a := asset.Futures de := &evkline.Kline{ Base: &event.Base{Exchange: testExchange, AssetType: a, CurrencyPair: cp}, } err = bt.Statistic.SetupEventForTime(de) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } ev := &fill.Fill{ Base: de.Base, } em := engine.SetupExchangeManager() exch, err := em.NewExchangeByName(testExchange) if err != nil { t.Fatal(err) } exch.SetDefaults() cfg, err := exch.GetDefaultConfig() if err != nil { t.Fatal(err) } err = exch.Setup(cfg) if err != nil { t.Fatal(err) } em.Add(exch) f, err := funding.SetupFundingManager(em, false, true) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } b, err := funding.CreateItem(testExchange, a, cp.Base, decimal.Zero, decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } quote, err := funding.CreateItem(testExchange, a, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } pair, err := funding.CreateCollateral(b, quote) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } err = f.AddItem(b) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } err = f.AddItem(quote) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } spotBase, err := funding.CreateItem(testExchange, asset.Spot, cp.Base, decimal.Zero, decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } spotQuote, err := funding.CreateItem(testExchange, asset.Spot, cp.Quote, decimal.NewFromInt(1337), decimal.Zero) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } spotPair, err := funding.CreatePair(spotBase, spotQuote) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } err = f.AddPair(spotPair) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt.exchangeManager = em bt.Funding = f err = pt.SetupCurrencySettingsMap(&exchange.Settings{ Exchange: exch, Pair: cp, Asset: a, }) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } bt.Exchange.SetExchangeAssetCurrencySettings(a, cp, &exchange.Settings{ Exchange: exch, Pair: cp, Asset: a, }) ev.Direction = gctorder.Short err = bt.Statistic.SetEventForOffset(ev) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } tt := time.Now() bt.Datas.Setup() k := kline.DataFromKline{ Item: gctkline.Item{ Exchange: testExchange, Pair: cp, Asset: a, Interval: gctkline.FifteenMin, Candles: []gctkline.Candle{{ Time: tt, Open: 1337, High: 1337, Low: 1337, Close: 1337, Volume: 1337, }}, }, Base: data.Base{}, RangeHolder: &gctkline.IntervalRangeHolder{ Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), Ranges: []gctkline.IntervalRange{ { Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), Intervals: []gctkline.IntervalData{ { Start: gctkline.CreateIntervalTime(tt), End: gctkline.CreateIntervalTime(tt.Add(gctkline.FifteenMin.Duration())), HasData: true, }, }, }, }, }, } err = k.Load() if err != nil { t.Error(err) } ev.Order = &gctorder.Detail{ Exchange: testExchange, AssetType: ev.AssetType, Pair: cp, Amount: 1, Price: 1, Side: gctorder.Short, OrderID: "1", Date: time.Now(), } bt.Datas.SetDataForCurrency(testExchange, a, cp, &k) err = bt.processFuturesFillEvent(ev, pair) if !errors.Is(err, expectedError) { t.Errorf("received '%v' expected '%v'", err, expectedError) } }