
Visualización de transacciones en un gráfico (Parte 2): Visualización gráfica de datos
Introducción
En este artículo, completaremos el script para visualizar operaciones en el gráfico que comenzamos a implementar en «Visualización de transacciones en un gráfico (Parte 1): Seleccionar un periodo para el análisis». Escribiremos el código para seleccionar los datos de una única operación seleccionada por el usuario, así como para dibujar los objetos de datos necesarios en el gráfico, que luego guardaremos en un archivo como pantalla de impresión de los gráficos correspondientes. El script nos permitirá ahorrar una cantidad significativa de tiempo en el trabajo técnico relacionado con la formación de gráficos de operaciones, así como en guardarlos en pantallas de impresión para su análisis retrospectivo. Quienes no deseen dedicar tiempo a montar los proyectos pueden descargar una versión ya preparada del script en el Market.
Seleccionar los datos de una operación
A diferencia de la selección de datos sobre operaciones durante un periodo determinado, la selección de datos sobre una única operación simplificará considerablemente la aplicación del caso de selección histórica de pedidos. La principal diferencia aquí es el hecho de que en lugar de la función HistorySelect() predefinida del terminal, vamos a utilizar el método HistorySelectByPosition() para solicitar datos históricos. Los parámetros del método deben recibir POSITION_IDENTIFIER que podemos encontrar en el terminal MetaTrader 5 (Ver -> Caja de Herramientas -> Historial -> Columna "Ticket"). El valor debe pasarse al script a través de la variable global de entrada inp_d_ticket.
En todos los demás aspectos, la lógica del caso Select_one_deal repite completamente la implementación de la lógica del caso anterior, y se presenta en su totalidad en el código siguiente con las mismas inserciones de información para los usuarios.
//--- if one deal is needed case Select_one_deal: res = MessageBox("You have selected analysis of one deal. Continue?","",MB_OKCANCEL); // informed in the message if(res == IDCANCEL) // if interrupted by user { printf("%s - %d -> Scrypt was stoped by user.",__FUNCTION__,__LINE__); // informed in the journal return; // interrupted } MessageBox("Please press 'Ok' and wait for the next message until script will be done."); // informed in the message //--- select by one position if(HistorySelectByPosition(inp_d_ticket)) // select position by id { int total = HistoryDealsTotal(); // total deals if(total <= 0) // if nothing found { printf("%s - %d -> Deal was not found.",__FUNCTION__,__LINE__); // notify MessageBox("Deal was not found with this tiket: "+IntegerToString(inp_d_ticket)+". Script is done."); // informed in the message return; } for(int i=0; i<total; i++) // iterate through the number of deals { //--- try to get deals ticket if((ticket=HistoryDealGetTicket(i))>0) // took the deal number { //--- get deals properties position_id = HistoryDealGetInteger(ticket,DEAL_POSITION_ID); // took the main id entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket,DEAL_ENTRY);// entry or exit? if(entry == DEAL_ENTRY_IN) // if this is an entry { open = HistoryDealGetDouble(ticket,DEAL_PRICE); // take open price time_open =(datetime)HistoryDealGetInteger(ticket,DEAL_TIME); // take open time symbol=HistoryDealGetString(ticket,DEAL_SYMBOL); // take symbol stop_loss = HistoryDealGetDouble(ticket,DEAL_SL); // take Stop Loss take_profit = HistoryDealGetDouble(ticket,DEAL_TP); // take Take Profit //--- magic = (int)HistoryDealGetInteger(ticket,DEAL_MAGIC); // take Magic comment=HistoryDealGetString(ticket,DEAL_COMMENT); // take comment externalID=HistoryDealGetString(ticket,DEAL_EXTERNAL_ID); // take external id volume = HistoryDealGetDouble(ticket,DEAL_VOLUME); // take volume commission = HistoryDealGetDouble(ticket,DEAL_COMMISSION); // take commission value } if(entry == DEAL_ENTRY_OUT) // if this is an exit { close = HistoryDealGetDouble(ticket,DEAL_PRICE); // take close price time_close =(datetime)HistoryDealGetInteger(ticket,DEAL_TIME);// take close time //--- reason = (ENUM_DEAL_REASON)HistoryDealGetInteger(ticket,DEAL_REASON); // take reason swap = HistoryDealGetDouble(ticket,DEAL_SWAP); // take swap profit = HistoryDealGetDouble(ticket,DEAL_PROFIT); // take profit fee = HistoryDealGetDouble(ticket,DEAL_FEE); // take fee } //--- enter data into the main storage //--- check if there is such id if(Find(PositionID,position_id)==-1) // if there is no such deal, { //--- change the dimensions of the arrays ArrayResize(arr_time_open,ArraySize(arr_time_open)+1); // open time ArrayResize(arr_time_close,ArraySize(arr_time_close)+1); // close time ArrayResize(arr_symbol,ArraySize(arr_symbol)+1); // symbols ArrayResize(arr_stop_loss,ArraySize(arr_stop_loss)+1); // stop levels ArrayResize(arr_take_profit,ArraySize(arr_take_profit)+1);// profits ArrayResize(arr_open,ArraySize(arr_open)+1); // entries ArrayResize(arr_close,ArraySize(arr_close)+1); // exits ArrayResize(PositionID,ArraySize(PositionID)+1); // position id //--- ArrayResize(arr_magic,ArraySize(arr_magic)+1); // Magic ArrayResize(arr_extermalID,ArraySize(arr_extermalID)+1); // external id ArrayResize(arr_comment,ArraySize(arr_comment)+1); // comment ArrayResize(arr_volume,ArraySize(arr_volume)+1); // volume ArrayResize(arr_commission,ArraySize(arr_commission)+1); // commission ArrayResize(arr_reason,ArraySize(arr_reason)+1); // reason ArrayResize(arr_swap,ArraySize(arr_swap)+1); // swap ArrayResize(arr_profit,ArraySize(arr_profit)+1); // profit ArrayResize(arr_fee,ArraySize(arr_fee)+1); // fee PositionID[ArraySize(arr_time_open)-1]=position_id; // id if(entry == DEAL_ENTRY_IN) // if this is an entry { arr_time_open[ ArraySize(arr_time_open)-1] = time_open; // deal time arr_symbol[ ArraySize(arr_symbol)-1] = symbol; // instrument symbol arr_stop_loss[ ArraySize(arr_stop_loss)-1] = stop_loss; // deal stop loss arr_take_profit[ ArraySize(arr_take_profit)-1] = take_profit; // deal take profit arr_open[ ArraySize(arr_open)-1] = open; // open price //--- arr_magic[ ArraySize(arr_magic)-1] = magic; // Magic arr_comment[ ArraySize(arr_comment)-1] = comment; // comment arr_extermalID[ ArraySize(arr_extermalID)-1] = externalID; // external id arr_volume[ ArraySize(arr_volume)-1] = volume; // volume arr_commission[ ArraySize(arr_commission)-1] = commission; // commission } if(entry == DEAL_ENTRY_OUT) // if this is an exit { arr_time_close[ ArraySize(arr_time_close)-1] = time_close; // close time arr_close[ ArraySize(arr_close)-1] = close; // close prices //--- arr_reason[ ArraySize(arr_reason)-1] = reason; // reason arr_swap[ ArraySize(arr_swap)-1] = swap; // swap arr_profit[ ArraySize(arr_profit)-1] = profit; // profit arr_fee[ ArraySize(arr_fee)-1] = fee; // fee } } else { int index = Find(PositionID,position_id); // if there was a record already, if(entry == DEAL_ENTRY_IN) // if this was an entry { arr_time_open[index] = time_open; // deal time arr_symbol[index] = symbol; // symbol arr_stop_loss[index] = stop_loss; // deal stop loss arr_take_profit[index] = take_profit; // deal take profit arr_open[index] = open; // open price //--- arr_magic[index] = magic; // Magic arr_comment[index] = comment; // comment arr_extermalID[index] = externalID; // external id arr_volume[index] = volume; // volume arr_commission[index] = commission; // commission } if(entry == DEAL_ENTRY_OUT) // if this is an exit { arr_time_close[index] = time_close; // deal close time arr_close[index] = close; // deal close price //--- arr_reason[index] = reason; // reason arr_swap[index] = swap; // swap arr_profit[index] = profit; // profit arr_fee[index] = fee; // fee } } } } } else { printf("%s - %d -> Error of selecting history deals: %d",__FUNCTION__,__LINE__,GetLastError()); // informed in the journal printf("%s - %d -> Deal was not found.",__FUNCTION__,__LINE__); // informed in the journal MessageBox("Deal was not found with this tiket: "+IntegerToString(inp_d_ticket)+". Script is done."); // informed in the message return; } break;
Ahora que se han descrito ambas opciones, y que todos los almacenamientos se han llenado con los datos necesarios durante la ejecución del programa, podemos empezar a visualizar estos datos en los gráficos del terminal.
Visualización de los gráficos necesarios
Para guardar operaciones en el gráfico, primero tenemos que abrir una nueva ventana con el símbolo necesario a nivel de programa, realizar los ajustes de diseño necesarios, incluido el desplazamiento individual de la sangría a la derecha para que toda la operación sea claramente visible, y llamar a una función predefinida que guardará una pantalla de impresión en la carpeta necesaria.
En primer lugar, vamos a declarar las variables locales que necesitamos para abrir la ventana del gráfico deseado. La variable 'bars' almacenará el valor de desplazamiento para el gráfico de la derecha, las variables 'chart_width' y 'chart_height' almacenarán los tamaños correspondientes para guardar, y el manejador del nuevo gráfico, cuando se abra, se almacenará en la variable 'handle' para acceder al gráfico en el futuro.
//--- data collected, moving on to printing int bars = -1; // number of bars in a shift int chart_width = -1; // chart width int chart_height =-1; // chart height long handle =-1; // chart handle
Antes de iniciar una solicitud para abrir nuevas ventanas de símbolos, debemos solicitar la validez de estos símbolos al historial. Esta comprobación es absolutamente necesaria para evitar el error de abrir un «símbolo inexistente» en la cuenta. Creo que es necesario explicar aquí de dónde puede venir un «símbolo inexistente» si se ha guardado en el historial de operaciones, lo que significa que alguna vez existió.
En primer lugar, esto puede estar relacionado con los tipos de cuenta del corredor. Hoy en día, la mayoría de los corredores ofrecen a los operadores varias opciones de cuenta para que su uso sea lo más rentable y cómodo posible en cuanto a las estrategias de negociación utilizadas. Algunas cuentas cobran una comisión por abrir operaciones pero tienen un diferencial muy bajo, mientras que otros tipos de cuentas tienen un diferencial alto pero no cobran comisión por operación. Por lo tanto, los operadores que operan a medio plazo pueden no pagar una comisión por operación, mientras que el tamaño del diferencial en las operaciones a medio plazo no es tan importante. Por el contrario, los operadores que negocian pequeños impulsos intradía prefieren pagar una comisión por abrir una operación que asumir una pérdida sólo porque el diferencial se haya ampliado «de repente». Normalmente, los corredores agrupan estas condiciones en tipos de cuenta, como Estándar, Oro, Platino, ESN, e introducen un nombre de símbolo para cada cuenta. Por ejemplo, en el caso del par EURUSD en una cuenta estándar, el símbolo en otro tipo de cuenta podría parecerse a EURUSDb, EURUSDz o EURUSD_i dependiendo del broker.
Además, los nombres de los símbolos pueden cambiar dependiendo de la fecha de vencimiento de ciertos instrumentos no relacionados con el comercio de pares de divisas en Forex, pero no vamos a considerar este punto en detalle aquí, ya que el artículo sigue dedicado específicamente a los pares de divisas.
Otra condición para la necesidad de comprobar la validez del símbolo es la ausencia puramente técnica de una suscripción a los instrumentos necesarios en la ventana 'Observación del Mercado' del terminal. Incluso si el nombre de un símbolo existe en la cuenta autorizada, pero no está seleccionado en el menú contextual del terminal (Ver -> Observación del Mercado), no podremos abrir su gráfico con el consiguiente error de la función de llamada.
Empezaremos a implementar la comprobación organizando un bucle para iterar sobre cada herramienta de nuestro almacén, como se muestra a continuación.
for(int i=0; i<ArraySize(arr_symbol); i++) // iterate through all deal symbols
Para comprobar la validez de un símbolo guardado en nuestro contenedor, utilizaremos la función predefinida del terminal SymbolSelect(). El primer parámetro que le pasaremos es el nombre del símbolo en formato string. Se trata de un símbolo cuya validez queremos comprobar. El valor lógico de «true» (verdadero) viene en segundo lugar. Si el segundo parámetro es «true», si el instrumento es válido pero no está seleccionado en la «Observación del Mercado», se seleccionará automáticamente. La lógica de comprobación completa es la siguiente.
//--- check for symbol availability for(int i=0; i<ArraySize(arr_symbol); i++) // iterate through all deal symbols { if(!SymbolSelect(arr_symbol[i],true)) // check if the symbol is in the book and add if not { printf("%s - %d -> Failed to add a symbol %s to the marketbook. Error: %d", __FUNCTION__,__LINE__,arr_symbol[i],GetLastError()); // informed in the journal MessageBox("Failed to add a symbol to the marketbook: "+arr_symbol[i]+ ". Por favor, seleccione 'Mostrar todo' en Observación del Mercado e inténtelo de nuevo. Script is done."); // informed in the message return; // if failed, abort } }
En consecuencia, si no se supera la comprobación de validez del símbolo, finalizamos la ejecución del programa con las notificaciones oportunas para el usuario. Una vez superadas todas las comprobaciones de validez, podemos proceder a abrir los gráficos de símbolos necesarios directamente en el terminal.
En primer lugar, vamos a proporcionar la variable auxiliar deal_close_date del tipo de datos MqlDateTime, que además nos ayudará a ordenar convenientemente todos los gráficos guardados en las carpetas de periodos de tiempo correspondientes. Para la reducción explícita del tipo de datos datetime a MqlDateTime en nuestro almacenamiento, utilizaremos la función terminal predefinida TimeToStruct() como se muestra a continuación.
MqlDateTime deal_close_date; // deal closure date in the structure TimeToStruct(arr_time_close[i],deal_close_date); // pass date to the structure
Los gráficos se dibujarán en función de los datos definidos por el usuario en las variables main_graph, addition_graph, addition_graph_2 y addition_graph_3. Si la variable contiene el valor de enumeración PERIOD_CURRENT, no dibujamos ningún gráfico. Si se introduce un valor específico en la variable (por ejemplo PERIOD_D1), tomamos este gráfico para dibujarlo. Realizaremos esta comprobación para todas las variables introducidas de la siguiente forma (la variable principal se muestra a continuación como ejemplo):
//--- check the main one if(main_graph != PERIOD_CURRENT) // if the main one selected
Para dibujar cada gráfico, abra un nuevo gráfico con el símbolo deseado. El gráfico de símbolos se abrirá utilizando la función predefinida de terminal ChartOpen(), mientras se pasa el símbolo y el marco temporal requeridos desde el almacenamiento, como se muestra a continuación.
//--- open the required chart handle = ChartOpen(arr_symbol[i],main_graph); // open the necessary symbol chart
Una vez abierto el gráfico, le aplicamos todos los ajustes de usuario estándar que he mencionado anteriormente. Para ello, utilizaremos la función predefinida terminal ChartApplyTemplate(), que nos ayudará mucho con esto y nos ahorrará escribir el código nosotros mismos. Los parámetros de la función ChartApplyTemplate() obtienen el manejador del gráfico obtenido al llamar a la función ChartOpen(), así como el nombre de la plantilla especificada por el usuario para el marco temporal de la operación en el formato dailyHistorytemp. A continuación se muestra el código para llamar a la función de aplicación de plantillas.
ChartApplyTemplate(handle,main_template); // apply template
Hagamos aquí una pequeña digresión para aquellos que no hayan utilizado plantillas en el terminal MetaTrader 5 hasta ahora. Si utilizamos una plantilla «fea», la pantalla de impresión guardada de la operación puede resultar «irritante» o incluso «inútil». Siga estos pasos para crear su propia plantilla dailyHistorytemp:
- Abra el gráfico de cualquier símbolo mediante Archivo -> Nuevo gráfico.
- Una vez abierto el gráfico, pulse F8 para abrir la ventana Propiedades, por ejemplo «PropertiesGBPAUD,Daily».
- La ventana Propiedades contiene varias pestañas: General, Mostrar y Colores. En cada uno de ellos, realice los ajustes que le resulten más familiares, por ejemplo, para un gráfico diario, y haga clic en Aceptar. Encuentre los detalles aquí: Ajustes de gráficos (ayuda oficial del terminal).
- Tras hacer clic en Aceptar, se cierra la ventana Propiedades y el gráfico adopta la forma que usted necesita.
- Ahora, en el menú contextual, seleccione Gráficos - Plantillas - Guardar plantilla. Aparece la ventana de guardado de plantillas. Introduzca dailyHistorytemp.tpl en «Nombre de archivo» y haga clic en Guardar.
- Después de eso, en la carpeta terminal ..MQL5\Profiles\Templates aparecerá el archivo dailyHistorytemp.tpl, que podrá utilizar en el script. Lo principal a tener en cuenta es que el nombre de la plantilla se introduce en el script sin la extensión .tpl.
Ahora volvamos a nuestro código. Una vez aplicada la plantilla deseada, tenemos que hacer un pequeño retraso en la ejecución del código para dar tiempo a que el gráfico se cargue con la calidad deseada. De lo contrario, es posible que el gráfico no se muestre correctamente debido al tiempo necesario para cargar los datos de precios históricos requeridos en el terminal. Por ejemplo, si no ha abierto un gráfico durante bastante tiempo, el terminal necesita tiempo para mostrarlo correctamente. Anunciaremos el tiempo de retardo a través de la función predefinida de terminal Sleep(), como se muestra a continuación.
Sleep(2000); // wait for the chart to load
Como retardo, utilizaremos el valor de 2000 milisegundos o 2 segundos, tomado puramente de la práctica, para que se garantice que el gráfico tenga tiempo de cargar, y la ejecución del script no llegue a largos minutos con un gran número de operaciones. Para personalizar este valor usted mismo, puede introducirlo de forma independiente en la configuración del script para acelerar o ralentizar el proceso, en función del rendimiento de su equipo o de su conexión a Internet. Como demuestra la práctica, dos segundos serán suficientes en la mayoría de los casos.
Ahora tenemos que desactivar el desplazamiento de los gráficos a los valores de barra más recientes, ya que estamos analizando la historia y no necesitamos nuevos ticks para desplazar nuestro gráfico a la derecha todo el tiempo. Esto se puede hacer estableciendo la propiedad CHART_AUTOSCROLL a 'false' del gráfico necesario a través de la función predefinida ChartSetInteger(), como se muestra a continuación.
ChartSetInteger(handle,CHART_AUTOSCROLL,false); // disable auto scroll
Ahora que el desplazamiento automático está desactivado, primero tenemos que contar el número de barras del gráfico del marco temporal correspondiente a la izquierda para desplazar el gráfico hacia el historial del periodo de cierre de la operación en cuestión. Podemos obtener el valor a través de la función predefinida de terminal iBarShift(), pasando como parámetros el símbolo, el timeframe del gráfico y la hora de cierre de la operación, ya que queremos ver en la pantalla de impresión la operación completa de principio a fin. En el parámetro 'exact', pasamos 'false' por si el historial es realmente profundo. Sin embargo, no es tan crítico para nuestra aplicación en este caso. A continuación se muestra la llamada completa al método con los parámetros.
bars = iBarShift(arr_symbol[i],main_graph,arr_time_close[i],false); // get the shift for the deal time
Una vez que conocemos el desplazamiento del gráfico que necesitamos, podemos mostrar exactamente el periodo que capturará la operación que necesitamos en el historial. Podemos desplazar el gráfico en la dirección deseada la distancia que necesitemos utilizando la variable terminal predefinida ChartNavigate() pasándole los siguientes parámetros, como se muestra a continuación.
ChartNavigate(handle,CHART_CURRENT_POS,-bars+bars_from_right_main); // shifted the chart with a custom margin
Para desplazar el gráfico, pasamos el manejador del gráfico, el valor de CHART_CURRENT_POS posición actual de la enumeración ENUM_CHART_POSITION, así como el desplazamiento a la operación que obtuvimos anteriormente en la variable bars con el desplazamiento introducido por el usuario para evaluar el potencial de movimiento del precio tras salir de la posición.
Después de las transformaciones del gráfico descritas, llame al método ChartRedraw() por si acaso y comience a dibujar datos adicionales en el gráfico para analizar las operaciones históricas.
Para dibujar los elementos del panel de información personalizado y las líneas que indican la apertura y el cierre de la posición, así como los niveles Stop Loss y Take Profit, utilizaremos las funciones personalizadas paintDeal() y paintPanel() correspondientes. Los definiremos nosotros mismos basándonos en patrones de comportamiento estándar para trabajar con gráficos de terminal, donde paintDeal() dibujará líneas de precios de apertura y cierre de la operación, así como Take Profit y Stop Loss , mientras que el método paintPanel() contendrá una tabla con toda la información de la operación en la esquina de la pantalla.
La definición detallada de los métodos figura en la sección siguiente. Aquí simplemente indicaremos que los métodos serán llamados en este segmento de código. Esto también se hace desde el punto de vista de que no necesariamente tiene que utilizar la implementación dada en el presente artículo para dibujar estos dos grupos de elementos. Puede redefinirlos usted mismo, manteniendo la firma deseada. La aplicación de estos métodos en el presente artículo es un ejemplo de la relación óptima entre belleza y contenido informativo de los gráficos en el momento de escribirlo en el código. El objetivo principal aquí es mantener la posición de las llamadas a métodos en el código principal.
//--- draw the deal paintDeal(handle,PositionID[i],arr_stop_loss[i],arr_take_profit[i],arr_open[i],arr_close[i],arr_time_open[i],arr_time_close[i]); //--- draw the information panel paintPanel(handle,PositionID[i],arr_stop_loss[i],arr_take_profit[i],arr_open[i], arr_close[i],arr_time_open[i],arr_time_close[i],arr_magic[i],arr_comment[i], arr_extermalID[i],arr_volume[i],arr_commission[i],arr_reason[i],arr_swap[i], arr_profit[i],arr_fee[i],arr_symbol[i],(int)SymbolInfoInteger(arr_symbol[i],SYMBOL_DIGITS));
Después de que los métodos hayan dibujado las líneas de reparto y el panel de información en el gráfico, podemos proceder a la implementación de guardar una pantalla de impresión de todo lo ocurrido en el gráfico actual. Para ello, primero determinamos las dimensiones futuras de la pantalla de impresión en anchura y altura simplemente solicitando estos datos al gráfico abierto mediante la función ChartSetInteger() redefinida del terminal, como se muestra a continuación.
//--- get data by screen size chart_width = (int) ChartGetInteger(handle,CHART_WIDTH_IN_PIXELS); // look at the chart width chart_height = (int) ChartGetInteger(handle,CHART_HEIGHT_IN_PIXELS); // look at the chart height
Pasamos los valores de enumeración ENUM_CHART_PROPERTY_INTEGER para el ancho CHART_WIDTH_IN_PIXELS y el alto CHART_HEIGHT_IN_PIXELS respectivamente como los parámetros correspondientes para mostrar el gráfico.
Una vez recibidos los datos de tamaño, tendremos que crear una ruta para guardar el gráfico de la pantalla de impresión del acuerdo en la carpeta estándar del terminal. Para evitar que el EA coloque todos los archivos en una carpeta, sino que los ordene para comodidad del usuario, automatizamos este proceso a través del nombre del archivo en la siguiente cadena.
string name_main_screen = brok_name+"/"+ IntegerToString(account_num)+"/"+ IntegerToString(deal_close_date.year)+"-"+IntegerToString(deal_close_date.mon)+ "-"+IntegerToString(deal_close_date.day)+"/"+ IntegerToString(PositionID[i])+"/"+ EnumToString(main_graph)+IntegerToString(PositionID[i])+".png"; // assign the name
Gráficamente, la estructura de clasificación de archivos en carpetas de un directorio estándar será la que se muestra en la Figura 1.
Figura 1. Estructura de direcciones de carpetas de pantallas de impresión guardadas por operaciones
Como podemos ver, los ficheros de gráficos se ordenarán por nombre de broker, número de cuenta, año, mes y día de ejecución, de forma que el usuario pueda encontrar fácilmente la operación deseada sin necesidad de buscar el nombre del fichero en una lista general. Los distintos plazos se ubicarán en la carpeta del número de posición correspondiente del terminal.
Guardaremos directamente la información llamando a la función predefinida de terminal ChartScreenShot(), pasándole como parámetros el manejador del gráfico requerido, los tamaños de pantalla de impresión que obtuvimos anteriormente, que se corresponden con los tamaños de los gráficos, y también el nombre del fichero que contiene toda la estructura de direcciones de carpetas, tal y como se muestra en la Figura 1 y en el código siguiente.
ChartScreenShot(handle,name_main_screen,chart_width,chart_height,ALIGN_LEFT); // make a screenshot
Si las carpetas especificadas en la jerarquía no existen en la carpeta estándar del terminal, éste las creará automáticamente sin intervención del usuario.
Después de guardar el archivo, podemos cerrar el gráfico para no saturar la vista del terminal, especialmente si la descarga contiene un gran número de operaciones históricas en la cuenta. Cerraremos el gráfico utilizando la función terminal predefinida ChartClose() pasándole el handle del gráfico requerido, para no cerrar nada innecesario. La llamada a la función se muestra a continuación.
ChartClose(handle); // closed the chart
Repetiremos esta operación de forma similar para todos los plazos especificados por el usuario en las entradas. Ahora, para completar nuestro script, necesitamos definir el comportamiento de los métodos paintDeal() y paintPanel() fuera del código principal del programa.
Dibujar objetos de datos en gráficos
Para colocar cómodamente la información en el gráfico de la pantalla de impresión, sólo tenemos que redefinir dos métodos, que determinarán cómo se dibujarán exactamente los datos requeridos por el usuario.
Comencemos con una descripción del método paintDeal(). Su objetivo es dibujar gráficos para una posición asociados a la ubicación de los precios de apertura y cierre, Stop Loss y Take Profit. Para ello, declare la descripción del método con la siguiente firma fuera del cuerpo del código principal:
void paintDeal(long handlE, ulong tickeT, double stop_losS, double take_profiT, double opeN, double closE, datetime timE, datetime time_closE)
Los siguientes valores se especifican en los parámetros del método: handlE - handle del gráfico que vamos a dibujar, tickeT - deal ticket, stop_losS - precio de Stop Loss si está presente, take_profiT - Take Profit si está presente, open price - opeN y close price - closE, deal open time - timE y deal close time - time_closE.
Empecemos a dibujar con el nombre del objeto, que corresponderá a un nombre único que no debe repetirse. Por lo tanto, en el nombre vamos a implementar una característica que este objeto corresponde a una parada en la forma de «name_sl_». Para que el nombre sea único, añadiremos también el número de billete de la operación, como se muestra a continuación.
string name_sl = "name_sl_"+IntegerToString(tickeT); // assign the name
Ahora podemos crear el objeto gráfico propiamente dicho utilizando la función ObjectCreate() predefinida del terminal, que dibuja el nivel Stop Loss por posición histórica en el gráfico. Los parámetros pasados son el manejador del gráfico y el nombre único de la variable name_sl. Especifique el valor OBJ_ARROW_LEFT_PRICE como un tipo de objeto, lo que significa la etiqueta de precio izquierda de la enumeración ENUM_OBJECT, así como el valor de precio real y el momento en que se colocó la etiqueta en el gráfico, como se muestra a continuación.
ObjectCreate(handlE,name_sl,OBJ_ARROW_LEFT_PRICE,0,timE,stop_losS); // create the left label object
Ahora que el objeto ha sido creado, vamos a establecer los valores de sus campos OBJPROP_COLOR y OBJPROP_TIMEFRAMES. Establezca OBJPROP_COLOR en clrRed, ya que Stop Loss suele ser de color rojo, mientras que OBJPROP_TIMEFRAMES se establece en OBJ_ALL_PERIODS para mostrar en todos los plazos. Aunque la segunda condición no es crítica en esta aplicación. En general, el bloque de dibujo Stop Loss tendrá el siguiente aspecto.
//--- draw stop loss string name_sl = "name_sl_"+IntegerToString(tickeT); // assign the name ObjectCreate(handlE,name_sl,OBJ_ARROW_LEFT_PRICE,0,timE,stop_losS); // create the left label object ObjectSetInteger(handlE,name_sl,OBJPROP_COLOR,clrRed); // add color ObjectSetInteger(handlE,name_sl,OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS); // set visibility ChartRedraw(handlE); // redraw
Después de dibujar cada bloque, llama al método ChartRedraw().
Dibujar el bloque Take Profit será similar a dibujar Stop Loss con las siguientes excepciones. En primer lugar, añada «name_tp_» más el ticket de la operación al nombre único del objeto, y establezca el color de la paleta verde, que corresponde a la designación tradicional del beneficio recibido, mediante el color clrLawnGreen. Por lo demás, la lógica es similar a la del bloque Stop Loss y se presenta completa aquí.
//--- draw take profit string name_tp = "name_tp_"+IntegerToString(tickeT); // assign the name ObjectCreate(handlE,name_tp,OBJ_ARROW_LEFT_PRICE,0,timE,take_profiT); // create the left label object ObjectSetInteger(handlE,name_tp,OBJPROP_COLOR,clrLawnGreen); // add color ObjectSetInteger(handlE,name_tp,OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS); // set visibility ChartRedraw(handlE); // redraw
Pasemos a la implementación de dibujar también el precio de entrada a través de la etiqueta de precio de la izquierda. La diferencia con los bloques anteriores está, en primer lugar, de nuevo en el nombre único del objeto. Añadiremos «name_open_» al principio. Otra diferencia es el color de la línea clrWhiteSmoke, para que no destaque demasiado en el gráfico, pero por lo demás todo es igual.
//--- draw entry price string name_open = "name_open_"+IntegerToString(tickeT); // assign the name ObjectCreate(handlE,name_open,OBJ_ARROW_LEFT_PRICE,0,timE,opeN); // create the left label object ObjectSetInteger(handlE,name_open,OBJPROP_COLOR,clrWhiteSmoke); // add color ObjectSetInteger(handlE,name_open,OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS); // set visibility ChartRedraw(handlE); // redraw
La línea que une las etiquetas de precio de apertura y cierre de la operación se muestra con el mismo color. El tipo de línea será diferente. Al crear un objeto en el parámetro ObjectCreate() del método, pasaremos el valor OBJ_TREND de la enumeración ENUM_OBJECT como tercer parámetro para crear una línea de tendencia. Para posicionar correctamente la línea de tendencia en el gráfico, necesitaremos especificar parámetros adicionales para la posición de dos puntos, donde cada punto tendrá dos atributos: precio y tiempo. Para ello, pasaremos los precios de apertura y cierre opeN y closE a los parámetros posteriores junto con las horas de cierre y apertura en las variables timE y time_closE, como se muestra a continuación.
//--- deal line string name_deal = "name_deal_"+IntegerToString(tickeT); // assign the name ObjectCreate(handlE,name_deal,OBJ_TREND,0,timE,opeN,time_closE,closE); // create the left label object ObjectSetInteger(handlE,name_deal,OBJPROP_COLOR,clrWhiteSmoke); // add color ObjectSetInteger(handlE,name_deal,OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS); // set visibility ChartRedraw(handlE); // redraw
Para visualizar completamente la operación en el gráfico, queda por dibujar la etiqueta de precio de cierre de la operación. Para conseguirlo, utilizaremos la etiqueta de precio correcto, de modo que la información se muestre en la pantalla de impresión de una forma visualmente más agradable. Para dibujar la etiqueta derecha, el método ObjectCreate() debe recibir el valor OBJ_ARROW_RIGHT_PRICE como tercer parámetro, que significa la etiqueta de precio derecha de la enumeración ENUM_OBJECT. Para el resto del dibujo sólo necesitamos el precio y la hora, que vamos a pasar a través de las correspondientes variables time_closE, closE como se muestra a continuación.
//--- draw exit price string name_close = "name_close"+IntegerToString(tickeT); // assign the name ObjectCreate(handlE,name_close,OBJ_ARROW_RIGHT_PRICE,0,time_closE,closE);// create the left label object ObjectSetInteger(handlE,name_close,OBJPROP_COLOR,clrWhiteSmoke); // add color ObjectSetInteger(handlE,name_close,OBJPROP_TIMEFRAMES,OBJ_ALL_PERIODS); // set visibility ChartRedraw(handlE); // redraw
Esto completa la descripción de nuestro método personalizado paintDeal() para dibujar las líneas de entrada y salida de la posición. Ahora podemos proceder a describir el método para dibujar el panel de información completa del acuerdo en el método paintPanel().
Describir el método para dibujar el panel nos obligará a tener una estructura más compleja de métodos responsables de dibujar etiquetas de texto, como OBJ_LABEL etiquetas de texto de tipo, ENUM_OBJECT enumeración y OBJ_RECTANGLE_LABEL objeto para crear y diseñar interfaces gráficas de usuario. Vamos a declarar los métodos personalizados correspondientes llamados LabelCreate() para crear etiquetas de texto y RectLabelCreate() para crear una etiqueta rectangular. Comenzaremos la descripción con los métodos auxiliares, para pasar después a la descripción del método principal paintPanel(), en el que utilizaremos métodos auxiliares.
En general, la estructura de nuestros métodos de script se parecerá a la de la Figura 2.
Figura 2. Estructura de los métodos personalizados para dibujar gráficos
Declare el método LabelCreate() con la siguiente firma como parámetros:
bool LabelCreate(const long chart_ID=0, // chart ID const string name="Label", // label name const int sub_window=0, // subwindow number const long x=0, // X coordinate const long y=0, // Y coordinate const ENUM_BASE_CORNER corner=CORNER_LEFT_UPPER, // chart corner for anchoring const string text="Label", // text const string font="Arial", // font const int font_size=10, // font size const color clr=clrRed, // color const double angle=0.0, // text angle const ENUM_ANCHOR_POINT anchor=ANCHOR_LEFT_UPPER, // anchor type const bool back=false, // in the background const bool selection=false, // select to move const bool hidden=true, // hidden in the list of objects const long z_order=0) // priority for clicking with a mouse
El parámetro chart_ID recibe el manejador del gráfico en el que tenemos que dibujar el objeto: 'name' es un nombre de objeto único, mientras que el valor 0 del parámetro sub_window significa que queremos dibujar el objeto en la ventana principal del gráfico. Las coordenadas de la esquina superior izquierda del objeto se pasarán a través de los parámetros X e Y respectivamente. Podemos cambiar la vinculación de la esquina del objeto al gráfico desde la esquina izquierda estándar pasando el valor correspondiente al parámetro 'corner', pero dejaremos ahí el valor por defecto de ANCHOR_LEFT_UPPER. Pase el valor de la cadena de la información a mostrar en el parámetro 'text'. El tipo de visualización, como el tipo de fuente y su tamaño, color y ángulo, se pasarán en los parámetros 'font', 'font_size', 'clr' y 'angle' correspondientes. También haremos que nuestro objeto quede oculto en la lista de objetos para el usuario y no seleccionable con el ratón, utilizando los parámetros 'selection' y 'hidden'. El parámetro z_order será el responsable del orden de prioridad de los clics del ratón.
Comencemos la descripción del método restableciendo la variable error para que sea posible controlar correctamente el resultado de la creación de un objeto en el futuro a través de la función terminal predefinida ResetLastError(). La creación del resultado de la creación del objeto de tipo OBJ_LABEL se maneja a través del operador lógico if mientras se llama a la función ObjectCreate() en él, como se muestra a continuación. Si el objeto no se crea, informe al usuario de ello en el registro del EA e interrumpa la ejecución del método mediante la sentencia return como de costumbre.
//--- reset the error value ResetLastError(); //--- create a text label if(!ObjectCreate(chart_ID,name,OBJ_LABEL,sub_window,0,0)) { Print(__FUNCTION__, ": failed to create the text label! Error code = ",GetLastError()); return(false); }
Si el objeto se ha creado correctamente, inicialice los campos de propiedad del objeto mediante las funciones terminales predefinidas ObjectSetInteger(), ObjectSetString() y ObjectSetDouble() para darle la apariencia requerida. Utilice la función ObjectSetInteger() para establecer los valores de las coordenadas correspondientes, el ángulo de anclaje del objeto, el tamaño de fuente, el método de anclaje del objeto, el color, el modo de visualización, así como las propiedades relacionadas con la visibilidad del objeto para el usuario. Establezca los valores de ángulo de la fuente mediante la función ObjectSetDouble(), mientras que la función ObjectSetString() se utiliza para definir el contenido del texto pasado y el tipo de fuente para su visualización. A continuación se presenta la aplicación completa del cuerpo del método.
//--- reset the error value ResetLastError(); //--- create a text label if(!ObjectCreate(chart_ID,name,OBJ_LABEL,sub_window,0,0)) { Print(__FUNCTION__, ": failed to create the text label! Error code = ",GetLastError()); return(false); } //--- set label coordinates ObjectSetInteger(chart_ID,name,OBJPROP_XDISTANCE,x); ObjectSetInteger(chart_ID,name,OBJPROP_YDISTANCE,y); //--- set the chart's corner, relative to which point coordinates are defined ObjectSetInteger(chart_ID,name,OBJPROP_CORNER,corner); //--- set the text ObjectSetString(chart_ID,name,OBJPROP_TEXT,text); //--- set the text font ObjectSetString(chart_ID,name,OBJPROP_FONT,font); //--- set font size ObjectSetInteger(chart_ID,name,OBJPROP_FONTSIZE,font_size); //--- set the text angle ObjectSetDouble(chart_ID,name,OBJPROP_ANGLE,angle); //--- set anchor type ObjectSetInteger(chart_ID,name,OBJPROP_ANCHOR,anchor); //--- set the color ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr); //--- display in the foreground (false) or background (true) ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back); //--- enable (true) or disable (false) the mode of moving the label by mouse ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection); ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection); //--- hide (true) or display (false) graphical object name in the object list ObjectSetInteger(chart_ID,name,OBJPROP_HIDDEN,hidden); //--- set the priority for receiving the event of a mouse click on the chart ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order); //--- successful execution return(true);
Declara el método RectLabelCreate() con la siguiente firma como parámetros de creación del objeto:
bool RectLabelCreate(const long chart_ID=0, // chart ID const string name="RectLabel", // label name const int sub_window=0, // subwindow number const int x=19, // X coordinate const int y=19, // Y coordinate const int width=150, // width const int height=20, // height const color back_clr=C'236,233,216', // background color const ENUM_BORDER_TYPE border=BORDER_SUNKEN, // border type const ENUM_BASE_CORNER corner=CORNER_LEFT_UPPER, // chart corner for anchoring const color clr=clrRed, // flat border color (Flat) const ENUM_LINE_STYLE style=STYLE_SOLID, // flat border style const int line_width=1, // flat border width const bool back=true, // 'true' in the background const bool selection=false, // select to move const bool hidden=true, // hidden in the list of objects const long z_order=0) // priority for clicking with a mouse
Los parámetros del método RectLabelCreate() son muy similares a los parámetros del método LabelCreate() declarado anteriormente, excepto por los ajustes adicionales para el borde de la etiqueta rectangular, que servirá de fondo para mostrar los datos del objeto del método anterior. Parámetros adicionales para configurar el borde del objeto: <border> - tipo de borde definido por la enumeración ENUM_BORDER_TYPE con el valor por defecto de BORDER_SUNKEN, <style> - estilo de borde definido por la enumeración ENUM_LINE_STYLE utilizando el valor por defecto de STYLE_SOLID y line_width - ancho de línea del borde como valor entero.
La definición del cuerpo del método tendrá un aspecto similar al anterior y, del mismo modo, constará de dos secciones globales: la creación del objeto y la definición de sus propiedades a través de los correspondientes métodos terminales predefinidos, tal y como se muestra a continuación.
//--- reset the error value ResetLastError(); // reset error //--- create a rectangle label if(ObjectCreate(chart_ID,name,OBJ_RECTANGLE_LABEL,sub_window,0,0)) // create object { //--- set label coordinates ObjectSetInteger(chart_ID,name,OBJPROP_XDISTANCE,x); // assign x coordinate ObjectSetInteger(chart_ID,name,OBJPROP_YDISTANCE,y); // assign y coordinate //--- set label size ObjectSetInteger(chart_ID,name,OBJPROP_XSIZE,width); // width ObjectSetInteger(chart_ID,name,OBJPROP_YSIZE,height); // height //--- set the background color ObjectSetInteger(chart_ID,name,OBJPROP_BGCOLOR,back_clr); // background color //--- set border type ObjectSetInteger(chart_ID,name,OBJPROP_BORDER_TYPE,border); // border type //--- set the chart corner, relative to which point coordinates are defined ObjectSetInteger(chart_ID,name,OBJPROP_CORNER,corner); // anchor corner //--- set flat border color (in Flat mode) ObjectSetInteger(chart_ID,name,OBJPROP_COLOR,clr); // frame //--- set flat border line style ObjectSetInteger(chart_ID,name,OBJPROP_STYLE,style); // style //--- set flat border width ObjectSetInteger(chart_ID,name,OBJPROP_WIDTH,line_width); // width //--- display in the foreground (false) or background (true) ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back); // default is background //--- enable (true) or disable (false) the mode of moving the label by mouse ObjectSetInteger(chart_ID,name,OBJPROP_SELECTABLE,selection); // is it possible to select ObjectSetInteger(chart_ID,name,OBJPROP_SELECTED,selection); // //--- hide (true) or display (false) graphical object name in the object list ObjectSetInteger(chart_ID,name,OBJPROP_HIDDEN,hidden); // is it visible in the list //--- set the priority for receiving the event of a mouse click on the chart ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order); // no events //--- successful execution } return(true);
Ahora que todos los métodos auxiliares han sido descritos, vamos a definir el cuerpo del método principal que dibujará todo el panel - paintPanel(). Las entradas contendrán los campos necesarios para mostrar la información completa de la operación al usuario, como se muestra a continuación.
void paintPanel(long handlE, ulong tickeT, double stop_losS, double take_profiT, double opeN, double closE, datetime timE, datetime time_closE, int magiC, string commenT, string externalIDD, double volumE, double commissioN, ENUM_DEAL_REASON reasoN, double swaP, double profiT, double feE, string symboL, int digitS )
Como en los métodos anteriores, el primer parámetro será el encargado de definir el manejador del gráfico, sobre el que se crearán todos los objetos asociados al panel de información. Todos los demás parámetros repetirán los campos del objeto de operación histórico.
Comenzaremos la implementación del método para dibujar el panel de datos del acuerdo definiendo las variables para almacenar el tamaño del panel, así como las coordenadas para anclar las columnas con el nombre de la información mostrada y los valores obtenidos previamente, tal y como se muestra a continuación.
int height=20, max_height =0, max_width = 0; // column height and max values for indent int x_column[2] = {10, 130}; // columns X coordinates int y_column[17]; // Y coordinates
La variable 'height' almacenará un valor estático de 20 como altura de cada columna para asegurar que cada fila se dibuja uniformemente, y los valores max_height y max_width almacenarán los valores máximos de cada columna para asegurar un dibujo uniforme. Las coordenadas requeridas a lo largo de los ejes X e Y se almacenarán en las matrices x_column[] e y_column[], respectivamente.
Ahora tenemos que declarar dos matrices que almacenarán los valores de fila para mostrar la columna de cabecera y la columna de valor. Declararemos la columna de cabecera a través de la matriz de datos de tipo string, como se muestra en el siguiente código.
string column_1[17] = { "Symbol", "Position ID", "External ID", "Magic", "Comment", "Reason", "Open", "Close", "Time open", "Time close", "Stop loss", "Take profit", "Volume", "Commission", "Swap", "Profit", "Fee" };
Todos los valores del array se declaran e inicializan estáticamente, ya que el panel no cambiará y los datos se mostrarán siempre en la misma secuencia. Esto debería ser conveniente para acostumbrarse a ver información sobre diferentes ofertas. Es posible implementar una funcionalidad que permita excluir del panel los datos que no contengan valores o sean iguales a cero, pero esto sería un inconveniente para la vista a la hora de buscar información rápidamente. Sigue siendo más familiar buscar información en un patrón de visualización conocido que mirar cada vez los valores de las columnas.
En la misma secuencia de datos, declare un segundo array, que ya contendrá los valores de las columnas declaradas en el array anterior. Describe la declaración del array de la siguiente manera:
string column_2[17] = { symboL, IntegerToString(tickeT), externalIDD, IntegerToString(magiC), commenT, EnumToString(reasoN), DoubleToString(opeN,digitS), DoubleToString(closE,digitS), TimeToString(timE), TimeToString(time_closE), DoubleToString(stop_losS,digitS), DoubleToString(take_profiT,digitS), DoubleToString(volumE,2), DoubleToString(commissioN,2), DoubleToString(swaP,2), DoubleToString(profiT,2), DoubleToString(feE,2) };
La matriz se declarará localmente, a nivel de método, y los campos se inicializarán al mismo tiempo, directamente a partir de los parámetros del método, utilizando las funciones terminales predefinidas correspondientes.
Ahora que hemos declarado los contenedores con los datos requeridos, necesitamos calcular los valores de anclaje de las coordenadas de cada celda, considerando la búsqueda del valor máximo en cada una de ellas. Podemos implementar esto con el siguiente código:
int count_rows = 1; for(int i=0; i<ArraySize(y_column); i++) { y_column[i] = height * count_rows; max_height = y_column[i]; count_rows++; int width_curr = StringLen(column_2[i]); if(width_curr>max_width) { max_width = width_curr; } } max_width = max_width*10; max_width += x_column[1]; max_width += x_column[0];
Aquí encontramos las coordenadas de anclaje de cada objeto haciendo un bucle sobre el número de filas, multiplicándolas por un valor fijo de altura Y para cada una. También comparamos cada valor con el valor de anchura máxima para obtener la coordenada X.
Una vez que tenemos todos los valores con sus coordenadas, podemos empezar a dibujar la información usando el método personalizado LabelCreate() previamente declarado, que llamaremos cíclicamente, según el número de nuestras filas a mostrar, como se muestra a continuación.
color back_Color = clrWhiteSmoke; color font_Color = clrBlueViolet; for(int i=0; i<ArraySize(column_1); i++) { //--- draw 1 string name_1 = column_1[i]+"_1_"+IntegerToString(tickeT); LabelCreate(handlE,name_1,0,x_column[0],y_column[i],CORNER_LEFT_UPPER,column_1[i],"Arial",10,font_Color,0,ANCHOR_LEFT_UPPER,false); //--- draw 2 string name_2 = column_1[i]+"_2_"+IntegerToString(tickeT); LabelCreate(handlE,name_2,0,x_column[1],y_column[i],CORNER_LEFT_UPPER,column_2[i],"Arial",10,font_Color,0,ANCHOR_LEFT_UPPER,false); }
Al final del método, sólo tenemos que dibujar el fondo utilizando el método personalizado RectLabelCreate() previamente declarado y descrito a estos valores y actualizar el gráfico mostrado, como se muestra a continuación.
//--- draw the background RectLabelCreate(handlE,"RectLabel",0,1,height,max_width,max_height,back_Color); ChartRedraw(handlE);
Esto completa la descripción de todos los métodos. El proyecto está listo para su montaje y uso.
Como resultado, el archivo gráfico tendrá el aspecto que se muestra en la Figura 3 después de utilizar el script.
Figura 3. El resultado de la operación mostrados
Como vemos, toda la información sobre la operación se presenta de forma generalizada en un único gráfico, lo que hace más cómodo el análisis y la evaluación de las operaciones realizadas por el usuario. El script ordena dichos archivos en las carpetas adecuadas, lo que también permite al usuario encontrar en cualquier momento la información necesaria sobre cualquier operación comercial en el historial de la cuenta.
Conclusión
Con este artículo, hemos terminado de escribir un script para la visualización automatizada de operaciones en un gráfico. Utilizando esta solución, puede mejorar significativamente sus operaciones corrigiendo posibles errores al elegir un punto de entrada, así como aumentar la expectativa matemática de toda su estrategia eligiendo los símbolos correctos y la ubicación esperada del impulso del precio. El uso del script le permitirá ahorrar mucho tiempo en la preparación de archivos de gráficos, que podrá dedicar al análisis y a la búsqueda de nuevas ideas para operar. Lo principal que hay que recordar es que el mercado cambia constantemente y, para garantizar un funcionamiento estable, hay que estar siempre en contacto y vigilar los cambios. Esta herramienta puede ayudarle a conseguirlo. Les deseo éxito en su trabajo. Por favor, deje su opinión en los comentarios.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/14961





- 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
Una gran idea y una explicación de primera para entenderlo. Muchas gracias.
Muchas gracias por su comentario. Estaré encantado si esto te ayuda en tu trabajo. Servus! :)