← Zurück

Запаздывание получения биржевых данных в коде одного из примеров

Здравствуйте. Недавно начал ознакамливаться с документацией S# (для Quik), в основном осталось положительное впечатление. Но в примере SampleSMA из документации я дописал к стратегии код, который перед каждой проверкой на пересечение (строчка if (_isShortLessThenLong != isShortLessThenLong)) должен записывать в файл значения Bid и Ask. Свечи используются 5-минутные, но запись ведётся с запозданием примерно в полторы минуты. Соединение нормальное, время на компьютере и время на бирже совпадают, в Quik данные приходят вовремя, запаздывание одинаковое от свечки к свечке. С чем это связано и как это исправить?

Kommentare (10)

Anmelden oder Konto erstellen, Melden Sie sich an oder registrieren Sie sich, um einen Kommentar zu hinterlassen

Moadip
Moadip

Если хотите получить ответ на вопрос, пишите максимум информации. Показывайте код, где и как вы делает сохранение, чтобы не приходилось догадываться. Телепаты в отпуске.🙂

UPD: Создавайте темы в соответствующих разделах. К разделу Обучение программированию роботов ваш вопрос отношения не имеет.

developer_29
developer_29
developer_29 Autor

Moadip: Показывайте код, где и как вы делает сохранение, чтобы не приходилось догадываться. Телепаты в отпуске.🙂

namespace SampleSMA
{
	using System;

	using StockSharp.Algo;
	using StockSharp.Algo.Candles;
	using StockSharp.Algo.Indicators.Trend;
	using StockSharp.Algo.Strategies;
	using StockSharp.BusinessEntities;

	class SmaStrategy : TimeFrameStrategy
	{
		private readonly CandleManager _candleManager;
		private bool _isShortLessThenLong;

		private DateTime _nextTime;

		public SmaStrategy(CandleManager candleManager, SimpleMovingAverage longSma, SimpleMovingAverage shortSma, TimeSpan timeFrame)
			: base(timeFrame)
		{
			_candleManager = candleManager;

			LongSma = longSma;
			ShortSma = shortSma;
		}

		public SimpleMovingAverage LongSma { get; private set; }
		public SimpleMovingAverage ShortSma { get; private set; }

		protected override void OnStarting()
		{
			// запоминаем текущее положение относительно друг друга
			_isShortLessThenLong = ShortSma.LastValue < LongSma.LastValue;

			// вычисляем время окончания текущей пятиминутки
			_nextTime = TimeFrame.GetCandleBounds(Trader).Max;

			base.OnStarting();
		}

		protected override ProcessResults OnProcess()
		{
			// если наша стратегия в процессе остановки
			if (ProcessState == ProcessStates.Stopping)
			{
				// отменяем активные заявки
				CancelActiveOrders();

				// так как все активные заявки гарантированно были отменены, то возвращаем ProcessResults.Stop
				return ProcessResults.Stop;
			}

			// событие обработки торговой стратегии вызвалось впервый раз, что раньше, чем окончания текущей 5-минутки.
			if (Trader.MarketTime < _nextTime)
			{
				// возвращаем ProcessResults.Continue, так как наш алгоритм еще не закончил свою работу, а просто ожидает следующего вызова.
				return ProcessResults.Continue;
			}

			// получаем сформированную свечку
			var candle = _candleManager.GetTimeFrameCandle(Security, TimeFrame, _nextTime - TimeFrame);

			// если свечки не существует (не было ни одной сделке в тайм-фрейме), то ждем окончания следующей свечки.
			if (candle == null)
			{
				// если прошло больше 10 секунд с момента окончания свечки, а она так и не появилась,
				// значит сделок в прошедшей 5-минутке не было, и переходим на следующую свечку
				if ((Trader.MarketTime - _nextTime) > TimeSpan.FromSeconds(10))
					_nextTime = TimeFrame.GetCandleBounds(Trader.MarketTime).Max;

				return ProcessResults.Continue;
			}

			_nextTime += TimeFrame;

			// вычисляем новое положение относительно друг друга
			var isShortLessThenLong = ShortSma.LastValue < LongSma.LastValue;

            // Вот здесь и происходить сохранение
            System.IO.File.AppendAllText(@"D:\temp.txt", Security.BestAsk + "\t\t" + Security.BestBid + "\r\n");

			return ProcessResults.Continue;
		}
	}
}
developer_29
developer_29 Autor

