We write trading robots using the StockSharp graphic framework. Part 2

3r33942. 3r3393955.  3r33965. 3r3393955.  3r33965. We continue to talk about creating trading robots using the platform. StockSharp . In The first material it was about creating a project and drawing the main elements of the trading system. In the final material of the cycle, we will deal directly with the implementation of a trading strategy.
3r3393955.  3r33965. 3r3393955.  3r33965.

Creating a panel of portfolios

3r3393955.  3r33965. By analogy with the toolbar, create a log panel. To do this, add another UserControl to the XAML folder. Give it the name PortfolioGridControl. Add a PortfolioGrid element to it. 3r3393955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927.
3r340. 3r33965. 3r33535. 3r3393935. 3r3393955.  3r33965. In the PortfolioGridControl designer, we need to subscribe to the events of the appearance of a new portfolio and the event of the emergence of a new position at the Connector. 3r3393955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. public PortfolioGridControl ()
{
InitializeComponent (); 3r33965. MainWindow.Instance.Connector.NewPortfolio + = PortfolioGrid.Portfolios.Add; 3r33965. MainWindow.Instance.Connector.NewPosition + = PortfolioGrid.Positions.Add; 3r33965.} 3r3393935. 3r3393955.  3r33965. Thus, when creating a new portfolio, it will appear in the portfolios panel, and when a new position appears in the portfolios panel, it will be updated. 3r3393955.  3r33965. 3r3393955.  3r33965. Add the PortfolioGridControl panel to the central part of the MainWindow:
 3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. 3r33535. 3r33965.
3r33965.
3r33965.
3r33965. 3r3675. 3r33965. 3r3645. 3r33965.
3r33965. 3r3675. 3r33965. 3r3675. 3r3393935. 3r3393955.  3r33965. Run to check:
 3r33965. 3r3393955.  3r33965. We write trading robots using the StockSharp graphic framework. Part 2 Creating a panel of orders
3r3393955.  3r33965. The order pane in S # .API has the ability to place bids, withdraw bids and re-register. By analogy with the toolbar, create an order pane, add another UserControl to the XAML folder. Give it the name OrderGridControl. Add an OrderGrid element to it:
 3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927.
3r3125. 3r33965. 3r33535. 3r3393935. 3r3393955.  3r33965. OrderGrid has an OrderRegistering order registration event, an OrderReReistering order re-registration event and an OrderCanceling order cancellation event. 3r3393955.  3r33965. 3r3393955.  3r33965. Create their handlers:
 3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927.
3r3151. OrderRegistering = "OrderGrid_OnOrderRegistering"
OrderReRegistering = "OrderGrid_OnOrderReRegistering"
OrderCanceling = "OrderGrid_OnOrderCanceling" />
3r33535. 3r3393935. 3r3393955.  3r33965. In the event registration event handler, we create an OrderWindow window in which you need to specify data sources for instruments, portfolios, and market data. In our case, this will all be Connector. 3r3393955.  3r33965. 3r3393955.  3r33965. After that, we call OrderWindow with the ShowModal method. If the OK button was pressed in this window, then we register a request through the RegisterOrder method: 3r33955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. 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),
}; 3r33965. if (newOrder.ShowModal (this))
MainWindow.Instance.Connector.RegisterOrder (newOrder.Order); 3r33965.} 3r3393935. 3r3393955.  3r33965. In the handler of the re-registration event, we do everything in the same way. Only in this case, the Order object arrives at the event — this is the application that needs to be reregistered. Therefore, in OrderWindow, we specify Order = order.ReRegisterClone (newVolume: order.Balance) to fill the fields in the OrderWindow window. 3r3393955.  3r33965. 3r3393955.  3r33965. After that, we call OrderWindow with the ShowModal method. If the OK button was pressed in this window, then we use the ReRegisterClone method to reregister the application. In it, we pass the old application, which must be canceled, and a new one, which must be set. 3r3393955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3197. 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)
}; 3r33965. if (window.ShowModal (this))
MainWindow.Instance.Connector.ReRegisterOrder (order, window.Order); 3r33965.} 3r3393935. 3r3393955.  3r33965. In the event handler for the cancellation of an application, it is enough to call the CancelOrder method and pass the order to it, which must be canceled: 3r33955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. private void OrderGrid_OnOrderCanceling (Order order)
{
MainWindow.Instance.Connector.CancelOrder (order); 3r33965.} 3r3393935. 3r3393955.  3r33965. In order for bids to be displayed in OrderGrid, it is necessary in the OrderGridControl constructor to subscribe to the events of the appearance of a new bid and to the event of a registration error, and then transfer these events to the OrderGrid:
 3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. public OrderGridControl ()
{
InitializeComponent (); 3r33965. MainWindow.Instance.Connector.NewOrder + = OrderGrid.Orders.Add; 3r33965. MainWindow.Instance.Connector.OrderRegisterFailed + = OrderGrid.AddRegistrationFail; 3r33965.} 3r3393935. 3r3393955.  3r33965. Add the created OrderGridControl panel to the central part of the MainWindow:
 3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. 3r33535. 3r33965. 3r33965. 3r33965. 3r33965. 3r3675. 3r33965. 3r3645. 3r33965. 3r33965. 3r3675. 3r33965. 3r3651. 3r33965. 3r33965. 3r3675. 3r33965. 3r3675. 3r3393935. 3r3393955.  3r33965. Run to check:
 3r33965. 3r3393955.  3r33965. Creating a panel of your own transactions 3r3393955.  3r33965. By analogy with the toolbar, create a panel of your own transactions. Add another UserControl to the XAML folder. Give it the name MyTradeGridControl. Add the MyTradeGrid element to it:
 3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927.
