In the OsEngine parameter window, it is possible to display not only parameters but also other elements including tables and charts.
This robot example serves as a demonstration of implementing a custom table in the parameters window.
It shows:
Dynamic Table: The table updates in real-time as new data arrives.
User Interaction: Users can modify data in the table and obtain values from specific cells.
Customizable Parameters: The ability to enable and disable the robot and also configure the trailing stop for exiting.
1. How it looks.
We enter the tester and run our robot, opening the parameters window.
The robot is called: CustomTableInTheParamWindowSample.
1. Settings Tab "Base settings":
Here are the settings:
Regime: enables the trading mode.
TrailingValue: coefficient for calculating the trailing stop.
MaxPositions: maximum number of open positions.
Volume type: mode for selecting the volume.
Contracts: number of contracts for the instrument.
Contract currency: currency of the contract.
Deposit percent: percentage of the deposit.
Volume: value of the volume. What this represents depends on the previous item. In the case of Contracts, this indicates the volume of the instrument. In the case of Contract currency, it specifies the amount in rubles or dollars needed to enter. In the case of Deposit percent, it denotes the % of the total deposit required to enter the contract.
Asset in portfolio: here you need to specify the name of the currency that will be used to calculate the volume if you chose the volume type “Deposit percent”. In the tester, we leave it as “Prime”. In crypto, this is usually “USDT”.
2. Settings Tab "Table settings":
Here we find the table itself:
Security: column with the names of the papers.
Count candle: the depth of data we check for movement (Movement to enter).
Movement to enter: we fill this column ourselves, as it is needed in the trading logic. If Current movement is greater than Movement to enter, we enter a position.
Current movement: current percentage movement of the paper's price.
Side: method for selecting the entry for each paper.
2. Where to find the robot in the project?
3. Robot Breakdown.
Lines 1-11:
Necessary namespaces are imported.
Lines 23-26:
The namespace OsEngine.Robots.TechSamples is defined to organize the code, and the class CustomTableInTheParamWindowSample inherits from BotPanel.
Lines 31-39:
The methods GetNameStrategyType and ShowIndividualSettingsDialog are overridden.
The first method returns the name of the strategy; the second is empty and is intended to display individual settings.
Lines 44-49:
The robot parameters are created.
Lines 51-52:
The bot panel is initialized.
A screener tab is created, and a reference to the tab is saved in _tab for further use.
Lines 56-64:
A tab and the table itself are created.
The tab is created and its dimensions are set.
Line 68:
The LoadLines method is called.
Lines 72-74:
Subscriptions to events are established.
These events include candle completion, robot deletion, and table value updates.
Lines 83-96:
This block is responsible for the set of robot variables.
Lines 101-118:
The method passes changes made by the user to the robot's logic.
Method CellValueChanget:
1. Iterating through Rows of DataGridView:
It goes through each row in the DataGridView.
2. Updating Object Properties of Lines:
Values are extracted from the cells of the current row and assigned to the corresponding properties of objects in the Lines collection.
Security: String value from the first cell.
CandleCount: Integer value from the second cell.
MovementToEnter: Decimal value from the third cell.
Side: Enumeration value of type Side obtained from the fifth cell.
3. Saving Changes:
Calls the SaveLines() method to save the updated data.
4. Exception Handling:
Wraps the entire code in a try-catch block to catch potential exceptions.
If an exception occurs, its message is logged using the _tab.SendNewLogMessage() method.
Lines 123-129:
This method is called when the robot is deleted.
Method DeleteBotEvent:
1. File Existence Check:
The File.Exists method checks if a file exists at the specified path.
2. File Deletion:
If the file exists, the File.Delete method removes it.
Lines 134-152:
Data from the table is saved to a .txt file.
Method SaveLines:
1. Creating a Write Stream:
Creates a new write stream (StreamWriter) in the specified file. The file is created or overwritten depending on the second argument of the constructor (false).
2. Iterating through Lines:
3. Writing Data to the File:
Each line is written to the file, separating cells with the “%” symbol.
4. Closing the Write Stream:
Closes the write stream.
5. Exception Handling:
Wraps the entire code in a try-catch block to capture potential exceptions.
If an exception occurs, its message is recorded in the log using the _tab.SendNewLogMessage() method.
Lines 157-187:
Loading saved data into the table.
Method LoadLines:
1. File Existence Check:
Checks if a file with the specified name exists.
2. Loading Data from the File:
If the file exists, it is opened for reading.
Each line is read from the file, creating a new object of TableBotLine and adding it to the Lines collection.
The read stream is then closed.
3. Updating DataGridView:
New rows are added to the DataGridView based on the data from the Lines collection.
Lines 193-199:
Candle completion event:
Method _tab_CandleFinishedEvent:
1. _tab_CandleFinishedEvent(List<Candle> candles, BotTabSimple tab):
This method is called upon the completion of each candle.
The tab argument represents an object that contains information about this new tab.
All methods within are invoked.
Lines 204-261:
A method responsible for creating columns in the table:
Method CreateColumnsTable:
1. Thread check:
MainWindow.GetDispatcher.CheckAccess(): Checks whether the method is called from the UI thread.
If the method is not called from the UI thread, it calls itself recursively in the context of the UI thread to ensure safe UI updates.
2. Creating a container for DataGrid:
A WindowsFormsHost object is created, which allows a Windows Forms control (DataGrid) to be hosted in a WPF application.
3. Creating DataGrid:
A DataGrid object is created, and its main properties are set:
Row selection mode: A mode is chosen that allows the selection of an entire row.
Automatic row height adjustment: Rows will automatically adjust their height based on content.
Text alignment: Text in cells is centered both horizontally and vertically.
4. Creating columns:
Five columns are created with the names "Security," "Count candle," "Movement to enter," "Current movement," and "Side." Each column is assigned a cell type (DataGridViewTextBoxCell), column width (automatic fill), and header.
5. Adding columns to DataGrid:
The created columns are added to the DataGrid's columns collection.
6. Placing DataGrid in the container:
The DataGrid object is set as a child element of the WindowsFormsHost object.
7. Error handling:
Potential exceptions are handled in a try-catch block. In case of an error, a message is logged.
Lines 266-302:
A method that adds new rows to the table:
Method AddNewLineInTable:
1. Tool availability check:
If no financial instrument is assigned to the tab (tab.Security == null), the method terminates.
2. Searching for an existing row:
The method iterates through all rows in the table. If the value in the first column of the row (the instrument name) matches the name of the instrument for the current tab, it is considered that the row for this instrument already exists.
3. Creating a new row:
If no row for the instrument is found, a new TableBotLine object is created with the necessary values (instrument name, number of candles, movement to enter, etc.).
This object is added to the rows collection.
The CreateRowsTable method is called to update the table display.
Lines 307-325:
Methods for updating the table display:
Method CreateRowsTable:
1. Thread check:
_tableDataGrid.InvokeRequired: Checks whether the method is called from the UI thread.
If the method is not called from the UI thread, it calls itself recursively in the context of the UI thread to ensure safe UI updates.
2. Adding a row:
The CreateLine method is called to create a new data row and add it to the table.
3. Saving changes:
The SaveLines method is called to save changes in the table.
4. Error handling:
Potential exceptions are handled in a try-catch block. In case of an error, a message is logged.
Lines 327-364:
This method creates a line and returns it to the calling method.
Method CreateLine:
1. Creating a row:
A new DataGridViewRow object is created - a row for the table.
2. Creating cells:
For each field of the TableBotLine object, a corresponding table cell (DataGridViewTextBoxCell or DataGridViewComboBoxCell) is created.
The cells are added to the row's collection of cells (row.Cells.Add).
For some cells, the ReadOnly property is set to true, making their content immutable by the user.
3. Filling cells with data:
In the try-catch block, the cells are filled with values from the Lines[index] object:
The "Security" cell is filled with the instrument name.
The "CandleCount" cell is filled with the number of candles for calculating the current movement.
The "MovementToEnter" cell is filled with the value of the movement to enter.
The "CurrentMovement" cell is filled with the value of the current movement by adding a percentage symbol "%".
The "Side" cell is filled with the textual representation of the trade side ("Buy" or "Sell") based on the value of the Side field in the TableBotLine object.
4. Error handling:
If an exception occurs while working with the data, an error message is logged.
5. Returning the row:
The filled row of the table is returned to the calling method.
Lines 369-420:
This method sorts the lines:
Method SortLine:
1. Thread check:
_tableDataGrid.InvokeRequired: Checks whether the method is called from the UI thread.
If the method is not called from the UI thread, it calls itself recursively in the context of the UI thread for safe UI updates.
2. Checking the last row:
Checks whether the last row of the table contains any value in the first column (_tableDataGrid.Rows[_tableDataGrid.Rows.Count - 1].Cells[0].Value.ToString()).
If the value is absent, the method terminates without further processing.
3. Iterating through rows:
Outer loop: Iterates through all rows of the table (_tableDataGrid.Rows.Count).
4. Searching for the instrument:
Inner loop: Iterates through all tabs of the application (_tab.Tabs.Count).
For each row, it checks whether the instrument name in the first column (_tableDataGrid.Rows[i].Cells[0].Value.ToString()) matches the instrument name of any tab.
If a match is found, the GotLine flag is set to true, indicating that the row has been found.
5. Removing unused rows:
After iterating through all the rows, if the GotLine flag remains false (no match found for any row with a tab), this row is unnecessary.
The index of the unused row is stored in the variable indexLine.
The row with this index is removed from the table using the _tableDataGrid.Rows.Remove method.
The SaveLines method is called to save the changes.
6. Error handling:
Potential exceptions during data processing are handled in a try-catch block. In case of an error, an error message is logged.
Lines 425-479:
Method for updating the current percentage change:
Method MovementPercentUpdate:
1. Thread check:
_tableDataGrid.InvokeRequired: Checks whether the method is called from the UI thread.
If the method is not called from the UI thread, it calls itself recursively in the context of the UI thread for safe UI updates.
2. Searching for the table row:
It searches for a row in the table corresponding to the instrument from the tab (tab.Security.Name).
It iterates through all rows in the Lines collection and compares the Security field's values with the instrument name.
If a match is found, the row index (indexLine) is saved, and the GotLine flag is set to true.
3. Checking the number of candles:
It compares the number of candles in the retrieved candle list (candles.Count) with the number of candles specified in the found row of the table (TableCandlesCount).
If the number of candles in the list is less than in the table, the method terminates, as there is insufficient data for calculations.
4. Updating the price change percentage value:
If the row is found (GotLine == true), the price change percentage is calculated based on the trade direction (Lines[indexLine].Side):
For buy transactions (Buy): the price change is calculated as the difference between the closing price of the penultimate candle (candles[candles.Count - Lines[indexLine].CandelCount].Close) and the closing price of the last candle (candles[candles.Count - 1].Close), divided by the closing price of the last candle and multiplied by 100.
For sell transactions (Sell): the calculation is similar, but the price difference is taken with the opposite sign.
The result of the calculation is rounded to two decimal places (Math.Round) and stored in the CurrentMovement field of the Lines[indexLine] object.
The percentage change in price, with the "%" symbol added, is set in the corresponding cell of the table (_tableDataGrid.Rows[indexLine].Cells[3].Value).
5. Saving changes:
The SaveLines method is called to save changes in the data table.
6. Error handling:
Possible exceptions encountered while working with data are handled in a try-catch block. In case of an error, a message is logged.
Lines 488-565:
Trading logic:
Method TradeLogicMethod:
1. Checking the active mode:
If the value is "Off," the method terminates without performing further actions.
2. Checking for candles:
If the list is empty, the method terminates, as there is insufficient data for analysis.
3. Searching for the table row:
It iterates through all rows in the Lines collection and compares the values of the Security field with the instrument name.
If a match is found, the row index (indexLine) is saved, and the GotLine flag is set to true.
4. Checking for the presence of a row:
If the row is not found in the table (GotLine == false), the method terminates.
5. Obtaining open positions:
A list of open positions (positions.Count) is extracted from the tab.
6. Logic for opening a position (if no open positions exist):
If there are no open positions (positions.Count == 0), a condition check for opening a position is performed:
We check whether the total number of positions exceeds a maximum value. If it does, we exit.
The value for entering a position (movementToEnter) is retrieved from the corresponding cell in the table (_tableDataGrid.Rows[indexLine].Cells[2].Value.ToString()) and converted into a decimal number.
The current price movement (Lines[indexLine].CurrentMovement) is compared to the specified movement for entry.
If the current movement exceeds the specified value (movementToEnter < Lines[indexLine].CurrentMovement), a market buy is executed based on the calculated lot volume using the GetVolume method, and a market sell is executed with the lot volume determined by the GetVolume method.
7. Logic for updating the stop loss (if there are open positions):
If the position is not open (PositionStateType.Open), the method terminates.
Depending on the direction of the open position (positions[0].Direction):
For a buy position (Buy):
The stop loss price is calculated as the difference between the minimum price of the last candle and the product of that price and the trailing value, divided by 100.
For a sell position (Sell):
The stop loss price is calculated as the sum of the maximum price of the last candle and the product of that price and the trailing value, divided by 100.
The CloseAtTrailingStop method of the tab is called to update the stop loss of the open position (positions[0]) at the calculated price.
Lines 567-656:
Method GetVolume:
1. Initialization:
A variable volume is created with a value of 0 to store the result.
2. Checking the volume type:
Contracts: If the volume type is set to "Contracts," then the volume is taken from the Volume variable.
Contract currency:
If the volume type is set to "Contract currency," the volume is calculated by dividing the Volume value by the last ask price.
Adjustment for OS Trader: If the application is running in OS Trader mode, the volume is additionally adjusted based on the instrument's lot and server settings.
Rounding: The result is rounded to the number of decimal places specified in the instrument's settings.
Percentage of deposit: If the volume type is set to "Deposit percent," then:
The current value of the portfolio in the base currency is determined.
The amount of money to be invested in the trade is calculated based on the specified percentage of the portfolio.
The volume is calculated by dividing this amount by the last ask price and the instrument’s lot.
The result is rounded.
3. Returning the result:
The calculated volume value is returned.
Lines 661-696:
Separate class for storing and transferring data about table rows:
Properties:
Security: A string containing the name of the financial instrument.
CandelCount: An integer indicating the number of candles related to this instrument.
MovementToEnter: A decimal number representing the price movement value necessary to enter a position.
CurrentMovement: A decimal number representing the current price movement.
Side: An enumeration (enum) defining the side of the trade (buy or sell).
Method GetSaveStr:
Initialization of the string: An empty string saveStr is created, which will be used to assemble the final result.
Concatenation of values:
The values of the variables Security, CandleCount, MovementToEnter, and CurrentMovement are sequentially added to the string saveStr, separated by the percent symbol (%).
Then, the value of the variable Side is added without an additional separator.
Return of result: The prepared string is returned as the result of the method.
Method SetFromStr:
Splitting the string: The string is split into an array of strings by the "%" sign.
Filling properties:
Security: The first value of the array is assigned to the Security property.
CandleCount: The second value is converted to an integer and assigned to the CandleCount property.
MovementToEnter: The third value is converted to a decimal and assigned to the MovementToEnter property.
CurrentMovement: The fourth value is stripped of the last character (it is assumed to be the percent sign) and converted to a decimal, assigned to the CurrentMovement property.
Side: The fifth value is used to attempt conversion to the Side enumeration. If the conversion is successful, the value is assigned to the Side property.
Output: This example of a robot will be useful for demonstrating how to create and configure custom elements in the robot's parameters window. It shows how to create a table for displaying values and using it by users, which can serve as a ready-made template for implementing similar tasks.
Good luck with your algorithms!