C чего взяли что задержка? Как часто вызывается OnProcess? Я и пытаюсь выяснить, в связи с чем она, записи же происходят раз в пять минут, как и положено стртегии на 5-минутных свечах, только вот происходят они с запаздыванием в полторы минуты почему-то.

А управление в свою очередь производится из этого кода, я оттуда удалил ни на что не влияющую прорисовку свечей. Код весь взят из SampleSMA, который находится у Вас на сайте stocksharp.com

namespace SampleSMA
{
	using System;
	using System.Collections.Generic;
	using System.Diagnostics;
	using System.Linq;
	using System.ComponentModel;
	using System.Globalization;
	using System.IO;
	using System.Threading;
	using System.Windows;
	using System.Windows.Forms;
	using MessageBox = System.Windows.MessageBox;

	using AmCharts.Windows.Stock;

	using Ecng.Collections;
	using Ecng.Common;
	using Ecng.Xaml;
	using Ecng.ComponentModel;

	using StockSharp.Algo.Logging;
	using StockSharp.Algo.Candles;
	using StockSharp.Algo.Reporting;
	using StockSharp.Algo.Strategies;
	using StockSharp.Algo.Indicators;
	using StockSharp.Algo.Indicators.Trend;
	using StockSharp.BusinessEntities;
	using StockSharp.Quik;
	using StockSharp.Xaml;

	public partial class MainWindow
	{
		private readonly TimeSpan _timeFrame = TimeSpan.FromMinutes(5);
		private QuikTrader _trader;
		private SmaStrategy _strategy;
		private bool _isDdeStarted;
		private DateTime _lastCandleTime;
		private bool _isTodaySmaDrawn;
		private CandleManager _candleManager;
		private readonly ICollection<CustomChartIndicator> _longSmaGraph;
		private readonly ICollection<CustomChartIndicator> _shortSmaGraph;
		private Security _lkoh;

		public MainWindow()
		{
			InitializeComponent();

			// изменяет текущий формат, чтобы нецелое числа интерпритировалось как разделенное точкой.
			var cci = new CultureInfo(Thread.CurrentThread.CurrentCulture.Name) { NumberFormat = { NumberDecimalSeparator = "." } };
			Thread.CurrentThread.CurrentCulture = cci;

			_longSmaGraph = _chart.CreateTrend("Длинная", GraphType.Line);
			_shortSmaGraph = _chart.CreateTrend("Короткая", GraphType.Line);
		}

		private void _orders_OrderSelected(object sender, EventArgs e)
		{
			CancelOrders.IsEnabled = !_orders.SelectedOrders.IsEmpty();
		}

		protected override void OnClosing(CancelEventArgs e)
		{
			if (_trader != null)
			{
				if (_isDdeStarted)
					StopDde();

				_trader.Dispose();
			}

			base.OnClosing(e);
		}

		private void FindPath_Click(object sender, RoutedEventArgs e)
		{
			var dlg = new FolderBrowserDialog();

			if (!Path.Text.IsEmpty())
				dlg.SelectedPath = Path.Text;

			if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
			{
				Path.Text = dlg.SelectedPath;
			}
		}

