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

в 14:58, , рубрики: Блог компании ITI Capital, Программирование, разбор, торговые роботы, финансы в IT

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

Мы продолжаем говорить о создании торговых роботов с помощью платформы StockSharp. В первом материале речь шла о создании проекта и отрисовке основных элементов торговой системы. В заключительном материале цикла займемся непосредственной реализацией торговой стратегии.

Создание панели портфелей

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

<UserControl
	x:Class="ShellNew.XAML.PortfolioGridControl"
	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:PortfolioGrid x:Name="PortfolioGrid" />
</UserControl>

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

public PortfolioGridControl()
{
	InitializeComponent();
	MainWindow.Instance.Connector.NewPortfolio += PortfolioGrid.Portfolios.Add;
	MainWindow.Instance.Connector.NewPosition += PortfolioGrid.Positions.Add;
}

Таким образом при создании нового портфеля, он появится на панели портфелей, а при появлении новой позиции на панели портфелей – она будет обновлена.

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

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

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

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

У нас появилась вкладка с портфелями.

Создание панели ордеров

Панель ордеров в S#.API имеет возможность выставления заявок, снятия заявок и перерегистрации. По аналогии с панелью инструментов создадим панель ордеров, в папку XAML добавляем еще один UserControl. Дадим ему имя OrderGridControl. В него добавим элемент OrderGrid:

<UserControl
	x:Class="ShellNew.XAML.OrderGridControl"
	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:OrderGrid x:Name="OrderGrid" />
</UserControl>

OrderGrid имеет событие регистрации заявки OrderRegistering, событие перерегистрации заявки OrderReRegistering и событие отмены заявки OrderCanceling.

Создадим их обработчики:

<UserControl
	x:Class="ShellNew.XAML.OrderGridControl"
	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:OrderGrid x:Name="OrderGrid"
	                OrderRegistering="OrderGrid_OnOrderRegistering"
	                OrderReRegistering="OrderGrid_OnOrderReRegistering"
	                OrderCanceling="OrderGrid_OnOrderCanceling" />
</UserControl>

В обработчике события регистрации заявки мы создаем окно OrderWindow, в котором необходимо указать источники данных для инструментов, портфелей, и рыночных данных. В нашем случае это все будет Connector.

После чего мы вызываем OrderWindow методом ShowModal. Если в этом окне была нажата кнопка ОК, то мы через коннектор методом RegisterOrder регистрируем заявку:

private void OrderGrid_OnOrderRegistering()
{
	var newOrder = new OrderWindow
	{
		Title = "Order registering",
		Order = new Order(),
		SecurityProvider = MainWindow.Instance.Connector,
		MarketDataProvider = MainWindow.Instance.Connector,
		Portfolios = new PortfolioDataSource(MainWindow.Instance.Connector),
	};
	if (newOrder.ShowModal(this))
		MainWindow.Instance.Connector.RegisterOrder(newOrder.Order);
		}

В обработчике события перерегистрации заявки мы делаем все аналогичным образом. Только в этом случае в событие нам приходит объект Order – это заявка, которую надо перерегистрировать. Поэтому в OrderWindow мы указываем Order = order.ReRegisterClone(newVolume: order.Balance), чтобы заполнить поля окна OrderWindow.

После чего мы вызываем OrderWindow методом ShowModal. Если в этом окне была нажата кнопка ОК, то мы через коннектор методом ReRegisterClone перерегистрируем заявку. В него мы передаем старую заявку, которую надо отменить, и новую, которую надо выставить.

private void OrderGrid_OnOrderReRegistering(Order order)
{
	var window = new OrderWindow
	{
		Title = "Order re-registering",
		SecurityProvider = MainWindow.Instance.Connector,
		MarketDataProvider = MainWindow.Instance.Connector,
		Portfolios = new PortfolioDataSource(MainWindow.Instance.Connector),
		Order = order.ReRegisterClone(newVolume: order.Balance)
	};
	if (window.ShowModal(this))
		MainWindow.Instance.Connector.ReRegisterOrder(order, window.Order);
}

В обработчике события отмены заявки достаточно вызвать метод CancelOrder и передать в него ордер, который необходимо отменить:

private void OrderGrid_OnOrderCanceling(Order order)
{
	MainWindow.Instance.Connector.CancelOrder(order);
}

Чтобы заявки отображались в OrderGrid, необходимо в конструкторе OrderGridControl подписаться на события появления новой заявки и на событие ошибки регистрации, а затем передавать эти события в OrderGrid:

public OrderGridControl()
{
	InitializeComponent();
	MainWindow.Instance.Connector.NewOrder += OrderGrid.Orders.Add;
	MainWindow.Instance.Connector.OrderRegisterFailed += OrderGrid.AddRegistrationFail;
}

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

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

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

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

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

По аналогии с панелью инструментов создадим панель собственных сделок. В папку XAML добавляем еще один UserControl. Дадим ему имя MyTradeGridControl. В него добавим элемент MyTradeGrid:

<UserControl
	x:Class="ShellNew.XAML.MyTradeGridControl"
	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:MyTradeGrid x:Name="MyTradeGrid" />
</UserControl>

В конструкторе MyTradeGridControl нам надо подписаться на события появления новой собственной сделки и передать ее в MyTradeGrid:

public MyTradeGridControl()
{
	InitializeComponent();
	MainWindow.Instance.Connector.NewMyTrade += MyTradeGrid.Trades.Add;
}

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

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

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

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

Создание панели со стратегией

Панель стратегий мы будем создавать так же, как и все предыдущие панели. В папку XAML добавляем еще один UserControl. Дадим ему имя StrategyControl. С помощью LayoutControl разобьём экранную форму на две части. В левой части будут вкладка с свечным графиком и вкладка статистикой стратегии:

<dxlc:LayoutGroup Orientation="Vertical">
	<dxlc:LayoutGroup View="Tabs" Name="StatistisAndChartLayoutGroup">
		<dxlc:LayoutGroup Header="Chart">
			<xaml:Chart x:Name="Chart" />
		</dxlc:LayoutGroup>
		<dxlc:LayoutGroup Header="Statistic">
			<dxlc:LayoutItem VerticalAlignment="Stretch" 								 dxlc:LayoutControl.AllowHorizontalSizing="True" >
				<xaml:StatisticParameterGrid 
					x:Name="StatisticParameterGrid" MaxHeight="2000"/>
			</dxlc:LayoutItem>
			<dxlc:LayoutItem VerticalAlignment="Stretch" >
				<xaml:EquityCurveChart x:Name="EquityCurveChart" />
			</dxlc:LayoutItem>
		</dxlc:LayoutGroup>
	</dxlc:LayoutGroup>
</dxlc:LayoutGroup>

Здесь для отображения статистики стратегии использована StatisticParameterGrid, а для отображения графика прибыли и убытка – EquityCurveChart. У StatisticParameterGrid необходимо задать какое-нибудь значение MaxHeight, иначе приложение не будет запускаться.

В правой части будет проводиться настройка свойств стратегии в PropertyGridEx. А также будут расположены кнопки запуска и остановки стратегии:

<dxlc:LayoutGroup View="Group" dxlc:LayoutControl.AllowHorizontalSizing="True" 
                  dxlc:DockLayoutControl.Dock="Right" Orientation="Vertical">
	<dxlc:LayoutItem VerticalAlignment="Stretch">
		<xaml:PropertyGridEx x:Name="PropertyGridEx" />
	</dxlc:LayoutItem>
	<dxlc:LayoutItem VerticalAlignment="Stretch" Height="20">
		<dx:SimpleButton x:Name="StartStrategyButton" Content="Start strategy" ToolTip="Start strategy" Click="StartStrategyButton_Click" />
	</dxlc:LayoutItem>
	<dxlc:LayoutItem VerticalAlignment="Stretch" Height="20">
		<dx:SimpleButton x:Name="StopStrategyButton" Content="Stop strategy" ToolTip="Stop strategy" Click="StopStrategyButton_Click" />
	</dxlc:LayoutItem>
</dxlc:LayoutGroup>

Полный код:

<UserControl x:Class="ShellNew.XAML.StrategyControl"
             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:xaml="http://schemas.stocksharp.com/xaml"
             xmlns:dx="http://schemas.devexpress.com/winfx/2008/xaml/core"
             mc:Ignorable="d">
	<dxlc:LayoutControl>
		<dxlc:LayoutGroup Orientation="Vertical">
			<dxlc:LayoutGroup View="Tabs" Name="StatistisAndChartLayoutGroup">
				<dxlc:LayoutGroup Header="Chart">
					<xaml:Chart x:Name="Chart" />
				</dxlc:LayoutGroup>
				<dxlc:LayoutGroup Header="Statistic">
					<dxlc:LayoutItem VerticalAlignment="Stretch" 
									 dxlc:LayoutControl.AllowHorizontalSizing="True" >
						<xaml:StatisticParameterGrid 
							x:Name="StatisticParameterGrid" MaxHeight="2000"/>
					</dxlc:LayoutItem>
					<dxlc:LayoutItem VerticalAlignment="Stretch" >
						<xaml:EquityCurveChart x:Name="EquityCurveChart" />
					</dxlc:LayoutItem>
				</dxlc:LayoutGroup>
			</dxlc:LayoutGroup>
		</dxlc:LayoutGroup>
		<dxlc:LayoutGroup View="Group" dxlc:LayoutControl.AllowHorizontalSizing="True" 
		                  dxlc:DockLayoutControl.Dock="Right" Orientation="Vertical">
			<dxlc:LayoutItem VerticalAlignment="Stretch">
				<xaml:PropertyGridEx x:Name="PropertyGridEx" />
			</dxlc:LayoutItem>
			<dxlc:LayoutItem VerticalAlignment="Stretch" Height="20">
				<dx:SimpleButton x:Name="StartStrategyButton" 
								 Content="Start strategy" ToolTip="Start strategy"
				                 Click="StartStrategyButton_Click" />
			</dxlc:LayoutItem>
			<dxlc:LayoutItem VerticalAlignment="Stretch" Height="20">
				<dx:SimpleButton x:Name="StopStrategyButton" 
								 Content="Stop strategy" ToolTip="Stop strategy"
				                 Click="StopStrategyButton_Click" />
			</dxlc:LayoutItem>
		</dxlc:LayoutGroup>
	</dxlc:LayoutControl>
</UserControl>

В конструкторе StrategyControl задаем Connector как источник данных для PropertyGridEx, почти в каждом контроле мы выполняли подобные действия:

public StrategyControl()
{
	InitializeComponent();
	PropertyGridEx.SecurityProvider = MainWindow.Instance.Connector;
	PropertyGridEx.Portfolios = 
		new PortfolioDataSource(MainWindow.Instance.Connector);
}

Нам необходимо как-то передать стратегию в наш контрол. Для этого в StrategyControl создаем метод BindStraregy, который будет принимать стратегию, сохранять ссылку на нее в локальной переменной, а также задавать стратегию в PropertyGridEx и StatisticParameterGrid.

С помощью метода SetChart в стратегию предаём график свечей Chart, после этого в стратегии Chart можно будет получить с помощью метода GetChart. Также задаем Connector для стратегии.

private Strategy _strategy;
public void BindStraregy(Strategy strategy)
{
	_strategy = strategy;
	PropertyGridEx.SelectedObject = strategy;
	StatisticParameterGrid.Parameters.
	AddRange(_strategy.StatisticManager.Parameters);
	_strategy.SetChart(Chart);
	_strategy.Connector = MainWindow.Instance.Connector;
} 

При работе с графиком прибыли и убытков надо учесть, что стратегия будем запускаться и останавливаться, возможно, несколько раз, поэму с каждым запуском стратегии график надо очищать. Для это создадим метод ResetEquityCurveChart в котором будем сначала очищать EquityCurveChart. После чего нам необходимо создать графические элементы для EquityCurveChart, им можно указать имя, цвет и тип линии.

После чего подпишемся на событие изменения PnL у стратегии и в обработчике этого события отрисовываем новое значение на графике прибыли/убытков EquityCurveChart:

