StockSharp

Форк
0
719 строк · 19.1 Кб
1
#region S# License
2
/******************************************************************************************
3
NOTICE!!!  This program and source code is owned and licensed by
4
StockSharp, LLC, www.stocksharp.com
5
Viewing or use of this code requires your acceptance of the license
6
agreement found at https://github.com/StockSharp/StockSharp/blob/master/LICENSE
7
Removal of this comment is a violation of the license agreement.
8

9
Project: SampleHistoryTesting.SampleHistoryTestingPublic
10
File: MainWindow.xaml.cs
11
Created: 2015, 11, 11, 2:32 PM
12

13
Copyright 2010 by StockSharp, LLC
14
*******************************************************************************************/
15
#endregion S# License
16
namespace SampleHistoryTesting
17
{
18
	using System;
19
	using System.IO;
20
	using System.Linq;
21
	using System.Windows;
22
	using System.Windows.Controls;
23
	using System.Windows.Media;
24
	using System.Collections.Generic;
25

26
	using Ecng.Xaml;
27
	using Ecng.Common;
28
	using Ecng.Collections;
29

30
	using StockSharp.Algo;
31
	using StockSharp.Algo.Commissions;
32
	using StockSharp.Algo.Storages;
33
	using StockSharp.Algo.Testing;
34
	using StockSharp.BusinessEntities;
35
	using StockSharp.Finam;
36
	using StockSharp.Yahoo;
37
	using StockSharp.Logging;
38
	using StockSharp.Messages;
39
	using StockSharp.Charting;
40
	using StockSharp.Xaml.Charting;
41
	using StockSharp.Localization;
42
	using StockSharp.Configuration;
43
	using StockSharp.Xaml;
44

45
	public partial class MainWindow
46
	{
47
		// emulation settings
48
		private sealed class EmulationInfo
49
		{
50
			public bool UseTicks { get; set; }
51
			public bool UseMarketDepth { get; set; }
52
			public DataType UseCandle { get; set; }
53
			public Color CurveColor { get; set; }
54
			public string StrategyName { get; set; }
55
			public bool UseOrderLog { get; set; }
56
			public bool UseLevel1 { get; set; }
57
            public Level1Fields? BuildField { get; set; }
58
            public Func<IdGenerator, IMessageAdapter> CustomHistoryAdapter { get; set; }
59
			public MarketDataStorageCache Cache { get; set; } = new();
60
		}
61

62
		private readonly List<ProgressBar> _progressBars = new();
63
		private readonly List<CheckBox> _checkBoxes = new();
64
		private readonly CachedSynchronizedList<HistoryEmulationConnector> _connectors = new();
65
		private DateTime _startEmulationTime;
66

67
		private readonly InMemoryExchangeInfoProvider _exchangeInfoProvider = new();
68
		private readonly (
69
			CheckBox enabled,
70
			ProgressBar progress,
71
			StatisticParameterGrid stat,
72
			EmulationInfo info,
73
			ChartPanel chart,
74
			EquityCurveChart equity,
75
			EquityCurveChart pos)[] _settings;
76

77
		public MainWindow()
78
		{
79
			InitializeComponent();
80

81
			HistoryPath.Folder = Paths.HistoryDataPath;
82

83
			SecId.Text = "SBER@TQBR";
84

85
			From.EditValue = Paths.HistoryBeginDate;
86
			To.EditValue = Paths.HistoryEndDate;
87

88
			CandleSettings.DataType = DataType.TimeFrame(TimeSpan.FromMinutes(1));
89

90
			_progressBars.AddRange(new[]
91
			{
92
				TicksProgress,
93
				TicksAndDepthsProgress,
94
				DepthsProgress,
95
				CandlesProgress,
96
				CandlesAndDepthsProgress,
97
				OrderLogProgress,
98
				LastTradeProgress,
99
				SpreadProgress,
100
				FinamCandlesProgress,
101
				YahooCandlesProgress,
102
				RandomProgress,
103
			});
104

105
			_checkBoxes.AddRange(new[]
106
			{
107
				TicksCheckBox,
108
				TicksAndDepthsCheckBox,
109
				DepthsCheckBox,
110
				CandlesCheckBox,
111
				CandlesAndDepthsCheckBox,
112
				OrderLogCheckBox,
113
				LastTradeCheckBox,
114
				SpreadCheckBox,
115
				FinamCandlesCheckBox,
116
				YahooCandlesCheckBox,
117
				RandomCheckBox,
118
			});
119

120
			// create backtesting modes
121
			_settings = new[]
122
			{
123
				(
124
					TicksCheckBox,
125
					TicksProgress,
126
					TicksParameterGrid,
127
					// ticks
128
					new EmulationInfo
129
					{
130
						UseTicks = true,
131
						CurveColor = Colors.DarkGreen,
132
						StrategyName = LocalizedStrings.Ticks
133
					},
134
					TicksChart,
135
					TicksEquity,
136
					TicksPosition
137
				),
138

139
				(
140
					TicksAndDepthsCheckBox,
141
					TicksAndDepthsProgress,
142
					TicksAndDepthsParameterGrid,
143
					// ticks + order book
144
					new EmulationInfo
145
					{
146
						UseTicks = true,
147
						UseMarketDepth = true,
148
						CurveColor = Colors.Red,
149
						StrategyName = LocalizedStrings.TicksAndDepths
150
					},
151
					TicksAndDepthsChart,
152
					TicksAndDepthsEquity,
153
					TicksAndDepthsPosition
154
				),
155

156
				(
157
					DepthsCheckBox,
158
					DepthsProgress,
159
					DepthsParameterGrid,
160
					// order book
161
					new EmulationInfo
162
					{
163
						UseMarketDepth = true,
164
						CurveColor = Colors.OrangeRed,
165
						StrategyName = LocalizedStrings.MarketDepths
166
					},
167
					DepthsChart,
168
					DepthsEquity,
169
					DepthsPosition
170
				),
171

172
				(
173
					CandlesCheckBox,
174
					CandlesProgress,
175
					CandlesParameterGrid,
176
					// candles
177
					new EmulationInfo
178
					{
179
						UseCandle = CandleSettings.DataType,
180
						CurveColor = Colors.DarkBlue,
181
						StrategyName = LocalizedStrings.Candles
182
					},
183
					CandlesChart,
184
					CandlesEquity,
185
					CandlesPosition
186
				),
187

188
				(
189
					CandlesAndDepthsCheckBox,
190
					CandlesAndDepthsProgress,
191
					CandlesAndDepthsParameterGrid,
192
					// candles + orderbook
193
					new EmulationInfo
194
					{
195
						UseMarketDepth = true,
196
						UseCandle = CandleSettings.DataType,
197
						CurveColor = Colors.Cyan,
198
						StrategyName = LocalizedStrings.CandlesAndDepths
199
					},
200
					CandlesAndDepthsChart,
201
					CandlesAndDepthsEquity,
202
					CandlesAndDepthsPosition
203
				),
204

205
				(
206
					OrderLogCheckBox,
207
					OrderLogProgress,
208
					OrderLogParameterGrid,
209
					// order log
210
					new EmulationInfo
211
					{
212
						UseOrderLog = true,
213
						CurveColor = Colors.CornflowerBlue,
214
						StrategyName = LocalizedStrings.OrderLog
215
					},
216
					OrderLogChart,
217
					OrderLogEquity,
218
					OrderLogPosition
219
				),
220

221
				(
222
					LastTradeCheckBox,
223
					LastTradeProgress,
224
					LastTradeParameterGrid,
225
					// order log
226
					new EmulationInfo
227
					{
228
						UseLevel1 = true,
229
						CurveColor = Colors.Aquamarine,
230
						StrategyName = LocalizedStrings.Level1,
231
						BuildField = Level1Fields.LastTradePrice,
232
					},
233
					LastTradeChart,
234
					LastTradeEquity,
235
					LastTradePosition
236
				),
237

238
				(
239
					SpreadCheckBox,
240
					SpreadProgress,
241
					SpreadParameterGrid,
242
					// order log
243
					new EmulationInfo
244
					{
245
						UseLevel1 = true,
246
						CurveColor = Colors.Aquamarine,
247
						StrategyName = LocalizedStrings.Level1,
248
						BuildField = Level1Fields.SpreadMiddle,
249
					},
250
					SpreadChart,
251
					SpreadEquity,
252
					SpreadPosition
253
				),
254

255
				(
256
					FinamCandlesCheckBox,
257
					FinamCandlesProgress,
258
					FinamCandlesParameterGrid,
259
					// candles
260
					new EmulationInfo
261
					{
262
						UseCandle = CandleSettings.DataType,
263
						CustomHistoryAdapter = g => new FinamMessageAdapter(g),
264
						CurveColor = Colors.DarkBlue,
265
						StrategyName = LocalizedStrings.FinamCandles
266
					},
267
					FinamCandlesChart,
268
					FinamCandlesEquity,
269
					FinamCandlesPosition
270
				),
271

272
				(
273
					YahooCandlesCheckBox,
274
					YahooCandlesProgress,
275
					YahooCandlesParameterGrid,
276
					// candles
277
					new EmulationInfo
278
					{
279
						UseCandle = CandleSettings.DataType,
280
						CustomHistoryAdapter = g => new YahooMessageAdapter(g),
281
						CurveColor = Colors.DarkBlue,
282
						StrategyName = LocalizedStrings.YahooCandles
283
					},
284
					YahooCandlesChart,
285
					YahooCandlesEquity,
286
					YahooCandlesPosition
287
				),
288

289
				(
290
					RandomCheckBox,
291
					RandomProgress,
292
					RandomParameterGrid,
293
					// candles
294
					new EmulationInfo
295
					{
296
						UseCandle = CandleSettings.DataType,
297
						CustomHistoryAdapter = g => new OwnMessageAdapter(g),
298
						CurveColor = Colors.DarkBlue,
299
						StrategyName = LocalizedStrings.Custom
300
					},
301
					RandomChart,
302
					RandomEquity,
303
					RandomPosition
304
				),
305
			};
306
		}
307

308
		private void StartBtnClick(object sender, RoutedEventArgs e)
309
		{
310
			if (_connectors.Count > 0)
311
			{
312
				foreach (var connector in _connectors.Cache)
313
					connector.Start();
314

315
				return;
316
			}
317

318
			if (HistoryPath.Folder.IsEmpty() || !Directory.Exists(HistoryPath.Folder))
319
			{
320
				MessageBox.Show(this, LocalizedStrings.WrongPath);
321
				return;
322
			}
323

324
			if (_connectors.Any(t => t.State != ChannelStates.Stopped))
325
			{
326
				MessageBox.Show(this, LocalizedStrings.AlreadyStarted);
327
				return;
328
			}
329

330
			var id = SecId.Text.ToSecurityId();
331

332
			var secCode = id.SecurityCode;
333
			var board = _exchangeInfoProvider.GetOrCreateBoard(id.BoardCode);
334

335
			// create test security
336
			var security = new Security
337
			{
338
				Id = SecId.Text, // sec id has the same name as folder with historical data
339
				Code = secCode,
340
				Board = board,
341
			};
342

343
			// storage to historical data
344
			var storageRegistry = new StorageRegistry
345
			{
346
				// set historical path
347
				DefaultDrive = new LocalMarketDataDrive(HistoryPath.Folder)
348
			};
349

350
			var startTime = ((DateTime)From.EditValue).UtcKind();
351
			var stopTime = ((DateTime)To.EditValue).UtcKind();
352

353
			// (ru only) ОЛ необходимо загружать с 18.45 пред дня, чтобы стаканы строились правильно
354
			if (OrderLogCheckBox.IsChecked == true)
355
				startTime = startTime.Subtract(TimeSpan.FromDays(1)).AddHours(18).AddMinutes(45).AddTicks(1).ApplyMoscow().UtcDateTime;
356

357
			// set ProgressBar bounds
358
			_progressBars.ForEach(p =>
359
			{
360
				p.Value = 0;
361
				p.Maximum = 100;
362
			});
363
			
364
			var logManager = new LogManager();
365
			var fileLogListener = new FileLogListener("sample.log");
366
			logManager.Listeners.Add(fileLogListener);
367
			//logManager.Listeners.Add(new DebugLogListener());	// for track logs in output window in Vusial Studio (poor performance).
368

369
			var generateDepths = GenDepthsCheckBox.IsChecked == true;
370
			var maxDepth = MaxDepth.Text.To<int>();
371
			var maxVolume = MaxVolume.Text.To<int>();
372
			var secId = security.ToSecurityId();
373

374
			SetIsEnabled(false, false, false);
375

376
			foreach (var (enabled, progressBar, statistic, emulationInfo, chart, equity, pos) in _settings)
377
			{
378
				if (enabled.IsChecked == false)
379
					continue;
380

381
				var title = emulationInfo.StrategyName;
382

383
				ClearChart(chart, equity, pos);
384

385
				var level1Info = new Level1ChangeMessage
386
				{
387
					SecurityId = secId,
388
					ServerTime = 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

398
				var secProvider = (ISecurityProvider)new CollectionSecurityProvider(new[] { security });
399
				var pf = Portfolio.CreateSimulator();
400
				pf.CurrentValue = 1000;
401

402
				// create backtesting connector
403
				var connector = new HistoryEmulationConnector(secProvider, new[] { pf })
404
				{
405
					EmulationAdapter =
406
					{
407
						Settings =
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)
412
							MatchOnTouch = false,
413

414
							// 1 cent commission for trade
415
							CommissionRules = new ICommissionRule[]
416
							{
417
								new CommissionPerTradeRule { Value = 0.01m },
418
							},
419
						}
420
					},
421

422
					//UseExternalCandleSource = emulationInfo.UseCandleTimeFrame != null,
423

424
					//CreateDepthFromOrdersLog = emulationInfo.UseOrderLog,
425
					//CreateTradesFromOrdersLog = emulationInfo.UseOrderLog,
426

427
					HistoryMessageAdapter =
428
					{
429
						StorageRegistry = storageRegistry,
430

431
						OrderLogMarketDepthBuilders =
432
						{
433
							{
434
								secId,
435
								new OrderLogMarketDepthBuilder(secId)
436
							}
437
						},
438

439
						AdapterCache = 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

448
				logManager.Sources.Add(connector);
449

450
				// create strategy based on 80 5-min и 10 5-min
451
				var strategy = new SmaStrategy
452
				{
453
					LongSma = 80,
454
					ShortSma = 10,
455
					Volume = 1,
456
					Portfolio = connector.Portfolios.First(),
457
					Security = security,
458
					Connector = connector,
459
					LogLevel = 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
463
					UnrealizedPnLInterval = ((stopTime - startTime).Ticks / 1000).To<TimeSpan>(),
464
				};
465

466
				if (emulationInfo.UseCandle is not null)
467
				{
468
					strategy.CandleType = emulationInfo.UseCandle;
469

470
					if (strategy.CandleType != DataType.TimeFrame(TimeSpan.FromMinutes(1)))
471
					{
472
						strategy.BuildFrom = DataType.TimeFrame(TimeSpan.FromMinutes(1));
473
					}
474
				}
475
				else if (emulationInfo.UseTicks)
476
					strategy.BuildFrom = DataType.Ticks;
477
				else if (emulationInfo.UseLevel1)
478
				{
479
					strategy.BuildFrom = DataType.Level1;
480
					strategy.BuildField = emulationInfo.BuildField;
481
				}
482
				else if (emulationInfo.UseOrderLog)
483
					strategy.BuildFrom = DataType.OrderLog;
484
				else if (emulationInfo.UseMarketDepth)
485
					strategy.BuildFrom = DataType.MarketDepth;
486

487
				chart.IsInteracted = false;
488
				strategy.SetChart(chart);
489

490
				logManager.Sources.Add(strategy);
491

492
				if (emulationInfo.CustomHistoryAdapter != null)
493
				{
494
					connector.Adapter.InnerAdapters.Remove(connector.MarketDataAdapter);
495

496
					var emu = connector.EmulationAdapter.Emulator;
497
					connector.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
501
				connector.HistoryMessageAdapter.StartDate = startTime;
502
				connector.HistoryMessageAdapter.StopDate = stopTime;
503

504
				connector.SecurityReceived += (subscr, s) =>
505
				{
506
					if (s != security)
507
						return;
508

509
					// fill level1 values
510
					connector.EmulationAdapter.SendInMessage(level1Info);
511

512
					if (emulationInfo.UseMarketDepth)
513
					{
514
						connector.SubscribeMarketDepth(security);
515

516
						if	(
517
								// if order book will be generated
518
								generateDepths ||
519
								// or backtesting will be on candles
520
								emulationInfo.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
525
							connector.RegisterMarketDepth(new TrendMarketDepthGenerator(connector.GetSecurityId(security))
526
							{
527
								Interval = TimeSpan.FromSeconds(1), // order book freq refresh is 1 sec
528
								MaxAsksDepth = maxDepth,
529
								MaxBidsDepth = maxDepth,
530
								UseTradeVolume = true,
531
								MaxVolume = maxVolume,
532
								MinSpreadStepCount = 2,	// min spread generation is 2 pips
533
								MaxSpreadStepCount = 5,	// max spread generation size (prevent extremely size)
534
								MaxPriceStepCount = 3	// pips size,
535
							});
536
						}
537
					}
538

539
					if (emulationInfo.UseOrderLog)
540
					{
541
						connector.SubscribeOrderLog(security);
542
					}
543

544
					if (emulationInfo.UseTicks)
545
					{
546
						connector.SubscribeTrades(security);
547
					}
548

549
					if (emulationInfo.UseLevel1)
550
					{
551
						connector.SubscribeLevel1(security);
552
					}
553
				};
554

555
				// fill parameters panel
556
				statistic.Parameters.Clear();
557
				statistic.Parameters.AddRange(strategy.StatisticManager.Parameters);
558

559
				var pnlCurve = equity.CreateCurve(LocalizedStrings.PnL + " " + emulationInfo.StrategyName, Colors.Green, Colors.Red, ChartIndicatorDrawStyles.Area);
560
				var realizedPnLCurve = equity.CreateCurve(LocalizedStrings.PnLRealized + " " + emulationInfo.StrategyName, Colors.Black, ChartIndicatorDrawStyles.Line);
561
				var unrealizedPnLCurve = equity.CreateCurve(LocalizedStrings.PnLUnreal + " " + emulationInfo.StrategyName, Colors.DarkGray, ChartIndicatorDrawStyles.Line);
562
				var commissionCurve = equity.CreateCurve(LocalizedStrings.Commission + " " + emulationInfo.StrategyName, Colors.Red, ChartIndicatorDrawStyles.DashedLine);
563
				
564
				strategy.PnLReceived2 += (s, pf, t, r, u, c) =>
565
				{
566
					var data = equity.CreateData();
567

568
					data
569
						.Group(t)
570
							.Add(pnlCurve, r - (c ?? 0))
571
							.Add(realizedPnLCurve, r)
572
							.Add(unrealizedPnLCurve, u ?? 0)
573
							.Add(commissionCurve, c ?? 0);
574

575
					equity.Draw(data);
576
				};
577

578
				var posItems = pos.CreateCurve(emulationInfo.StrategyName, emulationInfo.CurveColor, ChartIndicatorDrawStyles.Line);
579

580
				strategy.PositionReceived += (s, p) =>
581
				{
582
					var data = pos.CreateData();
583

584
					data
585
						.Group(p.LocalTime)
586
							.Add(posItems, p.CurrentValue);
587

588
					pos.Draw(data);
589
				};
590

591
				connector.ProgressChanged += steps => this.GuiAsync(() => progressBar.Value = steps);
592

593
				connector.StateChanged2 += state =>
594
				{
595
					if (state == ChannelStates.Stopped)
596
					{
597
						strategy.Stop();
598

599
						SetIsChartEnabled(chart, false);
600

601
						if (_connectors.All(c => c.State == ChannelStates.Stopped))
602
						{
603
							logManager.Dispose();
604
							_connectors.Clear();
605

606
							SetIsEnabled(true, false, false);
607
						}
608

609
						this.GuiAsync(() =>
610
						{
611
							if (connector.IsFinished)
612
							{
613
								progressBar.Value = progressBar.Maximum;
614
								MessageBox.Show(this, LocalizedStrings.CompletedIn.Put(DateTime.Now - _startEmulationTime), title);
615
							}
616
							else
617
								MessageBox.Show(this, LocalizedStrings.Cancelled, title);
618
						});
619
					}
620
					else if (state == ChannelStates.Started)
621
					{
622
						if (_connectors.All(c => c.State == ChannelStates.Started))
623
							SetIsEnabled(false, true, true);
624

625
						SetIsChartEnabled(chart, true);
626
					}
627
					else if (state == ChannelStates.Suspended)
628
					{
629
						if (_connectors.All(c => c.State == ChannelStates.Suspended))
630
							SetIsEnabled(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
646
				strategy.Start();
647

648
				_connectors.Add(connector);
649

650
				progressBar.Value = 0;
651
			}
652

653
			_startEmulationTime = DateTime.Now;
654

655
			// start emulation
656
			foreach (var connector in _connectors.Cache)
657
			{
658
				// raise NewSecurities and NewPortfolio for full fill strategy properties
659
				connector.Connect();
660

661
				// start historical data loading when connection established successfully and all data subscribed
662
				connector.Start();
663
			}
664

665
			TabControl.Items.Cast<TabItem>().First(i => i.Visibility == Visibility.Visible).IsSelected = true;
666
		}
667

668
		private void CheckBoxClick(object sender, RoutedEventArgs e)
669
		{
670
			var isEnabled = _checkBoxes.Any(c => c.IsChecked == true);
671

672
			StartBtn.IsEnabled = isEnabled;
673
			TabControl.Visibility = isEnabled ? Visibility.Visible : Visibility.Collapsed;
674
		}
675

676
		private void StopBtnClick(object sender, RoutedEventArgs e)
677
		{
678
			foreach (var connector in _connectors.Cache)
679
			{
680
				connector.Disconnect();
681
			}
682
		}
683

684
		private void PauseBtnClick(object sender, RoutedEventArgs e)
685
		{
686
			foreach (var connector in _connectors.Cache)
687
			{
688
				connector.Suspend();
689
			}
690
		}
691

692
		private void ClearChart(IChart chart, EquityCurveChart equity, EquityCurveChart position)
693
		{
694
			chart.ClearAreas();
695
			equity.Clear();
696
			position.Clear();
697
		}
698

699
		private void SetIsEnabled(bool canStart, bool canSuspend, bool canStop)
700
		{
701
			this.GuiAsync(() =>
702
			{
703
				StopBtn.IsEnabled = canStop;
704
				StartBtn.IsEnabled = canStart;
705
				PauseBtn.IsEnabled = canSuspend;
706

707
				foreach (var checkBox in _checkBoxes)
708
				{
709
					checkBox.IsEnabled = !canStop;
710
				}
711
			});
712
		}
713

714
		private void SetIsChartEnabled(IChart chart, bool started)
715
		{
716
			this.GuiAsync(() => chart.IsAutoRange = started);
717
		}
718
	}
719
}

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.