El enfoque orientado a objeto para construir paneles multiperíodo y multidivisa
Introducción
Este artículo describe cómo puede usarse la programación orientada a objeto para crear paneles multiperíodo y multidivisa para Meta Trader 5. El objetivo principal es construir un panel universal que pueda ser usado para mostrar en pantalla diferentes tipos de datos como precios, cambios en los precios, valores de indicador o condiciones sell/buy personalizadas sin necesidad de modificar el código del propio panel. De esta forma, solo será necesario escribir un poco de código para personalizar el panel en la forma que queramos.
La solución que voy a mostrar funciona de dos formas:
- El modo multiperíodo, que permite ver los contenidos de la tabla calculados en el símbolo actual pero en diferentes períodos;
- El modo multidivisa, que permite ver los contenidos de la tabla calculados en el símbolo actual pero en diferentes símbolos;
Las siguientes imágenes muestran el panel en estos dos modos.
El primero funciona en el modo multiperíodo y muestra los siguientes datos:
- Precio actual;
- Cambio actual del precio de la barra;
- Cambio actual del precio de la barra como porcentaje;
- Cambio actual del precio de la barra como una flecha (arriba/abajo);
- Valor del indicador RSI(14);
- Valor del indicador RSI(10);
- Condición personalizada: SMA(20) > precio actual.
Figura 1. Modo multiperíodo
El segundo funciona en el modo multidivisa y muestra:
- Precio actual;
- Cambio actual del precio de la barra;
- Cambio actual del precio de la barra como porcentaje;
- Cambio actual del precio de la barra como flecha;
- Valor del indicador RSI(10);
- Valor del indicador RSI(14);
- Condición personalizada: SMA(20) > precio actual.
Figura 2. Modo multidivisa
1. Implementación
El siguiente diagrama de clase describe el diseño de implementación del panel.
Figura 3. Diagrama de clase del panel
Permítanme que les describa los elementos del diagrama:
- CTable. Clase principal del panel. Es responsable de dibujar el panel y gestionar sus componentes.
- SpyAgent. Es un indicador encargado de "espiar" a otros símbolos (instrumentos). Cada agente se crea y se envía a un símbolo diferente. El agente reacciona al evento OnCalculate cuando llega un nuevo tick en el gráfico del símbolo y envía el evento CHARTEVENT_CUSTOM para informar al objeto CTable que debe actualizarse. La idea que subyace tras este enfoque está basada en el artículo "La implementación de un modo multi-divisa en Meta Trader 5. Puede encontrar todos los detalles técnicos ahí.
- CRow. La clase básica para todos los indicadores y condiciones usadas para crear el panel. Extendiendo esta clase es posible crear todos los componentes necesarios del panel.
- CPriceRow. Clase simple que extiende CRow y que se usa para mostrar en pantalla el precio de compra actual.
- CPriceChangeRow. Clase que extiende CRow usada para mostrar en pantalla el cambio del precio de la barra actual. Puede mostrar cambios en el precio, cambios en el porcentaje o flechas.
- CRSIRow. Clase que extiende CRow usada para mostrar en pantalla el valor del indicador RSI actual.
- CPriceMARow. Clase que amplía SMA > precio actual.
Las clases CTable y CRow así como el indicador SpyAgent son las partes principales del panel. CPriceRow, CPriceChangeRow, CRSIRow y CPriceMARow son los contenidos actuales del panel. La clase CRow es llamada para ser extendida por muchas clases nuevas para obtener el resultado deseado. Estas cuatro clases derivadas que hemos visto aquí son solo simples ejemplos de lo que se puede hacer y cómo se puede hacer.
2. SpyAgent
Comenzaremos con el indicador SpyAgent. Se usa solo en el modo multidivisa y es necesario para actualizar el panel adecuadamente cuando llega un nuevo tick en otros gráficos. No voy a entrar mucho más en los detalles de este concepto. Estos se describen en el artículo "La implementación de un modo multidivisa en Meta Trader 5".
El indicador SpyAgent se ejecuta en el gráfico del símbolo especificado y envía dos eventos: el evento de inicialización y el de nuevo tick. Ambos eventos son el tipo CHARTEVENT_CUSTOM. Para gestionar estos eventos tenemos que usar el controlador OnChartEvent(...) (lo veremos más tarde en este artículo).
Veamos el código de SpyAgent:
//+------------------------------------------------------------------+ //| 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 //+------------------------------------------------------------------+ //| Función de iteración del indicador | //+------------------------------------------------------------------+ 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); // envía el evento de inicialización else EventChartCustom(chart_id,(ushort)(custom_event_id+1),0,0.0,_Symbol); // envía el evento de nuevo tick return(rates_total); }Es muy simple. Lo único que hace es recibir nuevos ticks y enviar eventos CHARTEVENT_CUSTOM.
3. CTable
CTable es la clase central del panel. Almacena información sobre la configuración del panel y gestiona sus componentes. Actualiza (redibuja) el panel cuando es necesario.
Veamos cómo se declara CTable:
//+------------------------------------------------------------------+ //| Clase CTable | //+------------------------------------------------------------------+ class CTable { private: int xDistance; // Distancia desde el borde derecho del gráfico int yDistance; // Distancia desde la parte superior del gráfico int cellHeight; // Altura de la celda de la tabla int cellWidth; // Anchura de la celda de la tabla string font; // Nombre de la fuente int fontSize; color fontColor; CList *rowList; // lista de los objetos de la fila bool tfMode; // ¿está en modo multiperíodo? ENUM_TIMEFRAMES timeframes[]; // matriz de período para el modo multiperíodo string symbols[]; // matriz de pares de divisas para el modo multidivisa //--- métodos privados //--- establece los parámetros por defecto de la tabla void Init(); //--- dibuja la etiqueta de texto en la celda especificada de la tabla void DrawLabel(int x,int y,string text,string font,color col); //--- devuelve la representación textual de un período dado string PeriodToString(ENUM_TIMEFRAMES period); public: //--- constructor del modo multiperíodo CTable(ENUM_TIMEFRAMES &tfs[]); //--- constructor del modo multidivisa CTable(string &symb[]); //--- destructor ~CTable(); //--- redibuja la tabla void Update(); //--- métodos para establecer los parámetros de la tabla void SetDistance(int xDist,int yDist); void SetCellSize(int cellW,int cellH); void SetFont(string fnt,int size,color clr); //--- Anexa el objeto CRow al final de la tabla void AddRow(CRow *row); };
Como puede ver, todos los componentes del panel (filas) se guardan como punteros CRow, por lo que cada componente que queramos añadir al panel debe extender la clase CRow. CRow puede considerarse como un contrato entre el panel y sus componentes. CTable no contiene ningún código para el cálculo de sus celdas. Esto es responsabilidad de las clases que extienden CRow. CTable es solo una estructura para ubicar los componentes de CRow y redibujarlos cuando sea necesario.
Vamos a ver los métodos de CTable. La clase tiene dos constructores. El primero se usa para el modo multiperíodo y es muy simple. Solo tenemos que suministrar la matriz de períodos que queremos mostrar en pantalla.
//+------------------------------------------------------------------+ //| Constructor del modo multiperíodo | //+------------------------------------------------------------------+ CTable::CTable(ENUM_TIMEFRAMES &tfs[]) { //--- copia todos los períodos a su propia matriz ArrayResize(timeframes,ArraySize(tfs),0); ArrayCopy(timeframes,tfs); tfMode=true; //--- rellena la matriz de símbolos con el símbolo del gráfico actual ArrayResize(symbols,ArraySize(tfs),0); for(int i=0; i<ArraySize(tfs); i++) symbols[i]=Symbol(); //--- establece los parámetros por defecto Init(); }
El segundo constructor se usa para el modo multidivisa y toma una matriz de símbolos (instrumentos). Este también envía SpyAgents. Los adjunto uno por uno a los gráficos apropiados.
//+------------------------------------------------------------------+ //| Constructor del modo multidivisa | //+------------------------------------------------------------------+ CTable::CTable(string &symb[]) { //--- copia todos los símbolos a su propia matriz ArrayResize(symbols,ArraySize(symb),0); ArrayCopy(symbols,symb); tfMode=false; //--- rellena la matriz de períodos con el período actual ArrayResize(timeframes,ArraySize(symb),0); ArrayInitialize(timeframes,Period()); //--- establece los parámetros por defecto Init(); //--- envía SpyAgents a cada símbolo solicitado 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 al establecer SpyAgent en "+symbols[x]); return; } }
El método Init crea la lista de filas (como un objeto CList - CList es una lista dinámica de tipos CObject) y establece los valores por defecto para las variables internas de CTable (fuente, tamaño de fuente, color, dimensión de la celda y distancia desde la esquina superior derecha del gráfico).
//+------------------------------------------------------------------+ //| Establece los parámetros por defecto de la tabla | //+------------------------------------------------------------------+ CTable::Init() { //--- crea la lista para almacenar los objetos de la fila rowList=new CList; //--- establece los parámetros por defecto xDistance = 10; yDistance = 10; cellWidth = 60; cellHeight= 20; font="Arial"; fontSize=10; fontColor=clrWhite; }
El destructor es muy simple. Borra la lista de filas y todos los objetos del gráfico (etiquetas) creadas por el panel.
//+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CTable::~CTable() { int total=ObjectsTotal(0); //--- elimina todas las etiquetas de texto del gráfico (todos los nombres de objeto que comienzan por el prefijo nameBase) for(int i=total-1; i>=0; i--) if(StringFind(ObjectName(0,i),nameBase)!=-1) ObjectDelete(0,ObjectName(0,i)); //--- borra la lista de filas y libera memoria delete(rowList); }
El método AddRow anexa la nueva fila a la lista de filas. Tenga en cuenta que rowList es un objeto CList que se redimensiona automáticamente. Este método también llama al método Init para cada objeto CRow que se añade. Es necesario que el objeto inicialice adecuadamente sus variables internas. Por ejemplo, puede utilizar la llamada de Init para crear controladores de indicador o archivo.
//+------------------------------------------------------------------+ //| Anexa una nueva fila al final de la tabla | //+------------------------------------------------------------------+ CTable::AddRow(CRow *row) { rowList.Add(row); row.Init(symbols,timeframes); }
El método Update es un poco más complicado. Se usa para redibujar el panel.
Básicamente, consta de tres partes que son:
- Dibujar la primera columna (los nombres de las filas)
- Dibujar la primera fila (los nombres de los períodos o símbolos dependiendo del modo seleccionado)
- Dibujar las celdas internas (los valores de los componentes)
Conviene tener en cuenta que pedimos a cada componente calcular su propio valor en base al símbolo y período suministrado. También permitimos que el componente decida qué fuente y color se debe usar.
//+------------------------------------------------------------------+ //| Redibuja la tabla | //+------------------------------------------------------------------+ CTable::Update() { CRow *row; string symbol; ENUM_TIMEFRAMES tf; int rows=rowList.Total(); // número de filas int columns; // número de columnas if(tfMode) columns=ArraySize(timeframes); else columns=ArraySize(symbols); //--- dibuja la primera columna (nombres de las filas) for(int y=0; y<rows; y++) { row=(CRow*)rowList.GetNodeAtIndex(y); //--- nota: pedimos al objeto de la fila que devuelva su nombre DrawLabel(columns,y+1,row.GetName(),font,fontColor); } //--- dibuja la primera fila (nombres de los períodos o pares de divisas) 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); } //--- dibuja las celdas de la tabla interiores for(int y=0; y<rows; y++) for(int x=0; x<columns; x++) { row=(CRow*)rowList.GetNodeAtIndex(y); if(tfMode) { //--- en modo multiperíodo utiliza el símbolo actual y distintos períodos tf=timeframes[x]; symbol=_Symbol; } else { //--- en modo multi-divisa utiliza el símbolo actual y distintos períodos tf=Period(); symbol=symbols[x]; } //--- nota: pedimos al objeto de la fila que devuelva su fuente, //--- color y valor calculado actual para el período y símbolo DrawLabel(columns-x-1,y+1,row.GetValue(symbol,tf),row.GetFont(symbol,tf),row.GetColor(symbol,tf)); } //--- fuerza el redibujado del gráfico ChartRedraw(); }
El mátodo DrawLabel se usa para dibujar etiquetas en la celda especificada del panel. Primero comprueba si ya existe una etiqueta para esta celda. Si no, crea una nueva.
A continuación establece todas las propiedades de la etiqueta que sean necesarias y sus textos.
//+------------------------------------------------------------------+ //| Dibuja la etiqueta de texto en la celda especificada de la tabla | //+------------------------------------------------------------------+ CTable::DrawLabel(int x,int y,string text,string font,color col) { //--- crea un único nombre para esta celda string name=nameBase+IntegerToString(x)+":"+IntegerToString(y); //--- crea la etiqueta if(ObjectFind(0,name)<0) ObjectCreate(0,name,OBJ_LABEL,0,0,0); //--- establece las propiedades de la etiqueta 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); //--- establece el texto de la etiqueta ObjectSetString(0,name,OBJPROP_TEXT,text); }
No presentaremos aquí otros métodos ya que son muy simples y menos importantes. Puede descargar todo el código al final del artículo.
4. Extendiendo CRow
CRow es una clase básica para todos los componentes que puede ser usada por el panel.
Veamos el código de la clase CRow:
//+------------------------------------------------------------------+ //| Clase CRow | //+------------------------------------------------------------------+ //| Clase básica para crear filas de tabla personalizadas | //| Deben obviarse uno o más métodos de CRow | //| al crear sus propias filas de la tabla | //+------------------------------------------------------------------+ class CRow : public CObject { public: //--- método de inicialización por defecto virtual void Init(string &symb[],ENUM_TIMEFRAMES &tfs[]) { } //--- método por defecto para obtener el valor del string a mostrar en pantalla en la celda de la tabla virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf) { return("-"); } //--- método por defecto para obtener el color para una celda de la tabla virtual color GetColor(string symbol,ENUM_TIMEFRAMES tf) { return(clrWhite); } //--- método por defecto para obtener el nombre de la fila virtual string GetName() { return("-"); } //--- método por defecto para obtener la fuente de la tabla virtual string GetFont(string symbol,ENUM_TIMEFRAMES tf) { return("Arial"); } };
Extiende CObject porque solo CObject puede almacenarse en una estructura CList. Tiene cinco métodos que están casi vacíos. Para ser más precisos, la mayoría de ellos solo devuelven valores por defecto. Estos métodos son obviados cuando se extiende CRow. No necesitamos obviarlos todos, solo los que queremos.
Como ejemplo, vamos a crear el componente de panel más simple posible, el componente precio de compra actual. Puede usarse en el modo multidivisa para mostrar en pantalla los precios actuales de varios instrumentos.
Para conseguir esto creamos una clase CPriceRow como se muestra a continuación:
//+------------------------------------------------------------------+ //| Clase CPriceRow | //+------------------------------------------------------------------+ class CPriceRow : public CRow { public: //--- Pasa por alto el método default GetValue(..) de CRow virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf); //--- pasa por alto el método por defecto GetName() de CRow virtual string GetName(); }; //+------------------------------------------------------------------+ //| Pasa por alto el método por defecto GetValue(..) de CRow | //+------------------------------------------------------------------+ string CPriceRow::GetValue(string symbol,ENUM_TIMEFRAMES tf) { MqlTick tick; //--- obtiene el precio actual if(!SymbolInfoTick(symbol,tick)) return("-"); return(DoubleToString(tick.bid,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS))); } //+------------------------------------------------------------------+ //| Pasa por alto el método por defecto GetName() de CRow | //+------------------------------------------------------------------+ string CPriceRow::GetName() { return("Price"); }
Los métodos que hemos elegido pasar por alto aquí son GetValue y GetName. GetName simplemente devuelve el nombre para esta fila que será mostrado en pantalla en la primera columna del panel. GetValue obtiene el último tick del símbolo especificado y devuelve el último precio de compra. Eso es todo lo que necesitamos.
Fue muy sencillo. Hagamos algo diferente. Construiremos ahora un componente que muestre el valor RSI actual.
El código es similar al anterior:
//+------------------------------------------------------------------+ //| Clase CRSIRow | //+------------------------------------------------------------------+ class CRSIRow : public CRow { private: int rsiPeriod; // período RSI string symbols[]; // matriz de símbolos ENUM_TIMEFRAMES timeframes[]; // matriz de períodos int handles[]; // matriz de controladores RSI //--- encuentra el controlador de indicador para un símbolo y período dado int GetHandle(string symbol,ENUM_TIMEFRAMES tf); public: //--- constructor CRSIRow(int period); //--- pasa por alto el método por defecto GetValue(..) de CRow virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf); //--- pasa por alto el método por defecto GetName() de CRow virtual string GetName(); //--- pasa por alto el método por defecto Init(..) de CRow virtual void Init(string &symb[],ENUM_TIMEFRAMES &tfs[]); }; //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CRSIRow::CRSIRow(int period) { rsiPeriod=period; } //+------------------------------------------------------------------+ //| pasa por alto el método por defecto Init(..) de CRow | //+------------------------------------------------------------------+ void CRSIRow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[]) { int size=ArraySize(symb); ArrayResize(symbols,size); ArrayResize(timeframes,size); ArrayResize(handles,size); //--- copia los contenidos de las matrices en las propias matrices ArrayCopy(symbols,symb); ArrayCopy(timeframes,tfs); //--- obtiene los controladores RSI para todos los símbolos o períodos usados for(int i=0; i<ArraySize(symbols); i++) handles[i]=iRSI(symbols[i],timeframes[i],rsiPeriod,PRICE_CLOSE); } //+------------------------------------------------------------------+ //| pasa por alto el método por defecto GetValue(..) de CRow | //+------------------------------------------------------------------+ string CRSIRow::GetValue(string symbol,ENUM_TIMEFRAMES tf) { double value[1]; //--- obtiene el controlador del indicador RSI int handle=GetHandle(symbol,tf); if(handle==INVALID_HANDLE) return("err"); //--- obtiene el valor actual de RSI if(CopyBuffer(handle,0,0,1,value)<0) return("-"); return(DoubleToString(value[0],2)); } //+------------------------------------------------------------------+ //| pasa por alto el método por defecto GetName() de CRow | //+------------------------------------------------------------------+ string CRSIRow::GetName() { return("RSI("+IntegerToString(rsiPeriod)+")"); } //+------------------------------------------------------------------+ //| encuentra el controlador del indicador | //| para un período y símbolo dado | //+------------------------------------------------------------------+ 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); }
Tenemos algunos métodos nuevos aquí. El constructor permite suministrar el período RSI y lo guarda como variable miembro. El método Init se usa para crear el controlador del indicador RSI. Estos controladores se guardan en la matriz handle []. EL método GetValue copia el último valor del buffer de RSI y lo devuelve. Se usa el método privado GetHandle para encontrar el controlador de indicador adecuado en la matriz handle []. GetName se explica por sí mismo,
Como podemos ver, construir los componentes del panel es muy fácil. De la misma forma, podemos crear componentes para casi cualquier condición que queramos. No tiene por qué ser el valor del indicador. A continuación se muestra una condición personalizada basada en SMA. Comprueba si el precio actual se encuentra por encima de la media móvil y muestra en pantalla "Si" o "No".
//+------------------------------------------------------------------+ //| CPriceMARow class | //+------------------------------------------------------------------+ class CPriceMARow : public CRow { private: int maPeriod; // período de la media móvil int maShift; // cambio de la media móvil ENUM_MA_METHOD maType; // SMA, EMA, SMMA o LWMA string symbols[]; // smatriz de símbolos ENUM_TIMEFRAMES timeframes[]; // matriz de perídos de tiempo int handles[]; // matriz de los controladores de MA //--- encuentra el controlador de l indicador para un símbolo y período de tiempo dado int GetHandle(string symbol,ENUM_TIMEFRAMES tf); public: //--- constructor CPriceMARow(ENUM_MA_METHOD type,int period,int shift); //--- Pasa por alto el método por defecto GetValue(..) deCRow virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf); // Pasa por alto el método por defecto GetName() de CRow virtual string GetName(); //--- Pasa por alto el método por defecto Init(..) de CRow virtual void Init(string &symb[],ENUM_TIMEFRAMES &tfs[]); }; //+------------------------------------------------------------------+ //| constructor de la clase CPriceMARow | //+------------------------------------------------------------------+ CPriceMARow::CPriceMARow(ENUM_MA_METHOD type,int period,int shift) { maPeriod= period; maShift = shift; maType=type; } //+------------------------------------------------------------------+ //| Pasa por alto el método por defecto Init(..) de CRow | //+------------------------------------------------------------------+ void CPriceMARow::Init(string &symb[],ENUM_TIMEFRAMES &tfs[]) { int size=ArraySize(symb); ArrayResize(symbols,size); ArrayResize(timeframes,size); ArrayResize(handles,size); //--- copia los contenidos de las matrices en las propias matrices ArrayCopy(symbols,symb); ArrayCopy(timeframes,tfs); //---obtiene los controladores de MA para todos los símbolos o períodos de tiempo for(int i=0; i<ArraySize(symbols); i++) handles[i]=iMA(symbols[i],timeframes[i],maPeriod,maShift,maType,PRICE_CLOSE); } //+------------------------------------------------------------------+ //| Pasa por alto el método por defecto GetValue(..) de CRow | //+------------------------------------------------------------------+ string CPriceMARow::GetValue(string symbol,ENUM_TIMEFRAMES tf) { double value[1]; MqlTick tick; //---obtiene el controlador del indicador de MA int handle=GetHandle(symbol,tf); if(handle==INVALID_HANDLE) return("err"); //--- obtiene el último valor de MA if(CopyBuffer(handle,0,0,1,value)<0) return("-"); //--- obtiene el último precio if(!SymbolInfoTick(symbol,tick)) return("-"); //---comprobando la condición: precio > MA if(tick.bid>value[0]) return("Yes"); else return("No"); } //+------------------------------------------------------------------+ //| Pasa por alto el método por defecto GetName() de 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)+")"); } //+------------------------------------------------------------------+ //| encuentra el controlador del indicador para un símbolo | //| y período de tiempo dado | //+------------------------------------------------------------------+ 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); }
El código es más largo porque la media móvil tiene tres parámetros: período, cambio y tipo. GetName es un poco más complicado ya que construye el nombre según el tipo y período de MA. GetValue trabaja casi de la misma forma que en el caso de CRSIRow, pero en lugar de devolver el valor del indicador devuelve "Sí" si el precio es superior a SMA o "No" si es inferior.
El método de ejemplo es un poco más complicado. Es la clase CPriceChangeRow la que muestra el cambio actual del precio en la barra. Funciona de tres formas:
- Muestra las flechas (verde arriba o roja abajo);
- Muestra el cambio del precio como valor (verde o rojo);
- Muestra el cambio del precio como porcentaje (verde o rojo);
El código es como se muestra a continuación:
//+------------------------------------------------------------------+ //| CPriceChangeRow class | //+------------------------------------------------------------------+ class CPriceChangeRow : public CRow { private: bool percentChange; bool useArrows; public: //--- constructor CPriceChangeRow(bool arrows,bool percent=false); //--- Pasa por alto el método por defecto GetName() de CRow virtual string GetName(); //--- Pasa por alto el método por defecto GetFont() de CRow virtual string GetFont(string symbol,ENUM_TIMEFRAMES tf); //--- Pasa por alto el método por defecto GetValue(..) de CRow virtual string GetValue(string symbol,ENUM_TIMEFRAMES tf); //--- Pasa por alto el método por defecto GetColor(..) de CRow virtual color GetColor(string symbol,ENUM_TIMEFRAMES tf); }; //+------------------------------------------------------------------+ //| Constructor de la clase CPriceChangeRow | //+------------------------------------------------------------------+ CPriceChangeRow::CPriceChangeRow(bool arrows,bool percent=false) { percentChange=percent; useArrows=arrows; } //+------------------------------------------------------------------+ //| Pasa por alto el método por defecto GetName() de CRow | //+------------------------------------------------------------------+ string CPriceChangeRow::GetName() { return("PriceChg"); } //+------------------------------------------------------------------+ //| Pasa por alto el método por defecto GetFont() de CRow | //+------------------------------------------------------------------+ string CPriceChangeRow::GetFont(string symbol,ENUM_TIMEFRAMES tf) { //--- usamos las fuentes Wingdings para dibujar flechas (arriba/abajo) if(useArrows) return("Wingdings"); else return("Arial"); } //+------------------------------------------------------------------+ //| Pasa por alto el método por defecto GetValue(..) de CRow | //+------------------------------------------------------------------+ string CPriceChangeRow::GetValue(string symbol,ENUM_TIMEFRAMES tf) { double close[1]; double open[1]; //--- obtiene abierto y cerrado de la barra actual if(CopyClose(symbol,tf,0, 1, close) < 0) return(" "); if(CopyOpen(symbol, tf, 0, 1, open) < 0) return(" "); //--- cambio del precio de la barra actual 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) { //--- calcula el cambio de porcentaje return(DoubleToString(change/open[0]*100.0,3)+"%"); }else{ return(DoubleToString(change,(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS))); } } } //+------------------------------------------------------------------+ //| Pasa por alto el método por defecto GetColor(..) de CRow | //+------------------------------------------------------------------+ color CPriceChangeRow::GetColor(string symbol,ENUM_TIMEFRAMES tf) { double close[1]; double open[1]; //--- obtiene abierto y cerrado de la barra actual 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); }
El constructor tiene dos parámetros. El primero decide si muestra las flechas. Si es verdadero, el segundo parámetro es descartado. Si es falso, el segundo parámetro decide si muestra los cambios de porcentaje o solo los cambios de precio.
Para esta clase decidí pasar por alto cuatro métodos de CRow: GetName, GetValue, GetColor y GetFont. GetName es el más simple y solo devuelve el nombre. GetFont se usa porque permite la posibilidad de mostrar en pantalla flechas u otros caracteres de las fuentes Wingdings. GetColor devuelve el color lima cuando el precio sube y rojo cuando cae. Devuelve el color blanco cuando se queda en la posición o en caso de errores. GetValue obtiene los precios abierto y cerrado de la última barra, calcula la diferencia y la devuelve. En el modo flecha devuelve los códigos de caracteres Wingdings de las flechas arriba y abajo.
5. Cómo usarlo todo
Para usar el panel necesitamos crear un nuevo indicador. Vamos a llamarlo TableSample.
Los eventos que tenemos que controlar son:
- OnInit();
- OnDeinit();
- OnCalculate(...);
- OnChartEvent(...) (solo en caso de usar el modo multidivisa)
También necesitamos un puntero hacia el objeto CTable que será creado dinámicamente en OnInit(). En primer lugar, tenemos que decidir qué modo vamos a usar (multiperíodo o multidivisa). El ejemplo de código a continuación muestra el modo multidivisa, pero todo lo que se necesita para el modo multiperíodo está también aquí en los comentarios. Para el modo multidivisa, necesitamos crear una matriz de símbolos y pasarla al constructor de CTable. Para el modo multiperíodo, necesitamos crear una matriz de períodos y pasarla al segundo constructor de CTable.
Después de esto tenemos que crear todos los componentes necesarios y añadirlos al panel usando el método AddRow. Opcionalmente, pueden ajustarse los parámetros del panel. Después de todo, necesitamos dibujar el panel por primera vez, por lo que llamamos a Update al final de OnInit(). OnDeinit es simple. Lo único que hace es borrar el objeto CTable, lo que origina la llamada al contructor CTable.
OnCalculate(...) son OnChartEvent(...) idénticos. Solo llaman al método Update. OnChartEvent(...) solo es necesario si el panel funciona en el modo multidivisa. En este modo, gestiona eventos descubiertos por SpyAgent. En el modo multiperíodo solo se necesita a OnCalculate(...) ya que tenemos que monitorizar solo el símbolo actual del gráfico.
//+------------------------------------------------------------------+ //| 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; // puntero al objeto CTable //+------------------------------------------------------------------+ //| Unción de inicialización del indicador | //+------------------------------------------------------------------+ int OnInit() { //--- períodos usados en la tabla (en el modo multiperíodo) ENUM_TIMEFRAMES timeframes[4]={PERIOD_M1,PERIOD_H1,PERIOD_D1,PERIOD_W1}; //--- símbolos usados en la tabla (en el modo multidivisa) string symbols[4]={"EURUSD","GBPUSD","USDJPY","AUDCHF" }; //-- creación del objeto CTable // table = new CTable(timeframes); // modo multi-período table=new CTable(symbols); // modo multi-divisa //--- añadiendo filas a la tabla table.AddRow(new CPriceRow()); // muestra el precio actual table.AddRow(new CPriceChangeRow(false)); // muestra el cambio del precio en la última barra table.AddRow(new CPriceChangeRow(false,true)); // muestra el cambio del porcentaje del precio en la última barra table.AddRow(new CPriceChangeRow(true)); // muestra el cambio del precio como flechas table.AddRow(new CRSIRow(14)); // muestra RSI(14) table.AddRow(new CRSIRow(10)); // muestra RSI(10) table.AddRow(new CPriceMARow(MODE_SMA,20,0)); // muestra si SMA(20) > precio actual //--- estableciendo los parámetros de la tabla table.SetFont("Arial",10,clrYellow); // fuente, tamaño, color table.SetCellSize(60, 20); // anchura, altura table.SetDistance(10, 10); // distancia desde la esquina superior derecha del gráfico table.Update(); // fuerza el redibujado de la tabla return(0); } //+------------------------------------------------------------------+ //| Función de deinicialización del indicador | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- llama al destructor de la tabla y libera memoria delete(table); } //+------------------------------------------------------------------+ //| Función de iteración del indicador | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double &price[]) { //--- actualiza la table: recalcula/redibuja table.Update(); return(rates_total); } //+------------------------------------------------------------------+ //| Controlador de OnChartEvent | //| Controla los eventos CHARTEVENT_CUSTOM enviados | //| por los indicadores SpyAgent | //| ¡solo se necesita en el modo multidivisa! | //+------------------------------------------------------------------+ void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { table.Update(); // actualiza la tabla: recalcula/redibuja } //+------------------------------------------------------------------+
Después de adjuntar este indicador al gráfico comienza a actualizarse y podemos ver finalmente el panel funcionando.
6. Instalación
Se necesita compilar todos los archivos. SpyAgent y TableSample son indicadores y deben copiarse en terminal_data_folder\MQL5\Indicators. El resto de archivos son de tipo include y deben situarse en terminal_data_folder\MQL5\Include. Para ejecutar el panel adjunte el indicador TableSample a cualquier gráfico. No es necesario adjuntar los de SpyAgent. Estos se ejecutarán automáticamente.
Conclusión
Este artículo proporciona una implementación orientada a objeto del panel multiperíodo y multidivisa para Meta Trader 5. Muestra cómo conseguir un diseño fácilmente extensible y permite construir paneles personalizados con poco esfuerzo.
Todo el código utilizado en este artículo puede descargarse más abajo.
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/357
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso