Ready-made templates for including indicators to Expert Advisors (Part 1): Oscillators
Contents
- Introduction
- Average True Range
- Bears Power
- Bulls Power
- Chaikin Oscillator
- Commodity Channel Index
- DeMarker
- Force Index
- MACD
- Momentum
- Moving Average of Oscillator
- Relative Strength Index
- Relative Vigor Index
- Stochastic Oscillator
- Triple Exponential Average
- Williams' Percent Range
- Conclusion
Introduction
Including indicators to an EA and using data from the indicator buffer in the EA is a fairly simple task, although it requires constant browsing through reference materials. We need to remember all the parameters passed to the indicator creation function, formalize some of them as EA inputs, introduce validity checks, etc. To obtain data, we need to write functions that return the necessary data from the desired bar. All this involves spending time on accessing help, entering the required variables into the EA, writing functions for receiving and monitoring data to determine signals, etc.
The purpose of this article is to create templates for including indicators to EAs. Let's look at indicators from the oscillator category, their input variables, creating an indicator handle and obtaining the required data from it. Each indicator is to feature:
- sample inputs,
- initializing inputs and creating a handle,
- deinitialization
- receiving data of the specified indicator line according to the specified timeseries index,
- monitoring the values of the received data in terms of the line state relative to any level.
By the line state we mean its appearance and shape:
- Upward direction (value 2 is less than or equal to value 1, and value 1 is less than value 0),
- Downward direction (value 2 is greater than or equal to value 1, and value 1 is greater than value 0),
- Upward reversal (value 2 is greater than value 1, and value 1 is less than value 0),
- Downward reversal (value 2 is less than value 1, and value 1 is greater than value 0),
- Upward stop (value 2 is less than or equal to value 1, and value 1 is equal to value 0),
- Downward stop (value 2 is greater than or equal to value 1, and value 1 is equal to value 0),
- Undefined state (not intended states)
By the line state relative to any level we mean:
- Above the value (the line value is greater than the level value)
- Below the value (the line value is less than the level value)
- Crossing the value upward (value 1 is less than or equal to the level value on bar 1, and value 0 is greater than the level value on bar 0)
- Crossing the value downward (value 1 is greater than or equal to the level value on bar 1, and value 0 is less than the level value on bar 0)
- Touching the value from below (value 1 is less than the level value on bar 1, and value 0 is equal to the level value on bar 0)
- Touching the value from above (value 1 is greater than the level value on bar 1, and value 0 is equal to the level value on bar 0)
- Equal to value (the line values on bar 1 and 0 are equal to the level value on bar 1 and 0)
Such conditions are quite sufficient to determine the line states (its shape or figure on two segments between the 2nd, 1st and zero bar) and to determine intersections with other indicator lines, or horizontal levels.
To control these states, we will implement universal functions common to all indicators. Just like the function for obtaining data from the indicator buffer, it will be common for any and all indicators.
All examples and codes offered in the article will be complete code blocks. They can be used "as is" in custom programs.
Average True Range
Average True Range Technical Indicator (ATR) shows the market volatility. It was introduced by Welles Wilder in his book "New concepts in technical trading systems". This indicator has been used as a component of numerous other indicators and trading systems ever since.
Average True Range can often reach a high value at the bottom of the market after a sheer fall in prices occasioned by panic selling. Low values of the indicator are typical for the periods of sideways movement of long duration which happen at the top of the market and during consolidation. It can be intepreted according to same rules as other volatility indicators. The principle of forecasting based on this indicator can be worded the following way: the higher the value of the indicator, the higher the probability of a trend change; the lower the indicator’s value, the weaker the trend’s movement is.
True Range is the greatest of the following three values:
- difference between the current maximum and minimum (high and low);
- difference between the previous closing price and the current maximum;
- difference between the previous closing price and the current minimum.
ATR is a moving average of true range values.
Parameters
The indicator has only one adjustable parameter: the moving average smoothing period. The default value is 14.
Let’s agree that all indicator inputs will be specified as customizable EA parameters.
Create an empty EA template:
Enter the name and input parameter:
Select the timer and event handler from the handlers:
and click Finish. We get an empty EA template:
//+------------------------------------------------------------------+ //| TestOscillatorATR.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- input parameters input uint InpPeriod=14; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- } //+------------------------------------------------------------------+
We will enter the code only at the global level: variables and functions. In the OnInit() and OnDeinit() handlers, set initialization and control of indicator parameters, as well as creation and deletion of the indicator handle. The test EA will use a dashboard displaying the data received from the indicator with a description of the indicator line states. The OnChartEvent() EA handler sets only handling events for working with the panel. In other words, in order to fully handle the indicator in the EA, we just need to use examples of created variables, their initialization, creation and deletion of an indicator handle, and general functions for receiving data from any indicator buffer. Everything else in the examples is just working with the panel.
Add a description to the input parameter and the global variables for creating an indicator and working with it:
//--- input parameters input uint InpPeriod = 14; /* ATR Period */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // ATR calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description
In the previous article, we created the dashboard to be used in indicators and EAs. The class has now been slightly modified, so that it is possible to create any number of name plates to display various data. We will not describe the changes made here, but we will get back to them a bit later. In subsequent articles, we will briefly describe the changes and improvements made. To test the EAs in this article, the panel class file should be located in \MQL5\Include\Dashboard\Dashboard.mqh. The file with the source code of the panel classes is attached to the article along with the test EA files.
Include the dashboard file to the EA code and set global variables for working with the dashboard:
#property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Dashboard\Dashboard.mqh> //--- input parameters input uint InpPeriod = 14; /* ATR Period */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // ATR calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description //--- variables for the panel int mouse_bar_index; // Index of the bar the data is taken from CDashboard *panel=NULL; // Pointer to the panel object
Above we examined possible options for classifying the state of the indicator line. I believe, the most convenient thing is to create an enumeration with all possible options and receive the result from the function that determines the state of the indicator line in a variable with the type of this enumeration. Let's create such an enumeration in the global area and get the following header as a result:
#property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Dashboard\Dashboard.mqh> //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod = 14; /* ATR Period */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // ATR calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description //--- variables for the panel int mouse_bar_index; // Index of the bar the data is taken from CDashboard *panel=NULL; // Pointer to the panel object
Next, we need to check the values of the inputs and adjust them if necessary.
Initialization
In all indicators, the default value is set when entering zero as the period value. The EA should demonstrate the same behavior - so that when we set the smoothing period value to zero, the EA creates an indicator that is no different from the standard one, which also had its period value set to zero. In this case, it should become the default value for both indicators. In addition, some indicators are not calculated when entering one in the period value. In other words, their minimum period is two. Each indicator has a different number of digits after the decimal point. We should consider that as well. In all the templates published in the article, all the nuances are taken into account. All checks have already been made and are handled correctly. You just need to copy the code from the article into the code of your EA and use it.
OnInit() EA handler with setting the values of indicator variables and creating the indicator handle:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period=int(InpPeriod<1 ? 14 : InpPeriod); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("ATR(%lu)",period); ind_digits=Digits(); //--- Create indicator handle ResetLastError(); handle=iATR(Symbol(),PERIOD_CURRENT,period); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Successful initialization return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+
Add the code block in the OnInit() handler to use the dashboard in the EA. As a result, the full handler code will be as follows (creating the indicator and creating the panel):
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period=int(InpPeriod<1 ? 14 : InpPeriod); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("ATR(%lu)",period); ind_digits=Digits(); //--- Create indicator handle ResetLastError(); handle=iATR(Symbol(),PERIOD_CURRENT,period); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Dashboard //--- Create the panel panel=new CDashboard(1,20,20,199,225); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,97); //--- Create a table with ID 1 to display indicator data in it panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,3,2,18,97); //--- Display tabular data in the journal panel.GridPrint(0,2); panel.GridPrint(1,2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); }
You only need the highlighted code to create the indicator. Here and in subsequent test EAs in this article, the panel is created to visually display the data we will receive from the indicator.
Deinitialization
In the OnDeinit() EA handler, we need to release the handle of the created indicator
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); }
If the panel used, the created panel object should be deleted:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; }
Data retrieval
To get data from the indicator buffer, we need to use the CopyBuffer() function. The function receives the data of the specified indicator buffer in the specified quantity into the 'buffer' array.
There are three ways to obtain data:
Access by the initial position and the number of required elements:
int CopyBuffer( int indicator_handle, // indicator handle int buffer_num, // indicator buffer index int start_pos, // starting point int count, // amount to copy double buffer[] // array the data to be copied to );
Access by the start date and the number of required elements:
int CopyBuffer( int indicator_handle, // indicator handle int buffer_num, // indicator buffer index datetime start_time, // starting date int count, // amount to copy double buffer[] // array the data to be copied to );
Access by the initial and final dates of the required time interval:
int CopyBuffer( int indicator_handle, // indicator handle int buffer_num, // indicator buffer index datetime start_time, // starting date datetime stop_time, // end time double buffer[] // array the data to be copied to );
We will use the first option for obtaining data - by bar index.
The function will receive the necessary indicator handle, bar index and indicator buffer index and return the value obtained from the specified indicator line at the specified bar index, or EMPTY_VALUE if data retrieval fails:
//+------------------------------------------------------------------+ //| Return the indicator data on the specified bar | //+------------------------------------------------------------------+ double IndicatorValue(const int ind_handle,const int index,const int buffer_num) { double array[1]={0}; ResetLastError(); if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1) { PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError()); return EMPTY_VALUE; } return array[0]; }
It is worth noting that the CopyBuffer() function allows us to get not only one value from one bar at a time, but also the specified range of indicator line values. This feature will not be used here, so as not to complicate monitoring the state of the indicator line by storing the resulting range of values somewhere in memory, and then calculating the necessary values from it based on shifts from the specified index. Here all will be simpler: we get the value at the specified index and get another value to compare with the first one. It is simpler and more convenient in terms of the function versatility.
The function that returns the state of the indicator line:
//+------------------------------------------------------------------+ //| Return the state of the indicator line | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num) { //--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); const double value2=IndicatorValue(ind_handle,index+2,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) return LINE_STATE_NONE; //--- Line upward reversal (value2>value1 && value0>value1) if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_TURN_UP; //--- Line upward direction (value2<=value1 && value0>value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_UP; //--- Line upward stop (value2<=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_UP; //--- Line downward reversal (value2<value1 && value0<value1) if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_TURN_DOWN; //--- Line downward direction (value2>=value1 && value0<value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_DOWN; //--- Line downward stop (value2>=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_DOWN; //--- Undefined state return LINE_STATE_NONE; }
Here we get the indicator line values from three bars - from the current one and the two previous ones. This is sufficient to obtain a simple line shape configuration. The index of the rightmost bar out of three is passed to the function (for example, with index 15). The bars 17, 16 and 15 are used to calculate the state of the line in this case. Since here we work with real numbers, we use the normalized difference of two values and compare it with zero to compare the values from each bar.
The function that returns the state of the indicator line relative to the specified level:
//+------------------------------------------------------------------+ //| Return the state of the line relative to the specified level | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE) { //--- Get the values of the indicator line with the shift (0,1) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) return LINE_STATE_NONE; //--- Define the second level to compare double level=(level1==EMPTY_VALUE ? level0 : level1); //--- The line is below the level (value1<level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_UNDER; //--- The line is above the level (value1>level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_ABOVE; //--- The line crossed the level upwards (value1<=level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_CROSS_UP; //--- The line crossed the level downwards (value1>=level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_CROSS_DOWN; //--- The line touched the level from below (value1<level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- The line touched the level from above (value1>level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- Line is equal to the level value (value1==level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_EQUALS; //--- Undefined state return LINE_STATE_NONE; }
The function checks the relationship between two values of the indicator line on two adjacent bars with the level passed in the parameters. The parameters indicate the index of the right bar of the two the indicator line values are taken from, and the level value the values of the two line bars need to be compared with to identify their relationship.
If we need to get the relationship with the horizontal level, then it needs to be passed in the level0 parameter. The second level1 parameter defaults to EMPTY_VALUE, which means that both indicator line data values from two adjacent bars will be compared with only one value passed to level0. (the line bar at index+1 is compared with the level0 value, and the bar at index is compared with level0).
If level1 receives a value other thanEMPTY_VALUE, then each of the two indicator line bars is compared with two corresponding values in level1 and level0 (the line bar at index+1 is compared with the value of level1, and the bar at index is compared with level0). Thus, the indicator line can be compared with another line of the same or another indicator to identify their relationships, in particular, intersections.
To display descriptions of the identified states and relationships of the indicator line, write the function that returns a description of the indicator line state:
//+------------------------------------------------------------------+ //| Return the indicator line state description | //+------------------------------------------------------------------+ string LineStateDescription(const ENUM_LINE_STATE state) { switch(state) { case LINE_STATE_UP : return "Up"; case LINE_STATE_STOP_UP : return "Stop Up"; case LINE_STATE_TURN_UP : return "Turn Up"; case LINE_STATE_DOWN : return "Down"; case LINE_STATE_STOP_DOWN : return "Stop Down"; case LINE_STATE_TURN_DOWN : return "Turn Down"; case LINE_STATE_ABOVE : return "Above level"; case LINE_STATE_UNDER : return "Under level"; case LINE_STATE_CROSS_UP : return "Crossing Up"; case LINE_STATE_CROSS_DOWN : return "Crossing Down"; case LINE_STATE_TOUCH_BELOW: return "Touch from Below"; case LINE_STATE_TOUCH_ABOVE: return "Touch from Above"; case LINE_STATE_EQUALS : return "Equals"; default : return "Unknown"; } }
The three functions discussed above can be used "as is" in EAs to receive data from any indicators and control their status and relative position.
Below is an example of how we can receive data from an indicator and display data descriptions. Let’s create an EA function that displays the data obtained from the indicator on the dashboard:
//+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlTick tick={0}; MqlRates rates[1]; //--- Exit if unable to get the current prices if(!SymbolInfoTick(Symbol(),tick)) return; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Display the indicator data from the specified bar on the panel in table 1 panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value=IndicatorValue(handle,index,0); string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : ""); panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90); //--- Display a description of the indicator line state panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); ENUM_LINE_STATE state=LineState(handle,index,0); panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,90); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); }
A similar function has already been used in the examples of the previous article when creating the dashboard. Here, the bar data the cursor is located on is displayed in the first panel name plate, while the indicator data and the state of its line are displayed in the second one.
To let the panel work, we need to add the code in the OnChartEvent() event handler:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } }
We already looked at the handler logic in the previous article. Here we use it "as is".
If we now compile the EA and run it on a chart with default parameters (having previously added the ATR indicator to the chart with default parameters as well), we will see a dashboard, in which the data changes as the cursor moves across the chart:
You can view the TestOscillatorATR.mq5 test EA in the files attached to the article.
Bears Power
Everyday trading represents a battle of buyers ("Bulls") pushing prices up and sellers ("Bears") pushing prices down. Depending on what party scores off, the day will end with a price that is higher or lower than that of the previous day. Intermediate results, first of all the highest and lowest price, allow to judge about how the battle was developing during the day.
It is very important to be able to estimate the Bears Power balance since changes in this balance initially signalize about possible trend reversal. This task can be solved using the Bears Power oscillator developed by Alexander Elder and and described in his book titled Trading for a Living. Elder based on the following premises when deducing this oscillator:
- moving average is a price agreement between sellers and buyers for a certain period of time,
- the lowest price displays the maximum sellers' power within the day.
On these premises, Elder developed Bears Power as the difference between the lowest price and 13-period exponential moving average (LOW - ЕМА).
This indicator is better to use together with a trend indicator (most frequently Moving Average):
- if trend indicator is up-directed and the Bears Power index is below zero, but growing, it is a signal to buy;
- it is desirable that, in this case, the divergence of bases were being formed in the indicator chart.
Parameters
The indicator has one configurable parameter - the calculation period. The default value is 13.
Let's add parameters and variables for working with the indicator to the code:
//+------------------------------------------------------------------+ //| TestOscillatorBears.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod = 13; /* Bears Power Period */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // Bears Power calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description
When using the panel, include the dashboard class file and add the variables for working with it:
//+------------------------------------------------------------------+ //| TestOscillatorBears.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Dashboard\Dashboard.mqh> //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod = 13; /* Bears Power Period */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // Bears Power calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description //--- variables for the panel int mouse_bar_index; // Index of the bar the data is taken from CDashboard *panel=NULL; // Pointer to the panel object
Initialization
In the OnInit() handler, initialize and adjust the indicator inputs and create its handle:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period=int(InpPeriod<1 ? 13 : InpPeriod); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("Bears(%lu)",period); ind_digits=Digits()+1; //--- Create indicator handle ResetLastError(); handle=iBearsPower(Symbol(),PERIOD_CURRENT,period); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Successful initialization return(INIT_SUCCEEDED); }
If the EA involves using the dashboard, create it here:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period=int(InpPeriod<1 ? 13 : InpPeriod); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("Bears(%lu)",period); ind_digits=Digits()+1; //--- Create indicator handle ResetLastError(); handle=iBearsPower(Symbol(),PERIOD_CURRENT,period); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Dashboard //--- Create the panel panel=new CDashboard(1,20,20,199,225); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,97); //--- Create a table with ID 1 to display indicator data in it panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,3,2,18,97); //--- Display tabular data in the journal panel.GridPrint(0,2); panel.GridPrint(1,2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); }
Deinitialization
Release the indicator handle in the EA OnDeinit() handler:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); }
If the EA involves the panel, remove the created panel class object:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; }
Data retrieval
We have already implemented the functions common to all indicators to retrieve data from them:
//+------------------------------------------------------------------+ //| Return the indicator data on the specified bar | //+------------------------------------------------------------------+ double IndicatorValue(const int ind_handle,const int index,const int buffer_num) { double array[1]={0}; ResetLastError(); if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1) { PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError()); return EMPTY_VALUE; } return array[0]; } //+------------------------------------------------------------------+ //| Return the state of the indicator line | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num) { //--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); const double value2=IndicatorValue(ind_handle,index+2,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) return LINE_STATE_NONE; //--- Line upward reversal (value2>value1 && value0>value1) if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_TURN_UP; //--- Line upward direction (value2<=value1 && value0>value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_UP; //--- Line upward stop (value2<=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_UP; //--- Line downward reversal (value2<value1 && value0<value1) if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_TURN_DOWN; //--- Line downward direction (value2>=value1 && value0<value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_DOWN; //--- Line downward stop (value2>=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_DOWN; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the state of the line relative to the specified level | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE) { //--- Get the values of the indicator line with the shift (0,1) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) return LINE_STATE_NONE; //--- Define the second level to compare double level=(level1==EMPTY_VALUE ? level0 : level1); //--- The line is below the level (value1<level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_UNDER; //--- The line is above the level (value1>level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_ABOVE; //--- The line crossed the level upwards (value1<=level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_CROSS_UP; //--- The line crossed the level downwards (value1>=level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_CROSS_DOWN; //--- The line touched the level from below (value1<level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- The line touched the level from above (value1>level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- Line is equal to the level value (value1==level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_EQUALS; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the indicator line state description | //+------------------------------------------------------------------+ string LineStateDescription(const ENUM_LINE_STATE state) { switch(state) { case LINE_STATE_UP : return "Up"; case LINE_STATE_STOP_UP : return "Stop Up"; case LINE_STATE_TURN_UP : return "Turn Up"; case LINE_STATE_DOWN : return "Down"; case LINE_STATE_STOP_DOWN : return "Stop Down"; case LINE_STATE_TURN_DOWN : return "Turn Down"; case LINE_STATE_ABOVE : return "Above level"; case LINE_STATE_UNDER : return "Under level"; case LINE_STATE_CROSS_UP : return "Crossing Up"; case LINE_STATE_CROSS_DOWN : return "Crossing Down"; case LINE_STATE_TOUCH_BELOW: return "Touch from Below"; case LINE_STATE_TOUCH_ABOVE: return "Touch from Above"; case LINE_STATE_EQUALS : return "Equals"; default : return "Unknown"; } }
All examples here are used without any changes.
If the EA uses a dashboard, then we will implement the function that displays the received data from the indicator on the dashboard:
//+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlTick tick={0}; MqlRates rates[1]; //--- Exit if unable to get the current prices if(!SymbolInfoTick(Symbol(),tick)) return; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Display the indicator data from the specified bar on the panel in table 1 panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value=IndicatorValue(handle,index,0); string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : ""); panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90); //--- Display a description of the indicator line state panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); ENUM_LINE_STATE state=LineState(handle,index,0); //--- The label color changes depending on the value of the line above/below zero color clr=(value<0 ? clrRed : value>0 ? clrBlue : clrNONE); panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); }
Since the signals in the indicator depend on the location of the line relative to zero, we will highlight positive and negative values of the indicator in the dashboard.
In the OnChartEvent() event handler, write the code for working with panel events and indicating the index of the bar, above which the cursor is located:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } }
Compile the EA and run it on the chart with default values. Make sure you have added the indicator with the same parameters to the chart:
The "TestOscillatorBears.mq5" EA file is attached below.
Bulls Power
Everyday trading represents a battle of buyers ("Bulls") pushing prices up and sellers ("Bears") pushing prices down. Depending on what party scores off, the day will end with a price that is higher or lower than that of the previous day. Intermediate results, first of all the highest and lowest price, allow to judge about how the battle was developing during the day.
It is very important to be able to estimate the Bulls Power balance since changes in this balance initially signalize about possible trend reversal. This task can be solved using the Bulls Power oscillator developed by Alexander Elder and and described in his book titled Trading for a Living. Elder based on the following premises when deducing this oscillator:
- moving average is a price agreement between sellers and buyers for a certain period of time,
- the highest price displays the maximum buyers' power within the day.
On these premises, Elder developed Bulls Power as the difference between the highest price and 13-period exponential moving average (HIGH - ЕМА).
This indicator is better to use together with a trend indicator (most frequently Moving Average):
- if trend indicator is down-directed and the Bulls Power index is above zero, but falling, it is a signal to sell;
- it is desirable that, in this case, the divergence of peaks were being formed in the indicator chart.
Parameters
The indicator has one adjustable parameter — the smoothing period. Its default value is 13.
The list of input and global variables for use in the EA:
//+------------------------------------------------------------------+ //| TestOscillatorBulls.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod = 13; /* Bulls Power Period */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // Bulls Power calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description
When using the dashboard, the panel class file is included and the variables for working with it are added:
//+------------------------------------------------------------------+ //| TestOscillatorBulls.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Dashboard\Dashboard.mqh> //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod = 13; /* Bulls Power Period */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // Bulls Power calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description //--- variables for the panel int mouse_bar_index; // Index of the bar the data is taken from CDashboard *panel=NULL; // Pointer to the panel object
Initialization
The OnInit() handler for initializing indicator parameters and creating its handle:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period=int(InpPeriod<1 ? 13 : InpPeriod); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("Bulls(%lu)",period); ind_digits=Digits()+1; //--- Create indicator handle ResetLastError(); handle=iBullsPower(Symbol(),PERIOD_CURRENT,period); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Successful initialization return(INIT_SUCCEEDED); }
The panel is created when using the dashboard:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period=int(InpPeriod<1 ? 13 : InpPeriod); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("Bulls(%lu)",period); ind_digits=Digits()+1; //--- Create indicator handle ResetLastError(); handle=iBullsPower(Symbol(),PERIOD_CURRENT,period); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Dashboard //--- Create the panel panel=new CDashboard(1,20,20,199,225); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,97); //--- Create a table with ID 1 to display indicator data in it panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,3,2,18,97); //--- Display tabular data in the journal panel.GridPrint(0,2); panel.GridPrint(1,2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); }
Deinitialization
Release the indicator handle in the EA OnDeinit() handler:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); }
The created dashboard object is removed when using the dashboard:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; }
Data retrieval
General functions for obtaining data by the indicator handle:
//+------------------------------------------------------------------+ //| Return the indicator data on the specified bar | //+------------------------------------------------------------------+ double IndicatorValue(const int ind_handle,const int index,const int buffer_num) { double array[1]={0}; ResetLastError(); if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1) { PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError()); return EMPTY_VALUE; } return array[0]; } //+------------------------------------------------------------------+ //| Return the state of the indicator line | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num) { //--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); const double value2=IndicatorValue(ind_handle,index+2,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) return LINE_STATE_NONE; //--- Line upward reversal (value2>value1 && value0>value1) if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_TURN_UP; //--- Line upward direction (value2<=value1 && value0>value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_UP; //--- Line upward stop (value2<=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_UP; //--- Line downward reversal (value2<value1 && value0<value1) if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_TURN_DOWN; //--- Line downward direction (value2>=value1 && value0<value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_DOWN; //--- Line downward stop (value2>=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_DOWN; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the state of the line relative to the specified level | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE) { //--- Get the values of the indicator line with the shift (0,1) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) return LINE_STATE_NONE; //--- Define the second level to compare double level=(level1==EMPTY_VALUE ? level0 : level1); //--- The line is below the level (value1<level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_UNDER; //--- The line is above the level (value1>level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_ABOVE; //--- The line crossed the level upwards (value1<=level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_CROSS_UP; //--- The line crossed the level downwards (value1>=level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_CROSS_DOWN; //--- The line touched the level from below (value1<level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- The line touched the level from above (value1>level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- Line is equal to the level value (value1==level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_EQUALS; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the indicator line state description | //+------------------------------------------------------------------+ string LineStateDescription(const ENUM_LINE_STATE state) { switch(state) { case LINE_STATE_UP : return "Up"; case LINE_STATE_STOP_UP : return "Stop Up"; case LINE_STATE_TURN_UP : return "Turn Up"; case LINE_STATE_DOWN : return "Down"; case LINE_STATE_STOP_DOWN : return "Stop Down"; case LINE_STATE_TURN_DOWN : return "Turn Down"; case LINE_STATE_ABOVE : return "Above level"; case LINE_STATE_UNDER : return "Under level"; case LINE_STATE_CROSS_UP : return "Crossing Up"; case LINE_STATE_CROSS_DOWN : return "Crossing Down"; case LINE_STATE_TOUCH_BELOW: return "Touch from Below"; case LINE_STATE_TOUCH_ABOVE: return "Touch from Above"; case LINE_STATE_EQUALS : return "Equals"; default : return "Unknown"; } }
When using the dashboard, data is displayed on the panel using the function:
//+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlTick tick={0}; MqlRates rates[1]; //--- Exit if unable to get the current prices if(!SymbolInfoTick(Symbol(),tick)) return; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Display the indicator data from the specified bar on the panel in table 1 panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value=IndicatorValue(handle,index,0); string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : ""); panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90); //--- Display a description of the indicator line state panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); ENUM_LINE_STATE state=LineState(handle,index,0); //--- The label color changes depending on the value of the line above/below zero color clr=(value<0 ? clrRed : value>0 ? clrBlue : clrNONE); panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); }
Besides, the panel events handler is called in the OnChartEvent() EA event handler, as well as the events for receiving the bar index under the cursor are handled:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } }
After compiling the EA and launching it on the chart, we can monitor the state of the indicator line on the panel:
The "TestOscillatorBulls.mq5" EA file is attached below.
Chaikin Oscillator
Chaikin Oscillator (CHO) is the difference of moving averages of Accumulation/Distribution.
"The concept of this oscillator is based on three main theses. First: if a share or an index is higher when it closes than it was during the day (you can calculate the average value as [max+min]/2), it means that it was a day of accumulation. The closer the closing index of a share or an index gets to the maximum, the more active the accumulation is. Vice versa, if a share's closing price is lower than the average level of the day, it means that distribution took place. The closer to the minimum the share gets, the more active is the distribution.
Second: stable price growth is accompanied by increase in trade volume and strong accumulation of the volume. As the volume is like fuel that feeds market growth, the lag of volume along with the growth of prices shows that there isn't enough fuel to continue the rise.
Vice versa, a slump in prices is usually accompanied by low amount and ends up in panic liquidation of positions by institutional investors. Therefore, first of all we see a growth of volume, then a slump in prices accompanied by reduced volume and finally, when the market is close to foundation, some accumulation takes place.
Third: with a Chaikin's oscillator you can trace back the volume of money resources coming in to the market and leaving it. Comparing the dynamics of volume and prices allows finding out peaks and foundations of the market, both short- and medium-term.
Since there are no error-free methods of technical analysis, it is recommended to use this oscillator in conjunction with other technical indicators. The reliability of short-term and medium-term trading signals will be higher if you use, say, Envelopes based on the 21-day moving average and any overbought/oversold oscillator together with the Chaikin oscillator.
The most important signal arises when the prices reach a maximum or a minimum level (especially on the level of outbidding/resale), but the Chaikin's oscillator can't overcome its previous extremum and so it turns around.
- Signals moving in the direction of the medium-term trend are more reliable than those moving against it.
- The fact that an oscillator confirms a new maximum or minimum doesn't mean that the prices will move on in that direction. This event is regarded as insignificant.
Another way of using Chaikin's oscillator implies the following: a change in its direction is a signal for purchase or a sale, but only if it coincides with the price trend direction. For example, if a share is on the rise and its price is higher than a 90-day moving average, then an up-turn of the oscillator curve in the area of negative values can be regarded as a signal for purchase (but the share price must be higher than a 90-day moving average - not less).
A down-turn of the oscillator curve in the area of positive values (above zero) can be regarded as a signal for sale, but the share price must be lower than the 90-day moving average of closing prices."
Parameters
The indicator has four configured parameters:
- Volumes used, by default - tick,
- Fast MA calculation period, default - 3,
- Slow MA calculation period, default - 10,
- Calculation method, default - EMA.
Input and global variables for using the indicator in the EA:
//+------------------------------------------------------------------+ //| TestOscillatorCHO.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriodFast = 3; /* CHO Fast MA Period */ input uint InpPeriodSlow = 10; /* CHO Slow MA Period */ input ENUM_MA_METHOD InpMethod = MODE_EMA; /* Method */ input ENUM_APPLIED_VOLUME InpAppliedVol = VOLUME_TICK; /* Applied Volume */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period_fast=0; // Fast moving average calculation period int period_slow=0; // Slow moving average calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description
When using the dashboard, the panel class file is included and the variables for working with it are added:
//+------------------------------------------------------------------+ //| TestOscillatorCHO.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Dashboard\Dashboard.mqh> //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriodFast = 3; /* CHO Fast MA Period */ input uint InpPeriodSlow = 10; /* CHO Slow MA Period */ input ENUM_MA_METHOD InpMethod = MODE_EMA; /* Method */ input ENUM_APPLIED_VOLUME InpAppliedVol = VOLUME_TICK; /* Applied Volume */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period_fast=0; // Fast moving average calculation period int period_slow=0; // Slow moving average calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description //--- variables for the panel int mouse_bar_index; // Index of the bar the data is taken from CDashboard *panel=NULL; // Pointer to the panel object
Initialization
The OnInit() handler for initializing indicator parameters and creating its handle:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period_fast=int(InpPeriodFast<1 ? 3 : InpPeriodFast); period_slow=int(InpPeriodSlow<1 ? 10 : InpPeriodSlow); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("CHO(%lu,%lu)",period_slow,period_fast); ind_digits=0; //--- Create indicator handle ResetLastError(); handle=iChaikin(Symbol(),PERIOD_CURRENT,period_fast,period_slow,InpMethod,InpAppliedVol); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Successful initialization return(INIT_SUCCEEDED); }
The panel is created when using the dashboard:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period_fast=int(InpPeriodFast<1 ? 3 : InpPeriodFast); period_slow=int(InpPeriodSlow<1 ? 10 : InpPeriodSlow); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("CHO(%lu,%lu)",period_slow,period_fast); ind_digits=0; //--- Create indicator handle ResetLastError(); handle=iChaikin(Symbol(),PERIOD_CURRENT,period_fast,period_slow,InpMethod,InpAppliedVol); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Dashboard //--- Create the panel panel=new CDashboard(1,20,20,199,225); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,97); //--- Create a table with ID 1 to display indicator data in it panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,3,2,18,97); //--- Display tabular data in the journal panel.GridPrint(0,2); panel.GridPrint(1,2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); }
Deinitialization
Release the indicator handle in the EA OnDeinit() handler:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); }
The created dashboard object is removed when using the dashboard:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; }
Data retrieval
General functions for obtaining data by the indicator handle:
//+------------------------------------------------------------------+ //| Return the indicator data on the specified bar | //+------------------------------------------------------------------+ double IndicatorValue(const int ind_handle,const int index,const int buffer_num) { double array[1]={0}; ResetLastError(); if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1) { PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError()); return EMPTY_VALUE; } return array[0]; } //+------------------------------------------------------------------+ //| Return the state of the indicator line | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num) { //--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); const double value2=IndicatorValue(ind_handle,index+2,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) return LINE_STATE_NONE; //--- Line upward reversal (value2>value1 && value0>value1) if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_TURN_UP; //--- Line upward direction (value2<=value1 && value0>value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_UP; //--- Line upward stop (value2<=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_UP; //--- Line downward reversal (value2<value1 && value0<value1) if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_TURN_DOWN; //--- Line downward direction (value2>=value1 && value0<value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_DOWN; //--- Line downward stop (value2>=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_DOWN; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the state of the line relative to the specified level | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE) { //--- Get the values of the indicator line with the shift (0,1) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) return LINE_STATE_NONE; //--- Define the second level to compare double level=(level1==EMPTY_VALUE ? level0 : level1); //--- The line is below the level (value1<level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_UNDER; //--- The line is above the level (value1>level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_ABOVE; //--- The line crossed the level upwards (value1<=level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_CROSS_UP; //--- The line crossed the level downwards (value1>=level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_CROSS_DOWN; //--- The line touched the level from below (value1<level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- The line touched the level from above (value1>level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- Line is equal to the level value (value1==level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_EQUALS; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the indicator line state description | //+------------------------------------------------------------------+ string LineStateDescription(const ENUM_LINE_STATE state) { switch(state) { case LINE_STATE_UP : return "Up"; case LINE_STATE_STOP_UP : return "Stop Up"; case LINE_STATE_TURN_UP : return "Turn Up"; case LINE_STATE_DOWN : return "Down"; case LINE_STATE_STOP_DOWN : return "Stop Down"; case LINE_STATE_TURN_DOWN : return "Turn Down"; case LINE_STATE_ABOVE : return "Above level"; case LINE_STATE_UNDER : return "Under level"; case LINE_STATE_CROSS_UP : return "Crossing Up"; case LINE_STATE_CROSS_DOWN : return "Crossing Down"; case LINE_STATE_TOUCH_BELOW: return "Touch from Below"; case LINE_STATE_TOUCH_ABOVE: return "Touch from Above"; case LINE_STATE_EQUALS : return "Equals"; default : return "Unknown"; } }
When using the dashboard, data is displayed on the panel using the function:
//+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlTick tick={0}; MqlRates rates[1]; //--- Exit if unable to get the current prices if(!SymbolInfoTick(Symbol(),tick)) return; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Display the indicator data from the specified bar on the panel in table 1 panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value=IndicatorValue(handle,index,0); string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : ""); panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90); //--- Display a description of the indicator line state panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); ENUM_LINE_STATE state=LineState(handle,index,0); color clr=(value<0 ? clrRed : value>0 ? clrBlue : clrNONE); // The label color changes depending on the value of the line above/below zero panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); }
Indicator line values above/below zero are marked in color on the dashboard.
Besides, the panel events handler is called in the OnChartEvent() EA event handler, as well as the events for receiving the bar index under the cursor are handled:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } }
After compiling the EA and launching it on the chart, we can monitor the state of the indicator line on the panel:
The "TestOscillatorCHO.mq5" EA file is attached below.
Commodity Channel Index
Commodity Channel Index (CCI) measures the deviation of the commodity price from its average statistical price. High values of the index point out that the price is unusually high being compared with the average one, and low values show that the price is too low. In spite of its name, the Commodity Channel Index can be applied for any financial instrument, and not only for the wares.
There are two basic techniques of using Commodity Channel Index:
- To find divergences
The divergence appears when the price reaches a new maximum, and Commodity Channel Index can not grow above the previous maximums. This classical divergence is normally followed by the price correction. - As an overbought/oversold indicator
Commodity Channel Index usually varies in the range of ±100. Values above +100 inform about overbuying state (and about a probability of correcting decay), and the values below 100 inform about the overselling state (and about a probability of correcting increase).
Parameters
The indicator has two input parameters:
- Calculation period, default - 14
- Calculation price, default - Typical Price (HLC/3)
In addition to the standard indicator inputs, overbought and oversold levels can be used to search for its signals. They also need to be specified in the EA inputs.
Input and global variables for using the indicator in the EA:
//+------------------------------------------------------------------+ //| TestOscillatorCCI.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod = 14; /* CCI Period */ input ENUM_APPLIED_PRICE InpPrice = PRICE_TYPICAL; /* Applied Price */ input double InpOverbough= 100.0; /* Overbough level*/ input double InpOversold = -100.0; /* Oversold level */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // CCI calculation period int ind_digits=0; // Number of decimal places in the indicator values double overbough=0; // Overbought level double oversold=0; // Oversold level string ind_title; // Indicator description
When using the dashboard, the panel class file is included and the variables for working with it are added:
//+------------------------------------------------------------------+ //| TestOscillatorCCI.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Dashboard\Dashboard.mqh> //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod = 14; /* CCI Period */ input ENUM_APPLIED_PRICE InpPrice = PRICE_TYPICAL; /* Applied Price */ input double InpOverbough= 100.0; /* Overbough level*/ input double InpOversold = -100.0; /* Oversold level */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // CCI calculation period int ind_digits=0; // Number of decimal places in the indicator values double overbough=0; // Overbought level double oversold=0; // Oversold level string ind_title; // Indicator description //--- variables for the panel int mouse_bar_index; // Index of the bar the data is taken from CDashboard *panel=NULL; // Pointer to the panel object
Initialization
The OnInit() handler for initializing indicator parameters and creating its handle:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period and levels if necessary period=int(InpPeriod<1 ? 14 : InpPeriod<2 ? 2 : InpPeriod); overbough=InpOverbough; oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("CCI(%lu)",period); ind_digits=2; //--- Create indicator handle ResetLastError(); handle=iCCI(Symbol(),PERIOD_CURRENT,period,InpPrice); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Successful initialization return(INIT_SUCCEEDED); }
The panel is created when using the dashboard:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period and levels if necessary period=int(InpPeriod<1 ? 14 : InpPeriod<2 ? 2 : InpPeriod); overbough=InpOverbough; oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("CCI(%lu)",period); ind_digits=2; //--- Create indicator handle ResetLastError(); handle=iCCI(Symbol(),PERIOD_CURRENT,period,InpPrice); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Dashboard //--- Create the panel panel=new CDashboard(1,20,20,229,243); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,112); //--- Create a table with ID 1 to display indicator data in it panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,4,2,18,112); //--- Display tabular data in the journal panel.GridPrint(0,2); panel.GridPrint(1,2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); }
Deinitialization
Release the indicator handle in the EA OnDeinit() handler:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); }
The created dashboard object is removed when using the dashboard:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; }
Data retrieval
General functions for obtaining data by the indicator handle:
//+------------------------------------------------------------------+ //| Return the indicator data on the specified bar | //+------------------------------------------------------------------+ double IndicatorValue(const int ind_handle,const int index,const int buffer_num) { double array[1]={0}; ResetLastError(); if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1) { PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError()); return EMPTY_VALUE; } return array[0]; } //+------------------------------------------------------------------+ //| Return the state of the indicator line | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num) { //--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); const double value2=IndicatorValue(ind_handle,index+2,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) return LINE_STATE_NONE; //--- Line upward reversal (value2>value1 && value0>value1) if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_TURN_UP; //--- Line upward direction (value2<=value1 && value0>value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_UP; //--- Line upward stop (value2<=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_UP; //--- Line downward reversal (value2<value1 && value0<value1) if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_TURN_DOWN; //--- Line downward direction (value2>=value1 && value0<value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_DOWN; //--- Line downward stop (value2>=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_DOWN; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the state of the line relative to the specified level | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE) { //--- Get the values of the indicator line with the shift (0,1) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) return LINE_STATE_NONE; //--- Define the second level to compare double level=(level1==EMPTY_VALUE ? level0 : level1); //--- The line is below the level (value1<level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_UNDER; //--- The line is above the level (value1>level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_ABOVE; //--- The line crossed the level upwards (value1<=level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_CROSS_UP; //--- The line crossed the level downwards (value1>=level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_CROSS_DOWN; //--- The line touched the level from below (value1<level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- The line touched the level from above (value1>level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- Line is equal to the level value (value1==level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_EQUALS; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the indicator line state description | //+------------------------------------------------------------------+ string LineStateDescription(const ENUM_LINE_STATE state) { switch(state) { case LINE_STATE_UP : return "Up"; case LINE_STATE_STOP_UP : return "Stop Up"; case LINE_STATE_TURN_UP : return "Turn Up"; case LINE_STATE_DOWN : return "Down"; case LINE_STATE_STOP_DOWN : return "Stop Down"; case LINE_STATE_TURN_DOWN : return "Turn Down"; case LINE_STATE_ABOVE : return "Above level"; case LINE_STATE_UNDER : return "Under level"; case LINE_STATE_CROSS_UP : return "Crossing Up"; case LINE_STATE_CROSS_DOWN : return "Crossing Down"; case LINE_STATE_TOUCH_BELOW: return "Touch from Below"; case LINE_STATE_TOUCH_ABOVE: return "Touch from Above"; case LINE_STATE_EQUALS : return "Equals"; default : return "Unknown"; } }
When using the dashboard, data is displayed on the panel using the function:
//+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlTick tick={0}; MqlRates rates[1]; //--- Exit if unable to get the current prices if(!SymbolInfoTick(Symbol(),tick)) return; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Display the indicator data from the specified bar on the panel in table 1 panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value=IndicatorValue(handle,index,0); string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : ""); panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90); //--- Display a description of the indicator line state relative to the overbought level string ovb=StringFormat("%+.2f",overbough); panel.DrawText("Overbough", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2); panel.DrawText(ovb, panel.CellX(1,2,0)+66, panel.CellY(1,2,0)+2); ENUM_LINE_STATE state_ovb=LineStateRelative(handle,index,0,overbough); //--- The label color changes depending on the value of the line relative to the level color clr=(state_ovb==LINE_STATE_ABOVE || state_ovb==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE); string ovb_str=(state_ovb==LINE_STATE_ABOVE ? "Inside the area" : LineStateDescription(state_ovb)); panel.DrawText(ovb_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,90); //--- Display a description of the indicator line state relative to the oversold level panel.DrawText("Oversold", panel.CellX(1,3,0)+2, panel.CellY(1,3,0)+2); string ovs=StringFormat("%+.2f",oversold); panel.DrawText(ovs, panel.CellX(1,3,0)+68, panel.CellY(1,3,0)+2); ENUM_LINE_STATE state_ovs=LineStateRelative(handle,index,0,oversold); //--- The label color changes depending on the value of the line relative to the level clr=(state_ovs==LINE_STATE_UNDER || state_ovs==LINE_STATE_CROSS_UP ? clrBlue : clrNONE); string ovs_str=(state_ovs==LINE_STATE_UNDER ? "Inside the area" : LineStateDescription(state_ovs)); panel.DrawText(ovs_str,panel.CellX(1,3,1)+2,panel.CellY(1,3,1)+2,clr,90); //--- Display a description of the indicator line state panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); ENUM_LINE_STATE state=LineState(handle,index,0); //--- The label color changes depending on the location of the line in the overbought/oversold areas clr=(state_ovb==LINE_STATE_ABOVE || state_ovb==LINE_STATE_CROSS_DOWN ? clrRed : state_ovs==LINE_STATE_UNDER || state_ovs==LINE_STATE_CROSS_UP ? clrBlue : clrNONE); panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); }
The location of the indicator line in overbought and oversold areas, as well as signals in these areas, are marked with color on the panel.
Besides, the panel events handler is called in the OnChartEvent() EA event handler, as well as the events for receiving the bar index under the cursor are handled:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } }
After compiling the EA and launching it on the chart, we can monitor the state of the indicator line on the panel:
The "TestOscillatorCCI.mq5" EA file is attached below.
DeMarker
Demarker Technical Indicator (DeM) is based on the comparison of the current period maximum with the previous period maximum. If the current period (bar) maximum is higher, the respective difference between the two will be registered. If the current maximum is lower or equaling the maximum of the previous period, the naught value will be registered. The differences received for N periods are then summarized. The received value is used as the numerator of the DeMarker and will be divided by the same value plus the sum of differences between the price minima of the previous and the current periods (bars). If the current price minimum is greater than that of the previous bar, the naught value will be registered.
When the indicator falls below 30, the bullish price reversal should be expected. When the indicator rises above 70, the bearish price reversal should be expected.
If you use periods of longer duration, when calculating the indicator, you’ll be able to catch the long term market tendency. Indicators based on short periods let you enter the market at the point of the least risk and plan the time of transaction so that it falls in with the major trend.
Parameters
The indicator has one configurable parameter - the calculation period. Its default value is 14.
In addition, since overbought/oversold areas are used to search for indicator signals, they also need to be included in the input parameters of the EA.
Input and global variables for using the indicator in the EA:
//+------------------------------------------------------------------+ //| TestOscillatorDeM.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod = 14; /* Period */ input double InpOverbough= 0.7; /* Overbough level*/ input double InpOversold = 0.3; /* Oversold level */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // DeMarker calculation period int ind_digits=0; // Number of decimal places in the indicator values double overbough=0; // Overbought level double oversold=0; // Oversold level string ind_title; // Indicator description
When using the dashboard, the panel class file is included and the variables for working with it are added:
//+------------------------------------------------------------------+ //| TestOscillatorDeM.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Dashboard\Dashboard.mqh> //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod = 14; /* Period */ input double InpOverbough= 0.7; /* Overbough level*/ input double InpOversold = 0.3; /* Oversold level */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // DeMarker calculation period int ind_digits=0; // Number of decimal places in the indicator values double overbough=0; // Overbought level double oversold=0; // Oversold level string ind_title; // Indicator description //--- variables for the panel int mouse_bar_index; // Index of the bar the data is taken from CDashboard *panel=NULL; // Pointer to the panel object
Initialization
The OnInit() handler for initializing indicator parameters and creating its handle:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period and levels if necessary period=int(InpPeriod<1 ? 14 : InpPeriod); overbough=InpOverbough; oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("DeM(%lu)",period); ind_digits=3; //--- Create indicator handle ResetLastError(); handle=iDeMarker(Symbol(),PERIOD_CURRENT,period); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Successful initialization return(INIT_SUCCEEDED); }
The panel is created when using the dashboard:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period and levels if necessary period=int(InpPeriod<1 ? 14 : InpPeriod); overbough=InpOverbough; oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("DeM(%lu)",period); ind_digits=3; //--- Create indicator handle ResetLastError(); handle=iDeMarker(Symbol(),PERIOD_CURRENT,period); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Dashboard //--- Create the panel panel=new CDashboard(1,20,20,229,243); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,112); //--- Create a table with ID 1 to display indicator data in it panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,4,2,18,112); //--- Display tabular data in the journal panel.GridPrint(0,2); panel.GridPrint(1,2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); }
Deinitialization
Release the indicator handle in the EA OnDeinit() handler:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); }
The created dashboard object is removed when using the dashboard:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; }
Data retrieval
General functions for obtaining data by the indicator handle:
//+------------------------------------------------------------------+ //| Return the indicator data on the specified bar | //+------------------------------------------------------------------+ double IndicatorValue(const int ind_handle,const int index,const int buffer_num) { double array[1]={0}; ResetLastError(); if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1) { PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError()); return EMPTY_VALUE; } return array[0]; } //+------------------------------------------------------------------+ //| Return the state of the indicator line | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num) { //--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); const double value2=IndicatorValue(ind_handle,index+2,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) return LINE_STATE_NONE; //--- Line upward reversal (value2>value1 && value0>value1) if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_TURN_UP; //--- Line upward direction (value2<=value1 && value0>value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_UP; //--- Line upward stop (value2<=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_UP; //--- Line downward reversal (value2<value1 && value0<value1) if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_TURN_DOWN; //--- Line downward direction (value2>=value1 && value0<value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_DOWN; //--- Line downward stop (value2>=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_DOWN; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the state of the line relative to the specified level | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE) { //--- Get the values of the indicator line with the shift (0,1) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) return LINE_STATE_NONE; //--- Define the second level to compare double level=(level1==EMPTY_VALUE ? level0 : level1); //--- The line is below the level (value1<level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_UNDER; //--- The line is above the level (value1>level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_ABOVE; //--- The line crossed the level upwards (value1<=level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_CROSS_UP; //--- The line crossed the level downwards (value1>=level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_CROSS_DOWN; //--- The line touched the level from below (value1<level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- The line touched the level from above (value1>level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- Line is equal to the level value (value1==level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_EQUALS; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the indicator line state description | //+------------------------------------------------------------------+ string LineStateDescription(const ENUM_LINE_STATE state) { switch(state) { case LINE_STATE_UP : return "Up"; case LINE_STATE_STOP_UP : return "Stop Up"; case LINE_STATE_TURN_UP : return "Turn Up"; case LINE_STATE_DOWN : return "Down"; case LINE_STATE_STOP_DOWN : return "Stop Down"; case LINE_STATE_TURN_DOWN : return "Turn Down"; case LINE_STATE_ABOVE : return "Above level"; case LINE_STATE_UNDER : return "Under level"; case LINE_STATE_CROSS_UP : return "Crossing Up"; case LINE_STATE_CROSS_DOWN : return "Crossing Down"; case LINE_STATE_TOUCH_BELOW: return "Touch from Below"; case LINE_STATE_TOUCH_ABOVE: return "Touch from Above"; case LINE_STATE_EQUALS : return "Equals"; default : return "Unknown"; } }
When using the dashboard, data is displayed on the panel using the function:
//+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlTick tick={0}; MqlRates rates[1]; //--- Exit if unable to get the current prices if(!SymbolInfoTick(Symbol(),tick)) return; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Display the indicator data from the specified bar on the panel in table 1 panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value=IndicatorValue(handle,index,0); string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : ""); panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,100); //--- Display a description of the indicator line state relative to the overbought level string ovb=StringFormat("%+.2f",overbough); panel.DrawText("Overbough", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2); panel.DrawText(ovb, panel.CellX(1,2,0)+66, panel.CellY(1,2,0)+2); ENUM_LINE_STATE state_ovb=LineStateRelative(handle,index,0,overbough); //--- The label color changes depending on the value of the line relative to the level color clr=(state_ovb==LINE_STATE_ABOVE || state_ovb==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE); string ovb_str=(state_ovb==LINE_STATE_ABOVE ? "Inside the area" : LineStateDescription(state_ovb)); panel.DrawText(ovb_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,100); //--- Display a description of the indicator line state relative to the oversold level panel.DrawText("Oversold", panel.CellX(1,3,0)+2, panel.CellY(1,3,0)+2); string ovs=StringFormat("%+.2f",oversold); panel.DrawText(ovs, panel.CellX(1,3,0)+68, panel.CellY(1,3,0)+2); ENUM_LINE_STATE state_ovs=LineStateRelative(handle,index,0,oversold); //--- The label color changes depending on the value of the line relative to the level clr=(state_ovs==LINE_STATE_UNDER || state_ovs==LINE_STATE_CROSS_UP ? clrBlue : clrNONE); string ovs_str=(state_ovs==LINE_STATE_UNDER ? "Inside the area" : LineStateDescription(state_ovs)); panel.DrawText(ovs_str,panel.CellX(1,3,1)+2,panel.CellY(1,3,1)+2,clr,100); //--- Display a description of the indicator line state panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); ENUM_LINE_STATE state=LineState(handle,index,0); //--- The label color changes depending on the location of the line in the overbought/oversold areas clr=(state_ovb==LINE_STATE_ABOVE || state_ovb==LINE_STATE_CROSS_DOWN ? clrRed : state_ovs==LINE_STATE_UNDER || state_ovs==LINE_STATE_CROSS_UP ? clrBlue : clrNONE); panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,100); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); }
The location of the indicator line in overbought and oversold areas is marked with color on the dashboard.
Besides, the panel events handler is called in the OnChartEvent() EA event handler, as well as the events for receiving the bar index under the cursor are handled:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } }
After compiling the EA and launching it on the chart, we can monitor the state of the indicator line on the panel:
The "TestOscillatorDeM.mq5" EA file is attached below.
Force Index
Force Index (FRC) technical indicator was developed by Alexander Elder. This index measures the Bulls Power at each increase, and the Bears Power at each decrease. It connects the basic elements of market information: price trend, its drops, and volumes of transactions. This index can be used as it is, but it is better to approximate it using the Moving Average. Approximation with the help a short moving average (the author proposes to use 2 intervals) contributes to finding the best opportunity to open and close positions. If the approximations is performed using a long moving average (period 13), the index shows trends changes.
- It is better to buy when the forces become minus (fall below zero) in the period of indicator increasing tendency;
- The force index signalizes the continuation of the increasing tendency when it increases to the new peak;
- The signal to sell comes when the index becomes positive during the decreasing tendency;
- The force index signalizes the Bears' Power and continuation of the decreasing tendency when the index falls to the new depth;
- If price changes do not correlate to the corresponding changes in volume, the force indicator stays on one level, which tells you the trend is going to change soon.
Parameters
The indicator has three input parameters:
- Calculation period, default - 2,
- Calculation method, default - SMA,
- Volumes used, by default - tick.
Input and global variables for using the indicator in the EA:
//+------------------------------------------------------------------+ //| TestOscillatorForce.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod = 13; /* Period */ input ENUM_MA_METHOD InpMethod = MODE_SMA; /* Method */ input ENUM_APPLIED_VOLUME InpAppliedVol = VOLUME_TICK; /* Applied Volume */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // Force Index calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description
When using the dashboard, the panel class file is included and the variables for working with it are added:
//+------------------------------------------------------------------+ //| TestOscillatorForce.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Dashboard\Dashboard.mqh> //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod = 13; /* Period */ input ENUM_MA_METHOD InpMethod = MODE_SMA; /* Method */ input ENUM_APPLIED_VOLUME InpAppliedVol = VOLUME_TICK; /* Applied Volume */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // Force Index calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description //--- variables for the panel int mouse_bar_index; // Index of the bar the data is taken from CDashboard *panel=NULL; // Pointer to the panel object
Initialization
The OnInit() handler for initializing indicator parameters and creating its handle:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period=int(InpPeriod<1 ? 13 : InpPeriod); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("Force(%lu)",period); ind_digits=Digits()+1; //--- Create indicator handle ResetLastError(); handle=iForce(Symbol(),PERIOD_CURRENT,period,InpMethod,InpAppliedVol); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Successful initialization return(INIT_SUCCEEDED); }
The panel is created when using the dashboard:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period=int(InpPeriod<1 ? 13 : InpPeriod); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("Force(%lu)",period); ind_digits=Digits()+1; //--- Create indicator handle ResetLastError(); handle=iForce(Symbol(),PERIOD_CURRENT,period,InpMethod,InpAppliedVol); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Dashboard //--- Create the panel panel=new CDashboard(1,20,20,199,225); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,97); //--- Create a table with ID 1 to display indicator data in it panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,3,2,18,97); //--- Display tabular data in the journal panel.GridPrint(0,2); panel.GridPrint(1,2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); }
Deinitialization
Release the indicator handle in the EA OnDeinit() handler:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); }
The created dashboard object is removed when using the dashboard:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; }
Data retrieval
General functions for obtaining data by the indicator handle:
//+------------------------------------------------------------------+ //| Return the indicator data on the specified bar | //+------------------------------------------------------------------+ double IndicatorValue(const int ind_handle,const int index,const int buffer_num) { double array[1]={0}; ResetLastError(); if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1) { PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError()); return EMPTY_VALUE; } return array[0]; } //+------------------------------------------------------------------+ //| Return the state of the indicator line | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num) { //--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); const double value2=IndicatorValue(ind_handle,index+2,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) return LINE_STATE_NONE; //--- Line upward reversal (value2>value1 && value0>value1) if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_TURN_UP; //--- Line upward direction (value2<=value1 && value0>value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_UP; //--- Line upward stop (value2<=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_UP; //--- Line downward reversal (value2<value1 && value0<value1) if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_TURN_DOWN; //--- Line downward direction (value2>=value1 && value0<value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_DOWN; //--- Line downward stop (value2>=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_DOWN; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the state of the line relative to the specified level | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE) { //--- Get the values of the indicator line with the shift (0,1) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) return LINE_STATE_NONE; //--- Define the second level to compare double level=(level1==EMPTY_VALUE ? level0 : level1); //--- The line is below the level (value1<level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_UNDER; //--- The line is above the level (value1>level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_ABOVE; //--- The line crossed the level upwards (value1<=level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_CROSS_UP; //--- The line crossed the level downwards (value1>=level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_CROSS_DOWN; //--- The line touched the level from below (value1<level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- The line touched the level from above (value1>level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- Line is equal to the level value (value1==level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_EQUALS; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the indicator line state description | //+------------------------------------------------------------------+ string LineStateDescription(const ENUM_LINE_STATE state) { switch(state) { case LINE_STATE_UP : return "Up"; case LINE_STATE_STOP_UP : return "Stop Up"; case LINE_STATE_TURN_UP : return "Turn Up"; case LINE_STATE_DOWN : return "Down"; case LINE_STATE_STOP_DOWN : return "Stop Down"; case LINE_STATE_TURN_DOWN : return "Turn Down"; case LINE_STATE_ABOVE : return "Above level"; case LINE_STATE_UNDER : return "Under level"; case LINE_STATE_CROSS_UP : return "Crossing Up"; case LINE_STATE_CROSS_DOWN : return "Crossing Down"; case LINE_STATE_TOUCH_BELOW: return "Touch from Below"; case LINE_STATE_TOUCH_ABOVE: return "Touch from Above"; case LINE_STATE_EQUALS : return "Equals"; default : return "Unknown"; } }
When using the dashboard, data is displayed on the panel using the function:
//+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlTick tick={0}; MqlRates rates[1]; //--- Exit if unable to get the current prices if(!SymbolInfoTick(Symbol(),tick)) return; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Display the indicator data from the specified bar on the panel in table 1 panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value=IndicatorValue(handle,index,0); string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : ""); panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90); //--- Display a description of the indicator line state panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); ENUM_LINE_STATE state=LineState(handle,index,0); color clr=(value<0 ? clrRed : value>0 ? clrBlue : clrNONE); // The label color changes depending on the value of the line above/below zero panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); }
Indicator line values above/below zero are marked in color on the dashboard.
Besides, the panel events handler is called in the OnChartEvent() EA event handler, as well as the events for receiving the bar index under the cursor are handled:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } }
After compiling the EA and launching it on the chart, we can monitor the state of the indicator line on the panel:
The "TestOscillatorForce.mq5" EA file is attached below.
MACD
Moving Average Convergence/Divergence (MACD) is a trend-following dynamic indicator. It indicates the correlation between two price moving averages.
MACD is the difference between a 26-period and 12-period Exponential moving averages (EMA). In order to clearly show buy/sell opportunities, a so-called signal line (9-period moving average of the indicator) is plotted on the MACD chart.
The MACD proves most effective in wide-swinging trading markets. There are three popular ways to use the Moving Average Convergence/Divergence: crossovers, overbought/oversold conditions, and divergences.
Crosses
The basic MACD trading rule is to sell when the MACD falls below its signal line. Similarly, a buy signal occurs when the Moving Average Convergence/Divergence rises above its signal line. It is also popular to buy/sell when the MACD goes above/below zero.
Overbought/Oversold Conditions
The MACD is also useful as an overbought/oversold indicator. When the shorter moving average pulls away dramatically from the longer moving average (i.e., the MACD rises), it is likely that the symbol price is overextending and will soon return to more realistic levels.
Divergences
An indication that an end to the current trend may be near occurs when the MACD diverges from the symbol. A bullish divergence occurs when the Moving Average Convergence/Divergence indicator is making new lows while the price fails to do that. A bearish divergence occurs when MACD is making new highs while the price fails to reach them. Both of these divergences are most significant when they occur at relatively overbought/oversold levels.
Parameters
The indicator has four configured parameters:
- Fast EMA calculation period, default - 12
- Slow EMA calculation period, default - 26,
- Signal SMA calculation period, default - 9,
- Calculation price, default – Close.
Input and global variables for using the indicator in the EA:
//+------------------------------------------------------------------+ //| TestOscillatorMACD.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriodFast = 12; /* Fast MA Period */ input uint InpPeriodSlow = 26; /* Slow MA Period */ input uint InpPeriodSignal= 9; /* MACD SMA */ input ENUM_APPLIED_PRICE InpAppliedPrice= PRICE_CLOSE; /* Applied Price */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period_fast=0; // Fast EMA calculation period int period_slow=0; // Slow EMA calculation period int period_signal=0; // Signal line calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description
When using the dashboard, the panel class file is included and the variables for working with it are added:
//+------------------------------------------------------------------+ //| TestOscillatorMACD.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Dashboard\Dashboard.mqh> //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriodFast = 12; /* Fast MA Period */ input uint InpPeriodSlow = 26; /* Slow MA Period */ input uint InpPeriodSignal= 9; /* MACD SMA */ input ENUM_APPLIED_PRICE InpAppliedPrice= PRICE_CLOSE; /* Applied Price */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period_fast=0; // Fast EMA calculation period int period_slow=0; // Slow EMA calculation period int period_signal=0; // Signal line calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description //--- variables for the panel int mouse_bar_index; // Index of the bar the data is taken from CDashboard *panel=NULL; // Pointer to the panel object
Initialization
The OnInit() handler for initializing indicator parameters and creating its handle:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period_fast=int(InpPeriodFast<1 ? 12 : InpPeriodFast); period_slow=int(InpPeriodSlow<1 ? 26 : InpPeriodSlow==period_fast ? period_fast+1 : InpPeriodSlow); period_signal=int(InpPeriodSignal<1 ? 9 : InpPeriodSignal); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("MACD(%lu,%lu,%lu)",period_fast,period_slow,period_signal); ind_digits=Digits()+1; //--- Create indicator handle ResetLastError(); handle=iMACD(Symbol(),PERIOD_CURRENT,period_fast,period_slow,period_signal,InpAppliedPrice); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Successful initialization return(INIT_SUCCEEDED); }
The panel is created when using the dashboard:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period_fast=int(InpPeriodFast<1 ? 12 : InpPeriodFast); period_slow=int(InpPeriodSlow<1 ? 26 : InpPeriodSlow==period_fast ? period_fast+1 : InpPeriodSlow); period_signal=int(InpPeriodSignal<1 ? 9 : InpPeriodSignal); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("MACD(%lu,%lu,%lu)",period_fast,period_slow,period_signal); ind_digits=Digits()+1; //--- Create indicator handle ResetLastError(); handle=iMACD(Symbol(),PERIOD_CURRENT,period_fast,period_slow,period_signal,InpAppliedPrice); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Dashboard //--- Create the panel panel=new CDashboard(1,20,20,199,261); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,97); //--- Create a table with ID 1 to display indicator data in it panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,5,2,18,97); //--- Display tabular data in the journal panel.GridPrint(0,2); panel.GridPrint(1,2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); }
Deinitialization
Release the indicator handle in the EA OnDeinit() handler:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); }
The created dashboard object is removed when using the dashboard:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; }
Data retrieval
General functions for obtaining data by the indicator handle:
//+------------------------------------------------------------------+ //| Return the indicator data on the specified bar | //+------------------------------------------------------------------+ double IndicatorValue(const int ind_handle,const int index,const int buffer_num) { double array[1]={0}; ResetLastError(); if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1) { PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError()); return EMPTY_VALUE; } return array[0]; } //+------------------------------------------------------------------+ //| Return the state of the indicator line | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num) { //--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); const double value2=IndicatorValue(ind_handle,index+2,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) return LINE_STATE_NONE; //--- Line upward reversal (value2>value1 && value0>value1) if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_TURN_UP; //--- Line upward direction (value2<=value1 && value0>value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_UP; //--- Line upward stop (value2<=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_UP; //--- Line downward reversal (value2<value1 && value0<value1) if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_TURN_DOWN; //--- Line downward direction (value2>=value1 && value0<value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_DOWN; //--- Line downward stop (value2>=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_DOWN; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the state of the line relative to the specified level | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE) { //--- Get the values of the indicator line with the shift (0,1) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) return LINE_STATE_NONE; //--- Define the second level to compare double level=(level1==EMPTY_VALUE ? level0 : level1); //--- The line is below the level (value1<level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_UNDER; //--- The line is above the level (value1>level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_ABOVE; //--- The line crossed the level upwards (value1<=level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_CROSS_UP; //--- The line crossed the level downwards (value1>=level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_CROSS_DOWN; //--- The line touched the level from below (value1<level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- The line touched the level from above (value1>level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- Line is equal to the level value (value1==level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_EQUALS; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the indicator line state description | //+------------------------------------------------------------------+ string LineStateDescription(const ENUM_LINE_STATE state) { switch(state) { case LINE_STATE_UP : return "Up"; case LINE_STATE_STOP_UP : return "Stop Up"; case LINE_STATE_TURN_UP : return "Turn Up"; case LINE_STATE_DOWN : return "Down"; case LINE_STATE_STOP_DOWN : return "Stop Down"; case LINE_STATE_TURN_DOWN : return "Turn Down"; case LINE_STATE_ABOVE : return "Above level"; case LINE_STATE_UNDER : return "Under level"; case LINE_STATE_CROSS_UP : return "Crossing Up"; case LINE_STATE_CROSS_DOWN : return "Crossing Down"; case LINE_STATE_TOUCH_BELOW: return "Touch from Below"; case LINE_STATE_TOUCH_ABOVE: return "Touch from Above"; case LINE_STATE_EQUALS : return "Equals"; default : return "Unknown"; } }
When using the dashboard, data is displayed on the panel using the function:
//+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlTick tick={0}; MqlRates rates[1]; //--- Exit if unable to get the current prices if(!SymbolInfoTick(Symbol(),tick)) return; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Display the indicator data from the specified bar on the panel in table 1 panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value=IndicatorValue(handle,index,0); string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : ""); panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90); //--- Display the indicator signal line data from the specified bar on the panel in table 1 panel.DrawText("Signal", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); double signal=IndicatorValue(handle,index,1); string signal_str=(signal!=EMPTY_VALUE ? DoubleToString(signal,ind_digits) : ""); panel.DrawText(signal_str,panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,90); //--- Display a description of the indicator line state relative to zero panel.DrawText("MACD vs Zero", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2); ENUM_LINE_STATE state_zero=LineStateRelative(handle,index,0,0); string state_zero_str= ( state_zero==LINE_STATE_ABOVE ? "MACD > 0" : state_zero==LINE_STATE_UNDER ? "MACD < 0" : state_zero==LINE_STATE_TOUCH_ABOVE || state_zero==LINE_STATE_TOUCH_BELOW ? "Touch" : LineStateDescription(state_zero) ); //--- The label color changes depending on the value of the line relative to the level color clr=(state_zero==LINE_STATE_CROSS_UP ? clrBlue : state_zero==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE); panel.DrawText(state_zero_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,90); //--- Display a description of the indicator line state relative to the signal line panel.DrawText("MACD vs Signal", panel.CellX(1,3,0)+2, panel.CellY(1,3,0)+2); ENUM_LINE_STATE state_signal=LineStateRelative(handle,index,0,signal,IndicatorValue(handle,index+1,1)); string state_signal_str=(state_signal==LINE_STATE_ABOVE ? "MACD > Signal" : state_signal==LINE_STATE_UNDER ? "MACD < Signal" : LineStateDescription(state_signal)); //--- The label color changes depending on the value of the line relative to the level clr=(state_signal==LINE_STATE_CROSS_UP ? clrBlue : state_signal==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE); panel.DrawText(state_signal_str,panel.CellX(1,3,1)+2,panel.CellY(1,3,1)+2,clr,90); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); }
The ratio of the histogram relative to the signal line and to zero is highlighted in the panel with color.
Besides, the panel events handler is called in the OnChartEvent() EA event handler, as well as the events for receiving the bar index under the cursor are handled:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } }
After compiling the EA and launching it on the chart, we can monitor the state of the indicator lines on the panel:
The "TestOscillatorMACD.mq5" EA file is attached below.
Momentum
The Momentum Technical Indicator measures the change of price of a financial instrument over a given time span. There are basically two ways to use the Momentum indicator:
- As a trend-following indicator similar to Moving Average Convergence/Divergence (MACD). In this case a signal to buy occurs if the Momentum indicator makes up a trough and starts rising; a signal to sell occurs when it reaches peak and turns down. You may want to plot a short-term moving average of the indicator to determine when it is bottoming or peaking.
Extremely high or low values of Momentum imply continuation of the current trend. Thus if the indicator reaches extremely high values and then turns down, the further price growth should be expected. In any case, a position should be opened or closed only after prices confirm the signal generated by the indicator. - As a leading indicator. This method assumes that the final phase of an up-trend is usually accompanied by a rapid price increase (when everyone expects prices to go higher), and that the end of bears' market is characterized by rapid price declines (when everyone wants to get out). This is often the case, but it is also a broad generalization.
When market approaches a peak there is a sharp leap of the Momentum indicator. After that it starts to fall while prices keep on growing or move horizontally. Analogous to that, at the market bottom Momentum sharply falls and then turns up long before prices start growing. Both of these situations result in divergences between the indicator and prices.
Parameters
The indicator has two input parameters:
- Calculation period, default - 14,
- Calculation price, default – Close.
Input and global variables for using the indicator in the EA:
//+------------------------------------------------------------------+ //| TestOscillatorMomentum.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod= 14; /* Period */ input ENUM_APPLIED_PRICE InpPrice = PRICE_CLOSE; /* Applied Price */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // Momentum calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description
When using the dashboard, the panel class file is included and the variables for working with it are added:
//+------------------------------------------------------------------+ //| TestOscillatorMomentum.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Dashboard\Dashboard.mqh> //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod= 14; /* Period */ input ENUM_APPLIED_PRICE InpPrice = PRICE_CLOSE; /* Applied Price */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // Momentum calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description //--- variables for the panel int mouse_bar_index; // Index of the bar the data is taken from CDashboard *panel=NULL; // Pointer to the panel object
Initialization
The OnInit() handler for initializing indicator parameters and creating its handle:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period=int(InpPeriod<1 ? 14 : InpPeriod); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("Momentum(%lu)",period); ind_digits=2; //--- Create indicator handle ResetLastError(); handle=iMomentum(Symbol(),PERIOD_CURRENT,period,InpPrice); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Successful initialization return(INIT_SUCCEEDED); }
The panel is created when using the dashboard:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period=int(InpPeriod<1 ? 14 : InpPeriod); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("Momentum(%lu)",period); ind_digits=2; //--- Create indicator handle ResetLastError(); handle=iMomentum(Symbol(),PERIOD_CURRENT,period,InpPrice); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Dashboard //--- Create the panel panel=new CDashboard(1,20,20,199,225); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,97); //--- Create a table with ID 1 to display indicator data in it panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,3,2,18,97); //--- Display tabular data in the journal panel.GridPrint(0,2); panel.GridPrint(1,2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); }
Deinitialization
Release the indicator handle in the EA OnDeinit() handler:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); }
The created dashboard object is removed when using the dashboard:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; }
Data retrieval
General functions for obtaining data by the indicator handle:
//+------------------------------------------------------------------+ //| Return the indicator data on the specified bar | //+------------------------------------------------------------------+ double IndicatorValue(const int ind_handle,const int index,const int buffer_num) { double array[1]={0}; ResetLastError(); if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1) { PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError()); return EMPTY_VALUE; } return array[0]; } //+------------------------------------------------------------------+ //| Return the state of the indicator line | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num) { //--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); const double value2=IndicatorValue(ind_handle,index+2,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) return LINE_STATE_NONE; //--- Line upward reversal (value2>value1 && value0>value1) if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_TURN_UP; //--- Line upward direction (value2<=value1 && value0>value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_UP; //--- Line upward stop (value2<=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_UP; //--- Line downward reversal (value2<value1 && value0<value1) if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_TURN_DOWN; //--- Line downward direction (value2>=value1 && value0<value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_DOWN; //--- Line downward stop (value2>=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_DOWN; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the state of the line relative to the specified level | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE) { //--- Get the values of the indicator line with the shift (0,1) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) return LINE_STATE_NONE; //--- Define the second level to compare double level=(level1==EMPTY_VALUE ? level0 : level1); //--- The line is below the level (value1<level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_UNDER; //--- The line is above the level (value1>level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_ABOVE; //--- The line crossed the level upwards (value1<=level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_CROSS_UP; //--- The line crossed the level downwards (value1>=level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_CROSS_DOWN; //--- The line touched the level from below (value1<level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- The line touched the level from above (value1>level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- Line is equal to the level value (value1==level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_EQUALS; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the indicator line state description | //+------------------------------------------------------------------+ string LineStateDescription(const ENUM_LINE_STATE state) { switch(state) { case LINE_STATE_UP : return "Up"; case LINE_STATE_STOP_UP : return "Stop Up"; case LINE_STATE_TURN_UP : return "Turn Up"; case LINE_STATE_DOWN : return "Down"; case LINE_STATE_STOP_DOWN : return "Stop Down"; case LINE_STATE_TURN_DOWN : return "Turn Down"; case LINE_STATE_ABOVE : return "Above level"; case LINE_STATE_UNDER : return "Under level"; case LINE_STATE_CROSS_UP : return "Crossing Up"; case LINE_STATE_CROSS_DOWN : return "Crossing Down"; case LINE_STATE_TOUCH_BELOW: return "Touch from Below"; case LINE_STATE_TOUCH_ABOVE: return "Touch from Above"; case LINE_STATE_EQUALS : return "Equals"; default : return "Unknown"; } }
When using the dashboard, data is displayed on the panel using the function:
//+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlTick tick={0}; MqlRates rates[1]; //--- Exit if unable to get the current prices if(!SymbolInfoTick(Symbol(),tick)) return; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Display the indicator data from the specified bar on the panel in table 1 panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value=IndicatorValue(handle,index,0); string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : ""); panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90); //--- Display a description of the indicator line state panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); ENUM_LINE_STATE state=LineState(handle,index,0); panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,90); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); }
Besides, the panel events handler is called in the OnChartEvent() EA event handler, as well as the events for receiving the bar index under the cursor are handled:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } }
After compiling the EA and launching it on the chart, we can monitor the state of the indicator line on the panel:
The "TestOscillatorMomentum.mq5" EA file is attached below.
Moving Average of Oscillator
Moving Average of Oscillator (OsMA) is the difference between the oscillator and oscillator smoothing. In this case, Moving Average Convergence/Divergence base-line is used as the oscillator, and the signal line is used as the smoothing.
Parameters
The indicator has four configured parameters:
- Fast EMA MACD calculation period, default — 12,
- Slow EMA MACD calculation period, default — 26,
- Signal SMA MACD calculation period, default — 9,
- MACD calculation price, default – Close.
Input and global variables for using the indicator in the EA:
//+------------------------------------------------------------------+ //| TestOscillatorOsMA.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriodFast = 12; /* Fast MA Period */ input uint InpPeriodSlow = 26; /* Slow MA Period */ input uint InpPeriodSignal= 9; /* MACD SMA */ input ENUM_APPLIED_PRICE InpAppliedPrice= PRICE_CLOSE; /* Applied Price */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period_fast=0; // Fast EMA calculation period int period_slow=0; // Slow EMA calculation period int period_signal=0; // Signal line calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description
When using the dashboard, the panel class file is included and the variables for working with it are added:
//+------------------------------------------------------------------+ //| TestOscillatorOsMA.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Dashboard\Dashboard.mqh> //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriodFast = 12; /* Fast MA Period */ input uint InpPeriodSlow = 26; /* Slow MA Period */ input uint InpPeriodSignal= 9; /* MACD SMA */ input ENUM_APPLIED_PRICE InpAppliedPrice= PRICE_CLOSE; /* Applied Price */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period_fast=0; // Fast EMA calculation period int period_slow=0; // Slow EMA calculation period int period_signal=0; // Signal line calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description //--- variables for the panel int mouse_bar_index; // Index of the bar the data is taken from CDashboard *panel=NULL; // Pointer to the panel object
Initialization
The OnInit() handler for initializing indicator parameters and creating its handle:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period_fast=int(InpPeriodFast<1 ? 12 : InpPeriodFast); period_slow=int(InpPeriodSlow<1 ? 26 : InpPeriodSlow==period_fast ? period_fast+1 : InpPeriodSlow); period_signal=int(InpPeriodSignal<1 ? 9 : InpPeriodSignal); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("OsMA(%lu,%lu,%lu)",period_fast,period_slow,period_signal); ind_digits=Digits()+2; //--- Create indicator handle ResetLastError(); handle=iOsMA(Symbol(),PERIOD_CURRENT,period_fast,period_slow,period_signal,InpAppliedPrice); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Successful initialization return(INIT_SUCCEEDED); }
The panel is created when using the dashboard:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period_fast=int(InpPeriodFast<1 ? 12 : InpPeriodFast); period_slow=int(InpPeriodSlow<1 ? 26 : InpPeriodSlow==period_fast ? period_fast+1 : InpPeriodSlow); period_signal=int(InpPeriodSignal<1 ? 9 : InpPeriodSignal); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("OsMA(%lu,%lu,%lu)",period_fast,period_slow,period_signal); ind_digits=Digits()+2; //--- Create indicator handle ResetLastError(); handle=iOsMA(Symbol(),PERIOD_CURRENT,period_fast,period_slow,period_signal,InpAppliedPrice); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Dashboard //--- Create the panel panel=new CDashboard(1,20,20,199,225); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,97); //--- Create a table with ID 1 to display indicator data in it panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,3,2,18,97); //--- Display tabular data in the journal panel.GridPrint(0,2); panel.GridPrint(1,2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); }
Deinitialization
Release the indicator handle in the EA OnDeinit() handler:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); }
The created dashboard object is removed when using the dashboard:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; }
Data retrieval
General functions for obtaining data by the indicator handle:
//+------------------------------------------------------------------+ //| Return the indicator data on the specified bar | //+------------------------------------------------------------------+ double IndicatorValue(const int ind_handle,const int index,const int buffer_num) { double array[1]={0}; ResetLastError(); if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1) { PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError()); return EMPTY_VALUE; } return array[0]; } //+------------------------------------------------------------------+ //| Return the state of the indicator line | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num) { //--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); const double value2=IndicatorValue(ind_handle,index+2,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) return LINE_STATE_NONE; //--- Line upward reversal (value2>value1 && value0>value1) if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_TURN_UP; //--- Line upward direction (value2<=value1 && value0>value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_UP; //--- Line upward stop (value2<=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_UP; //--- Line downward reversal (value2<value1 && value0<value1) if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_TURN_DOWN; //--- Line downward direction (value2>=value1 && value0<value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_DOWN; //--- Line downward stop (value2>=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_DOWN; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the state of the line relative to the specified level | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE) { //--- Get the values of the indicator line with the shift (0,1) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) return LINE_STATE_NONE; //--- Define the second level to compare double level=(level1==EMPTY_VALUE ? level0 : level1); //--- The line is below the level (value1<level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_UNDER; //--- The line is above the level (value1>level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_ABOVE; //--- The line crossed the level upwards (value1<=level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_CROSS_UP; //--- The line crossed the level downwards (value1>=level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_CROSS_DOWN; //--- The line touched the level from below (value1<level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- The line touched the level from above (value1>level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- Line is equal to the level value (value1==level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_EQUALS; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the indicator line state description | //+------------------------------------------------------------------+ string LineStateDescription(const ENUM_LINE_STATE state) { switch(state) { case LINE_STATE_UP : return "Up"; case LINE_STATE_STOP_UP : return "Stop Up"; case LINE_STATE_TURN_UP : return "Turn Up"; case LINE_STATE_DOWN : return "Down"; case LINE_STATE_STOP_DOWN : return "Stop Down"; case LINE_STATE_TURN_DOWN : return "Turn Down"; case LINE_STATE_ABOVE : return "Above level"; case LINE_STATE_UNDER : return "Under level"; case LINE_STATE_CROSS_UP : return "Crossing Up"; case LINE_STATE_CROSS_DOWN : return "Crossing Down"; case LINE_STATE_TOUCH_BELOW: return "Touch from Below"; case LINE_STATE_TOUCH_ABOVE: return "Touch from Above"; case LINE_STATE_EQUALS : return "Equals"; default : return "Unknown"; } }
When using the dashboard, data is displayed on the panel using the function:
//+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlTick tick={0}; MqlRates rates[1]; //--- Exit if unable to get the current prices if(!SymbolInfoTick(Symbol(),tick)) return; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Display the indicator data from the specified bar on the panel in table 1 panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value=IndicatorValue(handle,index,0); string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : ""); panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90); //--- Display a description of the indicator line state panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); ENUM_LINE_STATE state=LineState(handle,index,0); //--- The label color changes depending on the value of the line above/below zero color clr=(value<0 ? clrRed : value>0 ? clrBlue : clrNONE); panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90); //--- Display a description of the indicator line state relative to zero panel.DrawText("OsMA vs Zero", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2); ENUM_LINE_STATE state_zero=LineStateRelative(handle,index,0,0); string state_zero_str= ( state_zero==LINE_STATE_ABOVE ? "OsMA > 0" : state_zero==LINE_STATE_UNDER ? "OsMA < 0" : state_zero==LINE_STATE_TOUCH_ABOVE || state_zero==LINE_STATE_TOUCH_BELOW ? "Touch" : LineStateDescription(state_zero) ); //--- The label color changes depending on the value of the line relative to the level clr=(state_zero==LINE_STATE_CROSS_UP ? clrBlue : state_zero==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE); panel.DrawText(state_zero_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,90); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); }
The states of the indicator line and its position relative to zero are highlighted in color on the dashboard.
Besides, the panel events handler is called in the OnChartEvent() EA event handler, as well as the events for receiving the bar index under the cursor are handled:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } }
After compiling the EA and launching it on the chart, we can monitor the state of the indicator line on the panel:
The "TestOscillatorOsMA.mq5" EA file is attached below.
Relative Strength Index
Relative Strength Index (RSI) is a price-following oscillator that ranges between 0 and 100. Describing Relative Strength Index, J. W. Wilder recommended using its 14-period version. Since then, the 9-period and 25-period Relative Strength Index indicators have also gained popularity. A popular method of analyzing the RSI is to look for a divergence in which the security is making a new high, but the RSI is failing to surpass its previous high. This divergence is an indication of an impending reversal. If the Rindicator then turns down and falls below its most recent trough, then it is said to have completed a "failure swing." This failure swing is considered to confirm the impending reversal.
The following signals of Relative Strength Index are used in chart analyzing:
- Tops and Bottoms
The Relative Strength Index usually tops above 70 and bottoms below 30. It usually forms these tops and bottoms before the underlying price chart. - Chart Formations
The RSI often forms chart patterns such as head and shoulders or triangles that may be or may not be visible on the price chart. - Failed Swing (Support or Resistance breakout)
This is where the Relative Strength Index surpasses a previous high (peak) or falls below a recent low (trough). - Support and Resistance levels
The Relative Strength Index shows, sometimes more clearly than price themselves, levels of support and resistance. - Divergences
As discussed above, divergences occur when the price makes a new high (or low) that is not confirmed by a new high (or low) in the Relative Strength Index. Prices usually correct and move in the direction of the RSI.
Parameters
The indicator has two input parameters:
- Calculation period, default - 14,
- Calculation price, default – Close.
Since overbought/oversold areas are applied to search for indicator signals, we will also use their values as EA inputs.
Input and global variables for using the indicator in the EA:
//+------------------------------------------------------------------+ //| TestOscillatorRSI.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod= 14; /* Period */ input ENUM_APPLIED_PRICE InpPrice = PRICE_CLOSE; /* Applied Price */ input double InpOverbough= 70; /* Overbough level*/ input double InpOversold = 30; /* Oversold level */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // RSI calculation period int ind_digits=0; // Number of decimal places in the indicator values double overbough=0; // Overbought level double oversold=0; // Oversold level string ind_title; // Indicator description
When using the dashboard, the panel class file is included and the variables for working with it are added:
//+------------------------------------------------------------------+ //| TestOscillatorRSI.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Dashboard\Dashboard.mqh> //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod= 14; /* Period */ input ENUM_APPLIED_PRICE InpPrice = PRICE_CLOSE; /* Applied Price */ input double InpOverbough= 70; /* Overbough level*/ input double InpOversold = 30; /* Oversold level */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // RSI calculation period int ind_digits=0; // Number of decimal places in the indicator values double overbough=0; // Overbought level double oversold=0; // Oversold level string ind_title; // Indicator description //--- variables for the panel int mouse_bar_index; // Index of the bar the data is taken from CDashboard *panel=NULL; // Pointer to the panel object
Initialization
The OnInit() handler for initializing indicator parameters and creating its handle:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period and levels if necessary period=int(InpPeriod<1 ? 14 : InpPeriod<2 ? 2 : InpPeriod); overbough=InpOverbough; oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("RSI(%lu)",period); ind_digits=2; //--- Create indicator handle ResetLastError(); handle=iRSI(Symbol(),PERIOD_CURRENT,period,InpPrice); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Successful initialization return(INIT_SUCCEEDED); }
The panel is created when using the dashboard:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period and levels if necessary period=int(InpPeriod<1 ? 14 : InpPeriod<2 ? 2 : InpPeriod); overbough=InpOverbough; oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("RSI(%lu)",period); ind_digits=2; //--- Create indicator handle ResetLastError(); handle=iRSI(Symbol(),PERIOD_CURRENT,period,InpPrice); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Dashboard //--- Create the panel panel=new CDashboard(1,20,20,229,243); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,112); //--- Create a table with ID 1 to display indicator data in it panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,4,2,18,112); //--- Display tabular data in the journal panel.GridPrint(0,2); panel.GridPrint(1,2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); }
Deinitialization
Release the indicator handle in the EA OnDeinit() handler:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); }
The created dashboard object is removed when using the dashboard:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; }
Data retrieval
General functions for obtaining data by the indicator handle:
//+------------------------------------------------------------------+ //| Return the indicator data on the specified bar | //+------------------------------------------------------------------+ double IndicatorValue(const int ind_handle,const int index,const int buffer_num) { double array[1]={0}; ResetLastError(); if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1) { PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError()); return EMPTY_VALUE; } return array[0]; } //+------------------------------------------------------------------+ //| Return the state of the indicator line | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num) { //--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); const double value2=IndicatorValue(ind_handle,index+2,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) return LINE_STATE_NONE; //--- Line upward reversal (value2>value1 && value0>value1) if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_TURN_UP; //--- Line upward direction (value2<=value1 && value0>value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_UP; //--- Line upward stop (value2<=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_UP; //--- Line downward reversal (value2<value1 && value0<value1) if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_TURN_DOWN; //--- Line downward direction (value2>=value1 && value0<value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_DOWN; //--- Line downward stop (value2>=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_DOWN; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the state of the line relative to the specified level | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE) { //--- Get the values of the indicator line with the shift (0,1) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) return LINE_STATE_NONE; //--- Define the second level to compare double level=(level1==EMPTY_VALUE ? level0 : level1); //--- The line is below the level (value1<level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_UNDER; //--- The line is above the level (value1>level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_ABOVE; //--- The line crossed the level upwards (value1<=level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_CROSS_UP; //--- The line crossed the level downwards (value1>=level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_CROSS_DOWN; //--- The line touched the level from below (value1<level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- The line touched the level from above (value1>level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- Line is equal to the level value (value1==level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_EQUALS; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the indicator line state description | //+------------------------------------------------------------------+ string LineStateDescription(const ENUM_LINE_STATE state) { switch(state) { case LINE_STATE_UP : return "Up"; case LINE_STATE_STOP_UP : return "Stop Up"; case LINE_STATE_TURN_UP : return "Turn Up"; case LINE_STATE_DOWN : return "Down"; case LINE_STATE_STOP_DOWN : return "Stop Down"; case LINE_STATE_TURN_DOWN : return "Turn Down"; case LINE_STATE_ABOVE : return "Above level"; case LINE_STATE_UNDER : return "Under level"; case LINE_STATE_CROSS_UP : return "Crossing Up"; case LINE_STATE_CROSS_DOWN : return "Crossing Down"; case LINE_STATE_TOUCH_BELOW: return "Touch from Below"; case LINE_STATE_TOUCH_ABOVE: return "Touch from Above"; case LINE_STATE_EQUALS : return "Equals"; default : return "Unknown"; } }
When using the dashboard, data is displayed on the panel using the function:
//+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlTick tick={0}; MqlRates rates[1]; //--- Exit if unable to get the current prices if(!SymbolInfoTick(Symbol(),tick)) return; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Display the indicator data from the specified bar on the panel in table 1 panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value=IndicatorValue(handle,index,0); string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : ""); panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,100); //--- Display a description of the indicator line state relative to the overbought level string ovb=StringFormat("%+.2f",overbough); panel.DrawText("Overbough", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2); panel.DrawText(ovb, panel.CellX(1,2,0)+66, panel.CellY(1,2,0)+2); ENUM_LINE_STATE state_ovb=LineStateRelative(handle,index,0,overbough); //--- The label color changes depending on the value of the line relative to the level color clr=(state_ovb==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE); string ovb_str=(state_ovb==LINE_STATE_ABOVE ? "Inside the area" : LineStateDescription(state_ovb)); panel.DrawText(ovb_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,100); //--- Display a description of the indicator line state relative to the oversold level panel.DrawText("Oversold", panel.CellX(1,3,0)+2, panel.CellY(1,3,0)+2); string ovs=StringFormat("%+.2f",oversold); panel.DrawText(ovs, panel.CellX(1,3,0)+68, panel.CellY(1,3,0)+2); ENUM_LINE_STATE state_ovs=LineStateRelative(handle,index,0,oversold); //--- The label color changes depending on the value of the line relative to the level clr=(state_ovs==LINE_STATE_CROSS_UP ? clrBlue : clrNONE); string ovs_str=(state_ovs==LINE_STATE_UNDER ? "Inside the area" : LineStateDescription(state_ovs)); panel.DrawText(ovs_str,panel.CellX(1,3,1)+2,panel.CellY(1,3,1)+2,clr,100); //--- Display a description of the indicator line state panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); ENUM_LINE_STATE state=LineState(handle,index,0); //--- The label color changes depending on the location of the line in the overbought/oversold areas clr=(state_ovb==LINE_STATE_ABOVE || state_ovb==LINE_STATE_CROSS_DOWN ? clrRed : state_ovs==LINE_STATE_UNDER || state_ovs==LINE_STATE_CROSS_UP ? clrBlue : clrNONE); panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,100); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); }
The location of the line in overbought/oversold zones, as well as the state of the line relative to the levels, are marked with color on the panel.
Besides, the panel events handler is called in the OnChartEvent() EA event handler, as well as the events for receiving the bar index under the cursor are handled:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } }
After compiling the EA and launching it on the chart, we can monitor the state of the indicator line on the panel:
The "TestOscillatorRSI.mq5" EA file is attached below.
Relative Vigor Index
The main point of Relative Vigor Index (RVI) is that on the bull market the closing price is, as a rule, higher, than the opening price. It is the other way round on the bear market. So the idea behind Relative Vigor Index is that the vigor, or energy, of the move is thus established by where the prices end up at the close. To normalize the index to the daily trading range, divide the change of price by the maximum range of prices for the day. To make a more smooth calculation, one uses Simple Moving Average. 10 is considered the best period. To avoid probable ambiguity one needs to construct a signal line, which is a 4-period symmetrically weighted moving average of Relative Vigor Index values. The concurrence of lines serves as a signal to buy or to sell.
Parameters
The indicator has one configurable parameter - the calculation period. The default value is 10.
Input and global variables for using the indicator in the EA:
//+------------------------------------------------------------------+ //| TestOscillatorRVI.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod = 10; /* Period */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // RVI calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description
When using the dashboard, the panel class file is included and the variables for working with it are added:
//+------------------------------------------------------------------+ //| TestOscillatorRVI.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Dashboard\Dashboard.mqh> //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod = 10; /* Period */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // RVI calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description //--- variables for the panel int mouse_bar_index; // Index of the bar the data is taken from CDashboard *panel=NULL; // Pointer to the panel object
Initialization
The OnInit() handler for initializing indicator parameters and creating its handle:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period=int(InpPeriod<1 ? 10 : InpPeriod); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("RVI(%lu)",period); ind_digits=3; //--- Create indicator handle ResetLastError(); handle=iRVI(Symbol(),PERIOD_CURRENT,period); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Successful initialization return(INIT_SUCCEEDED); }
The panel is created when using the dashboard:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period=int(InpPeriod<1 ? 10 : InpPeriod); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("RVI(%lu)",period); ind_digits=3; //--- Create indicator handle ResetLastError(); handle=iRVI(Symbol(),PERIOD_CURRENT,period); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Dashboard //--- Create the panel panel=new CDashboard(1,20,20,199,225); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,97); //--- Create a table with ID 1 to display indicator data in it panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,3,2,18,97); //--- Display tabular data in the journal panel.GridPrint(0,2); panel.GridPrint(1,2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); }
Deinitialization
Release the indicator handle in the EA OnDeinit() handler:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); }
The created dashboard object is removed when using the dashboard:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; }
Data retrieval
General functions for obtaining data by the indicator handle:
//+------------------------------------------------------------------+ //| Return the indicator data on the specified bar | //+------------------------------------------------------------------+ double IndicatorValue(const int ind_handle,const int index,const int buffer_num) { double array[1]={0}; ResetLastError(); if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1) { PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError()); return EMPTY_VALUE; } return array[0]; } //+------------------------------------------------------------------+ //| Return the state of the indicator line | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num) { //--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); const double value2=IndicatorValue(ind_handle,index+2,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) return LINE_STATE_NONE; //--- Line upward reversal (value2>value1 && value0>value1) if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_TURN_UP; //--- Line upward direction (value2<=value1 && value0>value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_UP; //--- Line upward stop (value2<=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_UP; //--- Line downward reversal (value2<value1 && value0<value1) if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_TURN_DOWN; //--- Line downward direction (value2>=value1 && value0<value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_DOWN; //--- Line downward stop (value2>=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_DOWN; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the state of the line relative to the specified level | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE) { //--- Get the values of the indicator line with the shift (0,1) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) return LINE_STATE_NONE; //--- Define the second level to compare double level=(level1==EMPTY_VALUE ? level0 : level1); //--- The line is below the level (value1<level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_UNDER; //--- The line is above the level (value1>level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_ABOVE; //--- The line crossed the level upwards (value1<=level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_CROSS_UP; //--- The line crossed the level downwards (value1>=level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_CROSS_DOWN; //--- The line touched the level from below (value1<level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- The line touched the level from above (value1>level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- Line is equal to the level value (value1==level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_EQUALS; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the indicator line state description | //+------------------------------------------------------------------+ string LineStateDescription(const ENUM_LINE_STATE state) { switch(state) { case LINE_STATE_UP : return "Up"; case LINE_STATE_STOP_UP : return "Stop Up"; case LINE_STATE_TURN_UP : return "Turn Up"; case LINE_STATE_DOWN : return "Down"; case LINE_STATE_STOP_DOWN : return "Stop Down"; case LINE_STATE_TURN_DOWN : return "Turn Down"; case LINE_STATE_ABOVE : return "Above level"; case LINE_STATE_UNDER : return "Under level"; case LINE_STATE_CROSS_UP : return "Crossing Up"; case LINE_STATE_CROSS_DOWN : return "Crossing Down"; case LINE_STATE_TOUCH_BELOW: return "Touch from Below"; case LINE_STATE_TOUCH_ABOVE: return "Touch from Above"; case LINE_STATE_EQUALS : return "Equals"; default : return "Unknown"; } }
When using the dashboard, data is displayed on the panel using the function:
//+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlTick tick={0}; MqlRates rates[1]; //--- Exit if unable to get the current prices if(!SymbolInfoTick(Symbol(),tick)) return; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Display the indicator data from the specified bar on the panel in table 1 panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value=IndicatorValue(handle,index,0); string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : ""); panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90); //--- Display the indicator signal line data from the specified bar on the panel in table 1 panel.DrawText("Signal", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); double signal=IndicatorValue(handle,index,1); string signal_str=(signal!=EMPTY_VALUE ? DoubleToString(signal,ind_digits) : ""); panel.DrawText(signal_str,panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,90); //--- Display a description of the indicator line state relative to the signal line panel.DrawText("RVI vs Signal", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2); ENUM_LINE_STATE state_signal=LineStateRelative(handle,index,0,signal,IndicatorValue(handle,index+1,1)); string state_signal_str=(state_signal==LINE_STATE_ABOVE ? "RVI > Signal" : state_signal==LINE_STATE_UNDER ? "RVI < Signal" : LineStateDescription(state_signal)); //--- The label color changes depending on the value of the line relative to the level color clr=(state_signal==LINE_STATE_CROSS_UP ? clrBlue : state_signal==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE); panel.DrawText(state_signal_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,90); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); }
Indicator signals (intersections of the indicator line with the signal line) are highlighted with color on the dashboard.
Besides, the panel events handler is called in the OnChartEvent() EA event handler, as well as the events for receiving the bar index under the cursor are handled:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } }
After compiling the EA and launching it on the chart, we can monitor the state of the indicator line on the panel:
The "TestOscillatorRVI.mq5" EA file is attached below.
Stochastic Oscillator
Stochastic Oscillator compares where a security’s price closed relative to its price range over a given time period. The Stochastic Oscillator is displayed as two lines. The main line is called %K. The second line called %D is a moving average of %K line. The %K line is usually displayed as a solid line and the %D line is usually displayed as a dotted line. There are several ways to interpret a Stochastic Oscillator. Three popular methods include:
- Buy when the Oscillator (either %K or %D) falls below a specific level (for example, 20) and then rises above that level. Sell when the Oscillator rises above a specific level (for example, 80) and then falls below that level.
- Buy when the %K line rises above %D. Sell when %K falls below %D.
- Look for divergences. For instance: where prices are making a series of new highs and the Stochastic Oscillator is failing to surpass its previous highs.
Parameters
The indicator has five input parameters:
- %K line calculation period, default — 5,
- %D line calculation period, default — 3,
- Slowing period, default — 3,
- Stochastic calculation price, default — Low/High,
- Calculation method, default — SMA.
In addition to these values, we need to specify the overbought and oversold levels in the EA inputs.
Input and global variables for using the indicator in the EA:
//+------------------------------------------------------------------+ //| TestOscillatorStoch.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriodK = 5; /* Period %K */ input uint InpPeriodD = 3; /* Period %D */ input uint InpSlowing = 3; /* Slowing */ input ENUM_STO_PRICE InpPrice = STO_LOWHIGH; /* Price */ input ENUM_MA_METHOD InpMethod = MODE_SMA; /* Method */ input double InpOverbough= 80; /* Overbough level*/ input double InpOversold = 20; /* Oversold level */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period_k=0; // %K period int period_d=0; // %D period int slowing=0; // Slowing period int ind_digits=0; // Number of decimal places in the indicator values double overbough=0; // Overbought level double oversold=0; // Oversold level string ind_title; // Indicator description
When using the dashboard, the panel class file is included and the variables for working with it are added:
//+------------------------------------------------------------------+ //| TestOscillatorStoch.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Dashboard\Dashboard.mqh> //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriodK = 5; /* Period %K */ input uint InpPeriodD = 3; /* Period %D */ input uint InpSlowing = 3; /* Slowing */ input ENUM_STO_PRICE InpPrice = STO_LOWHIGH; /* Price */ input ENUM_MA_METHOD InpMethod = MODE_SMA; /* Method */ input double InpOverbough= 80; /* Overbough level*/ input double InpOversold = 20; /* Oversold level */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period_k=0; // %K period int period_d=0; // %D period int slowing=0; // Slowing period int ind_digits=0; // Number of decimal places in the indicator values double overbough=0; // Overbought level double oversold=0; // Oversold level string ind_title; // Indicator description //--- variables for the panel int mouse_bar_index; // Index of the bar the data is taken from CDashboard *panel=NULL; // Pointer to the panel object
Initialization
The OnInit() handler for initializing indicator parameters and creating its handle:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period_k=int(InpPeriodK<1 ? 5 : InpPeriodK); period_d=int(InpPeriodD<1 ? 3 : InpPeriodD); slowing =int(InpSlowing<1 ? 3 : InpSlowing); overbough=InpOverbough; oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("Stoch(%lu,%lu,%lu)",period_k,period_d,slowing); ind_digits=2; //--- Create indicator handle ResetLastError(); handle=iStochastic(Symbol(),PERIOD_CURRENT,period_k,period_d,slowing,InpMethod,InpPrice); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Successful initialization return(INIT_SUCCEEDED); }
The panel is created when using the dashboard:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period_k=int(InpPeriodK<1 ? 5 : InpPeriodK); period_d=int(InpPeriodD<1 ? 3 : InpPeriodD); slowing =int(InpSlowing<1 ? 3 : InpSlowing); overbough=InpOverbough; oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("Stoch(%lu,%lu,%lu)",period_k,period_d,slowing); ind_digits=2; //--- Create indicator handle ResetLastError(); handle=iStochastic(Symbol(),PERIOD_CURRENT,period_k,period_d,slowing,InpMethod,InpPrice); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Dashboard //--- Create the panel panel=new CDashboard(1,20,20,229,261); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,112); //--- Create a table with ID 1 to display indicator data in it panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,5,2,18,112); //--- Display tabular data in the journal panel.GridPrint(0,2); panel.GridPrint(1,2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); }
Deinitialization
Release the indicator handle in the EA OnDeinit() handler:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); }
The created dashboard object is removed when using the dashboard:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; }
Data retrieval
General functions for obtaining data by the indicator handle:
//+------------------------------------------------------------------+ //| Return the indicator data on the specified bar | //+------------------------------------------------------------------+ double IndicatorValue(const int ind_handle,const int index,const int buffer_num) { double array[1]={0}; ResetLastError(); if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1) { PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError()); return EMPTY_VALUE; } return array[0]; } //+------------------------------------------------------------------+ //| Return the state of the indicator line | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num) { //--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); const double value2=IndicatorValue(ind_handle,index+2,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) return LINE_STATE_NONE; //--- Line upward reversal (value2>value1 && value0>value1) if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_TURN_UP; //--- Line upward direction (value2<=value1 && value0>value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_UP; //--- Line upward stop (value2<=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_UP; //--- Line downward reversal (value2<value1 && value0<value1) if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_TURN_DOWN; //--- Line downward direction (value2>=value1 && value0<value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_DOWN; //--- Line downward stop (value2>=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_DOWN; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the state of the line relative to the specified level | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE) { //--- Get the values of the indicator line with the shift (0,1) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) return LINE_STATE_NONE; //--- Define the second level to compare double level=(level1==EMPTY_VALUE ? level0 : level1); //--- The line is below the level (value1<level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_UNDER; //--- The line is above the level (value1>level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_ABOVE; //--- The line crossed the level upwards (value1<=level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_CROSS_UP; //--- The line crossed the level downwards (value1>=level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_CROSS_DOWN; //--- The line touched the level from below (value1<level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- The line touched the level from above (value1>level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- Line is equal to the level value (value1==level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_EQUALS; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the indicator line state description | //+------------------------------------------------------------------+ string LineStateDescription(const ENUM_LINE_STATE state) { switch(state) { case LINE_STATE_UP : return "Up"; case LINE_STATE_STOP_UP : return "Stop Up"; case LINE_STATE_TURN_UP : return "Turn Up"; case LINE_STATE_DOWN : return "Down"; case LINE_STATE_STOP_DOWN : return "Stop Down"; case LINE_STATE_TURN_DOWN : return "Turn Down"; case LINE_STATE_ABOVE : return "Above level"; case LINE_STATE_UNDER : return "Under level"; case LINE_STATE_CROSS_UP : return "Crossing Up"; case LINE_STATE_CROSS_DOWN : return "Crossing Down"; case LINE_STATE_TOUCH_BELOW: return "Touch from Below"; case LINE_STATE_TOUCH_ABOVE: return "Touch from Above"; case LINE_STATE_EQUALS : return "Equals"; default : return "Unknown"; } }
When using the dashboard, data is displayed on the panel using the function:
//+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlTick tick={0}; MqlRates rates[1]; //--- Exit if unable to get the current prices if(!SymbolInfoTick(Symbol(),tick)) return; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Display the indicator data from the specified bar on the panel in table 1 panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value=IndicatorValue(handle,index,0); string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : ""); panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,100); //--- Display the indicator signal line data from the specified bar on the panel in table 1 panel.DrawText("Signal", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); double signal=IndicatorValue(handle,index,1); string signal_str=(signal!=EMPTY_VALUE ? DoubleToString(signal,ind_digits) : ""); panel.DrawText(signal_str,panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clrNONE,100); //--- Display a description of the indicator line state relative to the signal line panel.DrawText("Stoch vs Signal", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2); ENUM_LINE_STATE state_signal=LineStateRelative(handle,index,0,signal,IndicatorValue(handle,index+1,1)); string state_signal_str=(state_signal==LINE_STATE_ABOVE ? "Stoch > Signal" : state_signal==LINE_STATE_UNDER ? "Stoch < Signal" : LineStateDescription(state_signal)); //--- The label color changes depending on the value of the line relative to the level color clr=(state_signal==LINE_STATE_CROSS_UP ? clrBlue : state_signal==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE); panel.DrawText(state_signal_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,100); //--- Display a description of the indicator line state relative to the overbought level string ovb=StringFormat("%+.2f",overbough); panel.DrawText("Overbough", panel.CellX(1,3,0)+2, panel.CellY(1,3,0)+2); panel.DrawText(ovb, panel.CellX(1,3,0)+66, panel.CellY(1,3,0)+2); ENUM_LINE_STATE state_ovb=LineStateRelative(handle,index,0,overbough); //--- The label color changes depending on the value of the line relative to the level clr=(state_ovb==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE); string ovb_str=(state_ovb==LINE_STATE_ABOVE ? "Inside the area" : LineStateDescription(state_ovb)); panel.DrawText(ovb_str,panel.CellX(1,3,1)+2,panel.CellY(1,3,1)+2,clr,100); //--- Display a description of the indicator line state relative to the oversold level panel.DrawText("Oversold", panel.CellX(1,4,0)+2, panel.CellY(1,4,0)+2); string ovs=StringFormat("%+.2f",oversold); panel.DrawText(ovs, panel.CellX(1,4,0)+68, panel.CellY(1,4,0)+2); ENUM_LINE_STATE state_ovs=LineStateRelative(handle,index,0,oversold); //--- The label color changes depending on the value of the line relative to the level clr=(state_ovs==LINE_STATE_CROSS_UP ? clrBlue : clrNONE); string ovs_str=(state_ovs==LINE_STATE_UNDER ? "Inside the area" : LineStateDescription(state_ovs)); panel.DrawText(ovs_str,panel.CellX(1,4,1)+2,panel.CellY(1,4,1)+2,clr,100); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); }
The state of the stochastic line, being in overbought/oversold zones and indicator signals are marked in color on the dashboard.
Besides, the panel events handler is called in the OnChartEvent() EA event handler, as well as the events for receiving the bar index under the cursor are handled:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } }
After compiling the EA and launching it on the chart, we can monitor the state of the indicator line on the panel:
The "TestOscillatorStoch.mq5" EA file is attached below.
Triple Exponential Average
Triple Exponential Average (TRIX) was developed by Jack Hutson as an oscillator of the overbought/oversold market conditions. It can also be used as the Momentum indicator. Triple smoothing is used for removing the cyclic components in price movements with the period less than that of TRIX.
The zone is used as the indicator of overbought or oversold state (positive and negative respectively). The signal to buy is crossing of the zero line from below, or "bulls'" divergence; the signal to sell is the indicator's crossing the zero line from above, or "bears'" divergence with prices. The distinctive feature of the indicator is the perfect filtration of price noises and absence of lag that is so typical of most moving averages.
Parameters
The indicator has two input parameters:
- Calculation period, default - 14,
- Calculation price, default – Close.
Input and global variables for using the indicator in the EA:
//+------------------------------------------------------------------+ //| TestOscillatorTRIX.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod= 14; /* Period */// input ENUM_APPLIED_PRICE InpPrice = PRICE_CLOSE; /* Applied Price */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // TRIX calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description
When using the dashboard, the panel class file is included and the variables for working with it are added:
//+------------------------------------------------------------------+ //| TestOscillatorTRIX.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Dashboard\Dashboard.mqh> //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod= 14; /* Period */// input ENUM_APPLIED_PRICE InpPrice = PRICE_CLOSE; /* Applied Price */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // TRIX calculation period int ind_digits=0; // Number of decimal places in the indicator values string ind_title; // Indicator description //--- variables for the panel int mouse_bar_index; // Index of the bar the data is taken from CDashboard *panel=NULL; // Pointer to the panel object
Initialization
The OnInit() handler for initializing indicator parameters and creating its handle:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period=int(InpPeriod<1 ? 14 : InpPeriod<2 ? 2 : InpPeriod); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("TRIX(%lu)",period); ind_digits=Digits(); //--- Create indicator handle ResetLastError(); handle=iTriX(Symbol(),PERIOD_CURRENT,period,InpPrice); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Successful initialization return(INIT_SUCCEEDED); }
The panel is created when using the dashboard:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period if necessary period=int(InpPeriod<1 ? 14 : InpPeriod<2 ? 2 : InpPeriod); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("TRIX(%lu)",period); ind_digits=Digits(); //--- Create indicator handle ResetLastError(); handle=iTriX(Symbol(),PERIOD_CURRENT,period,InpPrice); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Dashboard //--- Create the panel panel=new CDashboard(1,20,20,199,225); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,97); //--- Create a table with ID 1 to display indicator data in it panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,3,2,18,97); //--- Display tabular data in the journal panel.GridPrint(0,2); panel.GridPrint(1,2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); }
Deinitialization
Release the indicator handle in the EA OnDeinit() handler:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); }
The created dashboard object is removed when using the dashboard:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; }
Data retrieval
General functions for obtaining data by the indicator handle:
//+------------------------------------------------------------------+ //| Return the indicator data on the specified bar | //+------------------------------------------------------------------+ double IndicatorValue(const int ind_handle,const int index,const int buffer_num) { double array[1]={0}; ResetLastError(); if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1) { PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError()); return EMPTY_VALUE; } return array[0]; } //+------------------------------------------------------------------+ //| Return the state of the indicator line | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num) { //--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); const double value2=IndicatorValue(ind_handle,index+2,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) return LINE_STATE_NONE; //--- Line upward reversal (value2>value1 && value0>value1) if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_TURN_UP; //--- Line upward direction (value2<=value1 && value0>value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_UP; //--- Line upward stop (value2<=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_UP; //--- Line downward reversal (value2<value1 && value0<value1) if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_TURN_DOWN; //--- Line downward direction (value2>=value1 && value0<value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_DOWN; //--- Line downward stop (value2>=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_DOWN; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the state of the line relative to the specified level | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE) { //--- Get the values of the indicator line with the shift (0,1) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) return LINE_STATE_NONE; //--- Define the second level to compare double level=(level1==EMPTY_VALUE ? level0 : level1); //--- The line is below the level (value1<level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_UNDER; //--- The line is above the level (value1>level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_ABOVE; //--- The line crossed the level upwards (value1<=level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_CROSS_UP; //--- The line crossed the level downwards (value1>=level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_CROSS_DOWN; //--- The line touched the level from below (value1<level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- The line touched the level from above (value1>level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- Line is equal to the level value (value1==level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_EQUALS; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the indicator line state description | //+------------------------------------------------------------------+ string LineStateDescription(const ENUM_LINE_STATE state) { switch(state) { case LINE_STATE_UP : return "Up"; case LINE_STATE_STOP_UP : return "Stop Up"; case LINE_STATE_TURN_UP : return "Turn Up"; case LINE_STATE_DOWN : return "Down"; case LINE_STATE_STOP_DOWN : return "Stop Down"; case LINE_STATE_TURN_DOWN : return "Turn Down"; case LINE_STATE_ABOVE : return "Above level"; case LINE_STATE_UNDER : return "Under level"; case LINE_STATE_CROSS_UP : return "Crossing Up"; case LINE_STATE_CROSS_DOWN : return "Crossing Down"; case LINE_STATE_TOUCH_BELOW: return "Touch from Below"; case LINE_STATE_TOUCH_ABOVE: return "Touch from Above"; case LINE_STATE_EQUALS : return "Equals"; default : return "Unknown"; } }
When using the dashboard, data is displayed on the panel using the function:
//+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlTick tick={0}; MqlRates rates[1]; //--- Exit if unable to get the current prices if(!SymbolInfoTick(Symbol(),tick)) return; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Display the indicator data from the specified bar on the panel in table 1 panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value=IndicatorValue(handle,index,0); string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : ""); panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90); //--- Display a description of the indicator line state panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); ENUM_LINE_STATE state=LineState(handle,index,0); //--- The label color changes depending on the value of the line above/below zero color clr=(value<0 ? clrRed : value>0 ? clrBlue : clrNONE); panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90); //--- Display a description of the indicator line state relative to zero panel.DrawText("TRIX vs Zero", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2); ENUM_LINE_STATE state_zero=LineStateRelative(handle,index,0,0); string state_zero_str= ( state_zero==LINE_STATE_ABOVE ? "TRIX > 0" : state_zero==LINE_STATE_UNDER ? "TRIX < 0" : state_zero==LINE_STATE_TOUCH_ABOVE || state_zero==LINE_STATE_TOUCH_BELOW ? "Touch" : LineStateDescription(state_zero) ); //--- The label color changes depending on the value of the line relative to the level clr=(state_zero==LINE_STATE_CROSS_UP ? clrBlue : state_zero==LINE_STATE_CROSS_DOWN ? clrRed : clrNONE); panel.DrawText(state_zero_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,90); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); }
The states of the line and its relation to zero are marked in color on the dashboard.
Besides, the panel events handler is called in the OnChartEvent() EA event handler, as well as the events for receiving the bar index under the cursor are handled:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } }
After compiling the EA and launching it on the chart, we can monitor the state of the indicator line on the panel:
The "TestOscillatorTRIX.mq5" EA file is attached below.
Williams' Percent Range
Williams’ Percent Range (%R) is a dynamic technical indicator, which determines whether the market is overbought/oversold. Williams’ Percent Range is very similar to the Stochastic Oscillator. The only difference is that %R has an upside down scale and the Stochastic Oscillator has internal smoothing.
Indicator values ranging between -80% and -100% indicate that the market is oversold. Indicator values ranging between -0% and -20% indicate that the market is overbought. To show the indicator in this upside down fashion, one places a minus symbol before the Williams' Percent Range values (for example -30%). One should ignore the minus symbol when conducting the analysis.
As with all overbought/oversold indicators, it is best to wait for the symbol price to change direction before placing your trades. For example, if an overbought/oversold indicator is showing an overbought condition, it is wise to wait for the security’s price to turn down before selling the security.
An interesting phenomenon of the Williams' Percent Range indicator is its uncanny ability to anticipate a reversal in the underlying security’s price. The indicator almost always forms a peak and turns down a few days before the security’s price peaks and turns down. Likewise, Williams Percent Range usually creates a trough and turns up a few days before the security’s price turns up.
Parameters
The indicator has one configurable parameter - the calculation period. The default value is 14.
In order to determine the indicator signals, it is necessary to take into account the location of the line in the overbought/oversold area. To achieve this, the values of the levels should be added to the EA inputs.
Input and global variables for using the indicator in the EA:
//+------------------------------------------------------------------+ //| TestOscillatorWPR.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod = 14; /* Period */ input double InpOverbough= -20.0; /* Overbough level*/ input double InpOversold = -80.0; /* Oversold level */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // TRIX calculation period int ind_digits=0; // Number of decimal places in the indicator values double overbough=0; // Overbought level double oversold=0; // Oversold level string ind_title; // Indicator description
When using the dashboard, the panel class file is included and the variables for working with it are added:
//+------------------------------------------------------------------+ //| TestOscillatorWPR.mq5 | //| Copyright 2023, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- includes #include <Dashboard\Dashboard.mqh> //--- enums enum ENUM_LINE_STATE { LINE_STATE_NONE, // Undefined state LINE_STATE_UP, // Upward LINE_STATE_DOWN, // Downward LINE_STATE_TURN_UP, // Upward reversal LINE_STATE_TURN_DOWN, // Downward reversal LINE_STATE_STOP_UP, // Upward stop LINE_STATE_STOP_DOWN, // Downward stop LINE_STATE_ABOVE, // Above value LINE_STATE_UNDER, // Below value LINE_STATE_CROSS_UP, // Crossing value upwards LINE_STATE_CROSS_DOWN, // Crossing value downwards LINE_STATE_TOUCH_BELOW, // Touching value from below LINE_STATE_TOUCH_ABOVE, // Touch value from above LINE_STATE_EQUALS, // Equal to value }; //--- input parameters input uint InpPeriod = 14; /* Period */ input double InpOverbough= -20.0; /* Overbough level*/ input double InpOversold = -80.0; /* Oversold level */ //--- global variables int handle=INVALID_HANDLE; // Indicator handle int period=0; // TRIX calculation period int ind_digits=0; // Number of decimal places in the indicator values double overbough=0; // Overbought level double oversold=0; // Oversold level string ind_title; // Indicator description //--- variables for the panel int mouse_bar_index; // Index of the bar the data is taken from CDashboard *panel=NULL; // Pointer to the panel object
Initialization
The OnInit() handler for initializing indicator parameters and creating its handle:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period and levels if necessary period=int(InpPeriod<1 ? 14 : InpPeriod); overbough=InpOverbough; oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("%%R(%lu)",period); ind_digits=2; //--- Create indicator handle ResetLastError(); handle=iWPR(Symbol(),PERIOD_CURRENT,period); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Successful initialization return(INIT_SUCCEEDED); }
The panel is created when using the dashboard:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- create timer EventSetTimer(60); //--- Indicator //--- Set and adjust the calculation period and levels if necessary period=int(InpPeriod<1 ? 14 : InpPeriod); overbough=InpOverbough; oversold=(InpOversold>=overbough ? overbough-0.01 : InpOversold); //--- Set the indicator name and the number of decimal places ind_title=StringFormat("%%R(%lu)",period); ind_digits=2; //--- Create indicator handle ResetLastError(); handle=iWPR(Symbol(),PERIOD_CURRENT,period); if(handle==INVALID_HANDLE) { PrintFormat("%s: Failed to create indicator handle %s. Error %ld",__FUNCTION__,ind_title,GetLastError()); return INIT_FAILED; } //--- Dashboard //--- Create the panel panel=new CDashboard(1,20,20,229,243); if(panel==NULL) { Print("Error. Failed to create panel object"); return INIT_FAILED; } //--- Set font parameters panel.SetFontParams("Calibri",9); //--- Display the panel with the "Symbol, Timeframe description" header text panel.View(Symbol()+", "+StringSubstr(EnumToString(Period()),7)); //--- Create a table with ID 0 to display bar data in it panel.CreateNewTable(0); //--- Draw a table with ID 0 on the panel background panel.DrawGrid(0,2,20,6,2,18,112); //--- Create a table with ID 1 to display indicator data in it panel.CreateNewTable(1); //--- Get the Y2 table coordinate with ID 0 and //--- set the Y1 coordinate for the table with ID 1 int y1=panel.TableY2(0)+22; //--- Draw a table with ID 1 on the panel background panel.DrawGrid(1,2,y1,4,2,18,112); //--- Display tabular data in the journal panel.GridPrint(0,2); panel.GridPrint(1,2); //--- Initialize the variable with the index of the mouse cursor bar mouse_bar_index=0; //--- Display the data of the current bar on the panel DrawData(mouse_bar_index,TimeCurrent()); //--- Successful initialization return(INIT_SUCCEEDED); }
Deinitialization
Release the indicator handle in the EA OnDeinit() handler:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); }
The created dashboard object is removed when using the dashboard:
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- destroy timer EventKillTimer(); //--- Release handle of the indicator ResetLastError(); if(!IndicatorRelease(handle)) PrintFormat("%s: IndicatorRelease failed. Error %ld",__FUNCTION__,GetLastError()); //--- Clear all comments on the chart Comment(""); //--- If the panel object exists, delete it if(panel!=NULL) delete panel; }
Data retrieval
General functions for obtaining data by the indicator handle:
//+------------------------------------------------------------------+ //| Return the indicator data on the specified bar | //+------------------------------------------------------------------+ double IndicatorValue(const int ind_handle,const int index,const int buffer_num) { double array[1]={0}; ResetLastError(); if(CopyBuffer(ind_handle,buffer_num,index,1,array)!=1) { PrintFormat("%s: CopyBuffer failed. Error %ld",__FUNCTION__,GetLastError()); return EMPTY_VALUE; } return array[0]; } //+------------------------------------------------------------------+ //| Return the state of the indicator line | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineState(const int ind_handle,const int index,const int buffer_num) { //--- Get the values of the indicator line with the shift (0,1,2) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); const double value2=IndicatorValue(ind_handle,index+2,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE || value2==EMPTY_VALUE) return LINE_STATE_NONE; //--- Line upward reversal (value2>value1 && value0>value1) if(NormalizeDouble(value2-value1,ind_digits)>0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_TURN_UP; //--- Line upward direction (value2<=value1 && value0>value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)>0) return LINE_STATE_UP; //--- Line upward stop (value2<=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)<=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_UP; //--- Line downward reversal (value2<value1 && value0<value1) if(NormalizeDouble(value2-value1,ind_digits)<0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_TURN_DOWN; //--- Line downward direction (value2>=value1 && value0<value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)<0) return LINE_STATE_DOWN; //--- Line downward stop (value2>=value1 && value0==value1) else if(NormalizeDouble(value2-value1,ind_digits)>=0 && NormalizeDouble(value0-value1,ind_digits)==0) return LINE_STATE_STOP_DOWN; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the state of the line relative to the specified level | //+------------------------------------------------------------------+ ENUM_LINE_STATE LineStateRelative(const int ind_handle,const int index,const int buffer_num,const double level0,const double level1=EMPTY_VALUE) { //--- Get the values of the indicator line with the shift (0,1) relative to the passed index const double value0=IndicatorValue(ind_handle,index, buffer_num); const double value1=IndicatorValue(ind_handle,index+1,buffer_num); //--- If at least one of the values could not be obtained, return an undefined value if(value0==EMPTY_VALUE || value1==EMPTY_VALUE) return LINE_STATE_NONE; //--- Define the second level to compare double level=(level1==EMPTY_VALUE ? level0 : level1); //--- The line is below the level (value1<level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_UNDER; //--- The line is above the level (value1>level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_ABOVE; //--- The line crossed the level upwards (value1<=level && value0>level0) if(NormalizeDouble(value1-level,ind_digits)<=0 && NormalizeDouble(value0-level0,ind_digits)>0) return LINE_STATE_CROSS_UP; //--- The line crossed the level downwards (value1>=level && value0<level0) if(NormalizeDouble(value1-level,ind_digits)>=0 && NormalizeDouble(value0-level0,ind_digits)<0) return LINE_STATE_CROSS_DOWN; //--- The line touched the level from below (value1<level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)<0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- The line touched the level from above (value1>level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)>0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_TOUCH_BELOW; //--- Line is equal to the level value (value1==level0 && value0==level0) if(NormalizeDouble(value1-level,ind_digits)==0 && NormalizeDouble(value0-level0,ind_digits)==0) return LINE_STATE_EQUALS; //--- Undefined state return LINE_STATE_NONE; } //+------------------------------------------------------------------+ //| Return the indicator line state description | //+------------------------------------------------------------------+ string LineStateDescription(const ENUM_LINE_STATE state) { switch(state) { case LINE_STATE_UP : return "Up"; case LINE_STATE_STOP_UP : return "Stop Up"; case LINE_STATE_TURN_UP : return "Turn Up"; case LINE_STATE_DOWN : return "Down"; case LINE_STATE_STOP_DOWN : return "Stop Down"; case LINE_STATE_TURN_DOWN : return "Turn Down"; case LINE_STATE_ABOVE : return "Above level"; case LINE_STATE_UNDER : return "Under level"; case LINE_STATE_CROSS_UP : return "Crossing Up"; case LINE_STATE_CROSS_DOWN : return "Crossing Down"; case LINE_STATE_TOUCH_BELOW: return "Touch from Below"; case LINE_STATE_TOUCH_ABOVE: return "Touch from Above"; case LINE_STATE_EQUALS : return "Equals"; default : return "Unknown"; } }
When using the dashboard, data is displayed on the panel using the function:
//+------------------------------------------------------------------+ //| Display data from the specified timeseries index to the panel | //+------------------------------------------------------------------+ void DrawData(const int index,const datetime time) { //--- Declare the variables to receive data in them MqlTick tick={0}; MqlRates rates[1]; //--- Exit if unable to get the current prices if(!SymbolInfoTick(Symbol(),tick)) return; //--- Exit if unable to get the bar data by the specified index if(CopyRates(Symbol(),PERIOD_CURRENT,index,1,rates)!=1) return; //--- Set font parameters for bar and indicator data headers int size=0; uint flags=0; uint angle=0; string name=panel.FontParams(size,flags,angle); panel.SetFontParams(name,9,FW_BOLD); panel.DrawText("Bar data ["+(string)index+"]",3,panel.TableY1(0)-16,clrMaroon,panel.Width()-6); panel.DrawText("Indicator data ["+(string)index+"]",3,panel.TableY1(1)-16,clrGreen,panel.Width()-6); //--- Set font parameters for bar and indicator data panel.SetFontParams(name,9); //--- Display the data of the specified bar in table 0 on the panel panel.DrawText("Date", panel.CellX(0,0,0)+2, panel.CellY(0,0,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_DATE), panel.CellX(0,0,1)+2, panel.CellY(0,0,1)+2,clrNONE,90); panel.DrawText("Time", panel.CellX(0,1,0)+2, panel.CellY(0,1,0)+2); panel.DrawText(TimeToString( rates[0].time,TIME_MINUTES), panel.CellX(0,1,1)+2, panel.CellY(0,1,1)+2,clrNONE,90); panel.DrawText("Open", panel.CellX(0,2,0)+2, panel.CellY(0,2,0)+2); panel.DrawText(DoubleToString(rates[0].open,Digits()), panel.CellX(0,2,1)+2, panel.CellY(0,2,1)+2,clrNONE,90); panel.DrawText("High", panel.CellX(0,3,0)+2, panel.CellY(0,3,0)+2); panel.DrawText(DoubleToString(rates[0].high,Digits()), panel.CellX(0,3,1)+2, panel.CellY(0,3,1)+2,clrNONE,90); panel.DrawText("Low", panel.CellX(0,4,0)+2, panel.CellY(0,4,0)+2); panel.DrawText(DoubleToString(rates[0].low,Digits()), panel.CellX(0,4,1)+2, panel.CellY(0,4,1)+2,clrNONE,90); panel.DrawText("Close", panel.CellX(0,5,0)+2, panel.CellY(0,5,0)+2); panel.DrawText(DoubleToString(rates[0].close,Digits()), panel.CellX(0,5,1)+2, panel.CellY(0,5,1)+2,clrNONE,90); //--- Display the indicator data from the specified bar on the panel in table 1 panel.DrawText(ind_title, panel.CellX(1,0,0)+2, panel.CellY(1,0,0)+2); double value=IndicatorValue(handle,index,0); string value_str=(value!=EMPTY_VALUE ? DoubleToString(value,ind_digits) : ""); panel.DrawText(value_str,panel.CellX(1,0,1)+2,panel.CellY(1,0,1)+2,clrNONE,90); //--- Get the state of the line and the states relative to the levels ENUM_LINE_STATE state=LineState(handle,index,0); ENUM_LINE_STATE state_ovb=LineStateRelative(handle,index,0,overbough); ENUM_LINE_STATE state_ovs=LineStateRelative(handle,index,0,oversold); //--- Display a description of the indicator line state relative to the overbought level string ovb=StringFormat("%+.2f",overbough); panel.DrawText("Overbough", panel.CellX(1,2,0)+2, panel.CellY(1,2,0)+2); panel.DrawText(ovb, panel.CellX(1,2,0)+66, panel.CellY(1,2,0)+2); color clr=clrNONE; //--- The label color changes depending on the value of the line relative to the level clr= ( state_ovb==LINE_STATE_CROSS_DOWN || (state_ovb==LINE_STATE_ABOVE && state==LINE_STATE_TURN_DOWN) ? clrRed : clrNONE ); string ovb_str=(state_ovb==LINE_STATE_ABOVE ? "Inside the area" : LineStateDescription(state_ovb)); panel.DrawText(ovb_str,panel.CellX(1,2,1)+2,panel.CellY(1,2,1)+2,clr,90); //--- Display a description of the indicator line state relative to the oversold level panel.DrawText("Oversold", panel.CellX(1,3,0)+2, panel.CellY(1,3,0)+2); string ovs=StringFormat("%+.2f",oversold); panel.DrawText(ovs, panel.CellX(1,3,0)+68, panel.CellY(1,3,0)+2); //--- The label color changes depending on the value of the line relative to the level clr= ( state_ovs==LINE_STATE_CROSS_UP || (state_ovs==LINE_STATE_UNDER && state==LINE_STATE_TURN_UP) ? clrBlue : clrNONE ); string ovs_str=(state_ovs==LINE_STATE_UNDER ? "Inside the area" : LineStateDescription(state_ovs)); panel.DrawText(ovs_str,panel.CellX(1,3,1)+2,panel.CellY(1,3,1)+2,clr,90); //--- Display a description of the indicator line state panel.DrawText("Line state", panel.CellX(1,1,0)+2, panel.CellY(1,1,0)+2); //--- The label color changes depending on the location of the line in the overbought/oversold areas clr=(state_ovb==LINE_STATE_ABOVE ? clrOrangeRed : state_ovs==LINE_STATE_UNDER ? clrDodgerBlue : clrNONE); panel.DrawText(LineStateDescription(state),panel.CellX(1,1,1)+2,panel.CellY(1,1,1)+2,clr,90); //--- Redraw the chart to immediately display all changes on the panel ChartRedraw(ChartID()); }
The location of the indicator line in overbought and oversold areas, indicator signals and the line state are marked with color on the dashboard.
Besides, the panel events handler is called in the OnChartEvent() EA event handler, as well as the events for receiving the bar index under the cursor are handled:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handling the panel //--- Call the panel event handler panel.OnChartEvent(id,lparam,dparam,sparam); //--- If the cursor moves or a click is made on the chart if(id==CHARTEVENT_MOUSE_MOVE || id==CHARTEVENT_CLICK) { //--- Declare the variables to record time and price coordinates in them datetime time=0; double price=0; int wnd=0; //--- If the cursor coordinates are converted to date and time if(ChartXYToTimePrice(ChartID(),(int)lparam,(int)dparam,wnd,time,price)) { //--- write the bar index where the cursor is located to a global variable mouse_bar_index=iBarShift(Symbol(),PERIOD_CURRENT,time); //--- Display the bar data under the cursor on the panel DrawData(mouse_bar_index,time); } } //--- If we received a custom event, display the appropriate message in the journal if(id>CHARTEVENT_CUSTOM) { //--- Here we can implement handling a click on the close button on the panel PrintFormat("%s: Event id=%ld, object id (lparam): %lu, event message (sparam): %s",__FUNCTION__,id,lparam,sparam); } }
After compiling the EA and launching it on the chart, we can monitor the state of the indicator line on the panel:
The "TestOscillatorWPR.mq5" EA file is attached below.
Conclusion
The article considered connecting oscillators to EAs. Using the dashboard, we can monitor the values and signals of indicators obtained by common universal functions. All codes proposed here are easily transferred to EA codes. They can be used "as is" using Copy-Paste, or downloaded from the files attached below for further fine-tuning.
Next, we will consider the remaining indicators from the standard terminal delivery in the same vein.
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/13244
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use