public OhlcBacktesterTests() { _robotParametersMock = new Mock <IRobotParameters>(); var symbols = new List <ISymbol> { new OhlcSymbol(new Mock <IBars>().Object) { Name = "Main" }, new OhlcSymbol(new Mock <IBars>().Object) { Name = "First" }, new OhlcSymbol(new Mock <IBars>().Object) { Name = "Second" }, new OhlcSymbol(new Mock <IBars>().Object) { Name = "Third" }, }; var startTime = DateTimeOffset.Now.AddDays(-30); var endTime = DateTimeOffset.Now; var symbolsData = new List <ISymbolBacktestData>(); var random = new Random(0); symbols.ForEach(iSymbol => { var symbolData = new SymbolBacktestData(iSymbol, GetData(100 * random.Next(1, 5), startTime, endTime)); symbolsData.Add(symbolData); }); _backtestSettings = new BacktestSettings(startTime, endTime); _backtester = new OhlcBacktester { Interval = TimeSpan.FromHours(1) }; _robotParametersMock.SetupProperty(robotParameters => robotParameters.Symbols, symbols); _robotParametersMock.SetupProperty(robotParameters => robotParameters.SymbolsBacktestData, symbolsData); _robotParametersMock.SetupProperty(robotParameters => robotParameters.Backtester, _backtester); _robotParametersMock.SetupProperty(robotParameters => robotParameters.BacktestSettings, _backtestSettings); _robotParametersMock.SetupProperty(robotParameters => robotParameters.Server, new Server()); _robotParametersMock.SetupProperty(robotParameters => robotParameters.Account, new Mock <IAccount>().Object); _robotParametersMock.SetupProperty(robotParameters => robotParameters.Mode, Mode.Backtest); var tradeEngine = new BacktestTradeEngine(_robotParametersMock.Object.Server, _robotParametersMock.Object.Account); _robotParametersMock.SetupProperty(settings => settings.TradeEngine, tradeEngine); _robotMock = new Mock <Robot>(); }
/// <summary> /// Initializes a new instance of the <see cref="BacktestTimerProvider"/> class. /// </summary> /// <param name="loggerFactory">Used to create output.</param> /// <param name="settings">Provides startDate, endDate and outputFolder.</param> public BacktestTimerProvider(ILoggerFactory loggerFactory, BacktestSettings settings) : base(loggerFactory) { _logger = loggerFactory.CreateLogger(GetType()); _backtestOrders = new List <OrderUpdate>(); _stateSwitchEvents = new List <StateSwitchEvent>(); BeginTime = DateTimeOffset.FromUnixTimeMilliseconds( BacktestDaemonService.Instance.State.BeginTimeStamp); _currentTime = BeginTime; _lastCandleOpen = _currentTime; EndTime = DateTimeOffset.FromUnixTimeMilliseconds( BacktestDaemonService.Instance.State.EndTimeStamp); _outputFolder = settings.OutputFolder; }
/// <summary> /// Runs backtest (in separate thread) for all Selections and numeric Parameters /// </summary> /// <param name="settings">Settings to use for backtest</param> public void StartBacktest(BacktestSettings settings) { if (!_isBusy && State == SignalState.Backtesting) { BacktestSettings = settings ?? new BacktestSettings(); var thread = new System.Threading.Thread(() => Backtest()) { Name = "Signal Backtest", IsBackground = true }; thread.Start(); } else { if (State != SignalState.Backtesting) { Alert("Can't start backtest: signal is not in a backtest mode"); } else if (_isBusy) { Alert("Can't start backtest: signal is busy"); } } }
/// <summary> /// Runs backtest for single instrument and a set of parameter values /// </summary> /// <param name="instruments">Instruments to be backtested</param> /// <param name="parameters">Set of parameter values to use for backtest</param> /// <returns>List of generated trades</returns> protected override List <TradeSignal> BacktestSlotItem(IEnumerable <Selection> instruments, IEnumerable <object> parameters) { // Get data for all provided instruments var data = new Dictionary <Selection, List <Bar> >(instruments.Count()); if (BacktestSettings == null) { BacktestSettings = new BacktestSettings(); } if (BacktestSettings?.BarData != null && BacktestSettings.BarData.Any()) //provided with backtest settings { foreach (var item in instruments) { var bars = BacktestSettings.BarData .FirstOrDefault(b => b.Key.Symbol == item.Symbol && b.Key.TimeFactor == item.TimeFactor && b.Key.Timeframe == item.Timeframe).Value; if (bars != null && bars.Count > 0) { data.Add(item, bars); } } } else //need to request from data provider { foreach (var item in instruments) { var btInstrument = (Selection)item.Clone(); if (BacktestSettings.BarsBack > 0) { btInstrument.BarCount = BacktestSettings.BarsBack; } if (BacktestSettings.StartDate.Year > 2000) { btInstrument.From = BacktestSettings.StartDate; } if (BacktestSettings.EndDate > BacktestSettings.StartDate) { btInstrument.To = BacktestSettings.EndDate; } var bars = DataProvider.GetBars(btInstrument); if (bars != null && bars.Count > 0) { data.Add(btInstrument, bars); } } } int batchSize = GetBacktestBatchSize(parameters); if (data.Count == 0 || batchSize < 1) { return(new List <TradeSignal>(0)); } // Scan all data collections var result = new List <TradeSignal>(); var indices = data.ToDictionary(k => k.Key, v => 0); while (true) { // Get instrument with oldest/earliest data var time = DateTime.MaxValue; foreach (var item in data) { var idx = indices[item.Key]; if (idx >= 0 && idx < item.Value.Count && item.Value[idx].Date < time) { time = item.Value[idx].Date; } } if (time == DateTime.MaxValue) { break; } var selectionsToUse = new List <Selection>(); foreach (var item in data) { var idx = indices[item.Key]; if (idx >= 0 && idx < item.Value.Count && item.Value[idx].Date == time) { selectionsToUse.Add(item.Key); } } // Get necessary data frame to scan var dataFrames = new Dictionary <Selection, IEnumerable <Bar> >(selectionsToUse.Count); foreach (var item in data) { if (selectionsToUse.Contains(item.Key)) { dataFrames.Add(item.Key, item.Value.GetRange(indices[item.Key], batchSize)); indices[item.Key]++; } } // Evaluate current batch if (dataFrames.Count > 0) { var trades = Evaluate(dataFrames, parameters); var barData = dataFrames.Keys.FirstOrDefault(); var barToProccess = dataFrames.Values.FirstOrDefault()?.FirstOrDefault(); SimulationBroker.ProcessBar(barData?.Symbol, barToProccess); foreach (var account in BrokerAccounts) { foreach (var orderInfo in GenerateOrderParams(trades)) { PlaceOrder(orderInfo, account); } } if (trades != null && trades.Count > 0) { result.AddRange(trades); } } // Break if backtest has been aborted if (State != SignalState.Backtesting && State != SignalState.BacktestingPaused) { break; } } return(result); }
private List <TradeSignal> Evaluate(Dictionary <Selection, IEnumerable <Bar> > marketData, IEnumerable <object> parameterItem, Selection triggerInstrument = null, IEnumerable <Tick> ticks = null) { /* Evaluate supplied data bars using provided parameters * and return a collection of trades on successful evaluation * Hint: you can pass these bars to your IndicatorBase instance in its Calculate() method * and you can use current parameter values as that IndicatorBase parameters */ var dataTickframes = marketData.Keys.Where(p => p.Timeframe == Timeframe.Tick); Dictionary <Selection, IEnumerable <Bar> > trigInstrData = new Dictionary <Selection, IEnumerable <Bar> >(); #region Internal Backtest if (_execTradesParam.EvalCount % 10 == 0 && _internalBacktest == true) { _internalBacktest = false; var backtestSet = new BacktestSettings { InitialBalance = 10000, TransactionCosts = 0, Risk = 0, BarsBack = 5, }; Alert("----------------------------------"); Alert("START Internal Backtest"); Alert("----------------------------------"); var res = Backtest(false); var tradeCount = res?[0].Summaries?.Select(i => i.NumberOfTradeSignals).DefaultIfEmpty(0)?.Sum() ?? 0; _internalBacktest = true; Alert("Evaluate(): Internal Backtest Trades: " + tradeCount); Alert("----------------------------------"); Alert("STOP Internal Backtest"); Alert("----------------------------------"); } #endregion #region Prepare marketdata and pass it to trading logic for processing if (StartMethod == StartMethod.NewBar && triggerInstrument != null) { trigInstrData.Clear(); trigInstrData.Add(triggerInstrument, DataProvider.GetBars(triggerInstrument)); } if (State == SignalState.Backtesting) // && dataTickframes.Count() > 0) { var timer = new MicroStopwatch(); timer.Start(); var trades = new List <TradeSignal>(); trades = BacktestPriceSegmentation.BacktestPriceSegmentProcessor(this, marketData, _execTradesParam, backtestPriceConst, Calculate, trigInstrData, ticks); timer.Stop(); Alert($"Init instrumentData: ExecutionTime = {timer.ElapsedMicroseconds:#,0} µs"); return(trades); } try { return(Calculate(marketData)); } catch (Exception e) { Alert($"Evaluate(): Failed to Run on Usercode: {e.Message}"); return(new List <TradeSignal>()); } #endregion }