namespace SampleHistoryTesting { using System; using System.IO; using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Collections.Generic; using Ecng.Xaml; using Ecng.Common; using Ecng.Collections; using StockSharp.Algo.Candles; using StockSharp.Algo.Storages; using StockSharp.Algo.Testing; using StockSharp.Algo.Strategies; using StockSharp.Algo.Indicators.Trend; using StockSharp.BusinessEntities; using StockSharp.Xaml; using StockSharp.Logging; using StockSharp.Xaml.Charting; public partial class MainWindow { // вспомогательный класс для настроек тестирования internal sealed class EmulationInfo { public EmulationInfo() { UseCandleTimeFrame = TimeSpan.Zero; } public bool UseMarketDepth { get; set; } public TimeSpan UseCandleTimeFrame { get; set; } public Color CurveColor { get; set; } public string StrategyName { get; set; } } private DateTime _startEmulationTime; private readonly List _traders = new List(); public MainWindow() { InitializeComponent(); } private void FindPathClick(object sender, RoutedEventArgs e) { var dlg = new System.Windows.Forms.FolderBrowserDialog(); if (!HistoryPath.Text.IsEmpty()) dlg.SelectedPath = HistoryPath.Text; if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) { HistoryPath.Text = dlg.SelectedPath; } } private void StartBtnClick(object sender, RoutedEventArgs e) { if (HistoryPath.Text.IsEmpty() || !Directory.Exists(HistoryPath.Text)) { MessageBox.Show(this, "Неправильный путь."); return; } if (_traders.Any(t => t.State != EmulationStates.Stopped)) { MessageBox.Show(this, "Уже запущен."); return; } var timeFrame = TimeSpan.FromMinutes(5); // создаем настройки для тестирования var settings = new[] { new Tuple( TicksCheckBox, TicksTestingProcess, TicksParametersPanel, // тест только на тиках new EmulationInfo {CurveColor = Colors.DarkGreen, StrategyName = "На тиках"}), new Tuple( TicksAndDepthsCheckBox, TicksAndDepthsTestingProcess, TicksAndDepthsParametersPanel, // тест на тиках + стаканы new EmulationInfo {UseMarketDepth = true, CurveColor = Colors.Red, StrategyName = "На тиках & стаканах"}), new Tuple( CandlesCheckBox, CandlesTestingProcess, CandlesParametersPanel, // тест на свечках new EmulationInfo {UseCandleTimeFrame = timeFrame, CurveColor = Colors.DarkBlue, StrategyName = "На свечках"}), new Tuple( CandlesAndDepthsCheckBox, CandlesAndDepthsTestingProcess, CandlesAndDepthsParametersPanel, // тест на свечках + стаканы new EmulationInfo {UseMarketDepth = true, UseCandleTimeFrame = timeFrame, CurveColor = Colors.Cyan, StrategyName = "На свечках&стаканах"}) }; // хранилище, через которое будет производиться доступ к тиковой и котировочной базе var storageRegistry = new StorageRegistry(); // изменяем путь, используемый по умолчанию ((LocalMarketDataDrive)storageRegistry.DefaultDrive).Path = HistoryPath.Text; // используем алфавитное хранилище ((LocalMarketDataDrive)storageRegistry.DefaultDrive).UseAlphabeticPath = true; var startTime = new DateTime(2012, 10, 1); var stopTime = new DateTime(2012, 10, 25); // задаем шаг ProgressBar var progressStep = ((stopTime - startTime).Ticks / 100).To(); // в реальности период может быть другим, и это зависит от объема данных, // хранящихся по пути HistoryPath, TicksTestingProcess.Maximum = TicksAndDepthsTestingProcess.Maximum = CandlesTestingProcess.Maximum = 100; TicksTestingProcess.Value = TicksAndDepthsTestingProcess.Value = CandlesTestingProcess.Value = 0; var logManager = new LogManager(); logManager.MaxMessageCount = -1; // без этого будет буферизация логов и они будут неактуальны при остановке в дебаггере. logManager.Listeners.Add(new FileLogListener("sample.log")); //logManager.Listeners.Add(new DebugLogListener()); // чтобы смотреть логи в отладчике - работает медленно. var generateDepths = GenDepthsCheckBox.IsChecked; foreach (var set in settings) { if (set.Item1.IsChecked == false) continue; var progressBar = set.Item2; var statistic = set.Item3; var emulationInfo = set.Item4; // создаем тестовый инструмент, на котором будет производится тестирование var security = new Security { Id = "RIZ2@FORTS", // по идентификатору инструмента будет искаться папка с историческими маркет данными Code = "RIZ2", Name = "RTS-12.12", MinStepSize = 10, MinStepPrice = 2, MinPrice = 10, MaxPrice = 1000000, MarginBuy = 10000, // задаем ГО MarginSell = 10000, ExchangeBoard = ExchangeBoard.Forts, }; // тестовый портфель var portfolio = new Portfolio { Name = "test account", BeginValue = 1000000, }; // создаем шлюз для эмуляции // инициализируем настройки (инструмент в истории обновляется раз в секунду) var trader = new EmulationTrader( new[] { security }, new[] { portfolio }) { MarketTimeChangedInterval = timeFrame, StorageRegistry = storageRegistry, // использовать свечки UseCandlesTimeFrame = emulationInfo.UseCandleTimeFrame, MarketEmulator = { Settings = { // использовать стаканы UseMarketDepth = emulationInfo.UseMarketDepth, // проверка что стаканы соответствуют сделкам. Улучшает реалистичность тестирования. SyncDepthToTrades = true, // сведение сделки в эмуляторе если цена коснулась нашей лимитной заявки. // Если выключено - требуется "прохождение цены сквозь уровень" // (более "суровый" режим тестирования.) FillOnTouch = false, } } }; ((ILogSource)trader).LogLevel = DebugLogCheckBox.IsChecked == true ? LogLevels.Debug : LogLevels.Info; logManager.Sources.Add(trader); if (emulationInfo.UseMarketDepth) trader.RegisterMarketDepth(security); if (emulationInfo.UseMarketDepth // тест подразумевает наличие стаканов && (generateDepths == true // если выбрана генерация стаканов вместо реальных стаканов || emulationInfo.UseCandleTimeFrame != TimeSpan.Zero // для свечей генерируем стаканы всегда )) { // если история по стаканам отсутствует, но стаканы необходимы для стратегии, // то их можно сгенерировать на основании цен последних сделок или свечек. trader.RegisterMarketDepth(new TrendMarketDepthGenerator(security) { Interval = TimeSpan.FromSeconds(1), // стакан для инструмента в истории обновляется раз в секунду MaxAsksDepth = 1, MaxBidsDepth = 1, UseTradeVolume = true, MaxVolume = 1, MinSpreadStepCount = 2, // минимальный генерируемый спред - 2 минимальных шага цены MaxSpreadStepCount = 5, // не генерировать спрэд между лучшим бид и аск больше чем 5 минимальных шагов цены - нужно чтобы при генерации из свечей не получалось слишком широкого спреда. }); } // соединяемся с трейдером и запускаем экспорт, // чтобы инициализировать переданными инструментами и портфелями необходимые свойства EmulationTrader trader.Connect(); trader.StartExport(); var candleManager = new CandleManager(trader); var series = new CandleSeries(typeof(TimeFrameCandle), security, timeFrame); var basket = new BasketStrategy(BasketStrategyFinishModes.All) { Volume = 1, Portfolio = portfolio, Security = security, Trader = trader, LogLevel = DebugLogCheckBox.IsChecked == true ? LogLevels.Debug : LogLevels.Info }; // создаем торговую стратегию, скользящие средние на 80 5-минуток и 10 5-минуток var strategy1 = new SmaStrategy(series, new SimpleMovingAverage { Length = 80 }, new SimpleMovingAverage { Length = 10 }); var strategy2 = new SmaStrategy(series, new SimpleMovingAverage { Length = 70 }, new SimpleMovingAverage { Length = 15 }); var strategy3 = new SmaStrategy(series, new SimpleMovingAverage { Length = 60 }, new SimpleMovingAverage { Length = 20 }); basket.ChildStrategies.Add(strategy1); basket.ChildStrategies.Add(strategy2); basket.ChildStrategies.Add(strategy3); logManager.Sources.Add(basket); // копируем параметры на визуальную панель statistic.Parameters.Clear(); statistic.Parameters.AddRange(basket.StatisticManager.Parameters); var curveItems = Curve.CreateCurve(emulationInfo.StrategyName, emulationInfo.CurveColor); var posItems = PositionCurve.CreateCurve(emulationInfo.StrategyName, emulationInfo.CurveColor); basket.PnLChanged += () => { var data = new EquityData { Time = basket.GetMarketTime(), Value = basket.PnL, }; this.GuiAsync(() => curveItems.Add(data)); }; basket.PositionChanged += () => { var data = new EquityData { Time = basket.GetMarketTime(), Value = basket.Position }; this.GuiAsync(() => posItems.Add(data)); }; var nextTime = startTime + progressStep; // и подписываемся на событие изменения времени, чтобы обновить ProgressBar trader.MarketTimeChanged += d => { if (trader.CurrentTime >= nextTime || trader.CurrentTime >= stopTime) { nextTime += progressStep; this.GuiAsync(() => progressBar.Value++); } }; trader.StateChanged += (oldState, newState) => { if (trader.State == EmulationStates.Stopped) { this.GuiAsync(() => { if (trader.IsFinished) { progressBar.Value = progressBar.Maximum; MessageBox.Show("Закончено за " + (DateTime.Now - _startEmulationTime)); } else MessageBox.Show("Отменено"); }); } else if (trader.State == EmulationStates.Started) { // запускаем стратегию когда эмулятор запустился basket.Start(); candleManager.Start(series); } }; _traders.Add(trader); } _startEmulationTime = DateTime.Now; // запускаем эмуляцию foreach (var trader in _traders) { // указываем даты начала и конца тестирования trader.Start(startTime, stopTime); } TicksCheckBox.IsEnabled = TicksAndDepthsCheckBox.IsEnabled = CandlesCheckBox.IsEnabled = CandlesAndDepthsCheckBox.IsEnabled = false; } private void CheckBoxClick(object sender, RoutedEventArgs e) { if (TicksCheckBox.IsChecked == true || TicksAndDepthsCheckBox.IsChecked == true || CandlesCheckBox.IsChecked == true || CandlesAndDepthsCheckBox.IsChecked == true) { StartBtn.IsEnabled = true; TabControl.Visibility = Visibility.Visible; } else { StartBtn.IsEnabled = false; TabControl.Visibility = Visibility.Collapsed; } } } }