Создание роботов с помощью S#. Часть 3. Реализация интерфейсов
Atom Ответить
18.03.2012


Теперь приступим к реализации интерфейсов из 2 части статьи “Создание роботов с помощью S#. Часть 2. Базовый класс для всех стратегий”. Здесь западные специалисты рекомендуют вначале реализовывать тестовые версии интерфейсов. И писать сразу UNIT-тесты. Смотрите пример SampleEmulationTesting из библиотеки S#. Но ведь программист — это высшая форма существования разума во Вселенной, и поэтому, в принципе не может ошибаться. Сразу в бой!

QuikTraderBuilder
Реализуем создание торговой системы Quik. Основной метод:
Код
public ITrader BuildTrader()

Нужно учитывать, что метод могут одновременно вызвать из разных потоков и создавать только 1 Quik.
Код
	public sealed class QuikTraderBuilder : ITraderBuilder, ILogSource
	{
		private object lockObject = new object();
		private Task task;
		private bool isConnected;

		public string Path { get; set; }
		public string Login { get; set; }
		public string Password { get; set; }

		public string DdeServer { get; set; }
		public string DllName { get; set; }

		private string title;
		public string Title
		{
			get { return title ?? Login; }
			set { title = value; }
		}

		public ITrader Trader { get; private set; }
		public ICandleManager CandleManager { get; private set; }
		public event Action IsConnectedChanged;

		public ITrader BuildTrader()
		{
			lock (lockObject)
			{
				if (task == null)
					task = Task.Factory.StartNew(CreateTrader);
			}
			task.Wait();
			return Trader;
		}

		private void CreateTrader()
		{
			QuikTerminal terminal = RunTerminalInternal();
			QuikTrader quikTrader;
			WriteLog(ErrorTypes.None, "Создаем шлюз взаимодействия с системой Quik.");
			if (!string.IsNullOrEmpty(DdeServer))
			{
				if (!string.IsNullOrEmpty(DllName))
					quikTrader = new QuikTrader(terminal.DirectoryName, DdeServer, DllName);
				else
					quikTrader = new QuikTrader(terminal.DirectoryName, DdeServer);
			}
			else
			{
				quikTrader = new QuikTrader(terminal.DirectoryName);
			}
			quikTrader.SecuritiesTable.Columns.Add(DdeSecurityColumns.MarginBuy);
			quikTrader.SecuritiesTable.Columns.Add(DdeSecurityColumns.MinPrice);
			quikTrader.SecuritiesTable.Columns.Add(DdeSecurityColumns.MaxPrice);
			quikTrader.ReConnectionSettings.Interval = TimeSpan.FromSeconds(10);
			quikTrader.ReConnectionSettings.WorkingTime = Exchange.Rts.WorkingTime;

			quikTrader.Connected += () =>
			{
				UpdateIsConnected(null);
				if (!quikTrader.IsExportRunning)
				{
					WriteLog(ErrorTypes.None, "Запускаем экспорт данных.");
					quikTrader.StartExport();
					Verify(quikTrader.Terminal);
				}
			};
			quikTrader.ConnectionError += ex => UpdateIsConnected(ex);
			quikTrader.Disconnected += () => UpdateIsConnected(null);

			Trader = quikTrader;
			CandleManager = new CandleManager(quikTrader);

			WriteLog(ErrorTypes.None, "Производим подключение.");
			quikTrader.Connect();
		}

		private void UpdateIsConnected(Exception ex)
		{
			var trader = Trader;
			bool isConnectedNew = trader != null && trader.IsConnected;
			if (isConnected != isConnectedNew)
			{
				isConnected = isConnectedNew;
				WriteLog(ErrorTypes.None, isConnectedNew ? "Соединение подключено." : "Соединение отключено.");
				RaiseIsConnectedChanged();
				if (ex != null) WriteLog(ErrorTypes.Error, ex.Message);
			}
		}

		private void RaiseIsConnectedChanged()
		{
			var handler = IsConnectedChanged;
			if (handler != null) handler();
		}

		public void RunTerminal()
		{
			RunTerminalInternal();
		}

		private QuikTerminal RunTerminalInternal()
		{
			QuikTerminal terminal = QuikTerminal.Get(Path);
			if (!terminal.IsLaunched)
				terminal.Launch();
			if (!terminal.IsConnected)
				terminal.Login(Login, Password);
			return terminal;
		}

		private void Verify(QuikTerminal terminal)
		{
			var errors = terminal.GetTableSettings();
			foreach (var error in errors)
			{
				string message = string.Format("Таблица {0}. {1}", error.Table.Caption, error.Error.Message);
				WriteLog(ErrorTypes.Warning, message);
			}
		}

		public void Dispose()
		{
			if (task != null)
				task.Dispose();
			if (CandleManager != null)
				CandleManager.Dispose();
			if (Trader != null)
				Trader.Dispose();
		}

		#region ILogSource
		public Ecng.Collections.INotifyList<ILogSource> Childs
		{
			get { return null; }
		}

		private Guid id = Guid.NewGuid();
		public Guid Id
		{
			get { return id; }
		}

		public event Action<LogMessage> Log;

		public string Name
		{
			get { return Title; }
		}

		public ILogSource Parent
		{
			get { return null; }
		}

		private void RaiseLog(LogMessage logMessage)
		{
			var handler = Log;
			if (handler != null)
				handler(logMessage);
		}

		private void WriteLog(ErrorTypes errorType, string message)
		{
			LogMessage logMessage = new LogMessage(this, DateTime.Now, errorType, message);
			RaiseLog(logMessage);
		}
		#endregion
	}


