StockSharp
719 строк · 19.1 Кб
1#region S# License
2/******************************************************************************************
3NOTICE!!! This program and source code is owned and licensed by
4StockSharp, LLC, www.stocksharp.com
5Viewing or use of this code requires your acceptance of the license
6agreement found at https://github.com/StockSharp/StockSharp/blob/master/LICENSE
7Removal of this comment is a violation of the license agreement.
8
9Project: SampleHistoryTesting.SampleHistoryTestingPublic
10File: MainWindow.xaml.cs
11Created: 2015, 11, 11, 2:32 PM
12
13Copyright 2010 by StockSharp, LLC
14*******************************************************************************************/
15#endregion S# License
16namespace SampleHistoryTesting
17{
18using System;
19using System.IO;
20using System.Linq;
21using System.Windows;
22using System.Windows.Controls;
23using System.Windows.Media;
24using System.Collections.Generic;
25
26using Ecng.Xaml;
27using Ecng.Common;
28using Ecng.Collections;
29
30using StockSharp.Algo;
31using StockSharp.Algo.Commissions;
32using StockSharp.Algo.Storages;
33using StockSharp.Algo.Testing;
34using StockSharp.BusinessEntities;
35using StockSharp.Finam;
36using StockSharp.Yahoo;
37using StockSharp.Logging;
38using StockSharp.Messages;
39using StockSharp.Charting;
40using StockSharp.Xaml.Charting;
41using StockSharp.Localization;
42using StockSharp.Configuration;
43using StockSharp.Xaml;
44
45public partial class MainWindow
46{
47// emulation settings
48private sealed class EmulationInfo
49{
50public bool UseTicks { get; set; }
51public bool UseMarketDepth { get; set; }
52public DataType UseCandle { get; set; }
53public Color CurveColor { get; set; }
54public string StrategyName { get; set; }
55public bool UseOrderLog { get; set; }
56public bool UseLevel1 { get; set; }
57public Level1Fields? BuildField { get; set; }
58public Func<IdGenerator, IMessageAdapter> CustomHistoryAdapter { get; set; }
59public MarketDataStorageCache Cache { get; set; } = new();
60}
61
62private readonly List<ProgressBar> _progressBars = new();
63private readonly List<CheckBox> _checkBoxes = new();
64private readonly CachedSynchronizedList<HistoryEmulationConnector> _connectors = new();
65private DateTime _startEmulationTime;
66
67private readonly InMemoryExchangeInfoProvider _exchangeInfoProvider = new();
68private readonly (
69CheckBox enabled,
70ProgressBar progress,
71StatisticParameterGrid stat,
72EmulationInfo info,
73ChartPanel chart,
74EquityCurveChart equity,
75EquityCurveChart pos)[] _settings;
76
77public MainWindow()
78{
79InitializeComponent();
80
81HistoryPath.Folder = Paths.HistoryDataPath;
82
83SecId.Text = "SBER@TQBR";
84
85From.EditValue = Paths.HistoryBeginDate;
86To.EditValue = Paths.HistoryEndDate;
87
88CandleSettings.DataType = DataType.TimeFrame(TimeSpan.FromMinutes(1));
89
90_progressBars.AddRange(new[]
91{
92TicksProgress,
93TicksAndDepthsProgress,
94DepthsProgress,
95CandlesProgress,
96CandlesAndDepthsProgress,
97OrderLogProgress,
98LastTradeProgress,
99SpreadProgress,
100FinamCandlesProgress,
101YahooCandlesProgress,
102RandomProgress,
103});
104
105_checkBoxes.AddRange(new[]
106{
107TicksCheckBox,
108TicksAndDepthsCheckBox,
109DepthsCheckBox,
110CandlesCheckBox,
111CandlesAndDepthsCheckBox,
112OrderLogCheckBox,
113LastTradeCheckBox,
114SpreadCheckBox,
115FinamCandlesCheckBox,
116YahooCandlesCheckBox,
117RandomCheckBox,
118});
119
120// create backtesting modes
121_settings = new[]
122{
123(
124TicksCheckBox,
125TicksProgress,
126TicksParameterGrid,
127// ticks
128new EmulationInfo
129{
130UseTicks = true,
131CurveColor = Colors.DarkGreen,
132StrategyName = LocalizedStrings.Ticks
133},
134TicksChart,
135TicksEquity,
136TicksPosition
137),
138
139(
140TicksAndDepthsCheckBox,
141TicksAndDepthsProgress,
142TicksAndDepthsParameterGrid,
143// ticks + order book
144new EmulationInfo
145{
146UseTicks = true,
147UseMarketDepth = true,
148CurveColor = Colors.Red,
149StrategyName = LocalizedStrings.TicksAndDepths
150},
151TicksAndDepthsChart,
152TicksAndDepthsEquity,
153TicksAndDepthsPosition
154),
155
156(
157DepthsCheckBox,
158DepthsProgress,
159DepthsParameterGrid,
160// order book
161new EmulationInfo
162{
163UseMarketDepth = true,
164CurveColor = Colors.OrangeRed,
165StrategyName = LocalizedStrings.MarketDepths
166},
167DepthsChart,
168DepthsEquity,
169DepthsPosition
170),
171
172(
173CandlesCheckBox,
174CandlesProgress,
175CandlesParameterGrid,
176// candles
177new EmulationInfo
178{
179UseCandle = CandleSettings.DataType,
180CurveColor = Colors.DarkBlue,
181StrategyName = LocalizedStrings.Candles
182},
183CandlesChart,
184CandlesEquity,
185CandlesPosition
186),
187
188(
189CandlesAndDepthsCheckBox,
190CandlesAndDepthsProgress,
191CandlesAndDepthsParameterGrid,
192// candles + orderbook
193new EmulationInfo
194{
195UseMarketDepth = true,
196UseCandle = CandleSettings.DataType,
197CurveColor = Colors.Cyan,
198StrategyName = LocalizedStrings.CandlesAndDepths
199},
200CandlesAndDepthsChart,
201CandlesAndDepthsEquity,
202CandlesAndDepthsPosition
203),
204
205(
206OrderLogCheckBox,
207OrderLogProgress,
208OrderLogParameterGrid,
209// order log
210new EmulationInfo
211{
212UseOrderLog = true,
213CurveColor = Colors.CornflowerBlue,
214StrategyName = LocalizedStrings.OrderLog
215},
216OrderLogChart,
217OrderLogEquity,
218OrderLogPosition
219),
220
221(
222LastTradeCheckBox,
223LastTradeProgress,
224LastTradeParameterGrid,
225// order log
226new EmulationInfo
227{
228UseLevel1 = true,
229CurveColor = Colors.Aquamarine,
230StrategyName = LocalizedStrings.Level1,
231BuildField = Level1Fields.LastTradePrice,
232},
233LastTradeChart,
234LastTradeEquity,
235LastTradePosition
236),
237
238(
239SpreadCheckBox,
240SpreadProgress,
241SpreadParameterGrid,
242// order log
243new EmulationInfo
244{
245UseLevel1 = true,
246CurveColor = Colors.Aquamarine,
247StrategyName = LocalizedStrings.Level1,
248BuildField = Level1Fields.SpreadMiddle,
249},
250SpreadChart,
251SpreadEquity,
252SpreadPosition
253),
254
255(
256FinamCandlesCheckBox,
257FinamCandlesProgress,
258FinamCandlesParameterGrid,
259// candles
260new EmulationInfo
261{
262UseCandle = CandleSettings.DataType,
263CustomHistoryAdapter = g => new FinamMessageAdapter(g),
264CurveColor = Colors.DarkBlue,
265StrategyName = LocalizedStrings.FinamCandles
266},
267FinamCandlesChart,
268FinamCandlesEquity,
269FinamCandlesPosition
270),
271
272(
273YahooCandlesCheckBox,
274YahooCandlesProgress,
275YahooCandlesParameterGrid,
276// candles
277new EmulationInfo
278{
279UseCandle = CandleSettings.DataType,
280CustomHistoryAdapter = g => new YahooMessageAdapter(g),
281CurveColor = Colors.DarkBlue,
282StrategyName = LocalizedStrings.YahooCandles
283},
284YahooCandlesChart,
285YahooCandlesEquity,
286YahooCandlesPosition
287),
288
289(
290RandomCheckBox,
291RandomProgress,
292RandomParameterGrid,
293// candles
294new EmulationInfo
295{
296UseCandle = CandleSettings.DataType,
297CustomHistoryAdapter = g => new OwnMessageAdapter(g),
298CurveColor = Colors.DarkBlue,
299StrategyName = LocalizedStrings.Custom
300},
301RandomChart,
302RandomEquity,
303RandomPosition
304),
305};
306}
307
308private void StartBtnClick(object sender, RoutedEventArgs e)
309{
310if (_connectors.Count > 0)
311{
312foreach (var connector in _connectors.Cache)
313connector.Start();
314
315return;
316}
317
318if (HistoryPath.Folder.IsEmpty() || !Directory.Exists(HistoryPath.Folder))
319{
320MessageBox.Show(this, LocalizedStrings.WrongPath);
321return;
322}
323
324if (_connectors.Any(t => t.State != ChannelStates.Stopped))
325{
326MessageBox.Show(this, LocalizedStrings.AlreadyStarted);
327return;
328}
329
330var id = SecId.Text.ToSecurityId();
331
332var secCode = id.SecurityCode;
333var board = _exchangeInfoProvider.GetOrCreateBoard(id.BoardCode);
334
335// create test security
336var security = new Security
337{
338Id = SecId.Text, // sec id has the same name as folder with historical data
339Code = secCode,
340Board = board,
341};
342
343// storage to historical data
344var storageRegistry = new StorageRegistry
345{
346// set historical path
347DefaultDrive = new LocalMarketDataDrive(HistoryPath.Folder)
348};
349
350var startTime = ((DateTime)From.EditValue).UtcKind();
351var stopTime = ((DateTime)To.EditValue).UtcKind();
352
353// (ru only) ОЛ необходимо загружать с 18.45 пред дня, чтобы стаканы строились правильно
354if (OrderLogCheckBox.IsChecked == true)
355startTime = startTime.Subtract(TimeSpan.FromDays(1)).AddHours(18).AddMinutes(45).AddTicks(1).ApplyMoscow().UtcDateTime;
356
357// set ProgressBar bounds
358_progressBars.ForEach(p =>
359{
360p.Value = 0;
361p.Maximum = 100;
362});
363
364var logManager = new LogManager();
365var fileLogListener = new FileLogListener("sample.log");
366logManager.Listeners.Add(fileLogListener);
367//logManager.Listeners.Add(new DebugLogListener()); // for track logs in output window in Vusial Studio (poor performance).
368
369var generateDepths = GenDepthsCheckBox.IsChecked == true;
370var maxDepth = MaxDepth.Text.To<int>();
371var maxVolume = MaxVolume.Text.To<int>();
372var secId = security.ToSecurityId();
373
374SetIsEnabled(false, false, false);
375
376foreach (var (enabled, progressBar, statistic, emulationInfo, chart, equity, pos) in _settings)
377{
378if (enabled.IsChecked == false)
379continue;
380
381var title = emulationInfo.StrategyName;
382
383ClearChart(chart, equity, pos);
384
385var level1Info = new Level1ChangeMessage
386{
387SecurityId = secId,
388ServerTime = startTime,
389}
390//.TryAdd(Level1Fields.PriceStep, security.PriceStep)
391//.TryAdd(Level1Fields.StepPrice, 0.01m)
392.TryAdd(Level1Fields.MinPrice, 0.01m)
393.TryAdd(Level1Fields.MaxPrice, 1000000m)
394.TryAdd(Level1Fields.MarginBuy, 10000m)
395.TryAdd(Level1Fields.MarginSell, 10000m)
396;
397
398var secProvider = (ISecurityProvider)new CollectionSecurityProvider(new[] { security });
399var pf = Portfolio.CreateSimulator();
400pf.CurrentValue = 1000;
401
402// create backtesting connector
403var connector = new HistoryEmulationConnector(secProvider, new[] { pf })
404{
405EmulationAdapter =
406{
407Settings =
408{
409// match order if historical price touched our limit order price.
410// It is terned off, and price should go through limit order price level
411// (more "severe" test mode)
412MatchOnTouch = false,
413
414// 1 cent commission for trade
415CommissionRules = new ICommissionRule[]
416{
417new CommissionPerTradeRule { Value = 0.01m },
418},
419}
420},
421
422//UseExternalCandleSource = emulationInfo.UseCandleTimeFrame != null,
423
424//CreateDepthFromOrdersLog = emulationInfo.UseOrderLog,
425//CreateTradesFromOrdersLog = emulationInfo.UseOrderLog,
426
427HistoryMessageAdapter =
428{
429StorageRegistry = storageRegistry,
430
431OrderLogMarketDepthBuilders =
432{
433{
434secId,
435new OrderLogMarketDepthBuilder(secId)
436}
437},
438
439AdapterCache = emulationInfo.Cache,
440},
441
442// set market time freq as time frame
443//MarketTimeChangedInterval = timeFrame,
444};
445
446((ILogSource)connector).LogLevel = DebugLogCheckBox.IsChecked == true ? LogLevels.Debug : LogLevels.Info;
447
448logManager.Sources.Add(connector);
449
450// create strategy based on 80 5-min и 10 5-min
451var strategy = new SmaStrategy
452{
453LongSma = 80,
454ShortSma = 10,
455Volume = 1,
456Portfolio = connector.Portfolios.First(),
457Security = security,
458Connector = connector,
459LogLevel = DebugLogCheckBox.IsChecked == true ? LogLevels.Debug : LogLevels.Info,
460
461// by default interval is 1 min,
462// it is excessively for time range with several months
463UnrealizedPnLInterval = ((stopTime - startTime).Ticks / 1000).To<TimeSpan>(),
464};
465
466if (emulationInfo.UseCandle is not null)
467{
468strategy.CandleType = emulationInfo.UseCandle;
469
470if (strategy.CandleType != DataType.TimeFrame(TimeSpan.FromMinutes(1)))
471{
472strategy.BuildFrom = DataType.TimeFrame(TimeSpan.FromMinutes(1));
473}
474}
475else if (emulationInfo.UseTicks)
476strategy.BuildFrom = DataType.Ticks;
477else if (emulationInfo.UseLevel1)
478{
479strategy.BuildFrom = DataType.Level1;
480strategy.BuildField = emulationInfo.BuildField;
481}
482else if (emulationInfo.UseOrderLog)
483strategy.BuildFrom = DataType.OrderLog;
484else if (emulationInfo.UseMarketDepth)
485strategy.BuildFrom = DataType.MarketDepth;
486
487chart.IsInteracted = false;
488strategy.SetChart(chart);
489
490logManager.Sources.Add(strategy);
491
492if (emulationInfo.CustomHistoryAdapter != null)
493{
494connector.Adapter.InnerAdapters.Remove(connector.MarketDataAdapter);
495
496var emu = connector.EmulationAdapter.Emulator;
497connector.Adapter.InnerAdapters.Add(new EmulationMessageAdapter(emulationInfo.CustomHistoryAdapter(connector.TransactionIdGenerator), new InMemoryMessageChannel(new MessageByLocalTimeQueue(), "History out", err => err.LogError()), true, emu.SecurityProvider, emu.PortfolioProvider, emu.ExchangeInfoProvider));
498}
499
500// set history range
501connector.HistoryMessageAdapter.StartDate = startTime;
502connector.HistoryMessageAdapter.StopDate = stopTime;
503
504connector.SecurityReceived += (subscr, s) =>
505{
506if (s != security)
507return;
508
509// fill level1 values
510connector.EmulationAdapter.SendInMessage(level1Info);
511
512if (emulationInfo.UseMarketDepth)
513{
514connector.SubscribeMarketDepth(security);
515
516if (
517// if order book will be generated
518generateDepths ||
519// or backtesting will be on candles
520emulationInfo.UseCandle is not null
521)
522{
523// if no have order book historical data, but strategy is required,
524// use generator based on last prices
525connector.RegisterMarketDepth(new TrendMarketDepthGenerator(connector.GetSecurityId(security))
526{
527Interval = TimeSpan.FromSeconds(1), // order book freq refresh is 1 sec
528MaxAsksDepth = maxDepth,
529MaxBidsDepth = maxDepth,
530UseTradeVolume = true,
531MaxVolume = maxVolume,
532MinSpreadStepCount = 2, // min spread generation is 2 pips
533MaxSpreadStepCount = 5, // max spread generation size (prevent extremely size)
534MaxPriceStepCount = 3 // pips size,
535});
536}
537}
538
539if (emulationInfo.UseOrderLog)
540{
541connector.SubscribeOrderLog(security);
542}
543
544if (emulationInfo.UseTicks)
545{
546connector.SubscribeTrades(security);
547}
548
549if (emulationInfo.UseLevel1)
550{
551connector.SubscribeLevel1(security);
552}
553};
554
555// fill parameters panel
556statistic.Parameters.Clear();
557statistic.Parameters.AddRange(strategy.StatisticManager.Parameters);
558
559var pnlCurve = equity.CreateCurve(LocalizedStrings.PnL + " " + emulationInfo.StrategyName, Colors.Green, Colors.Red, ChartIndicatorDrawStyles.Area);
560var realizedPnLCurve = equity.CreateCurve(LocalizedStrings.PnLRealized + " " + emulationInfo.StrategyName, Colors.Black, ChartIndicatorDrawStyles.Line);
561var unrealizedPnLCurve = equity.CreateCurve(LocalizedStrings.PnLUnreal + " " + emulationInfo.StrategyName, Colors.DarkGray, ChartIndicatorDrawStyles.Line);
562var commissionCurve = equity.CreateCurve(LocalizedStrings.Commission + " " + emulationInfo.StrategyName, Colors.Red, ChartIndicatorDrawStyles.DashedLine);
563
564strategy.PnLReceived2 += (s, pf, t, r, u, c) =>
565{
566var data = equity.CreateData();
567
568data
569.Group(t)
570.Add(pnlCurve, r - (c ?? 0))
571.Add(realizedPnLCurve, r)
572.Add(unrealizedPnLCurve, u ?? 0)
573.Add(commissionCurve, c ?? 0);
574
575equity.Draw(data);
576};
577
578var posItems = pos.CreateCurve(emulationInfo.StrategyName, emulationInfo.CurveColor, ChartIndicatorDrawStyles.Line);
579
580strategy.PositionReceived += (s, p) =>
581{
582var data = pos.CreateData();
583
584data
585.Group(p.LocalTime)
586.Add(posItems, p.CurrentValue);
587
588pos.Draw(data);
589};
590
591connector.ProgressChanged += steps => this.GuiAsync(() => progressBar.Value = steps);
592
593connector.StateChanged2 += state =>
594{
595if (state == ChannelStates.Stopped)
596{
597strategy.Stop();
598
599SetIsChartEnabled(chart, false);
600
601if (_connectors.All(c => c.State == ChannelStates.Stopped))
602{
603logManager.Dispose();
604_connectors.Clear();
605
606SetIsEnabled(true, false, false);
607}
608
609this.GuiAsync(() =>
610{
611if (connector.IsFinished)
612{
613progressBar.Value = progressBar.Maximum;
614MessageBox.Show(this, LocalizedStrings.CompletedIn.Put(DateTime.Now - _startEmulationTime), title);
615}
616else
617MessageBox.Show(this, LocalizedStrings.Cancelled, title);
618});
619}
620else if (state == ChannelStates.Started)
621{
622if (_connectors.All(c => c.State == ChannelStates.Started))
623SetIsEnabled(false, true, true);
624
625SetIsChartEnabled(chart, true);
626}
627else if (state == ChannelStates.Suspended)
628{
629if (_connectors.All(c => c.State == ChannelStates.Suspended))
630SetIsEnabled(true, false, true);
631}
632};
633
634//if (ShowDepth.IsChecked == true)
635//{
636// MarketDepth.UpdateFormat(security);
637
638// connector.NewMessage += message =>
639// {
640// if (message is QuoteChangeMessage quoteMsg)
641// MarketDepth.UpdateDepth(quoteMsg);
642// };
643//}
644
645// start strategy before emulation started
646strategy.Start();
647
648_connectors.Add(connector);
649
650progressBar.Value = 0;
651}
652
653_startEmulationTime = DateTime.Now;
654
655// start emulation
656foreach (var connector in _connectors.Cache)
657{
658// raise NewSecurities and NewPortfolio for full fill strategy properties
659connector.Connect();
660
661// start historical data loading when connection established successfully and all data subscribed
662connector.Start();
663}
664
665TabControl.Items.Cast<TabItem>().First(i => i.Visibility == Visibility.Visible).IsSelected = true;
666}
667
668private void CheckBoxClick(object sender, RoutedEventArgs e)
669{
670var isEnabled = _checkBoxes.Any(c => c.IsChecked == true);
671
672StartBtn.IsEnabled = isEnabled;
673TabControl.Visibility = isEnabled ? Visibility.Visible : Visibility.Collapsed;
674}
675
676private void StopBtnClick(object sender, RoutedEventArgs e)
677{
678foreach (var connector in _connectors.Cache)
679{
680connector.Disconnect();
681}
682}
683
684private void PauseBtnClick(object sender, RoutedEventArgs e)
685{
686foreach (var connector in _connectors.Cache)
687{
688connector.Suspend();
689}
690}
691
692private void ClearChart(IChart chart, EquityCurveChart equity, EquityCurveChart position)
693{
694chart.ClearAreas();
695equity.Clear();
696position.Clear();
697}
698
699private void SetIsEnabled(bool canStart, bool canSuspend, bool canStop)
700{
701this.GuiAsync(() =>
702{
703StopBtn.IsEnabled = canStop;
704StartBtn.IsEnabled = canStart;
705PauseBtn.IsEnabled = canSuspend;
706
707foreach (var checkBox in _checkBoxes)
708{
709checkBox.IsEnabled = !canStop;
710}
711});
712}
713
714private void SetIsChartEnabled(IChart chart, bool started)
715{
716this.GuiAsync(() => chart.IsAutoRange = started);
717}
718}
719}