Для изменения размера нажмите или перетащите
На истории

Тестирование на исторических данных позволяет проводить как анализ рынка для поиска закономерностей, так и оптимизацию параметров стратегии. Вся работа при этом заключена в классе HistoryEmulationConnector (подробнее про настройки тестирования), который получает сохраненные в локальном хранилище данные через специальный API. Тестирование идет по тиковым сделкам (Trade) и стаканам (MarketDepth). Если на период в истории нет сохраненных стаканов, то они генерируются на основе сделок с помощью MarketDepthGenerator.

Данные для тестирования на истории должны быть заранее скачаны и сохранены в специальном S# формате. Это можно сделать самостоятельно, используя StockSharp.Algo.History и API, или настроить и запустить специальную программу S#.Data.

В дистрибутиве S# находится пример SampleHistoryTesting (а также архив HistoryData.zip, где лежат исторические данные по тикам, стаканам и свечам, для примера), который тестирует стратегию Cкользящая Cредняя на истории. Для сравнение скорости и качества, тестирование идет с различным набором маркет-данных:

Пример тестирования стратегии на истории.

Тестирование на истории стратегии скользящих средних из примеров SampleSMA и SampleSmartSMA

  1. В начале необходимо создать настройки для тестирования:

    C#
    // создаем настройки для тестирования
    var settings = new[]
    {
        Tuple.Create(
            TicksCheckBox,
            TicksTestingProcess,
            TicksParameterGrid,
    
            // ticks
            new EmulationInfo {UseTicks = true, CurveColor = Colors.DarkGreen, StrategyName = LocalizedStrings.Ticks}),
    
        Tuple.Create(
            TicksAndDepthsCheckBox,
            TicksAndDepthsTestingProcess,
            TicksAndDepthsParameterGrid,
    
            // ticks + order book
            new EmulationInfo {UseTicks = true, UseMarketDepth = true, CurveColor = Colors.Red, StrategyName = LocalizedStrings.XamlStr757}),
    
        Tuple.Create(
            DepthsCheckBox,
            DepthsTestingProcess,
            DepthsParameterGrid,
    
            // order book
            new EmulationInfo {UseMarketDepth = true, CurveColor = Colors.OrangeRed, StrategyName = LocalizedStrings.MarketDepths}),
    
    
        Tuple.Create(
            CandlesCheckBox,
            CandlesTestingProcess,
            CandlesParameterGrid,
            // candles
            new EmulationInfo {UseCandleTimeFrame = timeFrame, CurveColor = Colors.DarkBlue, StrategyName = LocalizedStrings.Candles}),
    
        Tuple.Create(
            CandlesAndDepthsCheckBox,
            CandlesAndDepthsTestingProcess,
            CandlesAndDepthsParameterGrid,
    
            // candles + orderbook
            new EmulationInfo {UseMarketDepth = true, UseCandleTimeFrame = timeFrame, CurveColor = Colors.Cyan, StrategyName = LocalizedStrings.XamlStr635}),
    
        Tuple.Create(
            OrderLogCheckBox,
            OrderLogTestingProcess,
            OrderLogParameterGrid,
            // order log
            new EmulationInfo {UseOrderLog = true, CurveColor = Colors.CornflowerBlue, StrategyName = LocalizedStrings.OrderLog})
    };
  2. Далее, создать объект IStorageRegistry, через который HistoryEmulationConnector будет получать исторические данные:

    C#
    // хранилище, через которое будет производиться доступ к тиковой и котировочной базе
    var storageRegistry = new StorageRegistry
    {
        // set historical path
        DefaultDrive = new LocalMarketDataDrive(HistoryPath.Text)
    };
    Внимание Внимание
    В конструктор StockSharp.Algo.StoragesLocalMarketDataDrive передается путь к директории, где лежит история для всех инструментов, а не к директории с конкретным инструментом. Например, если архив HistoryData.zip был распакован в директорию C:\R\RIZ2@FORTS\, то в StockSharp.Algo.StoragesLocalMarketDataDrive необходимо передать путь C:\. Подробнее, в разделе API.
  3. Далее в цикле создаются инструмент, портфель, стратегия, шлюз для тестирования и т.д. с соответсвущими настройками, в зависимости от значений флагов, установленных в главном окне (Тики, Тики и стаканы, Свечи и пр.). Если значение флага False, то программа переходит к следующему набору настроек.

    C#
    foreach (var set in settings)
       {
         if (set.Item1.IsChecked == false)
             continue;
       .................
    
    }
  4. Создаем инструменты и портфели, по которым будет производиться тестирование:

    C#
    // создаем тестовый инструмент, на котором будет производится тестирование
    var security = new Security
    {
        Id = SecId.Text, // sec id has the same name as folder with historical data
        Code = secCode,
        Board = board,
    };
    
    // тестовый портфель
    var portfolio = new Portfolio
    {
        Name = "test account",
        BeginValue = 1000000,
    };
  5. Создание самого HistoryEmulationConnector, куда передаются инструменты, портфели, интерфейс хранилища IStorageRegistry, а также настройки тестирования:

    C#
    // создаем шлюз для эмуляции
    // инициализируем настройки (инструмент в истории обновляется раз в секунду)
    var connector = new HistoryEmulationConnector(new[] { security }, new[] { portfolio })
                    {
                        StorageRegistry = storageRegistry,
                        MarketEmulator =
                        {
                            Settings =
                            {
                                // match order if historical price touched our limit order price. 
                                // It is terned off, and price should go through limit order price level
                                // (more "severe" test mode)
                                MatchOnTouch = false,
                            }
                        },
    
                        UseExternalCandleSource = emulationInfo.UseCandleTimeFrame != null,
                        CreateDepthFromOrdersLog = emulationInfo.UseOrderLog,
                        CreateTradesFromOrdersLog = emulationInfo.UseOrderLog,
    
                        HistoryMessageAdapter =
                        {
                            StorageRegistry = storageRegistry,
                            // set history range
                            StartDate = startTime,
                            StopDate = stopTime,
                        },
    
                        // set market time freq as time frame
                        MarketTimeChangedInterval = timeFrame,
                    };
  6. В событии получения нового инструмента задаем начальные значения Level1, регистрируем стакан или создаем и настраиваем генератор стакана. Также в зависимости от настроек регистрируем получение ордерлога и сделок. Запускаем стратегию и генерацию свечей. А также запускаем сам эмулятор.

    C#
    connector.NewSecurities += securities =>
                   {
                       if (securities.All(s => s != security))
                           return;
    
                       // fill level1 values
                       connector.SendInMessage(level1Info);
    
                       if (emulationInfo.UseMarketDepth)
                       {
                           connector.RegisterMarketDepth(security);
    
                           if (
                               // if order book will be generated
                                   generateDepths ||
                               // of backtesting will be on candles
                                   emulationInfo.UseCandleTimeFrame != TimeSpan.Zero
                               )
                           {
                               // if no have order book historical data, but strategy is required,
                               // use generator based on last prices
                               connector.RegisterMarketDepth(new TrendMarketDepthGenerator(connector.GetSecurityId(security))
                               {
                                   Interval = TimeSpan.FromSeconds(1), // order book freq refresh is 1 sec
                                   MaxAsksDepth = maxDepth,
                                   MaxBidsDepth = maxDepth,
                                   UseTradeVolume = true,
                                   MaxVolume = maxVolume,
                                   MinSpreadStepCount = 2,    // min spread generation is 2 pips
                                   MaxSpreadStepCount = 5,    // max spread generation size (prevent extremely size)
                                   MaxPriceStepCount = 3    // pips size,
                               });
                           }
                       }
    
                       if (emulationInfo.UseOrderLog)
                       {
                           connector.RegisterOrderLog(security);
                       }
    
                       if (emulationInfo.UseTicks)
                       {
                           connector.RegisterTrades(security);
                       }
    
                       // start strategy before emulation started
                       strategy.Start();
                       candleManager.Start(series);
    
                       // start historical data loading when connection established successfully and all data subscribed
                       connector.Start();
                   };
  7. Подключение:

    C#
    trader.Connect();

    Для переданных в конструктор HistoryEmulationConnector инструментов и портфелей вызывается IConnectorNewSecurities и IConnectorNewPortfolios.

  8. Создание менеджера свечей CandleManager (необходимо для стратегии скользящих средних), в котором в качестве источника указан шлюз HistoryEmulationConnector:

    C#
    var candleManager = emulationInfo.UseCandleTimeFrame == null
           ? new CandleManager(new TradeCandleBuilderSourceEx(connector))
           : new CandleManager(connector);
    var series = new CandleSeries(typeof(TimeFrameCandle), security, timeFrame);
  9. Создание самой стратегии Cкользящая Cредняя:

    C#
    // создаем торговую стратегию, скользящие средние на 80 5-минуток и 10 5-минуток
    var strategy = new SmaStrategy(_bufferedChart, _candlesElem, _tradesElem, _shortMa, _shortElem, _longMa, _longElem, series)
    {
                Volume = 1,
                Portfolio = portfolio,
                Security = security,
                Connector = connector,
                LogLevel = DebugLogCheckBox.IsChecked == true ? LogLevels.Debug : LogLevels.Info,
    
                // by default interval is 1 min,
                // it is excessively for time range with several months
                UnrealizedPnLInterval = ((stopTime - startTime).Ticks / 1000).To<TimeSpan>()
    };
  10. Подписка на событие PnLChanged, для расчета кривой эквити (подробнее, в разделе Кривая эквити), а также визуальное наблюдение за прогрессом тестирования (в примере используется элементы в виде полос прогресса):

    C#
    // копируем параметры на визуальную панель
    statistic.Parameters.Clear();
    statistic.Parameters.AddRange(strategy.StatisticManager.Parameters);
    
    var pnlCurve = Curve.CreateCurve("P&L " + emulationInfo.StrategyName, emulationInfo.CurveColor, EquityCurveChartStyles.Area);
    var unrealizedPnLCurve = Curve.CreateCurve(LocalizedStrings.PnLUnreal + emulationInfo.StrategyName, Colors.Black);
    var commissionCurve = Curve.CreateCurve(LocalizedStrings.Str159 + " " + emulationInfo.StrategyName, Colors.Red, EquityCurveChartStyles.DashedLine);
    var posItems = PositionCurve.CreateCurve(emulationInfo.StrategyName, emulationInfo.CurveColor);
    
    strategy.PnLChanged += () =>
    {
        var pnl = new EquityData
            {
                Time = strategy.CurrentTime,
                Value = strategy.PnL - strategy.Commission ?? 0
            };
    
        var unrealizedPnL = new EquityData
            {
                Time = strategy.CurrentTime,
                Value = strategy.PnLManager.UnrealizedPnL
            };
    
        var commission = new EquityData
            {
                Time = strategy.CurrentTime,
                Value = strategy.Commission ?? 0
            };
    
        pnlCurve.Add(pnl);
        unrealizedPnLCurve.Add(unrealizedPnL);
        commissionCurve.Add(commission);
    };                        
    
    // и подписываемся на событие изменения времени, чтобы обновить ProgressBar
    connector.MarketTimeChanged += d =>
    {
        if (connector.CurrentTime < nextTime && connector.CurrentTime < stopTime)
            return;
    
        var steps = (connector.CurrentTime - startTime).Ticks / progressStep.Ticks + 1;
            nextTime = startTime + (steps * progressStep.Ticks).To<TimeSpan>();
            this.GuiAsync(() => progressBar.Value = steps);
    };
  11. Запуск начала тестирования:

    C#
    // запускаем эмуляцию
    foreach (var connector in _connectors)
    {
            connector.Connect();
    
            // устанавливаем комиссию
            connector.SendInMessage(new CommissionRuleMessage
            {
                Rule = new CommissionPerTradeRule { Value = 0.01m }
            });
    }