3r3303. 3r33965. 3r33535. 3r3393935. 3r3393955.  3r33965. In the MyTradeGridControl constructor, we need to subscribe to the events of the appearance of a new own transaction and transfer it to MyTradeGrid:
 3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. public MyTradeGridControl ()
{
InitializeComponent (); 3r33965. MainWindow.Instance.Connector.NewMyTrade + = MyTradeGrid.Trades.Add; 3r33965.} 3r3393935. 3r3393955.  3r33965. Add the created OrderGridControl panel to the central part of the MainWindow:
 3r33965. 3r3393955.  3r33965. 3r33939. 3r33535. 3r33965. 3r33965. 3r33965. 3r33965. 3r3675. 3r33965. 3r3645. 3r33965. 3r33965. 3r3675. 3r33965. 3r3651. 3r33965. 3r33965. 3r3675. 3r33965. 3r33965. 3r3659. 3r33965. 3r3675. 3r33965. 3r3675. 3r3393935. 3r3393955.  3r33965. Run to check:
 3r33965. 3r3393955.  3r33965. 3r33333. 3r3393955.  3r33965. 3r3393955.  3r33965.

Creating a panel with a strategy

3r3393955.  3r33965. We will create a strategy panel in the same way as all previous panels. Add another UserControl to the XAML folder. Give it the name StrategyControl. Using LayoutControl, we split the screen form into two parts. On the left side there will be a tab with a candlestick chart and a strategy statistics tab: 3r-3955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. 3r33965. 3r33469. 3r33965. 3r33471. 3r33965. 3r33965. 3r3675. 3r33965. 3r33477. 3r33965. 3r33394. 3r33965.
3r? 3517. 3r33965. 3r33965. 3r33965. 3r? 3517. 3r33965. 3r3675. 3r33965. 3r3675. 3r33965. 3r3675. 3r3393935. 3r3393955.  3r33965. Here, StatisticParameterGrid is used to display strategy statistics, and EquityCurveChart is used to display profit and loss graphs. For StatisticParameterGrid, you need to set some MaxHeight value, otherwise the application will not start. 3r3393955.  3r33965. 3r3393955.  3r33965. In the right part, the properties of the strategy will be set in the PropertyGridEx. And also the start and stop buttons for the strategy will be located: 3r3393955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927.
3r3499. 3r33965. 3r3501. 3r33965. 3r? 3517. 3r33965. 3r33512. 3r33965. 3r33434. 3r33965. 3r? 3517. 3r33965. 3r33512. 3r33965. 3r33965. 3r? 3517. 3r33965. 3r3675. 3r3393935. 3r3393955.  3r33965. Full code:
 3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927.
3rr3465. 3r33965. 3r33965. 3r33469. 3r33965. 3r33471. 3r33965. 3r33965. 3r3675. 3r33965. 3r33477. 3r33965. 3r33479. dxlc: LayoutControl. AllowHorizontalSizing = "True">