Создание других торговых систем оставим читателю в качестве домашнего задания.

PortfolioSelector
Реализуем получение портфеля по его имени. Основной момент: ждем, когда будет инициализирована сумма на счете, т.к. она нужна для определения размера позиции.
Код
	public class PortfolioSelector : IPortfolioSelector
	{
		private string title;
		public string Title
		{
			get { return title ?? PortfolioName; }
			set { title = value; }
		}

		public string PortfolioName { get; set; }

		public Portfolio GetPortfolio(ITrader trader)
		{
			Portfolio result = FindPortfolio(trader);
			if (result != null)
				return result;
			ManualResetEvent manualResetEvent = new ManualResetEvent(false);
			Action<IEnumerable<Portfolio>> onPortfoliosChanged = p =>
			{
				if (FindPortfolio(trader) != null)
					manualResetEvent.Set();
			};
			trader.NewPortfolios += onPortfoliosChanged;
			trader.PortfoliosChanged += onPortfoliosChanged;
			manualResetEvent.WaitOne(TimeSpan.FromSeconds(30));
			trader.NewPortfolios -= onPortfoliosChanged;
			trader.PortfoliosChanged -= onPortfoliosChanged;
			result = FindPortfolio(trader);
			return result;
		}

		private Portfolio FindPortfolio(ITrader trader)
		{
			return trader.Portfolios.FirstOrDefault(p => p.Name == PortfolioName && p.BeginAmount.Value != 0);
		}
	}

SecuritySelector
Поиск инструмента по его коду. Реализация аналогична PortfolioSelector.
Код
	public class SecuritySelector : ISecuritySelector
	{
		private string title;
		public string Title
		{
			get { return title ?? SecurityCode; }
			set { title = value; }
		}

		public string SecurityCode { get; set; }

		public Security GetSecurity(ITrader trader)
		{
			Security result = FindSecurity(trader);
			if (result != null)
				return result;
			ManualResetEvent manualResetEvent = new ManualResetEvent(false);
			Action<IEnumerable<Security>> onNewSecurities = s =>
			{
				if (FindSecurity(trader) != null)
					manualResetEvent.Set();
			};
			trader.NewSecurities += onNewSecurities;
			manualResetEvent.WaitOne(TimeSpan.FromSeconds(30));
			trader.NewSecurities -= onNewSecurities;
			result = FindSecurity(trader);
			return result;
		}

		private Security FindSecurity(ITrader trader)
		{
			return trader.Securities.FirstOrDefault(s => s.Code == SecurityCode);
		}
	}

MarginVolumeSizer
Определение размера позиции по лимиту открытых позиций и гарантийному обеспечению.
Код
	public class MarginVolumeSizer : IVolumeSizer
	{
		public double Ratio { get; set; }
		public decimal MaxCapital { get; set; }

		public MarginVolumeSizer()
		{
			MaxCapital = decimal.MaxValue;
		}

		public int GetVolume(Portfolio portfolio, Security security)
		{
			decimal capital = Math.Min(portfolio.BeginAmount.Value, MaxCapital);
			int quantity = (int)(capital * (decimal)Ratio / security.MarginBuy);
			return quantity;
		}
	}

