package engine import ( "errors" "path/filepath" "testing" "time" "github.com/gofrs/uuid" "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/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/currency" "github.com/thrasher-corp/gocryptotrader/engine" "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 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{}), Statistic: &statistics.Statistic{}, } bt.Stop() tt := bt.MetaData.DateEnded bt.Stop() if !tt.Equal(bt.MetaData.DateEnded) { t.Errorf("received '%v' expected '%v'", bt.MetaData.DateEnded, tt) } bt = nil 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) } } func TestGenerateSummary(t *testing.T) { t.Parallel() bt := &BackTest{} sum, err := bt.GenerateSummary() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } if !sum.MetaData.ID.IsNil() { t.Errorf("received '%v' expected '%v'", sum.MetaData.ID, "") } id, err := uuid.NewV4() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } bt.MetaData.ID = id sum, err = bt.GenerateSummary() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } if sum.MetaData.ID != id { t.Errorf("received '%v' expected '%v'", sum.MetaData.ID, id) } bt = nil _, err = bt.GenerateSummary() if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } } func TestSetupMetaData(t *testing.T) { t.Parallel() bt := &BackTest{} err := bt.SetupMetaData() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } if bt.MetaData.ID.IsNil() { t.Errorf("received '%v' expected '%v'", bt.MetaData.ID, "an ID") } firstID := bt.MetaData.ID err = bt.SetupMetaData() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } if bt.MetaData.ID != firstID { t.Errorf("received '%v' expected '%v'", bt.MetaData.ID, firstID) } bt = nil err = bt.SetupMetaData() if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } } func TestIsRunning(t *testing.T) { t.Parallel() bt := &BackTest{} if bt.IsRunning() { t.Errorf("received '%v' expected '%v'", true, false) } bt.MetaData.DateStarted = time.Now() if !bt.IsRunning() { t.Errorf("received '%v' expected '%v'", false, true) } bt.MetaData.Closed = true if bt.IsRunning() { t.Errorf("received '%v' expected '%v'", true, false) } bt = nil if bt.IsRunning() { t.Errorf("received '%v' expected '%v'", true, false) } } func TestHasRan(t *testing.T) { t.Parallel() bt := &BackTest{} if bt.HasRan() { t.Errorf("received '%v' expected '%v'", true, false) } bt.MetaData.DateStarted = time.Now() if bt.HasRan() { t.Errorf("received '%v' expected '%v'", false, true) } bt.MetaData.Closed = true if !bt.HasRan() { t.Errorf("received '%v' expected '%v'", true, false) } bt = nil if bt.HasRan() { t.Errorf("received '%v' expected '%v'", true, false) } } func TestEqual(t *testing.T) { t.Parallel() bt := &BackTest{} bt2 := &BackTest{} bt3 := &BackTest{} if !bt.Equal(bt2) { t.Errorf("received '%v' expected '%v'", false, true) } err := bt.SetupMetaData() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } bt2.MetaData = bt.MetaData if !bt.Equal(bt2) { t.Errorf("received '%v' expected '%v'", false, true) } if bt.Equal(nil) { t.Errorf("received '%v' expected '%v'", true, false) } err = bt3.SetupMetaData() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } if bt.Equal(bt3) { t.Errorf("received '%v' expected '%v'", true, false) } bt = nil if bt.Equal(bt2) { t.Errorf("received '%v' expected '%v'", true, false) } } func TestMatchesID(t *testing.T) { t.Parallel() bt := &BackTest{} if bt.MatchesID(uuid.Nil) { t.Errorf("received '%v' expected '%v'", true, false) } err := bt.SetupMetaData() if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } if bt.MatchesID(uuid.Nil) { t.Errorf("received '%v' expected '%v'", true, false) } if !bt.MatchesID(bt.MetaData.ID) { t.Errorf("received '%v' expected '%v'", false, true) } id := bt.MetaData.ID bt.MetaData.ID = uuid.Nil if bt.MatchesID(id) { t.Errorf("received '%v' expected '%v'", true, false) } bt = nil if bt.MatchesID(id) { t.Errorf("received '%v' expected '%v'", true, false) } } func TestExecuteStrategy(t *testing.T) { t.Parallel() bt := &BackTest{} err := bt.ExecuteStrategy(false) if !errors.Is(err, errNotSetup) { t.Errorf("received '%v' expected '%v'", err, errNotSetup) } bt.MetaData.DateLoaded = time.Now() bt.MetaData.DateStarted = time.Now() err = bt.ExecuteStrategy(false) if !errors.Is(err, errRunIsRunning) { t.Errorf("received '%v' expected '%v'", err, errRunIsRunning) } strat1 := filepath.Join("..", "config", "strategyexamples", "dca-api-candles.strat") cfg, err := config.ReadStrategyConfigFromFile(strat1) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } bt, err = NewFromConfig(cfg, "", "", false) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } bt.Stop() err = bt.ExecuteStrategy(true) if !errors.Is(err, errAlreadyRan) { t.Errorf("received '%v' expected '%v'", err, errAlreadyRan) } strat2 := filepath.Join("..", "config", "strategyexamples", "dca-candles-live.strat") cfg, err = config.ReadStrategyConfigFromFile(strat2) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } bt, err = NewFromConfig(cfg, "", "", false) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } err = bt.ExecuteStrategy(true) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } bt.MetaData.DateStarted = time.Time{} err = bt.ExecuteStrategy(false) if !errors.Is(err, nil) { t.Errorf("received '%v' expected '%v'", err, nil) } bt.Stop() bt = nil err = bt.ExecuteStrategy(false) if !errors.Is(err, gctcommon.ErrNilPointer) { t.Errorf("received '%v' expected '%v'", err, gctcommon.ErrNilPointer) } }