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

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


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

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

Нужно учитывать, что метод могут одновременно вызвать из разных потоков и создавать только 1 Quik.
Code
	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
Реализуем получение портфеля по его имени. Основной момент: ждем, когда будет инициализирована сумма на счете, т.к. она нужна для определения размера позиции.
Code
	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.
Code
	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
Определение размера позиции по лимиту открытых позиций и гарантийному обеспечению.
Code
	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
Чтение и запись состояния стратегии из реестра.
Code
	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
Получение исторических свечей с сайта финама. Реализацию не выкладываю, т.к. много кода.
Code
	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)
		{
			…
}


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


1 2  >
Serg

Avatar
Date: 9/27/2012
Reply


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


Буду рад помощи)
Thanks:

FinDirector

Avatar
Date: 9/27/2012
Reply


Code
LogManager.Listeners.Add(new GuiLogListener(_monitor));
Thanks:

Serg

Avatar
Date: 9/27/2012
Reply


FinDirector
Code
LogManager.Listeners.Add(new GuiLogListener(_monitor));


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

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

FinDirector

Avatar
Date: 9/27/2012
Reply


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

Serg

Avatar
Date: 9/27/2012
Reply


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

Gunn7

Avatar
Date: 10/25/2012
Reply


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

Thanks:

FinDirector

Avatar
Date: 11/20/2012
Reply


Code

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;
		}
	}

Thanks: Den Serg

Gunn7

Avatar
Date: 11/21/2012
Reply


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

Den

Avatar
Date: 11/28/2012
Reply


FinDirector
Code

			FinamSecurityCode = 17455;


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

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

serebryakov_a

Avatar
Date: 12/2/2012
Reply


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


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

Attach files by dragging & dropping, , or pasting from the clipboard.

loading
clippy