		private void Connect_Click(object sender, RoutedEventArgs e)
		{
			if (_trader == null || !_trader.IsConnected)
			{
				if (_trader == null)
				{
					if (Path.Text.IsEmpty())
					{
						//MessageBox.Show(this, "Путь к Quik не выбран.");
						//return;
                        Path.Text = @"c:\BCS_Work\QUIK";
					}

					// создаем шлюз
					_trader = new QuikTrader(Path.Text);

					Portfolios.Trader = _trader;

					_trader.Connected += () =>
					{
						_candleManager = new CandleManager(_trader);
						foreach (var builder in _candleManager.Sources.OfType<CandleBuilder>())
						{
							builder.IsSyncRegister = true;
						}

						_trader.NewSecurities += securities => this.GuiAsync(() =>
						{
							// находим нужную бумагу
							var lkoh = securities.FirstOrDefault(s => s.Code == "RIH3");

							if (lkoh != null)
							{
								_lkoh = lkoh;

								this.GuiAsync(() =>
								{
									Start.IsEnabled = true;
								});
							}
						});

						_trader.NewMyTrades += trades => this.GuiAsync(() =>
						{
							if (_strategy != null)
							{
								// найти те сделки, которые совершила стратегия скользящей средней
								trades = trades.Where(t => _strategy.Orders.Any(o => o == t.Order));

								_trades.Trades.AddRange(trades);
							}
						});

						_candleManager.CandlesStarted += (token, candles) =>
						{
						};
						//_trader.ProcessDataError += ex => this.Sync(() => MessageBox.Show(this, ex.ToString()));
						_trader.ConnectionError += ex =>
						{
							if (ex != null)
								this.GuiAsync(() => MessageBox.Show(this, ex.ToString()));
						};

						this.GuiAsync(() =>
						{
							ConnectBtn.IsEnabled = false;
							ExportDde.IsEnabled = true;
							Report.IsEnabled = true;
						});
					};
				}

				_trader.Connect();
			}
			else
				_trader.Disconnect();
		}

		private void OnNewOrder(Order order)
		{
			_orders.Orders.Add(order);
			this.GuiAsync(() => _chart.Orders.Add(order));
		}

		private void OnLog(LogMessage message)
		{
			// если стратегия вывела не просто сообщение, то вывести на экран.
			if (message.Type != ErrorTypes.None)
				this.GuiAsync(() => MessageBox.Show(this, message.Message));
		}

		private void DrawSma1()
		{
			// нас не интересует текущая свечка, так как она еще не сформировалась
			// и из нее нельзя брать цену закрытия

			// вычисляем временные отрезки текущей свечки
			var bounds = _timeFrame.GetCandleBounds(_trader);

			// если появились новые полностью сформированные свечки
			if ((_lastCandleTime + _timeFrame) < bounds.Min)
			{
				// отступ с конца интервала, чтобы не захватить текущую свечку.
				var endOffset = TimeSpan.FromSeconds(1);

				bounds = new Range<DateTime>(_lastCandleTime + _timeFrame, bounds.Min - endOffset);

				// получаем эти свечки
				var candles = _candleManager.GetTimeFrameCandles(_strategy.Security, _timeFrame, bounds);

				if (!candles.IsEmpty())
				{
					foreach (var candle in candles)
					{
						// добавляем новую свечку
						_strategy.LongSma.Process((DecimalIndicatorValue)candle.ClosePrice);
						_strategy.ShortSma.Process((DecimalIndicatorValue)candle.ClosePrice);
					}

					// получаем время самой последней свечки и запоминаем его как новое начало
					_lastCandleTime = candles.Max(c => c.Time);
				}
			}
		}

		private void OnStrategyPropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			this.GuiAsync(() =>
			{
				Status.Content = _strategy.ProcessState;
				PnL.Content = _strategy.PnLManager.PnL;
				Slippage.Content = _strategy.SlippageManager.Slippage;
				Position.Content = _strategy.PositionManager.Position;
				Latency.Content = _strategy.LatencyManager.Latency;
			});
		}

		private void StartDde()
		{
			_trader.StartExport();
			_isDdeStarted = true;
		}

		private void StopDde()
		{
			_trader.StopExport();
			_isDdeStarted = false;
		}

		private void ExportDde_Click(object sender, RoutedEventArgs e)
		{
			if (_isDdeStarted)
				StopDde();
			else
				StartDde();
		}

		private void CancelOrders_Click(object sender, RoutedEventArgs e)
		{
			_orders.SelectedOrders.ForEach(_trader.CancelOrder);
		}

