Пишем торговых роботов с помощью графического фреймворка StockSharp. Часть 1

  • Tutorial


В нашем блоге мы много пишем о технологиях и полезных инструментах, связанных с биржевой торговлей. Один из них – бесплатная платформа StockSharp, которую можно использовать для профессиональной разработки торговых терминалов и торговых роботов на языке C#. В данной статье мы покажем, как использовать графический фреймворк, входящий в S#.API, с целью создания торгового терминала с возможностью запуска алгоритмических стратегий.

Что понадобится


  1. Visual Studio 2017 (Community, бесплатная версия), в ней мы будем программировать.
  2. Подключение к торгам на бирже, в примерах в данном тексте используется интерфейс SMARTcom от ITI Capital.

Создание проекта


Создадим новое WPF-приложение в Visual Studio:



После чего необходимо добавить S#.API библиотеки. О том, как это сделать, можно узнать в документации. Оптимальный вариант – установка с помощью Nuget.

Так как все графические элементы S#.API созданы на базе DevExpress, а библиотеки DevExpress идут вместе с S#.API, глупо будет ими не воспользоваться. Перейдем в редактор окна MainWindow.xaml:



Заменим Window на DXWindow, это нам понадобится для использования разных цветовых схем:



Visual Studio нам сама предложит вставить необходимые библиотеки.

Разобьем окно на три части – сверху будет полоса с кнопками настройки подключений и подключения, внизу – окно с логами, а в середине все остальные панели. Проще всего так разбить окно с помощью LayoutControl от DevExpress.

В получившиеся три части мы и будем добавлять необходимые нам элементы.

<dx:DXWindow x:Class="ShellNew.MainWindow"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
             xmlns:dxlc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol"
             mc:Ignorable="d"
             Title="MainWindow" Height="450" Width="800">
	<dxlc:LayoutControl Padding="0" Name="LayoutControlRoot" Orientation="Vertical">
		<dxlc:LayoutGroup HorizontalAlignment="Stretch" Height="25">
			<!-- верхняя часть-->
		</dxlc:LayoutGroup>
		<dxlc:LayoutGroup HorizontalAlignment="Stretch" >
			<!-- центральная часть-->
		</dxlc:LayoutGroup>
		<dxlc:LayoutGroup HorizontalAlignment="Stretch" >
			<!-- нижняя часть-->
		</dxlc:LayoutGroup>
	</dxlc:LayoutControl>
</dx:DXWindow>

Настройка подключения к коннектору


Добавим две кнопки, одна кнопка настройки подключения, а вторая кнопка подключения. Для этого воспользуемся кнопкой SimpleButton от DevExpress. Кнопки будут расположены в верхней части приложения. В каждую кнопку поместим картинки, привычные по S#.Designer, S#.Data и S#.Terminal.

<dx:DXWindow x:Class="ShellNew.MainWindow"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
             xmlns:dxlc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol"
             xmlns:xaml="http://schemas.stocksharp.com/xaml"
             mc:Ignorable="d"
             Title="MainWindow" Height="450" Width="800">
	<dxlc:LayoutControl Padding="0" Name="LayoutControlRoot" Orientation="Vertical">
		<dxlc:LayoutGroup HorizontalAlignment="Stretch" Height="25">
			<!-- верхняя часть-->
			<dxlc:LayoutItem Width="40">
				<dx:SimpleButton x:Name="SettingsButton" Click="SettingsButton_Click" >
					<Image Source="{xaml:ThemedIcons Key=Settings}" Width="16" />
				</dx:SimpleButton>
			</dxlc:LayoutItem>
			<dxlc:LayoutItem Width="40">
				<dx:SimpleButton x:Name="ConnectButton" Click="ConnectButton_Click" >
					<Image Source="{xaml:ThemedIcons Key=Connect}" Width="16" />
				</dx:SimpleButton>
			</dxlc:LayoutItem>
		</dxlc:LayoutGroup>
		<dxlc:LayoutGroup HorizontalAlignment="Stretch" View="Tabs">
			<!-- центральная часть-->
		</dxlc:LayoutGroup>
		<dxlc:LayoutGroup HorizontalAlignment="Stretch" >
			<!-- нижняя часть-->
		</dxlc:LayoutGroup>
	</dxlc:LayoutControl>
</dx:DXWindow>

В верхнем правом углу экранной формы увидим такую картину:



Двойным кликом на каждую кнопку создадим обработчики событий нажатия на кнопку. В коде MainWindow необходимо объявить коннектор, а также место и имя файла в котором будут храниться настройки коннектора.

public readonly Connector Connector;
private const string _dir = "Data";
private static readonly string _settingsFile = $@"{_dir}\connection.xml";

В обработчике события нажатия на кнопку настроек коннектора будем открывать окно конфигурации коннектора и сохранять его в файл.

private void SettingsButton_Click(object sender, RoutedEventArgs e)
{
	if (Connector.Configure(this))
	{
		new XmlSerializer<SettingsStorage>().Serialize(Connector.Save(), _settingsFile);
	}
}

В конструкторе будем проверять, есть ли каталог и файл с настройками коннектора, и если он есть, будем его загружать в коннектор:

//----------------------------------------------------------------------------------
Directory.CreateDirectory(_dir);
Connector = new Connector();
if (File.Exists(_settingsFile))
{
	Connector.Load(new XmlSerializer<SettingsStorage>().Deserialize(_settingsFile));
}
//----------------------------------------------------------------------------------

Большинство объектов S#.API имеют методы Save и Load, с помощью которых можно сохранить и загрузить этот объект из XML-файла.

В методе обработчике нажатия на кнопку подключения подключаем коннектор.

		private void ConnectButton_Click(object sender, RoutedEventArgs e)
		{
			Connector.Connect();
		}

Теперь можно запустить программу и проверить ее.

Установка темной темы


Многие трейдеры предпочитают темные темы торговых приложений. Поэтому сразу делаем так, чтобы тема программы была темной. Для нужно найти файл App.xaml:



И заменить в нем Application на charting:ExtendedBaseApplication, и Visual Studio нам сама предложит вставить необходимые библиотеки.

<charting:ExtendedBaseApplication
	x:Class="ShellNew.App"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:charting="http://schemas.stocksharp.com/xaml"
	StartupUri="MainWindow.xaml">
</charting:ExtendedBaseApplication>

А в файле App.xaml.cs нужно удалить «: Application».

namespace ShellNew
{
	/// <summary>
	/// Interaction logic for App.xaml
	/// </summary>
	public partial class App 
	{
	}
}

В конструкторе MainWindow пишем ApplicationThemeHelper.ApplicationThemeName = Theme.VS2017DarkName;

Полный код на текущий момент:

public partial class MainWindow
{
	public readonly Connector Connector;

	private const string _dir = "Data";
	private static readonly string _settingsFile = $@"{_dir}\connection.xml";

	public MainWindow()
	{
		//----------------------------------------------------------------------------------
		ApplicationThemeHelper.ApplicationThemeName = Theme.VS2017DarkName;
		//----------------------------------------------------------------------------------
		Directory.CreateDirectory(_dir);
		Connector = new Connector();
		if (File.Exists(_settingsFile))
		{
			Connector.Load(new XmlSerializer<SettingsStorage>().Deserialize(_settingsFile));
		}
		//----------------------------------------------------------------------------------
		InitializeComponent();
	}

	private void SettingsButton_Click(object sender, RoutedEventArgs e)
	{
		if (Connector.Configure(this))
		{
			new XmlSerializer<SettingsStorage>().Serialize(Connector.Save(), _settingsFile);
		}
	}

	private void ConnectButton_Click(object sender, RoutedEventArgs e)
	{
		Connector.Connect();
	}
}

Запускаем для проверки темной темы:



Создание панели инструментов


Добавим папку, где мы будем хранить все созданные нами контроллы, и назовем ее XAML. Добавим в нее свой первый UserControll, дадим ему имя SecurityGridControl.



В него добавляем один элемент SecurityPicker. В нем будут отображаться имеющиеся инструменты. По аналогии с главным окном будем использовать LayoutControl от DevExpress.

<UserControl
	x:Class="ShellNew.XAML.SecurityGridControl"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:xaml="http://schemas.stocksharp.com/xaml"
	mc:Ignorable="d"
	d:DesignHeight="450" d:DesignWidth="800">
	<xaml:SecurityPicker x:Name="SecPicker" />
</UserControl>

Перейдем в конструктор главного окна и изменим центральную часть в вид закладок. В одной из закладок расположим созданный нами контролл с SecurityPicker:

<dxlc:LayoutGroup HorizontalAlignment="Stretch" View="Tabs">
	<!-- центральная часть-->
	<dxlc:LayoutGroup  Header="Securities">
		<myxaml:SecurityGridControl x:Name="SecurityPanel" />
	</dxlc:LayoutGroup>
</dxlc:LayoutGroup>

Теперь, когда у нас есть панель инструментов, надо задать ей источник данных, в нашем случае это коннектор. Можно было просто в конструкторе MainWindow написать
SecurityPanel.SecPicker.SecurityProvider = Connector;
.

Но не стоит засорять MainWindow кодом, который к нему не относится. Поэтому создадим статическую переменную Instance а в конструкторе MainWindow присвою ему значение MainWindow:

…
public static MainWindow Instance;
…
Instance = this;
…

Теперь в любом месте нашей программы мы можем обращаться к свойствам MainWindow через код MainWindow.Instance.XXX.

В конструкторе SecurityGridControl таким образом указываем Connector как источник данных:

public SecurityGridControl()
{
	InitializeComponent();
	SecPicker.SecurityProvider = MainWindow.Instance.Connector;
}

Запустим для проверки:



Добавление логирования


Работу программы, коннектора или робота необходимо контролировать. Для этого в S#.API есть специальный класс LogManager. Данный класс принимает сообщения от источников и передает их в слушатели. В нашем случае источниками будут Connector, стратегии и т.д., а слушателем будет файл и панель логов.

В коде MainWindow объявляем объект LogManager и место, где он будет храниться:

public readonly LogManager LogManager;
private static readonly string _logsDir = $@"{_dir}\Logs\";

В конструкторе MainWindow создаем LogManager, задаем ему источник Connector и файл слушателя:

//----------------------------------------------------------------------------------
LogManager = new LogManager();
LogManager.Sources.Add(Connector);
LogManager.Listeners.Add(new FileLogListener
{
	SeparateByDates = SeparateByDateModes.SubDirectories,
	LogDirectory = _logsDir
});
//----------------------------------------------------------------------------------

По аналогии с панелью инструментов создадим, панель логов в папку XAML добавляем еще один UserControl. Дадим ему имя MonitorControl. В него добавим элемент Monitor.

<UserControl
	x:Class="ShellNew.XAML.MonitorControl"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:xaml="http://schemas.stocksharp.com/xaml"
	mc:Ignorable="d"
	d:DesignHeight="450" d:DesignWidth="800">
	<xaml:Monitor x:Name="Monitor" />
</UserControl>

В конструкторе MonitorControl зададим в LogManager еще и Monitor как слушателя:

public MonitorControl()
{
	InitializeComponent();
	MainWindow.Instance.LogManager.Listeners.Add(new GuiLogListener(Monitor));
}

В нижнюю часть MainWindow добавляем созданный MonitorControl:

<dxlc:LayoutGroup HorizontalAlignment="Stretch" dxlc:LayoutControl.AllowVerticalSizing="True">
	<!-- нижняя часть-->
	<myxaml:MonitorControl x:Name="MonitorControl" />
</dxlc:LayoutGroup>

Запускаем для проверки:



Создание панели стаканов


По аналогии с предыдущими панелями создадим панель стаканов, в папку XAML добавляем еще один UserControl. Дадим ему имя MarketDepthControl.

В MainWindow мы уже использовали LayoutControl, в этом контроле тоже воспользуемся LayoutControl. Разобьем панель на две части по горизонтали:

 <UserControl
	x:Class="ShellNew.XAML.MarketDepthControl"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:dxlc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol"
	mc:Ignorable="d">
	<dxlc:LayoutControl Padding="0" Name="LayoutControlRoot" Orientation="Horizontal">
		<dxlc:LayoutGroup>
		<!--Left-->
		</dxlc:LayoutGroup>
		<dxlc:LayoutGroup Orientation="Vertical" dxlc:LayoutControl.AllowHorizontalSizing="True">
		<!--Rigth-->
		</dxlc:LayoutGroup>
	</dxlc:LayoutControl>
</UserControl>

В левую часть добавим SecurityPicker – с ним мы встречались, когда создавали панель инструментов.

<dxlc:LayoutGroup>
	<xaml:SecurityPicker x:Name="SecPicker" SecuritySelected="SecPicker_SecuritySelected" />
</dxlc:LayoutGroup>
Правую часть разобьем на части по вертикали. Сверху правой части будет стакан:
<dxlc:LayoutGroup Orientation="Vertical"
                  dxlc:LayoutControl.AllowHorizontalSizing="True">
	<dxlc:LayoutItem VerticalAlignment="Stretch">
		<xaml:MarketDepthControl x:Name="MarketDepth" MaxHeight="2000"
                         SelectionChanged="MarketDepth_SelectionChanged" />
	</dxlc:LayoutItem>
</dxlc:LayoutGroup>

У MarketDepthControl необходимо задать какое-нибудь значение MaxHeight, иначе приложение не будет запускаться.

Под стаканом расположим элементы задания портфеля, цены, и объёма заявки:

<dxlc:LayoutItem Label="Portfolio" Height="20">
	<xaml:PortfolioComboBox x:Name="PortfolioComboBox" />
</dxlc:LayoutItem>
<dxlc:LayoutItem Label="Price" Height="20">
	<dxe:SpinEdit MinValue="0" Name="SpinEditPrice" />
</dxlc:LayoutItem>
<dxlc:LayoutItem Label="Volume" Height="20">
	<dxe:SpinEdit MinValue="0" Name="SpinEditVolume" />
</dxlc:LayoutItem>

Здесь стоит отметить свойство Label у LayoutItem, оно позволяет задать текст перед элементом. А также элемент SpinEdit от DevExpress в котором удобно задавать численные значения. Выглядят эти элементы следующим образом:



Еще ниже расположим кнопки купить и продать:

<dxlc:LayoutGroup Orientation="Horizontal" Height="20" VerticalAlignment="Stretch">
	<dxlc:LayoutItem VerticalAlignment="Stretch">
		<dx:SimpleButton Content="Buy" x:Name="BuyButton" Click="BuyButton_Click" />
	</dxlc:LayoutItem>
	<dxlc:LayoutItem VerticalAlignment="Stretch">
		<dx:SimpleButton Content="Sell" x:Name="SelltButton" Click="SelltButton_Click" />
	</dxlc:LayoutItem>
</dxlc:LayoutGroup>

Полный код:

<UserControl
	x:Class="ShellNew.XAML.MarketDepthControl"
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:dxlc="http://schemas.devexpress.com/winfx/2008/xaml/layoutcontrol"
	xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
	xmlns:xaml="http://schemas.stocksharp.com/xaml"
	xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
	mc:Ignorable="d">
	<dxlc:LayoutControl Padding="0" Name="LayoutControlRoot" Orientation="Horizontal">
		<dxlc:LayoutGroup>
			<xaml:SecurityPicker x:Name="SecPicker" 
								 SecuritySelected="SecPicker_SecuritySelected" />
		</dxlc:LayoutGroup>
		<dxlc:LayoutGroup Orientation="Vertical" 
						  dxlc:LayoutControl.AllowHorizontalSizing="True">
			<dxlc:LayoutItem VerticalAlignment="Stretch">
				<xaml:MarketDepthControl x:Name="MarketDepth" MaxHeight="2000" 
										 SelectionChanged="MarketDepth_SelectionChanged" />
			</dxlc:LayoutItem>
			<dxlc:LayoutItem Label="Portfolio" Height="20">
				<xaml:PortfolioComboBox x:Name="PortfolioComboBox" />
			</dxlc:LayoutItem>
			<dxlc:LayoutItem Label="Price" Height="20">
				<dxe:SpinEdit MinValue="0" Name="SpinEditPrice" />
			</dxlc:LayoutItem>
			<dxlc:LayoutItem Label="Volume" Height="20">
				<dxe:SpinEdit MinValue="0" Name="SpinEditVolume" />
			</dxlc:LayoutItem>
			<dxlc:LayoutGroup Orientation="Horizontal" Height="20" 
							  VerticalAlignment="Stretch">
				<dxlc:LayoutItem VerticalAlignment="Stretch">
					<dx:SimpleButton Content="Buy" x:Name="BuyButton" 
									 Click="BuyButton_Click" />
				</dxlc:LayoutItem>
				<dxlc:LayoutItem VerticalAlignment="Stretch">
					<dx:SimpleButton Content="Sell" x:Name="SelltButton" 
									 Click="SelltButton_Click" />
				</dxlc:LayoutItem>
			</dxlc:LayoutGroup>
		</dxlc:LayoutGroup>
	</dxlc:LayoutControl>
