Создание роботов с помощью 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 
VassilSanych

Avatar
Date: 12/21/2012
Reply


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

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

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

Avatar
Date: 12/21/2012
Reply


VassilSanych

Code
public override LogLevels LogLevel
{
	get { return LogLevels.Debug; }
	set { }
} 

по вкусу :)

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

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

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

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

loading
clippy