		private void Start_Click(object sender, RoutedEventArgs e)
		{
			if (_strategy == null)
			{
				if (Portfolios.SelectedPortfolio == null)
				{
					MessageBox.Show(this, "Портфель не выбран.");
					return;
				}

				var candles = File.ReadAllLines("LKOH_history.txt").Select(line =>
				{
				    var parts = line.Split(',');
				    var time = DateTime.ParseExact(parts[0] + parts[1], "yyyyMMddHHmmss", CultureInfo.InvariantCulture);
				    return new TimeFrameCandle
				    {
				        OpenPrice = parts[2].To<decimal>(),
				        HighPrice = parts[3].To<decimal>(),
				        LowPrice = parts[4].To<decimal>(),
				        ClosePrice = parts[5].To<decimal>(),
				        TimeFrame = _timeFrame,
				        Time = time,
						TotalVolume = parts[6].To<decimal>(),
				        Security = _lkoh,
				    };
				});

				// создаем торговую стратегию, скользящие средние на 80 5-минуток и 10 5-минуток
				_strategy = new SmaStrategy(_candleManager, new SimpleMovingAverage { Length = 80 }, new SimpleMovingAverage { Length = 10 }, _timeFrame)
				{
					Volume = 1,
					Security = _lkoh,
					Portfolio = Portfolios.SelectedPortfolio,
					Trader = _trader,
				};
				_strategy.Log += OnLog;
				_strategy.NewOrder += OnNewOrder;
				_strategy.PropertyChanged += OnStrategyPropertyChanged;

				var index = 0;

				// начинаем вычислять скользящие средние
				foreach (var candle in candles)
				{
				    _strategy.LongSma.Process((DecimalIndicatorValue)candle.ClosePrice);
				    _strategy.ShortSma.Process((DecimalIndicatorValue)candle.ClosePrice);

				    index++;

				    _lastCandleTime = candle.Time;
				}

				// регистрируем наш тайм-фрейм
				_candleManager.RegisterTimeFrameCandles(_lkoh, _timeFrame);

				// вычисляем временные отрезки текущей свечки
				var bounds = _timeFrame.GetCandleBounds(_trader);

				candles = _candleManager.GetTimeFrameCandles(_strategy.Security, _timeFrame, new Range<DateTime>(_lastCandleTime + _timeFrame, bounds.Min));

				foreach (var candle in candles)
				{
					_strategy.LongSma.Process((DecimalIndicatorValue)candle.ClosePrice);
					_strategy.ShortSma.Process((DecimalIndicatorValue)candle.ClosePrice);

					_lastCandleTime = candle.Time;
				}

				_isTodaySmaDrawn = true;

				Report.IsEnabled = true;
			}

			if (_strategy.ProcessState == ProcessStates.Stopped)
			{
				// запускаем процесс получения стакана, необходимый для работы алгоритма котирования
				_trader.RegisterQuotes(_strategy.Security);
				_strategy.Start();
				Start.Content = "Стоп";
			}
			else
			{
				_trader.UnRegisterQuotes(_strategy.Security);
				_strategy.Stop();
				Start.Content = "Старт";
			}
		}

		private void Report_Click(object sender, RoutedEventArgs e)
		{
			// сгерерировать отчет по прошедшему тестированию
			new ExcelStrategyReport(_strategy, "sma.xls").Generate();

			// открыть отчет
			Process.Start("sma.xls");
		}
	}
}
Alexander
Alexander

C чего взяли что задержка? Как часто вызывается OnProcess? Я и пытаюсь выяснить, в связи с чем она, записи же происходят раз в пять минут, как и положено стртегии на 5-минутных свечах, только вот происходят они с запаздыванием в полторы минуты почему-то.

А управление в свою очередь производится из этого кода, я оттуда удалил ни на что не влияющую прорисовку свечей. Код весь взят из SampleSMA, который находится у Вас на сайте stocksharp.com

Что именно происходит с запаздыванием в 1.5 минуты?

У вас код

System.IO.File.AppendAllText(@"D:\temp.txt", Security.BestAsk + "\t\t" + Security.BestBid + "\r\n");

будет выполняться не чаще чем каждые 5 минут - когда вызывается OnProcess. Может выполняться и реже - когда сработал один из return до этого года.

Т.е. он может выполняться и раз в 15 минут, и раз в 5 минут - всё зависит от условий до вышего вывода.

Что вы ожидаете от этого кода?

developer_29
developer_29 Autor

Alexander Mukhanchikov: У вас код

System.IO.File.AppendAllText(@"D:\temp.txt", Security.BestAsk + "\t\t" + Security.BestBid + "\r\n");

> будет выполняться не чаще чем каждые 5 минут - когда вызывается OnProcess. Может выполняться и реже - когда сработал один из return до этого года.
> 
> Т.е. он может выполняться и раз в 15 минут, и раз в 5 минут - всё зависит от условий до вышего вывода.
> 
> Что вы ожидаете от этого кода?

Код выполняется не в 11-00, 11-05, 11-10 и т.д., а в 11-01, 11-06, 11-11 соответственно. Т.е., срабатывает примерно на минуты полторы позже, чем положено, причём -- каждый раз на полторы минуты позже, чем положено. Время на моём компьютере с точностью до нескольких секунд совпадает со временем Quik.
Я ожидаю от кода, что запись будет происходить в 11-00, 11-05, 11-10 и т.д, а не в 11-01, 11-06, 11-11, как у меня сейчас.

> **Alexander Mukhanchikov:**
Так Вы тот самый основатель, если верить нику! Слушал Ваше интервью на youtube ещё до подробного знакомства с S#.
Alexander
Alexander

C чего взяли что задержка? Как часто вызывается OnProcess?

developer_29
Alexander
Alexander

Вы используете таймфрейм стратегию, вызов OnProcess просто срабатывает раз в 5 минут. Никто не обещает что он срабатывает именно в 11-00, 11-05 и т.д. Он берёт время когда вы создаёте и запускаете стратегию и дальше делает +5 минут. Хотите чтоб чаще срабатывал OnProcess - задайте Interval.

P.S. Я не основатель :)

developer_29
developer_29
developer_29 Autor

Alexander Mukhanchikov: Он берёт время когда вы создаёте и запускаете стратегию и дальше делает +5 минут. Хотите чтоб чаще срабатывал OnProcess - задайте Interval. Спасибо, с данным вопросом разобрался, буду разбираться с остальными.

И ещё: торги начинаются в 10-00, а если я запущу вышенаписанный код в 9-00 (и пойду по делам), то он просто будет ждать начала торгов, а затем нормально отработает весь день (если ничего не портить и не ломать)? Иными словами, есть ли необходимость самостоятельно дописывать код, чтобы он "включался самостоятельно" к началу торгов или это уже учтено?

Alexander
Alexander

S# просто запускает OnProcess раз в timeframe. Всё что выполняется далее зависит от того что написано в OnProcess. Если у вас там написано слать заявки на каждый вызов - он будет слать заявки и в клиринг, и ночью.

developer_29
developer_29
developer_29 Autor

Спасибо, понял. Только вот по ходу написания робота возникло несколько вопросов, напишу здесь, чтобы не плодить заголовков:

1 - при подключении к Quik сразу же возникают 2 портфеля, которые я не создавал. Каким из них пользоваться и если они служат для разных целей, то для каких именно и почему у них такие странные имена? 2 - Как создать заявку, которая просто "сметает" те предолжения, которые сейчас есть, невзирая на их цену (конечно же, они должны начинаться от лучшего к худшему)? Например, когда пишем


            var order = this.CreateOrder(direction, Security.GetMarketPrice(direction), Volume);

            // регистрируем заявку (обычным способом - лимитированной заявкой)
             RegisterOrder(order);

Затем цена рынка поменялась в ту или иную сторону, меня она в любом случае устраивает, но заявка не выполняется, т.к. цена изменилась, и предложений по цене Security.GetMarketPrice(direction) сейчас нет. Что делать, чтобы в любом случае выполнилась заявка (съела часть стакана, даже если что-то выполнилось по худшей цене)? 3 - Правильно ли я понял, что для закрытия позиции надо совершить противоположную сделку с тем же инструментом (если купил, то потом продать такой же объём)? 4 - Как выставить stop-loss или зафиксировать прибыль: так

// registeredOrder - это ранее зарегистрированная заявка.
trader.CancelOrder(registeredOrder);

или созданием противоположной заявки, как только цена достигнет того уровня, на который мы ориентируемся? Под снятием заявки у Вас подразумеваются уже исполненные заявки или те, которые ещё не успели исполниться, но которые по какой-то причине надо снять? Можно ли заранее прописать stop-loss, чтобы сделка автоматически закрывалась, как только цена достигнет определённного уровня?