Object-Oriented Approach to Building Multi-Timeframe and Multi-Currency Panels
Introduction
This article describes how object-oriented programming can be used for creating multi-timeframe and multi-currency panels for MetaTrader 5. The main goal is to build a universal panel, which can be used for displaying many different kinds of data, such as prices, price changes, indicator values or custom buy/sell conditions without the need to modify the code of the panel itself. In this way very little coding will be necessary to customize the panel in any way we need.
The solution I will describe works in two modes:
- Multi-timeframe mode - allows to see the table contents calculated on current symbol, but on different timeframes;
- Multi-currency mode - allows to see the table contents calculated on current timeframe, but on different symbols.
The following pictures show the panel in these two modes.
First one works in multi-timeframe mode and displays the following data:
- Current price;
- Current bar's price change;
- Current bar's price change as a percent;
- Current bar's price change as an arrow (up/down);
- RSI(14) indicator value;
- RSI(10) indicator value;
- Custom condition: SMA(20) > current price.
Figure 1. Mult-timeframe mode
Second one works in multi-currency mode and shows:
- Current price;
- Current bar's price change;
- Current bar's price change as a percent;
- Current bar's price change as an arrow;
- RSI(10) indicator value;
- RSI(14) indicator value;
- Custom condition: SMA(20) > current price.
Figure 2. Mult-currency mode
1. Implementation
The following class diagram describes the implementation design of the panel.
Figure 3. Class diagram of the panel
Let me describe the elements of the diagram:
- CTable. Core class of the panel. It is responsible for drawing the panel and managing its components.
- SpyAgent. It is an indicator responsible for 'spying' on other symbols (instruments). Every agent is created and sent to a different symbol. The agent reacts to OnCalculate event when a new tick arrives on the symbol chart and sends CHARTEVENT_CUSTOM event to inform the CTable object that it has to update. The whole idea behind this approach is based on the article "The Implementation of a Multi-currency Mode in MetaTrader 5". You can find all the technical details there.
- CRow. Base class for all indicators and conditions used to create the panel. By extending this class it is possible to create all the necessary components of the panel.
- CPriceRow. Simple class extending CRow, which is used to display the current bid price.
- CPriceChangeRow. Class extending CRow, used for displaying current bar's price change. It can show price changes, percent changes or arrows.
- CRSIRow. Class extending CRow, used for displaying current RSI indicator value.
- CPriceMARow. Class extending CRow, showing a custom condition: SMA > current price.
CTable and CRow classes as well as SpyAgent indicator are the core parts of the panel. CPriceRow, CPriceChangeRow, CRSIRow and CPriceMARow are actual contents of the panel. CRow class is designed to be extended by many new classes in order to get the desired result. The four presented derived classes are just simple examples of what can be done and how.
2. SpyAgent
We will begin with the SpyAgent indicator. It is used only in multi-currency mode and is necessary to properly update the panel, when a new tick arrives on other charts. I won't go much into details of this concept. They are described in the article "The Implementation of a Multi-currency Mode in MetaTrader 5".
The SpyAgent indicator runs on the chart of the specified symbol and sends two events: initialization event and new tick event. Both events are of CHARTEVENT_CUSTOM type. In order to handle these events we have to use the OnChartEvent(...) handler (it will be shown later in the article).
Let's have a look at SpyAgent's code:
//+------------------------------------------------------------------+ //| SpyAgent.mq5 | //| Marcin Konieczny | //| | //+------------------------------------------------------------------+ #property copyright "Marcin Konieczny" #property indicator_chart_window #property indicator_plots 0 input long chart_id=0; // chart id input ushort custom_event_id=0; // event id //+------------------------------------------------------------------+ //| Indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { if(prev_calculated==0) EventChartCustom(chart_id,0,0,0.0,_Symbol); // sends initialization event else EventChartCustom(chart_id,(ushort)(custom_event_id+1),0,0.0,_Symbol); // sends new tick event return(rates_total); }It is quite simple. The only thing it does is receiving new ticks and sending CHARTEVENT_CUSTOM events.
3. CTable
CTable is the core class of the panel. It stores information about the panel settings and manages its components. It updates (redraws) the panel, when necessary.
Let's look at the declaration of CTable:
//+------------------------------------------------------------------+ //| CTable class | //+------------------------------------------------------------------+ class CTable { private: int xDistance; // distance from right border of the chart int yDistance; // distance from top of the chart int cellHeight; // table cell height int cellWidth; // table cell width string font; // font name int fontSize; color fontColor; CList *rowList; // list of row objects bool tfMode; // is in multi-timeframe mode? ENUM_TIMEFRAMES timeframes[]; // array of timeframes for multi-timeframe mode string symbols[]; // array of currency pairs for multi-currency mode //--- private methods //--- sets default parameters of the table void Init(); //--- draws text label in the specified table cell void DrawLabel(int x,int y,string text,string font,color col); //--- returns textual representation of given timeframe string PeriodToString(ENUM_TIMEFRAMES period); public: //--- multi-timeframe mode constructor CTable(ENUM_TIMEFRAMES &tfs[]); //--- multi-currency mode constructor CTable(string &symb[]); //--- destructor ~CTable(); //--- redraws table void Update(); //--- methods for setting table parameters void SetDistance(int xDist,int yDist); void SetCellSize(int cellW,int cellH); void SetFont(string fnt,int size,color clr); //--- appends CRow object to the of the table void AddRow(CRow *row); };
As you can see, all panel components (rows) are stored as a list of CRow pointers, so every component that we want to add to the panel has to extend CRow class. CRow can be seen as a contract between the panel and its components. CTable does not contain any code for calculation of its cells. It is a responsibility of classes extending CRow. CTable is only a structure for holding CRow components and redrawing them when necessary.
Let's go through the methods of CTable. The class has two constructors. The first is used for multi-timeframe mode and is quite simple. We only have to supply an array of timeframes that we want to display.
//+------------------------------------------------------------------+ //| Multi-timeframe mode constructor | //+------------------------------------------------------------------+ CTable::CTable(ENUM_TIMEFRAMES &tfs[]) { //--- copy all timeframes to own array ArrayResize(timeframes,ArraySize(tfs),0); ArrayCopy(timeframes,tfs); tfMode=true; //--- fill symbols array with current chart symbol ArrayResize(symbols,ArraySize(tfs),0); for(int i=0; i<ArraySize(tfs); i++) symbols[i]=Symbol(); //--- set default parameters Init(); }
The second constructor is used for multi-currency mode and takes an array of symbols (instruments). This one also sends SpyAgents. It attaches them one by one to appropriate charts.
//+------------------------------------------------------------------+ //| Multi-currency mode constructor | //+------------------------------------------------------------------+ CTable::CTable(string &symb[]) { //--- copy all symbols to own array ArrayResize(symbols,ArraySize(symb),0); ArrayCopy(symbols,symb); tfMode=false; //--- fill timeframe array with current timeframe ArrayResize(timeframes,ArraySize(symb),0); ArrayInitialize(timeframes,Period()); //--- set default parameters Init(); //--- send SpyAgents to every requested symbol for(int x=0; x<ArraySize(symbols); x++) if(symbols[x]!=Symbol()) // don't send SpyAgent to own chart if(iCustom(symbols[x],0,"SpyAgent",ChartID(),0)==INVALID_HANDLE) { Print("Error in setting of SpyAgent on "+symbols[x]); return; } }
The Init method creates the list of rows (as a CList object - CList is a dynamic list of CObject types) and sets the default values for CTable internal variables (font, font size, color, cell dimension and distance from the upper right chart corner).
//+------------------------------------------------------------------+ //| Sets default parameters of the table | //+------------------------------------------------------------------+ CTable::Init() { //--- create list for storing row objects rowList=new CList; //--- set defaults xDistance = 10; yDistance = 10; cellWidth = 60; cellHeight= 20; font="Arial"; fontSize=10; fontColor=clrWhite; }
The destructor is fairly simple. It deletes the list of rows and deletes all chart objects (labels) created by the panel.
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CTable::~CTable() { int total=ObjectsTotal(0); //--- remove all text labels from the chart (all object names starting with nameBase prefix) for(int i=total-1; i>=0; i--) if(StringFind(ObjectName(0,i),nameBase)!=-1) ObjectDelete(0,ObjectName(0,i)); //--- delete list of rows and free memory delete(rowList); }
The AddRow method appends the new row to the list of rows. Note that rowList is a CList object, which resizes automatically. This method also calls the Init method for every added CRow object. It is necessary to allow the object to initialize its internal variables properly. For example, it may use the Init call to create indicator or file handles.
//+------------------------------------------------------------------+ //| Appends new row to the end of the table | //+------------------------------------------------------------------+ CTable::AddRow(CRow *row) { rowList.Add(row); row.Init(symbols,timeframes); }
The Update method is a bit more complicated. It is used for redrawing the panel.
Basically, it consists of three parts, which are:
- Drawing the first column (the names of rows)
- Drawing the first row (the names of timeframes or symbols depending on the selected mode)
- Drawing the internal cells (the components' values)
It is worth noting that we ask every component to calculate its own value basing on the supplied symbol and timeframe. We also let the component decide, what font and color should be used.
//+------------------------------------------------------------------+ //| Redraws the table | //+------------------------------------------------------------------+ CTable::Update() { CRow *row; string symbol; ENUM_TIMEFRAMES tf; int rows=rowList.Total(); // number of rows int columns; // number of columns if(tfMode) columns=ArraySize(timeframes); else columns=ArraySize(symbols); //--- draw first column (names of rows) for(int y=0; y<rows; y++) { row=(CRow*)rowList.GetNodeAtIndex(y); //--- note: we ask row object to return its name DrawLabel(columns,y+1,row.GetName(),font,fontColor); } //--- draws first row (names of timeframes or currency pairs) for(int x=0; x<columns; x++) { if(tfMode) DrawLabel(columns-x-1,0,PeriodToString(timeframes[x]),font,fontColor); else DrawLabel(columns-x-1,0,symbols[x],font,fontColor); } //--- draws inside table cells for(int y=0; y<rows; y++) for(int x=0; x<columns; x++) { row=(CRow*)rowList.GetNodeAtIndex(y); if(tfMode) { //--- in multi-timeframe mode use current symbol and different timeframes tf=timeframes[x]; symbol=_Symbol; } else { //--- in multi-currency mode use current timeframe and different symbols tf=Period(); symbol=symbols[x]; } //--- note: we ask row object to return its font, //--- color and current calculated value for given timeframe and symbol DrawLabel(columns-x-1,y+1,row.GetValue(symbol,tf),row.GetFont(symbol,tf),row.GetColor(symbol,tf)); } //--- forces chart to redraw ChartRedraw(); }
The DrawLabel method is used for drawing text labels in the specified cell of the panel. Firstly, it checks if a label for this cell already exists. If not, it creates a new one.
Then, it sets all necessary label properties and its text.
//+------------------------------------------------------------------+ //| Draws text label in the specified cell of the table | //+------------------------------------------------------------------+ CTable::DrawLabel(int x,int y,string text,string font,color col) { //--- create unique name for this cell string name=nameBase+IntegerToString(x)+":"+IntegerToString(y); //--- create label if(ObjectFind(0,name)<0) ObjectCreate(0,name,OBJ_LABEL,0,0,0); //--- set label properties ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_RIGHT_UPPER); ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_RIGHT_UPPER); ObjectSetInteger(0,name,OBJPROP_XDISTANCE,xDistance+x*cellWidth); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,yDistance+y*cellHeight); ObjectSetString(0,name,OBJPROP_FONT,font); ObjectSetInteger(0,name,OBJPROP_COLOR,col); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,fontSize); //--- set label text ObjectSetString(0,name,OBJPROP_TEXT,text); }
Other methods won't be presented here, because they are very simple and less important. The full code can be downloaded at the bottom of the article.
4. Extending CRow
CRow is a base class for all components, which can be used by the panel.
Let's look at the code of the CRow class:
//+------------------------------------------------------------------+ //| CRow class | //+------------------------------------------------------------------+ //| Base class for creating custom table rows | //| one or more methods of CRow should be overriden | //| when creating own table rows | //+------------------------------------------------------------------+ class CRow : public CObject { public: //--- default initialization method virtual void Init(string &symb[],ENUM_TIMEFRAMES &tfs[]) { } //--- default method for obtaining string value to display in the table cell virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf) { return("-"); } //--- default method for obtaining color for table cell virtual color GetColor(string symbol,ENUM_TIMEFRAMES tf) { return(clrWhite); } //--- default method for obtaining row name virtual string GetName() { return("-"); } //--- default method for obtaining font for table cell virtual string GetFont(string symbol,ENUM_TIMEFRAMES tf) { return("Arial"); } };
It extends CObject, because only CObjects can be stored in a CList structure. It has five methods, which are almost empty. To be more precise, most of them only return default values. These methods are designed to be overriden when extending CRow. We don't need to override all of them, only the ones we want.
As an example let's create the simpliest possible panel component - current bid price component. It can be used in multi-currency mode to display current prices of various instruments.
To achieve this we create a CPriceRow class, which looks like this:
//+------------------------------------------------------------------+ //| CPriceRow class | //+------------------------------------------------------------------+ class CPriceRow : public CRow { public: //--- overrides default GetValue(..) method from CRow virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf); //--- overrides default GetName() method from CRow virtual string GetName(); }; //+------------------------------------------------------------------+ //| Overrides default GetValue(..) method from CRow | //+------------------------------------------------------------------+ string CPriceRow::GetValue(string symbol,ENUM_TIMEFRAMES tf) { MqlTick tick; //--- gets current price if(!SymbolInfoTick(symbol,tick)) return("-"); return(DoubleToString(tick.bid,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS))); } //+------------------------------------------------------------------+ //| Overrides default GetName() method from CRow | //+------------------------------------------------------------------+ string CPriceRow::GetName() { return("Price"); }
The methods we chose to override here are GetValue and GetName. GetName simply returns the name for this row, which will be displayed in the first column of the panel. GetValue gets the latest tick on the specified symbol and returns the latest bid price. That is all we need.
This was quite simple. Let's do something different. We will now build a component, which shows the current RSI value.
The code is similiar to the previous one:
//+------------------------------------------------------------------+ //| CRSIRow class | //+------------------------------------------------------------------+ class CRSIRow : public CRow { private: int rsiPeriod; // RSI period string symbols[]; // symbols array ENUM_TIMEFRAMES timeframes[]; // timeframes array int handles[]; // array of RSI handles //--- finds the indicator handle for a given symbol and timeframe int GetHandle(string symbol,ENUM_TIMEFRAMES tf); public: //--- constructor CRSIRow(int period); //--- overrides default GetValue(..) method from CRow virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf); //--- overrides default GetName() method from CRow virtual string GetName(); //--- overrides default Init(..) method from CRow virtual void Init(string &symb[],ENUM_TIMEFRAMES &tfs[]); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CRSIRow::CRSIRow(int period) { rsiPeriod=period; } //+------------------------------------------------------------------+ //| Overrides default Init(..) method from CRow | //+------------------------------------------------------------------+ void CRSIRow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[]) { int size=ArraySize(symb); ArrayResize(symbols,size); ArrayResize(timeframes,size); ArrayResize(handles,size); //--- copies arrays contents into own arrays ArrayCopy(symbols,symb); ArrayCopy(timeframes,tfs); //--- gets RSI handles for all used symbols or timeframes for(int i=0; i<ArraySize(symbols); i++) handles[i]=iRSI(symbols[i],timeframes[i],rsiPeriod,PRICE_CLOSE); } //+------------------------------------------------------------------+ //| Overrides default GetValue(..) method from CRow | //+------------------------------------------------------------------+ string CRSIRow::GetValue(string symbol,ENUM_TIMEFRAMES tf) { double value[1]; //--- gets RSI indicator handle int handle=GetHandle(symbol,tf); if(handle==INVALID_HANDLE) return("err"); //--- gets current RSI value if(CopyBuffer(handle,0,0,1,value)<0) return("-"); return(DoubleToString(value[0],2)); } //+------------------------------------------------------------------+ //| Overrides default GetName() method from CRow | //+------------------------------------------------------------------+ string CRSIRow::GetName() { return("RSI("+IntegerToString(rsiPeriod)+")"); } //+------------------------------------------------------------------+ //| finds the indicator handle for a given symbol and timeframe | //+------------------------------------------------------------------+ int CRSIRow::GetHandle(string symbol,ENUM_TIMEFRAMES tf) { for(int i=0; i<ArraySize(timeframes); i++) if(symbols[i]==symbol && timeframes[i]==tf) return(handles[i]); return(INVALID_HANDLE); }
We have a few new methods here. The constructor allows to supply the RSI period and stores it as a member variable. The Init method is used for creating RSI indicator handles. These handles are stored in the handles[] array. The GetValue method copies the last value from the RSI buffer and returns it. Private GetHandle method is used for finding proper indicator handle in the handles[] array. GetName is self-explanatory.
As we can see, building panel components is quite easy. In the same way we can create components for almost any custom condition. It does not have to be the indicator value. Below I present a custom condition based on SMA. It checks if current price is above the moving average and displays 'Yes' or 'No'.
//+------------------------------------------------------------------+ //| CPriceMARow class | //+------------------------------------------------------------------+ class CPriceMARow : public CRow { private: int maPeriod; // period of moving average int maShift; // shift of moving average ENUM_MA_METHOD maType; // SMA, EMA, SMMA or LWMA string symbols[]; // symbols array ENUM_TIMEFRAMES timeframes[]; // timeframes array int handles[]; // array of MA handles //--- finds the indicator handle for a given symbol and timeframe int GetHandle(string symbol,ENUM_TIMEFRAMES tf); public: //--- constructor CPriceMARow(ENUM_MA_METHOD type,int period,int shift); //--- overrides default GetValue(..) method of CRow virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf); // overrides default GetName() method CRow virtual string GetName(); //--- overrides default Init(..) method from CRow virtual void Init(string &symb[],ENUM_TIMEFRAMES &tfs[]); }; //+------------------------------------------------------------------+ //| CPriceMARow class constructor | //+------------------------------------------------------------------+ CPriceMARow::CPriceMARow(ENUM_MA_METHOD type,int period,int shift) { maPeriod= period; maShift = shift; maType=type; } //+------------------------------------------------------------------+ //| Overrides default Init(..) method from CRow | //+------------------------------------------------------------------+ void CPriceMARow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[]) { int size=ArraySize(symb); ArrayResize(symbols,size); ArrayResize(timeframes,size); ArrayResize(handles,size); //--- copies arrays contents into own arrays ArrayCopy(symbols,symb); ArrayCopy(timeframes,tfs); //--- gets MA handles for all used symbols or timeframes for(int i=0; i<ArraySize(symbols); i++) handles[i]=iMA(symbols[i],timeframes[i],maPeriod,maShift,maType,PRICE_CLOSE); } //+------------------------------------------------------------------+ //| Overrides default GetValue(..) method of CRow | //+------------------------------------------------------------------+ string CPriceMARow::GetValue(string symbol,ENUM_TIMEFRAMES tf) { double value[1]; MqlTick tick; //--- obtains MA indicator handle int handle=GetHandle(symbol,tf); if(handle==INVALID_HANDLE) return("err"); //--- gets the last MA value if(CopyBuffer(handle,0,0,1,value)<0) return("-"); //--- gets the last price if(!SymbolInfoTick(symbol,tick)) return("-"); //--- checking the condition: price > MA if(tick.bid>value[0]) return("Yes"); else return("No"); } //+------------------------------------------------------------------+ //| Overrides default GetName() method of CRow | //+------------------------------------------------------------------+ string CPriceMARow::GetName() { string name; switch(maType) { case MODE_SMA: name = "SMA"; break; case MODE_EMA: name = "EMA"; break; case MODE_SMMA: name = "SMMA"; break; case MODE_LWMA: name = "LWMA"; break; } return("Price>"+name+"("+IntegerToString(maPeriod)+")"); } //+------------------------------------------------------------------+ //| finds the indicator handle for a given symbol and timeframe | //+------------------------------------------------------------------+ int CPriceMARow::GetHandle(string symbol,ENUM_TIMEFRAMES tf) { for(int i=0; i<ArraySize(timeframes); i++) if(symbols[i]==symbol && timeframes[i]==tf) return(handles[i]); return(INVALID_HANDLE); }
The code is longer, because Moving Average has three parameters: period, shift and type. GetName is a little more complicated as it builds the name based on MA type and period. GetValue works almost in the same way as in case of CRSIRow, but instead of returning the indicator value it returns 'Yes' if price is above the SMA or 'No' if it is below.
The last example is a bit more complex. It is the CPriceChangeRow class, which shows the current bar's price change. It works in three modes:
- Displaying arrows (green up or red down);
- Displaying price change as a value (green or red);
- Displaying price change as a percent (green or red).
The code looks as follows:
//+------------------------------------------------------------------+ //| CPriceChangeRow class | //+------------------------------------------------------------------+ class CPriceChangeRow : public CRow { private: bool percentChange; bool useArrows; public: //--- constructor CPriceChangeRow(bool arrows,bool percent=false); //--- overrides default GetName() method from CRow virtual string GetName(); //--- overrides default GetFont() method from CRow virtual string GetFont(string symbol,ENUM_TIMEFRAMES tf); //--- overrides default GetValue(..) method from CRow virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf); //--- overrides default GetColor(..) method from CRow virtual color GetColor(string symbol,ENUM_TIMEFRAMES tf); }; //+------------------------------------------------------------------+ //| CPriceChangeRow class constructor | //+------------------------------------------------------------------+ CPriceChangeRow::CPriceChangeRow(bool arrows,bool percent=false) { percentChange=percent; useArrows=arrows; } //+------------------------------------------------------------------+ //| Overrides default GetName() method from CRow | //+------------------------------------------------------------------+ string CPriceChangeRow::GetName() { return("PriceChg"); } //+------------------------------------------------------------------+ //| Overrides default GetFont() method from CRow | //+------------------------------------------------------------------+ string CPriceChangeRow::GetFont(string symbol,ENUM_TIMEFRAMES tf) { //--- we use Wingdings font to draw arrows (up/down) if(useArrows) return("Wingdings"); else return("Arial"); } //+------------------------------------------------------------------+ //| Overrides default GetValue(..) method from CRow | //+------------------------------------------------------------------+ string CPriceChangeRow::GetValue(string symbol,ENUM_TIMEFRAMES tf) { double close[1]; double open[1]; //--- gets open and close of current bar if(CopyClose(symbol,tf,0, 1, close) < 0) return(" "); if(CopyOpen(symbol, tf, 0, 1, open) < 0) return(" "); //--- current bar price change double change=close[0]-open[0]; if(useArrows) { if(change > 0) return(CharToString(233)); // returns up arrow code if(change < 0) return(CharToString(234)); // returns down arrow code return(" "); }else{ if(percentChange) { //--- calculates percent change return(DoubleToString(change/open[0]*100.0,3)+"%"); }else{ return(DoubleToString(change,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS))); } } } //+------------------------------------------------------------------+ //| Overrides default GetColor(..) method from CRow | //+------------------------------------------------------------------+ color CPriceChangeRow::GetColor(string symbol,ENUM_TIMEFRAMES tf) { double close[1]; double open[1]; //--- gets open and close of current bar if(CopyClose(symbol,tf,0, 1, close) < 0) return(clrWhite); if(CopyOpen(symbol, tf, 0, 1, open) < 0) return(clrWhite); if(close[0] > open[0]) return(clrLime); if(close[0] < open[0]) return(clrRed); return(clrWhite); }
The constructor has two parameters. First one decides whether to show arrows. If it's true, the second parameter is discarded. If it's false, the second parameter decides whether to show percent changes or just price changes.
For this class I decided to override four methods of CRow: GetName, GetValue, GetColor and GetFont. GetName is the simpliest and just returns the name. GetFont is used, because it gives the possibility to display arrows or other characters from Wingdings font. GetColor returns lime color when the price rises and red when it falls. White color is returned when it stays in place or in case of errors. GetValue gets the open and close prices of the last bar, calculates the difference and returns it. In the arrow mode it returns Wingdings character codes of up and down arrows.
5. How to use the whole thing
In order to use the panel we need to create a new indicator. Let's call it TableSample.
The events we need to handle are:
- OnInit();
- OnDeinit();
- OnCalculate(...);
- OnChartEvent(...) (only in case of using multi-currency mode)
We also need a pointer to CTable object, which will be created dynamically in OnInit(). First of all, we must decide, which mode we will use (multi-timeframe or multi-currency). The code sample below shows the multi-currency mode, but eveything that is needed for the multi-timeframe mode is also here in comments. For multi-currency mode we need to create an array of symbols and pass it to the CTable constructor. For multi-timeframe mode we would create an array of timeframes and pass it to the second CTable constructor.
After that we have to create all the necessary components and add them to the panel using the AddRow method. Optionally, panel parameters may be adjusted. After all, we need to draw the panel for the first time, thus we call Update at the end of OnInit(). OnDeinit is simple. The only thing it does is deleting the CTable object, what causes the CTable destructor to be called.
OnCalculate(...) and OnChartEvent(...) are identical. They only call the Update method. OnChartEvent(...) is only necessary if the panel works in multi-currency mode. In this mode it handles events raised by SpyAgents. In multi-timeframe mode only OnCalculate(...) is needed, because we have to monitor only the current chart's symbol.
//+------------------------------------------------------------------+ //| TableSample.mq5 | //| Marcin Konieczny | //| | //+------------------------------------------------------------------+ #property copyright "Marcin Konieczny" #property version "1.00" #property indicator_chart_window #property indicator_plots 0 #include <Table.mqh> #include <PriceRow.mqh> #include <PriceChangeRow.mqh> #include <RSIRow.mqh> #include <PriceMARow.mqh> CTable *table; // pointer to CTable object //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- timeframes used in table (in multi-timeframe mode) ENUM_TIMEFRAMES timeframes[4]={PERIOD_M1,PERIOD_H1,PERIOD_D1,PERIOD_W1}; //--- symbols used in table (in multi-currency mode) string symbols[4]={"EURUSD","GBPUSD","USDJPY","AUDCHF" }; //-- CTable object creation // table = new CTable(timeframes); // multi-timeframe mode table=new CTable(symbols); // multi-currency mode //--- adding rows to the table table.AddRow(new CPriceRow()); // shows current price table.AddRow(new CPriceChangeRow(false)); // shows change of price in the last bar table.AddRow(new CPriceChangeRow(false,true)); // shows percent change of price in the last bar table.AddRow(new CPriceChangeRow(true)); // shows change of price as arrows table.AddRow(new CRSIRow(14)); // shows RSI(14) table.AddRow(new CRSIRow(10)); // shows RSI(10) table.AddRow(new CPriceMARow(MODE_SMA,20,0)); // shows if SMA(20) > current price //--- setting table parameters table.SetFont("Arial",10,clrYellow); // font, size, color table.SetCellSize(60, 20); // width, height table.SetDistance(10, 10); // distance from upper right chart corner table.Update(); // forces table to redraw return(0); } //+------------------------------------------------------------------+ //| Indicator deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- calls table destructor and frees memory delete(table); } //+------------------------------------------------------------------+ //| Indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { //--- update table: recalculate/repaint table.Update(); return(rates_total); } //+------------------------------------------------------------------+ //| OnChartEvent handler | //| Handles CHARTEVENT_CUSTOM events sent by SpyAgent indicators | //| nedeed only in multi-currency mode! | //+------------------------------------------------------------------+ void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { table.Update(); // update table: recalculate/repaint } //+------------------------------------------------------------------+
After attaching this indicator to the chart, it starts updating and we can finally see the panel working.
6. Installation
All files need to be compiled. SpyAgent and TableSample are indicators and should be copied to terminal_data_folder\MQL5\Indicators. The remaining files are include files and should be placed inside terminal_data_folder\MQL5\Include. To run the panel, attach the TableSample indicator to any chart. There is no need to attach SpyAgent's. They will be launched automatically.
Conclusion
The article provides an object-oriented implementation of multi-timeframe and multi-currency panel for MetaTrader 5. It shows how to achieve a design, which is easily extensible and allows to build customized panels with little effort.
All the code presented in this article can be downloaded below.
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
Thank you very much.
Can you add UML chart please.
Hello guys,
this one dont compiles anymore..