RegistrySettingsProvider
Чтение и запись состояния стратегии из реестра.
Код
	public class RegistrySettingsProvider : ISettingsProvider
	{
		public string SubKey { get; set; }

		public RegistrySettingsProvider()
		{
			SubKey = @"Software\FinDirector";
		}

		public string ReadSetting(string name)
		{
			using (RegistryKey registryKey = Registry.CurrentUser.CreateSubKey(SubKey))
			{
				return (string)registryKey.GetValue(name);
			}
		}

		public void WriteSetting(string name, string value)
		{
			using (RegistryKey registryKey = Registry.CurrentUser.CreateSubKey(SubKey))
			{
				registryKey.SetValue(name, value);
			}
		}
	}

FinamHistoryCandleProvider
Получение исторических свечей с сайта финама. Реализацию не выкладываю, т.к. много кода.
Код
	public class FinamHistoryCandleProvider : IHistoryCandleProvider
	{
		public int FinamSecurityCode { get; set; }
		public TimeSpan TimeFrame { get; set; }

		public FinamHistoryCandleProvider()
		{
			FinamSecurityCode = 17455;
			TimeFrame = TimeSpan.FromHours(1);
		}

		public List<TimeFrameCandle> GetHistoryCandles(DateTime beginDate, DateTime endDate)
		{
			…
}


Автор статьи — Вадим Чижов



Спасибо: OvcharenkoVI StockSharp Marcopolo wulff AlexLan73 Serg Sergey Masyura kahuna




12 Ответов
Serg

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


Спасибо за статьи)
Пытаюсь разбираться. Завис на реализации QuikTraderBuilder. Никак не разберусь почему монитор не получает сообщения от QuikTraderBuilder, видимо что-то с реализацией ILogSource.
Код
private void RaiseLog(LogMessage logMessage)
  {
    var handler = Log;
    if (handler != null)
      handler(logMessage);   <<-- здесь возникает ошибка Current thread is not a GUI.
  }


Буду рад помощи)
Спасибо:

FinDirector

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


Код
LogManager.Listeners.Add(new GuiLogListener(_monitor));
Автор топика
Спасибо:

Serg

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


FinDirector Перейти
Код
LogManager.Listeners.Add(new GuiLogListener(_monitor));


Да я вроде так и делаю но почемуто все вообще виснет. Может проблема в версии 4.0.23? Вы какую версию используете?

ps: да и в логфайл сообщения от трейдера также не попадают( Мне кажется что гуи синхронизация здесь непричем. Скорее проблема с реализацией ILogSourse. Хотя могу только догадыватся так как не профи)
FinDirector, подскажите, у вас то все работает или всетаки чтото меняли после публикации своих статей?
Спасибо:

FinDirector

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


Тоже использую 4.0.23. Все работает. Могу только посоветовать создать новый пустой проект и наладить в нем логи, и в уже имеющемся проекте отключить логи. Тогда и разберетесь, что именно и почему не работает.
Автор топика
Спасибо: Serg

Serg

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


Даже не знаю в чем точто была проблема но все заработало.
FinDirector, с вашего позволения могу выложить проект-шаблон целиком.
Спасибо:

Gunn7

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


Присоединяюсь к serq, спасибо за статьи.
Может все же выложите реализацию получения свечей с финама. Повис на этом уже который день...

Спасибо:

FinDirector

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


Код

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Net;
using FinDirector.Infrastructure;

namespace FinDirector.Modules.SharedServices
{
	public class FinamHistoryCandleProvider : IHistoryCandleProvider
	{
		public int FinamSecurityCode { get; set; }

		[Description("Таймфрейм.")]
		public TimeSpan TimeFrame { get; set; }

		public FinamHistoryCandleProvider()
		{
			FinamSecurityCode = 17455;
			TimeFrame = TimeSpan.FromHours(1);
		}

		public IList<Candle> GetHistoryCandles(DateTime beginDate, DateTime endDate)
		{
			string uri = GetInstrumentUrl(beginDate, endDate);
			WebRequest request = WebRequest.Create(uri);
			using (StreamReader sr = new StreamReader(request.GetResponse().GetResponseStream()))
			{
				IList<Candle> result = new CandleReader().ReadCandles(sr);
				return result;
			}
		}

		private int GetPeriodCode()
		{
			int period = 3;
			if (TimeFrame == TimeSpan.FromMinutes(5))
				period = 3;
			else if (TimeFrame == TimeSpan.FromHours(1))
				period = 7;
			else if (TimeFrame == TimeSpan.FromDays(1))
				period = 8;
			return period;
		}