3r? 3517. 3r33965. 3r33965. 3r33965. 3r? 3517. 3r33965. 3r3675. 3r33965. 3r3675. 3r33965. 3r3675. 3r33965.
3r3499. 3r33965. 3r3501. 3r33965. 3r? 3517. 3r33965. 3r33512. 3r33965.
3r? 3517. 3r33965. 3r33512. 3r33965. 3r???. Content = "Stop strategy" ToolTip = "Stop strategy"
Click = "StopStrategyButton_Click" />
3r? 3517. 3r33965. 3r3675. 3r33965. 3r33521. 3r33965. 3r33535. 3r3393935. 3r3393955.  3r33965. In the StrategyControl constructor, we set the Connector as the data source for the PropertyGridEx, in almost every control we performed the following actions: 3r3393955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. public StrategyControl ()
{
InitializeComponent (); 3r33965. PropertyGridEx.SecurityProvider = MainWindow.Instance.Connector; 3r33965. PropertyGridEx.Portfolios =
new PortfolioDataSource (MainWindow.Instance.Connector); 3r33965.} 3r3393935. 3r3393955.  3r33965. We need to somehow transfer the strategy to our control. To do this, we create a BindStraregy method in StrategyControl that will accept the strategy, save a reference to it in a local variable, and also set a strategy in the PropertyGridEx and StatisticParameterGrid. 3r3393955.  3r33965. 3r3393955.  3r33965. Using the SetChart method into the strategy, we betray the chart of the Chart candles, after which the Chart strategy can be obtained using the GetChart method. We also set the Connector for the strategy. 3r3393955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. private Strategy _strategy; 3r33965. public void BindStraregy (Strategy strategy)
{
_strategy = strategy; 3r33965. PropertyGridEx.SelectedObject = strategy; 3r33965. StatisticParameterGrid.Parameters. 3r33965. AddRange (_strategy.StatisticManager.Parameters); 3r33965. _strategy.SetChart (Chart); 3r33965. _strategy.Connector = MainWindow.Instance.Connector; 3r33965.} 3r3393935. 3r3393955.  3r33965. When working with the profit and loss schedule, it is necessary to take into account that we will start and stop the strategy, perhaps several times; the poem must be cleared with each launch of the strategy. For this, we will create a ResetEquityCurveChart method in which we will first clear EquityCurveChart. After that, we need to create graphic elements for EquityCurveChart, they can specify the name, color and line type. 3r3393955.  3r33965. 3r3393955.  3r33965. After that we subscribe to the PnL change event of the strategy and in the handler of this event we draw a new value on the EquityCurveChart profit /loss graph:
 3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. private void ResetEquityCurveChart ()
{
EquityCurveChart.Clear (); 3r33965. var pnl = EquityCurveChart. 3r33965. CreateCurve ("PNL", Colors.Green, ChartIndicatorDrawStyles.Area); 3r33965. var unrealizedPnL = EquityCurveChart. 3r33965. CreateCurve ("unrealizedPnL", Colors.Black, ChartIndicatorDrawStyles.Line); 3r33965. var commissionCurve = EquityCurveChart
.CreateCurve ("commissionCurve", Colors.Red, ChartIndicatorDrawStyles.Line); 3r33965. 3r33965. _strategy.PnLChanged + = () =>
{
var data = new ChartDrawData (); 3r33965. data.Group (_strategy.CurrentTime)
.Add (pnl, _strategy.PnL)
.Add (unrealizedPnL, _strategy.PnLManager.UnrealizedPnL ?? 0)
.Add (commissionCurve, _strategy.Commission ?? 0); 3r33965. EquityCurveChart.Draw (data); 3r33965.}; 3r33965.} 3r3393935. 3r3393955.  3r33965. In the event handler for pressing the Start button, we will call this method. And also we will reset the state of the strategy and run it: 3r3393955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. private void StartStrategyButton_Click (object sender, RoutedEventArgs e)
{
ResetEquityCurveChart (); 3r33965. _strategy.Reset (); 3r33965. _strategy.Start (); 3r33965.} 3r3393935. 3r3393955.  3r33965. In the event handler, pressing the Stop button will stop the strategy. 3r3393955.  3r33965. private void StopStrategyButton_Click (object sender, RoutedEventArgs e):
 3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. {
_strategy.Stop (); 3r33965.} 3r3393935. 3r3393955.  3r33965. In the central part of the MainWindow, add the created StrategyControl panel:
 3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. 3r33535. 3r33965. 3r33965. 3r33965. 3r33965. 3r3675. 3r33965. 3r3645. 3r33965. 3r33965. 3r3675. 3r33965. 3r3651. 3r33965. 3r33965. 3r3675. 3r33965. 3r33965. 3r3659. 3r33965. 3r3675. 3r33965. 3r3663. 3r33965. 3r36565. 3r33965. 3r3675. 3r33965. 3r3669. 3r33965. 3r33965. 3r3675. 3r33965. 3r3675. 3r3393935. 3r3393955.  3r33965.

Creating a Strategy 3r34848. 3r3393955.  3r33965. For example, consider creating a simple strategy with candles. She will buy if the candle is growing (green) and sell if the candle is decreasing (red). 3r3393955.  3r33965. 3r3393955.  3r33965. Create another folder in the project - we will keep all our strategies in it. In this folder, create a new class, let's call it SimpleStrategy. All S # strategies must inherit from the base Strategy class. 3r3393955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. public class SimpleStrategy: Strategy {} 3r3393935. 3r3393955.  3r33965. Since our strategy uses candles, we will create a public property CandleSeries and in the constructor of our strategy we will give it a default value. 3r3393955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. public class SimpleStrategy: Strategy
{
public CandleSeries Series {get; set;}
public SimpleStrategy ()
{
Series = new CandleSeries (typeof (TimeFrameCandle), new Security (), TimeSpan.FromSeconds (15))
{
BuildCandlesMode = MarketDataBuildModes.Build
}; 3r33965.}
} 3r3393935. 3r3393955.  3r33965. Here we indicated that the candles in the CandleSeries will be TimeFrameCandle, with an interval of 15 seconds (TimeSpan.FromSeconds (15)). For CandleSeries, you can specify the BuildCandlesMode candlestick creation mode. We indicated that candles will be built (MarketDataBuildModes.Build). By default, they will be built from ticks, but other data types can be specified. 3r3393955.  3r33965. 3r3393955.  3r33965. Since we made the CandleSeries a public property, the CandleSeries can be further configured from the PropertyGridEx described in the previous paragraph. All strategies have methods that can be overridden, we need to override the OnStarted method. It is called before launching the strategy and allows you to pre-set its starting state. 3r3393955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. protected override void OnStarted ()
{
_connector = (Connector) Connector; 3r33965. Series.Security = Security; 3r33965. _connector
.WhenCandlesFinished (Series)
.Do (ProcessCandle)
.Apply (this); 3r33965. _connector.SubscribeCandles (Series); 3r33965. base.OnStarted (); 3r33965.} 3r3393935. 3r3393955.  3r33965. Here we set the tool for CandleSeries, which is specified in the PropertyGridEx. Then create a rule for processing the finished candle. In the rule we specify the method that will process each completed candle - in our case it is the ProcessCandle method. It will be described later. After everything is set, we subscribe to the appearance of candles on the CandleSeries in the connector via the SubscribeCandles method. In our case, the ProcessCandle method contains the main logic of the strategy:
 3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. private void ProcessCandle (Candle candle)
{
if (! IsRealTime (candle)) return; 3r33965. 3r33965. if (candle.OpenPrice < candle.ClosePrice && Position <= 0)
{
RegisterOrder (this.BuyAtMarket (Volume + Math.Abs ​​(Position)));
} 3r36565. else
if (candle.OpenPrice> candle.
{
RegisterOrder (this.SellAtMarket (Volume + Math. Abs (Position)));
} 3r3659.} 3r33434. 3r3935. 3r3393955.  3r33965. First of all, we need to determine whether the candle is being built in real time or is historical. If the candle is historical, then we ignore it. Not all strategies require this, for example, strategies based on glasses do not require this as the glasses always go real time. There is no universal way to determine whether a candle is “real-time” or historical, and in each strategy this problem will have to be solved independently depending on the requirements. In this case, we will simply compare the time the candle closes with the time in the connector and if it does not exceed a certain lag, then the candle will have real-time status. 3r3393955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. private bool IsRealTime (Candle candle)
{
return (Connector.CurrentTime - candle.CloseTime) .TotalSeconds < 10;
} 3r3393935. 3r3393955.  3r33965. Next, we look at what kind of candle it is and what the current position of the strategy is. If the candle is growing, then with a position equal to 0 we will open the position with a market request for the volume specified by us in PropertyGridEx. If the candle is growing and the position is less than ? then we “turn over” the position:
 3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. if (candle.OpenPrice < candle.ClosePrice && Position <= 0)
{
RegisterOrder (this.BuyAtMarket (Volume + Math.Abs ​​(Position)));
} 3r33434. 3r33935.
 3r33965. 3r3393955.  3r33965. The opposite actions are done for a diminishing candle: 3r3393955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. else
if (candle.OpenPrice> candle.ClosePrice && Position> = 0)
{
RegisterOrder (this.SellAtMarket (Volume + Math.Abs ​​(Position))); 3r33965.} 3r3393935. 3r3393955.  3r33965. At the moment our strategy is ready to go. It must be passed to SimpleStrategyControl, which we created in the previous paragraph using the BindStraregy method. We do this in the MainWindow constructor immediately after the initialization of the MainWindow components. 3r3393955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. SimpleStrategyControl.BindStraregy (new SimpleStrategy ()); 3r3393935. 3r3393955.  3r33965. 3r3393955.  3r33965. Run to check:
 3r33965. 3r3393955.  3r33965. Adding candles and trades to the chart from the

strategy. 3r3393955.  3r33965. In the paragraph about the strategy panel with the help of the SetChart method in the strategy, we betrayed the chart of candles Chart. In the OnStarted strategy method, we check whether the Graph is installed on the strategy and if it is installed, then we initialize the chart, and also subscribe to the events of the appearance of a new own transaction and a change in the candle. 3r3393955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. protected override void OnStarted ()
{
_connector = (Connector) Connector; 3r33965. if (this.GetChart () is Chart chart)
{
InitChart (chart); 3r33965. NewMyTrade + = DrawMyTrade; 3r33965. _connector.CandleSeriesProcessing + =
CandleSeriesProcessing; 3r33965.}
Series.Security = Security; 3r33965. _connector
.WhenCandlesFinished (Series)
.Do (ProcessCandle)
.Apply (this); 3r33965. 3r33965. _connector.SubscribeCandles (Series); 3r33965. base.OnStarted (); 3r33965.} 3r3393935. 3r3393955.  3r33965. InitChart chart initialization method:
 3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. private ChartCandleElement _chartCandleElement; 3r33965. private ChartTradeElement _tradesElem; 3r33965. private Chart _chart; 3r33965. private void InitChart (Chart chart)
{
_chart = chart; 3r33965. _chart. .chart.AddElement (candlesArea, _chartCandleElement);
_tradesElem = new ChartTradeElement {FullTitle = "Trade"};
_chart.AddElement (candlesarea, _tradesElem); 3r33965.} 3r3393935. 3r3393955.  3r33965. Here we store the reference to Chart in a local variable. Clear the schedule. We also create and transfer to the chart the chart elements for candles and deals. Construction _chart.GuiSync (() => {}); It is necessary for the initialization of the schedule to be executed in the main thread. 3r3393955.  3r33965. 3r3393955.  3r33965. Candle drawing method on CandleSeries Process:
 3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. private void CandleSeriesProcessing (CandleSeries candleSeries,
Candle candle)
{
var data = new ChartDrawData (); 3r33965. data.Group (candle.OpenTime)
.Add (_chartCandleElement, candle); 3r33965. _chart.Draw (data); 3r33965.} 3r3393935. 3r3393955.  3r33965. Here we get a candle from the event of the CandleSeriesProcessing connector, create a ChartDrawData to display it on the chart. Specify the time data.Group (candle.OpenTime), indicate that the candle should be added to the candlestick element of the graph .Add (_chartCandleElement, candle) ;. And indicate that the graphics need to draw new data. 3r3393955.  3r33965. 3r3393955.  3r33965. We perform similar actions for transactions: 3r3393955.  3r33965. 3r3393955.  3r33965. 3r33939. 3r3393927. public void DrawMyTrade (MyTrade myTrade)
{
var data = new ChartDrawData (); 3r33965. data.Group (myTrade.Trade.Time)
.Add (_tradesElem, myTrade); 3r33965. _chart.Draw (data); 3r33965.} 3r3393935. 3r3393955.  3r33965. Run to check:
 3r33965. 3r3393955.  3r33965. 3r33942. 3r3393955.  3r33965. 3r3393955.  3r33965.

Conclusion

3r3393955.  3r33965. To create a sophisticated and professional looking application, you don’t need to spend a lot of time. In a few hours we created a full-fledged application with the ability to configure, display direct trading and algorithmic trading. 3r3393955.  3r33965. Do not be afraid to try and create your own trading programs. We hope this article will help you get started in this matter. 3r3393955.  3r33965. 3r3393955.  3r33965. 3r3957. Author [/b] : Ivan Zalutsky
3r33965. 3r33965. 3r33965.
3r33965. 3r33966.
+ 0 -

Add comment