Cambiar los Parámetros del Asesor Experto desde el Panel de Usuario "Sobre la Marcha"
Contenidos
Introducción
1. Asuntos en el Punto de Mira
2. Estructura del Asesor Experto
3. Interacción con el Panel de Usuario
Conclusión
Introducción
Al desarrollar Asesores Expertos complejos, el número de parámetros externos puede ser muy grande. Y la configuración debe cambiarse manualmente muy a menudo, lo que hace el proceso entero muy largo en los casos de listas masivas de parámetros. Por supuesto, un usuario puede preparar conjuntos por adelantado y guardarlos, pero puede que no sean exactamente lo que se requiere en algunos casos. Aquí es donde MQL5 puede resultar de utilidad, como siempre.
Creemos un panel de usuario que nos permita cambiar los parámetros de un Asesor Experto "sobre la marcha" durante el proceso de trading. Esto podría ser relevante para aquellos que realizan operaciones de trading manualmente o en modo semi-automático. Al realizar cualquier cambio, los parámetros se escribirán en un archivo desde donde los leerá el Asesor Experto para mostrarlos después en el panel.
1. Asuntos en el Punto de Mira
Para ilustrar esto, desarrollaremos un EA (Asesor Experto) sencillo que abrirá una posición en la dirección del indicador JMA. El EA funcionará en barras completas del símbolo e intervalo cronológico actual. Los parámetros externos incluirán Indicator Period, Stop Loss, Take Profit, Reverse y Lot. Estas opciones serán más que suficientes para nuestro ejemplo.
Añadamos dos parámetros adicionales para poder encender y apagar el panel (el Panel On/Off) y activar/desactivar el modo de configuración de parámetros del Asesor Experto (configuración "On The Fly", o "Sobre la Marcha"). Allá donde el número de parámetros es grande siempre será más conveniente colocar opciones adicionales al principio o final de la lista para un acceso fácil y rápido.
Fig. 1. Panel de Información con parámetros del Asesor Experto.
El modo de configuración "On The Fly" está desactivado por defecto. Cuando active este modo por primera vez, el Asesor Experto creará un archivo para guardar todos los parámetros que tiene actualmente. Lo mismo ocurrirá si el archivo de elimina por accidente. El Asesor Experto detectará la eliminación y recreará el archivo. Con el modo de configuración "On The Fly" desactivado, el Asesor Experto se guiará por parámetros externos.
Si este modo está activado, el Asesor Experto leerá los parámetros del archivo y, simplemente haciendo click en cualquier parámetro en el panel de información, usted podrá seleccionar el valor requerido o introducir un nuevo valor en el cuadro de diálogo emergente. Los datos del archivo se actualizarán cada vez que se seleccione un nuevo valor.
2. Estructura del Asesor Experto
Aunque el programa es pequeño y todas las funciones podrían caber perfectamente en un archivo, es mucho más conveniente categorizar toda la información correctamente para tener un mejor acceso a ella después. Por tanto, es mejor categorizar las funciones por tipo y guardarlas en diferentes archivos desde el principio para incluirlas después en un archivo maestro. La figura de abajo muestra una carpeta de proyecto compartida con el Asesor Experto OnTheFly y todos los archivos include. Los archivos include se colocan en una carpeta separada (Include).
Fig. 2. Los archivos del proyecto en la ventana de navegador de MetaEditor
Cuando los archivos include están en la misma carpeta con el archivo maestro, el código tiene el siguiente aspecto:
//+------------------------------------------------------------------+ //| CUSTOM LIBRARIES | //+------------------------------------------------------------------+ #include "Include/!OnChartEvent.mqh" #include "Include/CREATE_PANEL.mqh" #include "Include/FILE_OPERATIONS.mqh" #include "Include/ERRORS.mqh" #include "Include/ARRAYS.mqh" #include "Include/TRADE_SIGNALS.mqh" #include "Include/TRADE_FUNCTIONS.mqh" #include "Include/GET_STRING.mqh" #include "Include/GET_COLOR.mqh" #include "Include/ADD_FUNCTIONS.mqh"
Puede encontrar más información sobre cómo incluir archivos en el material de Referencia MQL5.
Necesitaremos variables globales, copias de parámetros externos. Sus valores se asignarán desde los parámetros externos del archivo, dependiendo del modo del Asesor Experto. Estas variables se usan a través del código de programa entero, por ejemplo al mostrar valores en el panel de información, al operar con funciones, etc.
// COPY OF EXTERNAL PARAMETERS int gPeriod_Ind = 0; double gTakeProfit = 0.0; double gStopLoss = 0.0; bool gReverse = false; double gLot = 0.0;
Al igual que con todos los demás Asesores Expertos, tendremos las funciones principales: OnInit, OnTick y OnDeinit. Y también tendremos la función OnTimer. En cada segundo comprobará la existencia del archivo de parámetros y lo restaurará en caso de que haya sido eliminado por accidente. Como debemos interactuar con el panel de usuario, se usará también la función OnChartEvent. Esta función se ha colocado en un archivo separado (!OnChartEvent.mqh), junto con las otras funciones relacionadas.
El código base del archivo maestro tiene el siguiente aspecto:
#define szArrIP 5 // Size of the parameter array #define NAME_EXPERT MQL5InfoString(MQL5_PROGRAM_NAME) // Name of EA #define TRM_DP TerminalInfoString(TERMINAL_DATA_PATH) // Folder that contains the terminal data //+------------------------------------------------------------------+ //| STANDARD LIBRARIES | //+------------------------------------------------------------------+ #include <Trade/SymbolInfo.mqh> #include <Trade/Trade.mqh> //+------------------------------------------------------------------+ //| CUSTOM LIBRARIES | //+------------------------------------------------------------------+ #include "Include/!OnChartEvent.mqh" #include "Include/CREATE_PANEL.mqh" #include "Include/FILE_OPERATIONS.mqh" #include "Include/ERRORS.mqh" #include "Include/ARRAYS.mqh" #include "Include/TRADE_SIGNALS.mqh" #include "Include/TRADE_FUNCTIONS.mqh" #include "Include/GET_STRING.mqh" #include "Include/GET_COLOR.mqh" #include "Include/ADD_FUNCTIONS.mqh" //+------------------------------------------------------------------+ //| CREATING CLASS INSTANCES | //+------------------------------------------------------------------+ CSymbolInfo mysymbol; // CSymbolInfo class object CTrade mytrade; // CTrade class object //+------------------------------------------------------------------+ //| EXTERNAL PARAMETERS | //+------------------------------------------------------------------+ input int Period_Ind = 10; // Indicator Period input double TakeProfit = 100; // Take Profit (p) input double StopLoss = 30; // Stop Loss (p) input bool Reverse = false; // Reverse Position input double Lot = 0.1; // Lot //--- input string slash=""; // * * * * * * * * * * * * * * * * * * * sinput bool InfoPanel = true; // On/Off Info Panel sinput bool SettingOnTheFly = false; // "On The Fly" Setting //+------------------------------------------------------------------+ //| GLOBAL VARIABLES | //+------------------------------------------------------------------+ int hdlSI=INVALID_HANDLE; // Signal indicator handle double lcheck=0; // For the check of parameter values bool isPos=false; // Position availability //--- COPY OF EXTERNAL PARAMETERS int gPeriod_Ind = 0; double gTakeProfit = 0.0; double gStopLoss = 0.0; bool gReverse = false; double gLot = 0.0; //+------------------------------------------------------------------+ //| EXPERT ADVISOR INITIALIZATION | //+------------------------------------------------------------------+ void OnInit() { if(NotTest()) { EventSetTimer(1); } // If it's not the tester, set the timer //--- Init_arr_vparams(); // Initialization of the array of parameter values SetParameters(); // Set the parameters GetIndicatorsHandles(); // Get indicator handles NewBar(); // New bar initialization SetInfoPanel(); // Info panel } //+------------------------------------------------------------------+ //| CURRENT SYMBOL TICKS | //+------------------------------------------------------------------+ void OnTick() { // If the bar is not new, exit if(!NewBar()) { return; } else { TradingBlock(); } } //+------------------------------------------------------------------+ //| TIMER | //+------------------------------------------------------------------+ void OnTimer() { SetParameters(); SetInfoPanel(); } //+------------------------------------------------------------------+ //| EXPERT ADVISOR DEINITIALIZATION | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Get the deinitialization reason code if(NotTest()) { { Print(getUnitReasonText(reason)); } //--- // When deleting from the chart if(reason==REASON_REMOVE) { // Delete all objects created by the Expert Advisor DeleteAllExpertObjects(); //--- if(NotTest()) { EventKillTimer(); } // Stop the timer IndicatorRelease(hdlSI); // Delete the indicator handle } }
También he incluido algunas funciones más en el archivo maestro:
- GetIndicatorsHandles – obtiene el identificador del indicador.
- NewBar – determina el evento de barra nueva.
- SetParameters – configura los parámetros dependiendo del modo.
- iZeroMemory – pone a cero algunas variables y arrays.
//+------------------------------------------------------------------+ //| SETTING PARAMETERS IN TWO MODES | //+------------------------------------------------------------------+ // If this variable is set to false, the parameters in the file are read from the array // where they are saved for quick access after they have been read for the first time. // The variable is zeroed out upon the change in the value on the panel. bool flgRead=false; double arrParamIP[]; // Array where the parameters from the file are saved //--- void SetParameters() { // If currently in the tester or // in real time but with the "On The Fly" Setting mode disabled if(!NotTest() || (NotTest() && !SettingOnTheFly)) { // Zero out the variable and parameter array flgRead=false; ArrayResize(arrParamIP,0); //--- // Check the Indicator Period for correctness if(Period_Ind<=0) { lcheck=10; } else { lcheck=Period_Ind; } gPeriod_Ind=(int)lcheck; //--- gStopLoss=StopLoss; gTakeProfit=TakeProfit; gReverse=Reverse; //--- // Check the Lot for correctness if(Lot<=0) { lcheck=0.1; } else { lcheck=Lot; } gLot=lcheck; } else // If "On The Fly" Setting mode is enabled { // Check whether there is a file to write/read parameters to/from the file string lpath=""; //--- // If the folder exists if((lpath=CheckCreateGetPath())!="") { // Write or read the file WriteReadParameters(lpath); } } }
El código fuente para la función SetParameters es sencillo y directo. Echemos un vistazo más detallado a la función WriteReadParameters. Todo es bastante sencillo en este caso. Primero comprobaremos si existe el archivo con parámetros. Si existe, leeremos el archivo y escribiremos valores de parámetro en un array usando la función GetValuesParamsFromFile. Si el archivo no existe, se creará con los parámetros externos actuales escritos en él.
Abajo se encuentra el código con comentarios más detallados para la implementación de las acciones descritas arriba:
//+------------------------------------------------------------------+ //| WRITE DATA TO FILE | //+------------------------------------------------------------------+ void WriteReadParameters(string pth) { string nm_fl=pth+"ParametersOnTheFly.ini"; // File name and path //--- // Get the file handle to read the file int hFl=FileOpen(nm_fl,FILE_READ|FILE_ANSI); //--- if(hFl!=INVALID_HANDLE) // If the handle has been obtained, the file exists { // Get parameters from the file if(!flgRead) { // Set the array size ArrayResize(arrParamIP,szArrIP); //--- // Fill the array with values from the file flgRead=GetValuesParamsFromFile(hFl,arrParamIP); } //--- // If the array size is correct,... if(ArraySize(arrParamIP)==szArrIP) { // ...set the parameters to the variables //--- // Check the Indicator Period for correctness if((int)arrParamIP[0]<=0) { lcheck=10; } else { lcheck=(int)arrParamIP[0]; } gPeriod_Ind=(int)lcheck; //--- gTakeProfit=arrParamIP[1]; gStopLoss=arrParamIP[2]; gReverse=arrParamIP[3]; //--- // Check the Lot for correctness if(arrParamIP[4]<=0) { lcheck=0.1; } else { lcheck=arrParamIP[4]; } gLot=lcheck; } } else // If the file does not exist { iZeroMemory(); // Zero out variables //--- // When creating the file, write current parameters of the Expert Advisor //--- // Get the file handle to write to the file int hFl2=FileOpen(nm_fl,FILE_WRITE|FILE_CSV|FILE_ANSI,""); //--- if(hFl2!=INVALID_HANDLE) // If the handle has been obtained { string sep="="; //--- // Parameter names and values are obtained from arrays in the ARRAYS.mqh file for(int i=0; i<szArrIP; i++) { FileWrite(hFl2,arr_nmparams[i],sep,arr_vparams[i]); } //--- FileClose(hFl2); // Close the file //--- Print("File with parameters of the "+NAME_EXPERT+" Expert Advisor created successfully."); } } //--- FileClose(hFl); // Close the file }
Las funciones WriteReadParameters y GetValuesParamsFromFile se pueden encontrar en el archivo FILE_OPERATIONS.mqh.
Algunas de las funciones ya se han descrito en mi artículo anterior "How to Prepare MetaTrader 5 Quotes for Other Applications" ("Cómo Preparar Cuotas de MetaTrader 5 para Otras Aplicaciones"), por tanto no insistiremos en ello aquí. Tampoco debería tener dificultades con funciones de trading, puesto que son muy claras y se comentan con detenimiento. Ahora nos centraremos en el tema principal de este artículo.
3. Interacción con el Panel de Usuario
El archivo !OnChartEvent.mqh contiene funciones para la interacción con el panel de usuario. Las variables y arrays que se usan en muchas funciones se declaran en el ámbito global al principio:
// Current value on the panel or // entered in the input box string currVal=""; bool flgDialogWin=false; // Flag for panel existence int szArrList=0,// Size of the option list array number=-1; // Parameter number in the panel list string nmMsgBx="", // Name of the dialog window nmValObj=""; // Name of the selected object //--- // Option list arrays in the dialog window string lenum[],lenmObj[]; //--- // colors of the dialog window elements color clrBrdBtn=clrWhite, clrBrdFonMsg=clrDimGray,clrFonMsg=C'15,15,15', clrChoice=clrWhiteSmoke,clrHdrBtn=clrBlack, clrFonHdrBtn=clrGainsboro,clrFonStr=C'22,39,38';
A esto le siguen las funciones principales que gestionan eventos. En nuestro ejemplo, necesitaremos gestionar dos eventos:
- El evento CHARTEVENT_OBJECT_CLICK - click con el botón izquierdo en el objeto gráfico.
- El evento CHARTEVENT_OBJECT_EDIT - final de edición de texto en el objeto gráfico Edit.
Puede leer más sobre otros eventos de MQL5 en el material de Referencia de MQL5.
Primero configuremos una comprobación de la gestión de eventos solo en tiempo real, siempre suponiendo que el modo de configuración "On The Fly" esté activado (SettingOnTheFly). La gestión de eventos se llevará a cabo con funciones separadas: ChartEvent_ObjectClick y ChartEvent_ObjectEndEdit.
//+------------------------------------------------------------------+ //| USER EVENTS | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // If the event is real time and the "On The Fly" setting mode is enabled if(NotTest() && SettingOnTheFly) { //+------------------------------------------------------------------+ //| THE CHARTEVENT_OBJECT_CLICK EVENT | //+------------------------------------------------------------------+ if(ChartEvent_ObjectClick(id,lparam,dparam,sparam)) { return; } //--- //+------------------------------------------------------------------+ //| THE CHARTEVENT_OBJECT_ENDEDIT EVENT | //+------------------------------------------------------------------+ if(ChartEvent_ObjectEndEdit(id,lparam,dparam,sparam)) { return; } } //--- return; }
Al hacer click en el objeto que pertenece a la lista, aparecerá un cuadro de diálogo en el panel de información que le permitirá seleccionar otro valor o introducir un valor nuevo en el campo de entrada.
Fig. 3. Cuadro de diálogo para modificar el valor del parámetro seleccionado
Echemos un vistazo más detallado a su funcionamiento. Al hacer click en un objeto gráfico, el programa usa primero la función ChartEvent_ObjectClick para comprobar por medio del identificador del evento si realmente se dio un click en un objeto gráfico.
Si desea que el cuadro de diálogo se abra en medio del gráfico, debe saber el tamaño del gráfico. Se puede obtener indicando las propiedades CHART_WIDTH_IN_PIXELS y CHART_HEIGHT_IN_PIXELS en la función ChartGetInteger. Entonces, el programa cambia al panel DialogWindowInfoPanel. Se puede familiarizar con todas las propiedades de los gráficos en el material de Referencia MQL5.
Abajo puede ver el código para la implementación de las acciones mencionadas:
//+------------------------------------------------------------------+ //| THE CHARTEVENT_OBJECT_CLICK EVENT | //+------------------------------------------------------------------+ bool ChartEvent_ObjectClick(int id,long lparam,double dparam,string sparam) { // If there was an event of clicking on a graphical object if(id==CHARTEVENT_OBJECT_CLICK) // id==1 { Get_STV(); // Get all data on the symbol //--- string clickedChartObject=sparam; // Name of the clicked object //--- // Get the chart size width_chart=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS,0); height_chart=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS,0); //--- DialogWindowInfoPanel(clickedChartObject); } //--- return(false); }
Usando la función DialogWindowInfoPanel, comprobaremos primero si el cuadro de diálogo está actualmente abierto. Si el cuadro de diálogo no se encuentra, la función GetNumberClickedObjIP comprueba si el click se hizo en relación a un objeto de la lista en el panel de información. Si el objeto en el que ha hecho click es el objeto de la lista, la función devolverá el número de elemento relevante del array de objetos. Usando ese número, la función InitArraysAndDefault determinará a continuación el tamaño del array de lista en el cuadro de diálogo y valores por defecto. Si todas las acciones se realizan con éxito, el cuadro de diálogo aparecerá.
Si la función DialogWindowInfoPanel determina que el cuadro de diálogo ya está abierto, el programa comprobará si hubo un click en un objeto en el cuadro de diálogo. Por ejemplo, al abrir el cuadro de diálogo, la línea cuyo valor ya se muestra en el panel aparecerá como seleccionada. Si hace click en otra opción en la lista, el programa usará la función SelectionOptionInDialogWindow, que selecciona la opción de lista de cuadro de diálogo cliqueada.
Si hace click en la opción de lista que ya está seleccionada, este objeto se identificará como un objeto a editar, y aparecerá un campo de entrada para introducir un valor nuevo al hacer click en el campo. La función SetEditObjInDialogWindow es responsable de la configuración del campo de entrada.
Y finalmente, si se hizo click en el botón Apply (Aplicar), el programa comprobará si el valor se ha modificado. Si se modificó, el nuevo valor aparecerá en el panel y se escribirá en el archivo.
El código de la función principal del cuadro de diálogo se muestra a continuación:
//+------------------------------------------------------------------+ //| DIALOG WINDOW OF THE INFO PANEL | //+------------------------------------------------------------------+ void DialogWindowInfoPanel(string clickObj) { // If there is currently no dialog window if(!flgDialogWin) { // Get the object number in the array // Exit if none of the parameters displayed on the panel has been clicked if((number=GetNumberClickedObjIP(clickObj))==-1) { return; } //--- // Initialization of default values //and determination of the list array size if(!InitArraysAndDefault()) { return; } //--- // Set the dialog window SetDialogWindow(); //--- flgDialogWin=true; // Mark the dialog window as open ChartRedraw(); } else // If the dialog window is open { // Set the input box for the modification of the value SetEditObjInDialogWindow(clickObj); //--- // If one of the buttons in the dialog box is clicked if(clickObj=="btnApply" || clickObj=="btnCancel") { // If the Apply button is clicked if(clickObj=="btnApply") { // Compare values on the panel with the ones on the list // If the value on the list is different from the one that is currently displayed on the panel // (which means it is different from the one in the file), // ...change the value on the panel and update the file if(currVal!=ObjectGetString(0,nmValObj,OBJPROP_TEXT)) { // Update the value on the panel ObjectSetString(0,nmValObj,OBJPROP_TEXT,currVal); ChartRedraw(); //--- // Read all data on the panel and write it to the file WriteNewData(); } } //--- DelDialogWindow(lenmObj); // Delete the dialog window iZeroMemory(); // Zero out the variables //--- // Update the data SetParameters(); GetHandlesIndicators(); SetInfoPanel(); //--- ChartRedraw(); } else // If neither Apply nor Cancel has been clicked { // Selection of the dialog window list option SelectionOptionInDialogWindow(clickObj); //--- ChartRedraw(); } } }
Cada vez que se introduce un nuevo valor en el campo de entrada, se genera el evento CHARTEVENT_OBJECT_EDIT, y el programa cambia a la función ChartEvent_ObjectEndEdit. Si el valor del cuadro de diálogo se modificó, el valor introducido se guardará, se revisará su corrección y se asignará al objeto en la lista. Puede verlo más en detalle en el código de abajo:
//+------------------------------------------------------------------+ //| THE CHARTEVENT_OBJECT_ENDEDIT EVENT | //+------------------------------------------------------------------+ bool ChartEvent_ObjectEndEdit(int id,long lparam,double dparam,string sparam) { if(id==CHARTEVENT_OBJECT_ENDEDIT) // id==3 { string editObject=sparam; // Name of the edited object //--- // If the value has been entered in the input box in the dialog window if(editObject=="editValIP") { // Get the entered value currVal=ObjectGetString(0,"editValIP",OBJPROP_TEXT); //--- // (0) Period Indicator if(number==0) { // Correct the value if it is wrong if(currVal=="0" || currVal=="" || SD(currVal)<=0) { currVal="1"; } //--- // Set the entered value ObjectSetString(0,"enumMB0",OBJPROP_TEXT,currVal); } //--- // (4) Lot if(number==4) { // Correct the value if it is wrong if(currVal=="0" || currVal=="" || SD(currVal)<=0) { currVal=DS(SS.vol_min,2); } //--- // Set the entered value ObjectSetString(0,"enumMB0",OBJPROP_TEXT,DS2(SD(currVal))); } //--- // (1) Take Profit (p) // (2) Stop Loss (p) if(number==1 || number==2) { // Correct the value if it is wrong if(currVal=="0" || currVal=="" || SD(currVal)<=0) { currVal="1"; } //--- // Set the entered value ObjectSetString(0,"enumMB1",OBJPROP_TEXT,currVal); } //--- DelObjbyName("editValIP"); ChartRedraw(); } } //--- return(false); }
Puede ver el Asesor Experto en acción en el vídeo de abajo:
Conclusión
Puede descargarse los archivos comprimidos al final del artículo para estudiarlos más a fondo.
Espero que este artículo sea de ayuda para aquellos que hayan empezado a estudiar MQL5 para encontrar respuestas rápidas a muchas preguntas usando los sencillos ejemplos dados. No incluí a propósito algunas comprobaciones de los fragmentos facilitados.
Por ejemplo, si cambia la altura/anchura del gráfico cuando el cuadro de diálogo está abierto, el cuadro de diálogo no se centrará automáticamente. Y si además usted lo complementa seleccionando otra opción de la lista, el objeto que sirve para seleccionar la línea relevante cambiará considerablemente. Estos serán sus deberes. Es muy importante practicar programación, y cuanto más practique, mejor hará las cosas.
¡Buena suerte!
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/572
- 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