private void ResetEquityCurveChart()
{
	EquityCurveChart.Clear();
	var pnl = EquityCurveChart.
		CreateCurve("PNL", Colors.Green, ChartIndicatorDrawStyles.Area);
	var unrealizedPnL = EquityCurveChart.
		CreateCurve("unrealizedPnL", Colors.Black, ChartIndicatorDrawStyles.Line);
	var commissionCurve = EquityCurveChart
		.CreateCurve("commissionCurve", Colors.Red, ChartIndicatorDrawStyles.Line);

	_strategy.PnLChanged += () =>
	{
		var data = new ChartDrawData();
		data.Group(_strategy.CurrentTime)
			.Add(pnl, _strategy.PnL)
			.Add(unrealizedPnL, _strategy.PnLManager.UnrealizedPnL ?? 0)
			.Add(commissionCurve, _strategy.Commission ?? 0);
		EquityCurveChart.Draw(data);
	};
}

В обработчике события нажатия на кнопку Старт будем вызвать этот метод. А также будем сбрасывать состояние стратегии и запускать ее:

private void StartStrategyButton_Click(object sender, RoutedEventArgs e)
{
	ResetEquityCurveChart();
	_strategy.Reset();
	_strategy.Start();
}

В обработчике события нажатия на кнопку Стоп будем останавливать стратегию.
private void StopStrategyButton_Click(object sender, RoutedEventArgs e):

{
	_strategy.Stop();
}

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

<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  Header="SimpleStrategyControl">
		<myxaml:StrategyControl x:Name="SimpleStrategyControl" />
	</dxlc:LayoutGroup>
</dxlc:LayoutGroup>

Создание стратегии

Для примера рассмотрим создание простой стратегии со свечами. Она будет покупать, если свеча растущая (зеленая) и продавать, если свеча убывающая (красная).

Создадим еще одну папку в проекте – в ней будем хранить все наши стратегии. В этой папке создаем новый класс, назовем его SimpleStrategy. Все стратегии S# должны наследоваться от базового класса стратегии Strategy.

public class SimpleStrategy : Strategy	{}

Так как наша стратегия использует свечи, то создадим публичное свойство CandleSeries а в конструкторе нашей стратегии зададим ему значение по умолчанию.

public class SimpleStrategy : Strategy
{
	public CandleSeries Series { get; set; }
	public SimpleStrategy()
	{
		Series = new CandleSeries(typeof(TimeFrameCandle), new Security(), TimeSpan.FromSeconds(15))
		{
			BuildCandlesMode = MarketDataBuildModes.Build
		};
	}
}

Здесь мы указали, что свечи в CandleSeries будут TimeFrameCandle, с интервалом 15 секунд (TimeSpan.FromSeconds(15)). Для CandleSeries можно указать режим создания свечей BuildCandlesMode. Мы указали, что свечи будут построены (MarketDataBuildModes.Build). По умолчанию они будут строиться из тиков, но можно указать и другие типы данных.

Так как CandleSeries мы сделали публичным свойством, то CandleSeries можно будет дополнительно настроить из PropertyGridEx описанном в предыдущем пункте. Все стратегии имеют методы который можно переопределить, нам понадобиться переопределить метод OnStarted. Он вызывается перед запуском стратегии и позволяет предварительно задать ей стартовое состояние.

protected override void OnStarted()
{
	_connector = (Connector)Connector;
	Series.Security = Security;
	_connector
		.WhenCandlesFinished(Series)
		.Do(ProcessCandle)
		.Apply(this);
	_connector.SubscribeCandles(Series);
	base.OnStarted();
}

Здесь мы для CandleSeries задаем инструмент, который указывается в PropertyGridEx. После чего создаем правило обработки законченной свечи. В правиле указываем метод, который будет обрабатывать каждую законченную свечу – в нашем случае это метод ProcessCandle. Он будет описан позже. После того, как все задано, подписываемся на появление свечей по CandleSeries в коннекторе через метод SubscribeCandles. В нашем случае метод ProcessCandle и содержит основную логику стратегии:

private void ProcessCandle(Candle candle)
{
	if (!IsRealTime(candle)) return;

	if (candle.OpenPrice < candle.ClosePrice && Position <= 0)
	{
		RegisterOrder(this.BuyAtMarket(Volume + Math.Abs(Position)));
	}
	else
	if (candle.OpenPrice > candle.ClosePrice && Position >= 0)
	{
		RegisterOrder(this.SellAtMarket(Volume + Math.Abs(Position)));
	}
}

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

private bool IsRealTime(Candle candle)
{
	return (Connector.CurrentTime - candle.CloseTime).TotalSeconds < 10;
}

Далее смотрим на то, какая это свеча, и какая текущая позиция у стратегии. Если свеча растущая, то при позиции равной 0 мы откроем позицию рыночной заявкой на объём, заданный нами в PropertyGridEx. Если свеча растущая и позиция меньше 0, то мы «переворачиваем» позицию:

if (candle.OpenPrice < candle.ClosePrice && Position <= 0)
{
	RegisterOrder(this.BuyAtMarket(Volume + Math.Abs(Position)));
}

Противоположные действия делаем для убывающей свечи:

else
if (candle.OpenPrice > candle.ClosePrice && Position >= 0)
{
	RegisterOrder(this.SellAtMarket(Volume + Math.Abs(Position)));
}

На данный момент наша стратегия готова к работе. Ее необходимо передать в SimpleStrategyControl, который мы создали в предыдущем пункте с помощью метода BindStraregy. Это мы делаем в конструкторе MainWindow сразу после инициализации компонентов MainWindow.

SimpleStrategyControl.BindStraregy(new SimpleStrategy());

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

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

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

Стратегия работает, совершаются сделки, но пока нет свечей и сделок на графике.

Добавление свечей и сделок на график из стратегии

В пункте про панель стратегий с помощь метода SetChart в стратегию мы предали график свечей Chart. В методе OnStarted стратегии проверяем установлен ли График у стратегии и если он установлен, то инициализируем график, а также подписываемся на события появления новой собственной сделки и изменения свечи.

protected override void OnStarted()
{
	_connector = (Connector)Connector;
	if (this.GetChart() is Chart chart)
	{				
		InitChart(chart);
		NewMyTrade += DrawMyTrade;
		_connector.CandleSeriesProcessing += 
			CandleSeriesProcessing;
	}
	Series.Security = Security;
	_connector
		.WhenCandlesFinished(Series)
		.Do(ProcessCandle)
		.Apply(this);

	_connector.SubscribeCandles(Series);
	base.OnStarted();
		}

Метод инициализации графика InitChart:

private ChartCandleElement _chartCandleElement;
private ChartTradeElement _tradesElem;
private Chart _chart;
private void InitChart(Chart chart)
{
	_chart = chart;
	_chart.GuiSync(() =>
	{
		_chart.ClearAreas();
		var candlesArea = new ChartArea();
		_chart.AddArea(candlesArea);

		_chartCandleElement = new ChartCandleElement();
		_chart.AddElement(candlesArea, _chartCandleElement);

		_tradesElem = new ChartTradeElement { FullTitle = "Trade" };
		_chart.AddElement(candlesArea, _tradesElem);
	});
}

Здесь мы сохраняем ссылку на Сhart в локальной переменной. Очищаем график. Также создаем и передаем на график элементы графика для свечей и сделок. Конструкция _chart.GuiSync(() =>{… }); нужна для того чтобы инициализация графика выполнилась в главном потоке.

Метод отрисовки свечей на графике CandleSeriesProcessing:

private void CandleSeriesProcessing(CandleSeries candleSeries, 
	Candle candle)
{
	var data = new ChartDrawData();
	data.Group(candle.OpenTime)
		.Add(_chartCandleElement, candle);
_chart.Draw(data);
}

Здесь мы получаем свечу из события CandleSeriesProcessing коннектора, создаем ChartDrawData для отображения его на графике. Указываем время data.Group(candle.OpenTime), указываем что свечу надо добавить в свечной элемент графика .Add(_chartCandleElement, candle);. И указываем что графику надо прорисовать новые данные.

Аналогичные действия выполняем для сделок:

public void DrawMyTrade(MyTrade myTrade)
{
	var data = new ChartDrawData();
	data.Group(myTrade.Trade.Time)
		.Add(_tradesElem, myTrade);
	_chart.Draw(data);
}

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

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

Краткий вывод

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

Автор: Иван Залуцкий

Автор: itinvest

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js