		private string GetInstrumentUrl(DateTime beginDate, DateTime endDate)
		{
			int period = GetPeriodCode();

			string Ticker = "data";
			string fileName = Ticker + ".txt";
			string url = string.Format(CultureInfo.InvariantCulture,
				"http://195.128.78.52/{0}?d=d&market=14&em={8}&df={2}&mf={3}&yf={4}&dt={5}&mt={6}&yt={7}&p={9}&f={0}&e=.txt&cn={1}&dtf=1&tmf=1&MSOR=0&sep=1&sep2=1&datf=1&at=1",
				fileName, Ticker,
				beginDate.Day, beginDate.Month - 1, beginDate.Year,
				endDate.Day, endDate.Month - 1, endDate.Year,
				FinamSecurityCode, period);
			return url;
		}
	}
}

	class CandleReader
	{
		private bool SkipFirstLine = true;
		private char Separator = ',';

		public IList<Candle> ReadCandles(StreamReader sr)
		{
			List<Candle> result = new List<Candle>();
			if (SkipFirstLine)
			{
				sr.ReadLine();
			}
			while (!sr.EndOfStream)
			{
				string line = sr.ReadLine();
				string[] values = line.Split(new char[] { Separator });
				Candle candle = ReadCandle(values);
				result.Add(candle);
			}
			return result;
		}

		private Candle ReadCandle(string[] line)
		{
			int dateIndex = 2;
			int timeIndex = 3;
			int openIndex = 4;
			int highIndex = 5;
			int lowIndex = 6;
			int closeIndex = 7;

			int date = Int32.Parse(line[dateIndex], CultureInfo.InvariantCulture);
			int time = Int32.Parse(line[timeIndex], CultureInfo.InvariantCulture);

			var result = new Candle()
			{
				DateTime = new DateTime(date / 10000, (date / 100) % 100, date % 100,
					time / 10000, (time / 100) % 100, 0),
				OpenPrice = double.Parse(line[openIndex], CultureInfo.InvariantCulture),
				ClosePrice = double.Parse(line[closeIndex], CultureInfo.InvariantCulture),
				HighPrice = double.Parse(line[highIndex], CultureInfo.InvariantCulture),
				LowPrice = double.Parse(line[lowIndex], CultureInfo.InvariantCulture)
			};

			return result;
		}
	}

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

Gunn7

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


Огромное спасибо!
Спасибо:

Den

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


FinDirector Перейти
Код

			FinamSecurityCode = 17455;


Пока, я просто выбираю руками нужный инструмент и слушаю tcpmon'ом запрос браузра и оттуда достаю код.

Подскажите, пожалуйста, как получить список всех доступных кодов инструментов?
Спасибо:

serebryakov_a

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


Serg Перейти
Даже не знаю в чем точто была проблема но все заработало.
FinDirector, с вашего позволения могу выложить проект-шаблон целиком.


Добрый день! Попытался реализовать описанный выше каркас - появились ошибки реализации интерфейсов(ITraderBuilder, ILogSource). версия библиотеки 4.1.6. Можете ли вы поделиться реализацией проекта-шаблона или подсказать в чем там были траблы?
Спасибо:

VassilSanych

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


Смотрю, многие, в том числе и я, пользовались приведённым кодом, как основой. У многих в цитируемом тексте присутствует WriteLog :)
Небольшое замечание: для подключения источника логирования не нужно реализовывать ILogSource.
Достаточно унаследоваться от BaseLogReceiver<ВашКласс> и тогда можно замечательно использовать метод AddLog, и методы предоставляемые для него классом LoggingHelper: AddErrorLog, AddInfoLog и т.п.
Правда для полного логирования необходимо добавить ещё
Код
public override LogLevels LogLevel
{
	get { return LogLevels.Debug; }
	set { }
} 

по вкусу :)
Спасибо: Геннадий Ванин (Gennady Vanin) kornego

Геннадий Ванин (Gennady Vanin)

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


VassilSanych Перейти

Код
public override LogLevels LogLevel
{
	get { return LogLevels.Debug; }
	set { }
} 

по вкусу :)

Если этого альтруиста не забанить, то, тогда, прийдётся менять бизнес-модель с бесплатного коммерческого продукта на платный некоммерческий продукт

Т.е., вместо того, чтобы пытаться продавать техподдержку и обучение того, чем нельзя пользоватьсяновичкам (и, соответственно, не находит широкого использования) на продажу небесплатного продукта с бесплатной техподдержкой-обучением-раскруткой , которым все легко пользуются и с более широким вовлечением волонтеров-разработчиков.

VassilSanych - коварный и лукавый волонтёр-разработчик, который хочет бесплатно поучаствовать в развитии проекта StockSharp, поломав давно установившийся и отлаженный статус-кво!
Спасибо:


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

loading
clippy