Controls enable certain interactions involving cBots, indicators and plugins. Using the below guide, you can easily create basic and advanced UI controls directly on a chart.
There are several built-in classes that represent popular controls such as buttons, text blocks, text boxes, and shapes. However, you can also create custom controls.
If you build the above indicator and create an instance, you should see a grey 'Click Me' button right at the centre of the chart.
The Difference Between Controls and Chart Objects¶
In an earlier section, we have already covered chart objects. Controls enable users to interact with cBots/indicators. Conversely, chart objects give the opportunity to draw something on the trade chart or in a detached indicator output window.
Chart controls derive from the ControlBase class while chart objects are derived from the ChartObject class.
Chart controls are positioned statically by using different alignment options. While chart objects can be positioned in exactly the same way, their position can also change dynamically depending on certain X and Y coordinates.
Similarly to chart objects, chart controls can be added both to the 'main' chart and/or any indicator output windows (if they exist). An example of such positioning is given below.
The ColorPicker control empowers traders to select their preferred colour for key elements or objects in cTrader. For example, a developer may integrate the ColorPicker control into a cBot that draws trendlines so that users can select their preferred colour for each trendline. This setup makes it easier for traders to distinguish between various trendline types.
A plugin that adds indicators to charts can also implement the ColorPicker control since this feature allows users to choose a colour for different indicator lines.
When users click the colour square, the colour selector appears. Users can then choose between standard and custom colours.
The DropZone control enables traders to perform drag-and-drop actions involving cBots, indicators or plugins. To implement this control in your algorithm, use the DropZone class. The DropZone control can be manipulated like any other control.
When a user drops a compatible file or folder into the box, the Dropped event is generated. When a user drops a folder or multiple folders into the box, only compatible files with the specified extensions in FilterExtensions are processed and copied.
Note
The dropped files are usually copied and saved in this location: ..\Documents\cAlgo\Data\{AlgoType}\{AlgoName}\Selected Files\
The location for cBot instances differs from the above: ..\Documents\cAlgo\Data\cBots\{cBotName}\{unique-instance-number}\Selected files\
This plugin code shows you how to add a DropZone control to Trade Watch:
usingSystem;usingSystem.Drawing;usingcAlgo.API;usingcAlgo.API.Collections;usingcAlgo.API.Indicators;usingcAlgo.API.Internals;namespacecAlgo.Plugins{[Plugin(AccessRights = AccessRights.None)]publicclassTradeWatchTabSample:Plugin{protectedoverridevoidOnStart(){vartab=TradeWatch.AddTab("DropZone");var_border=newBorder{BorderThickness=3,BorderColor=Color.DarkGray,Opacity=0.7,HorizontalAlignment=HorizontalAlignment.Center,VerticalAlignment=VerticalAlignment.Center,Height=200,Width=350,BackgroundColor=Color.LightGray};var_textBlock=newTextBlock{Text="Drag your files here",Opacity=1,ForegroundColor=Color.DarkGray,FontSize=16,VerticalAlignment=VerticalAlignment.Center,HorizontalAlignment=HorizontalAlignment.Center};var_dropzone=newDropZone{FilterExtensions=newstring[]{"txt","algo","csv"},IsDirectoryDropAllowed=true,IsVisible=true,IsEnabled=true};_border.Child=_textBlock;_dropzone.Child=_border;// _dropzone.Child = _textBlock;tab.Child=_dropzone;_dropzone.Dropped+=Dropped_file;}privatevoidDropped_file(DroppedEventArgsobj){Print("File has been added!");}}}
The ProgressBar control displays the progression of an ongoing operation. Progress bars make algorithms more user-friendly and help manage traders' expectations regarding the wait time for certain operations.
cTrader Algo API allows developers to add two types of ProgressBar controls: Infinite Control and Determinate Control.
You may want to use this control when you can determine the wait time for an operation and want users to see an indicator based on that time.
To display the progress of the operation, use the Value property to set a figure. To set the progress bar percentage, use the Minimum and Maximum properties.
The plugin code below shows you how to create a progress bar using both infinite and determinate (green) controls:
cTrader Algo API provides the OpenFileDialog interface to enable users to select a file for an algorithm. When a user selects a file in the resulting window, the file is copied to the algorithm Selected files folder. An algorithm can work files inside its Selected files folder without restrictions.
Tip
Use the OpenFileDialog functionality when you need to load important data files, backup or configuration files, custom indicators or scripts.
The code below shows you how to use the OpenFileDialog dialogue in an indicator:
cTrader Algo API provides the OpenFolderDialog interface to enable users to specify a folder for an algorithm. When a user selects a folder in the resulting window, all files and folders with files inside the selected folder are copied to the algorithm Selected files folder.
Tip
Use the OpenFolderDialog functionality when you need to load a folder containing critical data files, backup or configuration files, custom indicators or scripts.
Selected files and folders are usually copied to the Selected files folder because an algorithm can work with items inside that folder even when its AccessRights is set to None.
Note
An algorithm Selected files folder is usually along this path: ..\Documents\cAlgo\Data\{AlgoType}\{AlgoName}\Selected files\
The Selected files folder for a cBot instance differs from the above: ..\Documents\cAlgo\Data\cBots\{cBotName}\{Instance-Id}\Selected files\
The code below shows you how to use the OpenFolderDialog dialogue in an indicator:
cTrader Algo API provides the SaveFileDialog interface to enable users to save files (locally) to their computers. When the relevant window appears, a user selects the folder to which they want to save the file and enters a name for the file. The file is then saved in the specified location.
Tip
Use theSaveFileDialog functionality when you need to do any of the following:
Save performance reports, backtesting results, configuration files or optimisation data.
Export trading logs, operation journals or general user activity data.
Store custom indicators, data for charts and visualisations or parameters for a strategy.
The code below shows you how to use the SaveFileDialog dialogue in an indicator:
usingSystem;usingcAlgo.API;usingSystem.Text;namespacecAlgo{[Indicator(AccessRights = AccessRights.None, IsOverlay = true)]publicclassSaveFileDialogExample:Indicator{privateSaveFileDialog_saveFileDialog;protectedoverridevoidInitialize(){_saveFileDialog=newSaveFileDialog(){InitialDirectory=Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),Title="My Save File Dialog Title"};varshowSaveFileDialogText=newButton{Text="Show Save File Dialog (Set Content as text)"};varshowSaveFileDialogBytes=newButton{Text="Show Save File Dialog (Set Content as bytes)"};showSaveFileDialogText.Click+=showSaveFileDialogText_Click;showSaveFileDialogBytes.Click+=showSaveFileDialogBytes_Click;varpanel=newStackPanel{Orientation=Orientation.Vertical,HorizontalAlignment=HorizontalAlignment.Center,VerticalAlignment=VerticalAlignment.Center};panel.AddChild(showSaveFileDialogText);panel.AddChild(showSaveFileDialogBytes);Chart.AddControl(panel);}privatevoidshowSaveFileDialogText_Click(ButtonClickEventArgsobj){varresult=_saveFileDialog.ShowDialog();Print($"Result: {result}");if(result!=FileDialogResult.Cancel){_saveFileDialog.WriteToFile("Test in text");}}privatevoidshowSaveFileDialogBytes_Click(ButtonClickEventArgsobj){varresult=_saveFileDialog.ShowDialog();Print($"Result: {result}");if(result!=FileDialogResult.Cancel){_saveFileDialog.WriteToFile(Encoding.UTF8.GetBytes("Test in bytes"));}}publicoverridevoidCalculate(intindex){}}}
A custom control is a control that is, in essence, 'made up' of several pre-defined controls. In other words, it is a control for which other controls constitute its content.
Custom controls act as reusable classes similarly to built-in controls.
To make working with controls more convenient, you may want to place several controls into a distinct 'group' with its own position in the UI. To do this, you can use Panels class and its derivatives.
Think of a panel as a control with other controls as its contents. cTrader supports five different classes inheriting from the base Panels class (which itself inherits from the Control class).
Canvas
DockPanel
Grid
StackPanel
WrapPanel
Each of the above classes uses different panel layouts and positioning strategies as discussed below.
The canvas is a panel that allows for positioning controls based on certain X and Y coordinates.
Notably, the X and Y axes are different from the ones used by chart objects/drawings. The X and Y coordinates used by the Canvas class represent numeric values starting from (0, 0) from the top left corner of the chart.
usingcAlgo.API;namespaceChartControlsTest{[Indicator(IsOverlay = true, AccessRights = AccessRights.None)]publicclassChartControls:Indicator{[Parameter(DefaultValue = "Click Me")]publicstringText{get;set;}[Parameter(DefaultValue = 0)]publicdoubleLeft{get;set;}[Parameter(DefaultValue = 0)]publicdoubleTop{get;set;}[Parameter(DefaultValue = 0)]publicdoubleMargin{get;set;}[Parameter(DefaultValue = 10)]publicdoublePadding{get;set;}protectedoverridevoidInitialize(){varbutton=newButton{Text=Text,Left=Left,Top=Top,Margin=Margin,Padding=Padding};button.Click+=Button_Click;varcanvas=newCanvas();/* We add our button control to the canvas panel. */canvas.AddChild(button);// We add our canvas panel to the chart.Chart.AddControl(canvas);}privatevoidButton_Click(ButtonClickEventArgsobj){obj.Button.Text="You clicked me, thanks";}publicoverridevoidCalculate(intindex){}}}
Upon creating an instance of the above indicator, you should see the 'Click Me' button in the top left corner of the chart.
The Top property of a control determines its positioning on the Y-axis. In turn, the Left property defines its positioning on the X-axis.
The above code also uses the Padding and Margin properties. Padding refers to the distance between the control contents and its outer borders. Margin is the distance between the control and the borders of its 'parent'. The Padding and Margin properties are applicable to all panels, not just the canvas class. They are useful for managing spacing between your controls.
In the majority of cases, the Canvas class is used to group only a small number of controls.
The DockPanel class is used to 'dock' a control in a static location on the chart. There are four possible dock positions.
Top
Bottom
Left
Right
Each control has a Dock property. When using the DockPanel class, you can use this property to position a control inside a DockPanel. This is illustrated in the following example.
usingcAlgo.API;namespaceChartControlsTest{[Indicator(IsOverlay = true, AccessRights = AccessRights.None)]publicclassChartControls:Indicator{[Parameter(DefaultValue = Dock.Top)]publicDockTextBoxDock{get;set;}[Parameter(DefaultValue = Dock.Bottom)]publicDockButtonDock{get;set;}privateTextBox_textBox;protectedoverridevoidInitialize(){_textBox=newTextBox{Margin=5,Text="Write here...",ForegroundColor=Color.Yellow,Dock=TextBoxDock,Width=200};varbutton=newButton{Text="Tell what I wrote?",Dock=ButtonDock,Width=200};button.Click+=Button_Click;vardockPanel=newDockPanel{HorizontalAlignment=HorizontalAlignment.Center,VerticalAlignment=VerticalAlignment.Center,};dockPanel.AddChild(_textBox);dockPanel.AddChild(button);Chart.AddControl(dockPanel);}privatevoidButton_Click(ButtonClickEventArgsobj){obj.Button.Text=$"You wrote: {_textBox.Text}";}publicoverridevoidCalculate(intindex){}}}
When creating an instance of this indicator, you should see a dock panel in the centre of the chart containing a text field and a clickable button.
Note that dock panels can be populated either horizontally or vertically. You cannot use both of these alignments at the same time. The dock panel orientation is set automatically when setting up the first control in a DockPanel. If the Dock property of the first control is set to either Top or Bottom, the entire DockPanel will be oriented vertically, and vice versa.
Stack panels are one of the most frequently used controls due to their simplicity and usability. Stack panels align 'child' controls either horizontally or vertically one by one. As shown below, you only have to set up its orientation and the class will manage the remainder.
After creating an indicator instance, you should see a horizontal stack panel with two text fields and a 'Submit' button in the bottom-right corner of the main chart.
Wrap panels are mostly identical to stack panels. However, when there is not enough space to fit all elements of a wrap panel, it will automatically 'wrap' the remaining controls to the next line on the Y axis.
Think of grids as spreadsheets with a set number of columns and rows. When using grids, you can add controls to each separate cell.
As demonstrated below, when you create an instance of the Grid class, you pass the number of its rows and columns as integer arguments. When adding new 'children' or controls, you similarly have to specify the number of 'child' rows and columns.
When creating an instance of the above indicator, you should see a 10x2 grid right in the centre of the chart.
Positioning Controls Within Price and Time Coordinates¶
In addition to panel controls, cTrader allows for specifying price and time coordinates for controls directly in the chart area. The AddControl() and MoveControl() methods provide algo developers with this functionality.
Use the following method overloads to manage the coordinates of your chart controls depending on your preferences.
To add a chart control to the chart/indicator area on absolute bar index and price (x, y) coordinates.
To add a chart control to the chart/indicator area on absolute time (x) coordinate.
1
voidAddControl(ControlBasecontrol,DateTimetime)
To move a chart control to the chart/indicator area on absolute time (x) coordinate.
1
voidMoveControl(ControlBasecontrol,DateTimetime)
To add a chart control to the chart/indicator area on absolute bar index (x) coordinate.
1
voidAddControl(ControlBasecontrol,intbarIndex)
To move a chart control to the chart/indicator area on absolute bar index (x) coordinate.
1
voidMoveControl(ControlBasecontrol,intbarIndex)
To add a chart control to the chart/indicator area on absolute price (y) coordinate.
1
voidAddControl(ControlBasecontrol,doubley)
To move a chart control to the chart/indicator area on absolute price (y) coordinate.
1
voidMoveControl(ControlBasecontrol,doubley)
The ControlBase parameter class can include any other subordinate class (e.g., Control, Button, etc.) for the method to be called in accordance with the above signatures.
The following cBot example adds the 'Click me!' button above the last bar on the chart. The button is moved ahead together with every new bar added. After clicking on the button, a message box appears on the screen.
The number of root controls to be added on a chart is limited to 100 because of possible performance issues. This limitation applies only to one algorithm instance and controls attached to price and/or bars.
The Margin property defines the space between the borders of a Control object and its parent (e.g., a chart, a panel, a border, etc.).
In turn, the Padding property determines the space between the control contents and its borders. You can vary the property values so that they are all different for different sides.
usingcAlgo.API;namespaceChartControlsTest{[Indicator(IsOverlay = true, AccessRights = AccessRights.None)]publicclassChartControls:Indicator{[Parameter(DefaultValue = "Click Me")]publicstringText{get;set;}[Parameter(DefaultValue = 0)]publicdoubleLeft{get;set;}[Parameter(DefaultValue = 0)]publicdoubleTop{get;set;}[Parameter(DefaultValue = 0, Group = "Margin")]publicdoubleLeftMargin{get;set;}[Parameter(DefaultValue = 0, Group = "Margin")]publicdoubleTopMargin{get;set;}[Parameter(DefaultValue = 0, Group = "Margin")]publicdoubleRightMargin{get;set;}[Parameter(DefaultValue = 0, Group = "Margin")]publicdoubleBottomMargin{get;set;}[Parameter(DefaultValue = 5, Group = "Padding")]publicdoubleLeftPadding{get;set;}[Parameter(DefaultValue = 5, Group = "Padding")]publicdoubleTopPadding{get;set;}[Parameter(DefaultValue = 5, Group = "Padding")]publicdoubleRightPadding{get;set;}[Parameter(DefaultValue = 5, Group = "Padding")]publicdoubleBottomPadding{get;set;}protectedoverridevoidInitialize(){varbutton=newButton{Text=Text,Left=Left,Top=Top,Margin=newThickness(LeftMargin,TopMargin,RightMargin,BottomMargin),Padding=newThickness(LeftPadding,TopPadding,RightPadding,BottomPadding)};button.Click+=Button_Click;varcanvas=newCanvas();canvas.AddChild(button);Chart.AddControl(canvas);}privatevoidButton_Click(ButtonClickEventArgsobj){obj.Button.Text="You clicked me, thanks";}publicoverridevoidCalculate(intindex){}}}
An instance of this indicator will create a grey 'Click Me' button in the top-left corner of the chart. Modify the parameters in the 'Add instance'/'Modify parameters' windows to see how exactly different margin and padding values change how the control is displayed.
When using styles you can give a similar look to several different types of controls. This is particularly useful when dealing with a large (five or more) number of controls.
The Style class allows for setting values for different properties of controls such as Margin or BackgroundColor once. Afterward, you can reuse these values as a template for multiple other controls.
We can also create a consistent look for multiple controls without using the Style class at all.
usingcAlgo.API;namespaceChartControlsTest{[Indicator(IsOverlay = true, AccessRights = AccessRights.None)]publicclassChartControls:Indicator{protectedoverridevoidInitialize(){vartextBoxStyle=newStyle();textBoxStyle.Set(ControlProperty.ForegroundColor,Color.Red);textBoxStyle.Set(ControlProperty.Margin,5);textBoxStyle.Set(ControlProperty.FontFamily,"Cambria");textBoxStyle.Set(ControlProperty.FontSize,12);textBoxStyle.Set(ControlProperty.Width,150);// Here we change the foreground color to Yellow if mouse hover over the textboxtextBoxStyle.Set(ControlProperty.ForegroundColor,Color.Yellow,ControlState.Hover);varfirstTextBox=newTextBox{Text="Type...",Style=textBoxStyle};varsecondTextBox=newTextBox{Text="Type...",Style=textBoxStyle};varthirdTextBox=newTextBox{Text="Type...",Style=textBoxStyle};varpanel=newStackPanel{Orientation=Orientation.Vertical,HorizontalAlignment=HorizontalAlignment.Center,VerticalAlignment=VerticalAlignment.Center};panel.AddChild(firstTextBox);panel.AddChild(secondTextBox);panel.AddChild(thirdTextBox);Chart.AddControl(panel);}publicoverridevoidCalculate(intindex){}}}
The Image control can be used to display a locally stored image. The Image control uses the .NET Bitmap class, which means that it supports the majority of popular image formats such as the following.
.PNG
.JPG
.BMP
.GIF
.TIFF
Consult the .NET Bitmap class documentation to learn more about it.
To use an Image control, set its Source property to an image file data in a byte array (byte[]).
After launching an instance of the above indicator, you should see nothing new on the main chart. However, input a valid file path as a value of the 'Image File Path' parameter, and you should see the chosen image displayed in the centre of the screen.
You can also use your project resources for storing images and displaying them via the Image control.
To do so, open the project resources in Visual Studio and switch to the 'Resources' tab. In it, create a new resource and add an existing image.
Afterward, you will be able to this image as a source in any Image control via the Project_Name_Space.Properties.Resources._Image_Name property.
For an illustration, save the below image as image.png on your system.
As an example copy the below image and save it as "image.png" on your system:
Create a new indicator in cTrader, set its name to 'Image Sample', and open it via Visual Studio. Afterward, add the logo indicator project as a resource. To do so, right-click on your project, select 'Properties' and click on 'Resources', and, subsequently, 'Create and manage assembly resources'. Copy the logo.png file into the newly opened tab.
Copy the below code to your indicator main source code file.