</UserControl>

В конструкторе MarketDepthControl зададим источник инструментов для SecurityPicker и источник портфелей для PortfolioComboBox, в нашем случае это будет Connector:

public MarketDepthControl()
{
	InitializeComponent();
	SecPicker.SecurityProvider = MainWindow.Instance.Connector;
	PortfolioComboBox.Portfolios = new PortfolioDataSource(MainWindow.Instance.Connector);
}

Создадим обработчик события выделения инструмента в SecurityPicker. В нем проверяем не равен ли нулю полученный инструмент. Если он не равен нулю, сохраняем полученный инструмент в локальную переменную, нам он пригодится при обновлении стакана. После чего очищаем и регистрируем полученный инструмент в Connector на получение стакана с помощью метода RegisterMarketDepth. С помощью метода GetMarketDepth получаем текущий стакан по инструменту, чтобы им обновить MarketDepthControl.

private Security _selectedSecurity;
private void SecPicker_SecuritySelected(Security security)
{
	if (security == null) return;
	_selectedSecurity = security;
	MainWindow.Instance.Connector.RegisterMarketDepth(_selectedSecurity);
	var marketDepth = MainWindow.Instance.Connector.GetMarketDepth(_selectedSecurity);
	MarketDepth.UpdateDepth(marketDepth);
}

Чтобы стакан постоянно обновлялся в конструкторе MarketDepthControl, подпишемся на событие изменения стакана MarketDepthChanged у коннектора. В обработчике этого события будем проверять, какому инструменту принадлежит полученный стакан, и если он принадлежит выделенному инструменту в SecurityPicker, то обновляем им: MarketDepthControl.

public MarketDepthControl()
{
	InitializeComponent();
	SecPicker.SecurityProvider = MainWindow.Instance.Connector;
	PortfolioComboBox.Portfolios = new PortfolioDataSource(MainWindow.Instance.Connector);
	MainWindow.Instance.Connector.MarketDepthChanged += Connector_MarketDepthChanged;
}

private void Connector_MarketDepthChanged(MarketDepth marketDepth)
{
	if (_selectedSecurity == null || 
		marketDepth.Security != _selectedSecurity) return;
	MarketDepth.UpdateDepth(marketDepth);
}

В центральную части MainWindow добавляем созданную панель MarketDepthControl:

<dxlc:LayoutGroup HorizontalAlignment="Stretch" View="Tabs">
	<!-- центральная часть-->
	<dxlc:LayoutGroup  Header="Securities">
		<myxaml:SecurityGridControl x:Name="SecurityPanel" />
	</dxlc:LayoutGroup>
	<dxlc:LayoutGroup  Header="Portfolios">
		<myxaml:PortfolioGridControl x:Name="PortfolioGridControl" />
	</dxlc:LayoutGroup>
	<dxlc:LayoutGroup  Header="Orders">
		<myxaml:OrderGridControl x:Name="OrderGridControl" />
	</dxlc:LayoutGroup>
	<dxlc:LayoutGroup  Header="MyTrades">
		<myxaml:MyTradeGridControl x:Name="MyTradeGridControl" />
	</dxlc:LayoutGroup>
	<dxlc:LayoutGroup  Header="MarketDepth">
		<myxaml:MarketDepthControl x:Name="MarketDepthControl" />
	</dxlc:LayoutGroup>
</dxlc:LayoutGroup>

На данном этапе можно запустить программу и проверить работу обновления стаканов.
Создадим обработчика события нажатия на кнопки купить и продать. В каждом обработчике создаем Order, в нем указываем инструмент выбранный в SecurityPicker, портфель выбранный в PortfolioComboBox, объём и цену из соответствующих SpinEdit. Регистрируем заявку в Connector с помощью метода RegisterOrder.

private void BuyButton_Click(object sender, RoutedEventArgs e)
{
	Order order = new Order()
	{
		Security = _selectedSecurity,
		Portfolio = PortfolioComboBox.SelectedPortfolio,
		Volume = SpinEditVolume.Value,
		Price = SpinEditPrice.Value,
		Direction = StockSharp.Messages.Sides.Buy,
	};
	MainWindow.Instance.Connector.RegisterOrder(order);
}

private void SelltButton_Click(object sender, RoutedEventArgs e)
{
	Order order = new Order()
	{
		Security = _selectedSecurity,
		Portfolio = PortfolioComboBox.SelectedPortfolio,
		Volume = SpinEditVolume.Value,
		Price = SpinEditPrice.Value,
		Direction = StockSharp.Messages.Sides.Sell,
	};
	MainWindow.Instance.Connector.RegisterOrder(order);
}

Оба обработчика отличаются только направлением заявки.

Сделаем чтобы при выделении котировки в стакане значение SpinEditPrice менялось на цену выделенной котировки. Для этого создадим обработчик события SelectionChanged у MarketDepthControl. В котором будем обновлять значение SpinEditPrice ценой выделенной котировки если выделенная котировка не равна нулю.

private void MarketDepth_SelectionChanged(object sender, 
	GridSelectionChangedEventArgs e)
{
	if (MarketDepth.SelectedQuote == null)
		return;
	SpinEditPrice.Value = MarketDepth.SelectedQuote.Price;
}

Запускаем для проверки:



Сохранение маркет-данных


Для сохранения портфелей, инструментов, площадок нам необходим класс CsvEntityRegistry. В него надо переделать место хранения сущностей и вызвать метод Init, для их загрузки.

	_csvEntityRegistry = new CsvEntityRegistry(_csvEntityRegistryDir);
	_csvEntityRegistry.Init();

Для сохранения свечей, сделок и т.д. нам понадобится StorageRegistry:

	_storageRegistry = new StorageRegistry
	{
		DefaultDrive = new LocalMarketDataDrive(_storageRegistryDir),
	};

Также нам понадобится реестр хранилищ-снэпшотов SnapshotRegistry:

_snapshotRegistry = new SnapshotRegistry(_snapshotRegistryDir);

Все это мы передаем в Connector при его создании:

Connector = new Connector(_csvEntityRegistry, _storageRegistry, _snapshotRegistry)
{
	IsRestoreSubscriptionOnReconnect = true,
	StorageAdapter = { DaysLoad = TimeSpan.FromDays(3) },
};
Connector.LookupAll();

Здесь мы также указали, что Connector будет переподключаться при разрыве подключения, а также указали сколько дней истории необходимо загружать. Строка Connector.LookupAll(); запрашивает имеющиеся данные:

//----------------------------------------------------------------------------------
Directory.CreateDirectory(_dir);

_csvEntityRegistry = new CsvEntityRegistry(_csvEntityRegistryDir);
_csvEntityRegistry.Init();
_storageRegistry = new StorageRegistry
{
	DefaultDrive = new LocalMarketDataDrive(_storageRegistryDir),
};
_snapshotRegistry = new SnapshotRegistry(_snapshotRegistryDir);
Connector = new Connector(_csvEntityRegistry, _storageRegistry, _snapshotRegistry)
	{
		IsRestoreSubscriptionOnReconnect = true,
		StorageAdapter = { DaysLoad = TimeSpan.FromDays(3) },
	};
Connector.LookupAll();

if (File.Exists(_settingsFile))
{
		Connector.Load(new XmlSerializer<SettingsStorage>().Deserialize(_settingsFile));
}
//----------------------------------------------------------------------------------

После загрузки приложения перейдя в папку Data мы увидим, что появились новые папки:



При повторном подключении панели инструментов и портфелей уже будут заполнены.

Мы плавно подошли к окончанию первой части. На данном этапе программа позволяет выводить на экран все доступные нам маркет-данные. В следующей части будет продемонстрирована самое лакомое — а именно торговля как в ручном, так и в автоматическом режиме.

Продолжение следует...

Автор: Иван Залуцкий
  • +18
  • 6,4k
  • 2
ITI Capital
266,00
Лучший онлайн-брокер для работы на бирже
Поделиться публикацией

Комментарии 2

    0
    С какими биржами есть возможность связать?
      0
      У них куча коннекторов к разным площадкам. Постоянно появляются новые. Если нужна конкретная площадка, лучше спросить на форуме у разработчиков. Часть коннекторов может быть платна. Функционал библиотеки и тех инструментов, которые они предлагают бесплатно очень велик. Несколько лет назад пользовался библиотекой для разработки системы автоматической торговли на ММВБ через брокерский QUIK. Впечатления от библиотеки только положительные.

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое