S#

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


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


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

Теги:


Спасибо:




10 Ответов
Moadip

Фотография
Автор статей Программист
Дата: 15.01.2013
Ответить


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

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

developer_29

Фотография
Дата: 15.01.2013
Ответить


Moadip Перейти

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


Код
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;
		}
	}
}


Автор топика
Спасибо:

Alexander

Фотография
Дата: 15.01.2013
Ответить


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

developer_29

Фотография
Дата: 15.01.2013
Ответить


Цитата:
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

Фотография
Дата: 15.01.2013
Ответить


developer_29 Перейти
Цитата:
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

Фотография
Дата: 15.01.2013
Ответить


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

Фотография
Дата: 15.01.2013
Ответить


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

P.S. Я не основатель :)
Спасибо: developer_29

developer_29

Фотография
Дата: 15.01.2013
Ответить


Alexander Mukhanchikov Перейти

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

Спасибо, с данным вопросом разобрался, буду разбираться с остальными.

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

Автор топика
Спасибо:

Alexander

Фотография
Дата: 15.01.2013
Ответить


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

developer_29

Фотография
Дата: 20.01.2013
Ответить


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

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, чтобы сделка автоматически закрывалась, как только цена достигнет определённного уровня?
Автор топика
Спасибо:


Добавить файлы через драг-н-дроп, , или вставить из буфера обмена.

loading
clippy