De novato a experto: Utilidad de control de parámetros
Contenido:
Introducción
Hoy seguimos profundizando en los fundamentos que sentamos en nuestro artículo anterior. Si nos han estado siguiendo, recordarán que desarrollamos un indicador diseñado para visualizar períodos de marcos temporales superiores (HTF) directamente en gráficos de marcos temporales inferiores. Este concepto resultó ser una poderosa herramienta analítica, que revelaba los intrincados movimientos de precios ocultos dentro de las barras más grandes de marcos temporales superiores.
Este nivel de detalle es invaluable para un trader; por ejemplo, lo que parece una simple mecha en un marco temporal superior puede, al examinarse en un marco temporal inferior, revelar patrones distintos, niveles de soporte y resistencia, o incluso bloques de órdenes. Comprender esta estructura interna del mercado permite a los traders anticipar mejor el comportamiento futuro de los precios y perfeccionar sus estrategias. En la ilustración de la Figura 1 a continuación, el período A de la vela H1 de la izquierda muestra un nivel de resistencia dentro de su mecha alcista (el área de rechazo), que posteriormente fue probado y respetado por el período B de la derecha. Esta interacción pone de relieve cómo las zonas de mecha pasadas pueden servir como puntos de referencia significativos para futuras reacciones de los precios.

Figura 1: Análisis del período H1 en M1 mediante Market Periods Synchronizer
Sin embargo, un desafío que suele surgir al desarrollar herramientas complejas es la falta de controles intuitivos y de fácil acceso. Tradicionalmente, hemos recurrido a la pestaña de Entradas de un EA o indicador para ajustar los parámetros, un proceso que puede volverse rápidamente tedioso al ajustar múltiples valores o experimentar con la configuración visual.
En este proyecto, abordamos ese desafío creando una herramienta de control en tiempo real: un asesor experto (EA) que transforma los parámetros de entrada estáticos en controles interactivos que se muestran en el gráfico. Esta "Utilidad de control del sincronizador de períodos de mercado" amplía el concepto de indicador anterior a un panel de control dinámico con funciones avanzadas, que ofrece información inmediata y un flujo de trabajo analítico más eficiente.
Principales ventajas de la nueva utilidad
- Acceso instantáneo a los parámetros: ajuste la configuración clave directamente desde el gráfico sin abrir la ventana de propiedades.
- Actualizaciones visuales en tiempo real: vea los cambios inmediatos en los objetos, los colores y los intervalos de tiempo a medida que modifica los controles.
- Análisis acelerado: elimine los pasos repetitivos de guardar y recargar, agilizando su flujo de trabajo.
- Interfaz visual moderna: utiliza CCanvas para crear paneles fluidos, semitransparentes y visualmente atractivos.
- Sincronización de múltiples marcos temporales: visualice y controle sin problemas las estructuras de marcos temporales principales y secundarios.
- Controles deslizantes interactivos: ajuste rápidamente valores como el ancho y los intervalos de actualización.
- Interruptores de palanca: active o desactive funciones al instante con un solo clic.
A continuación se muestra un ejemplo de lo que pretendemos lograr al finalizar este artículo. Después, nos adentraremos en la fase de implementación, donde desglosaremos cada paso del desarrollo, explicaremos la estructura del código principal y concluiremos con la implementación y las pruebas de rendimiento.

Figura 2: Captura de pantalla de las funciones de control de parámetros.
Implementación
Implementaremos un asesor experto modular que convierte datos de entrada estáticos en un panel de control que se muestra en el gráfico. El EA (1) lee las barras HTF (tiempo, apertura, cierre), (2) dibuja elementos visuales HTF de fondo (líneas verticales, rellenos de cuerpo opcionales, horizontales de apertura/cierre) y (3) expone controles de tiempo de ejecución a través de un lienzo semitransparente y objetos de gráfico (botones, etiquetas, deslizadores verticales, menús desplegables TF emergentes). La interfaz de usuario actualiza las variables de estado en tiempo de ejecución (copias mutables de las entradas), y RefreshLines() reutiliza esos valores para actualizar los objetos del gráfico inmediatamente. El diseño prioriza la claridad (contenedor de la interfaz de usuario + etiquetas), la capacidad de respuesta (el arrastre del control deslizante se actualiza inmediatamente) y la no interferencia (los objetos HTF se crean con OBJPROP_BACK=true para que la interfaz de usuario siga siendo interactiva).
Notas clave sobre la implementación que los lectores no deben pasar por alto:
- Las entradas se tratan únicamente como valores predeterminados: el panel de control modifica las copias en tiempo de ejecución (con el prefijo g_) para que las interacciones de la interfaz de usuario persistan mientras se ejecuta el EA.
- Los controles deslizantes son verticales y se implementan con dos botones gráficos: una pista (visual) y un control deslizante móvil (seleccionable); al arrastrar el control deslizante móvil, se actualiza el valor subyacente en tiempo real.
- Las formas HTF se dibujan en segundo plano (OBJPROP_BACK = true) para evitar robar eventos del ratón de la interfaz de usuario.
- OnTick() comprueba únicamente los cambios de tiempo (a través de iTime) para evitar redibujados innecesarios.
1) Encabezado, inclusiones y entradas del usuario: propósito y comportamiento
Empezamos incluyendo Canvas.mqh y declarando todos los parámetros de entrada. La línea #include <Canvas/Canvas.mqh> incorpora una pequeña clase auxiliar de interfaz de usuario (CCanvas) que utilizamos para dibujar un mapa de bits de fondo semitransparente para nuestro panel de control. Hacemos esto porque los objetos del gráfico por sí solos (botones/etiquetas) se ven poco estéticos en diferentes temas de gráficos; el lienzo proporciona un contenedor único y consistente que podemos personalizar una sola vez y reutilizar. Tenga en cuenta que Canvas.mqh debe estar presente en la ruta de inclusión; de lo contrario, la compilación de EA fallará. También usamos #property strict para que el compilador aplique las reglas modernas de tipo y firma de MQL.
El bloque de entrada está organizado intencionalmente en tres categorías prácticas para que los usuarios comprendan de inmediato dónde buscar y cómo se relaciona la herramienta con los conceptos de negociación. La primera categoría —Marco temporal principal y elementos visuales— incluye el marco temporal que define las barras principales que marcamos (por ejemplo, H1), una profundidad histórica (cuántas barras de marcos temporales superiores dibujamos), un color y un ancho predeterminados para los separadores verticales principales, y un intervalo de actualización en segundos. Estos ajustes definen la estructura principal: las barras mayores constituyen el marco en el que se contextualiza todo lo demás (rellenos, líneas horizontales de apertura y cierre, y períodos menores). El periodo de retrospectiva es especialmente importante porque determina cuántos objetos de período principal podemos crear; un periodo de retrospectiva amplio puede generar muchos objetos y ralentizar los gráficos, por lo que recomendamos utilizar valores predeterminados razonables (200) y límites máximos en una versión de producción.
La segunda categoría se refiere a los marcadores de apertura y cierre y a los rellenos del cuerpo de la vela. Incluimos interruptores y colores para los marcadores horizontales de apertura y cierre (útiles para ver dónde se abrió y cerró cada barra HTF en relación con el marco temporal inferior), ancho y estilo de línea para estos horizontales, un desplazamiento en las barras TF actuales para controlar hasta dónde se extiende la línea horizontal, y valores booleanos más colores para los rellenos del cuerpo de la vela (alcista/bajista). Estas opciones son opcionales pero muy útiles: los rellenos proporcionan una representación visual rápida de las barras de marcos temporales superiores (HTF) alcistas frente a las bajistas, mientras que las líneas de apertura/cierre nos ayudan a ver dónde la estructura intrabarra puede haber creado soporte/resistencia dentro del cuerpo o las mechas de la barra HTF. En el código, almacenamos estos valores como entradas predeterminadas (inmutables) y los copiamos en variables de tiempo de ejecución para que el panel de control pueda alternarlos en tiempo real.
La tercera categoría abarca los períodos menores. Ofrecemos dos opciones de TF secundarias (Minor1, Minor2), cada una con un botón de activación/desactivación, una selección de intervalo de tiempo, un color y un ancho. El objetivo es permitirnos visualizar la estructura interna de los HTF: por ejemplo, en un H1 mayor, un M15 menor puede mostrar las subdivisiones internas. El EA dibuja verticales menores solo cuando la marca temporal del marco menor cae estrictamente entre dos marcas temporales consecutivas del marco mayor (o dentro de la barra mayor actual); ese comportamiento reproduce la lógica del indicador original. Al habilitar dos capas menores separadas, podemos ver la estructura anidada (por ejemplo, H1 mayor, M30 menor, M15 micro-menor) simultáneamente.
Un aspecto crucial de la arquitectura y un error frecuente: los parámetros de entrada en MQL son constantes de tiempo de compilación para el tiempo de ejecución; el EA no puede reasignarlos mientras se está ejecutando. Para proporcionar un panel de control verdaderamente interactivo, copiamos cada entrada en una variable g_ (global mutable) correspondiente durante OnInit. Nuestro código de interfaz de usuario actualiza las variables g_ (por ejemplo, g_WidthMajor o g_ShowFill) y RefreshLines() lee esos valores g_ para actualizar los objetos del gráfico inmediatamente. Esta separación evita confusiones y nos permite tratar las entradas como valores predeterminados seguros, al tiempo que proporciona un control total en tiempo de ejecución. Esto también significa que, si el usuario desea que la configuración se mantenga entre sesiones, debemos agregar una lógica explícita para guardar/cargar, una mejora que se implementará en el futuro.
Por último, utilizamos convenciones de nomenclatura y prefijos de forma intensiva para que el programa pueda gestionar, actualizar y eliminar objetos de gráficos de forma determinista. Los prefijos como HTF_MAJ_, HTF_MIN1_ y MPS_UI_ nos permiten buscar, actualizar o eliminar únicamente los objetos que hemos creado sin afectar a otros dibujos que el usuario pueda tener en el gráfico. Este es un detalle de ingeniería pequeño pero vital: sin una nomenclatura coherente, es fácil borrar o sobrescribir accidentalmente objetos que no están relacionados. También mantenemos una matriz interna tf_list[] (el conjunto de intervalos de tiempo permitidos) para que los menús desplegables y la lógica de ciclo sean consistentes y estén localizados.
#include <Canvas/Canvas.mqh> // Canvas helper library (expects Canvas.mqh to be present) // --------------------------- USER INPUTS --------------------------- // Major timeframe + lookback + default visuals input ENUM_TIMEFRAMES InpHigherTF = PERIOD_H1; // Major higher timeframe input int InpLookback = 200; // Lookback (bars) input color InpColorMajor = clrRed; // Major line color input int InpWidthMajor = 2; // Major line width input int InpRefreshSec = 5; // Refresh interval (seconds) // Open/Close marker settings input bool InpShowOpenClose = true; // show open/close markers input color InpColorOpen = clrGreen; input color InpColorClose = clrLime; input int InpWidthOC = 1; input ENUM_LINE_STYLE InpStyleOC = STYLE_DASH; input int InpHorizOffsetBars= 3; // Body fill for majors input bool InpShowFill = true; input color InpFillBull = clrLime; input color InpFillBear = clrPink; // Minor periods input bool InpShowMinor1 = false; input ENUM_TIMEFRAMES InpMinor1TF = PERIOD_M30; input color InpColorMin1 = clrOrange; input int InpWidthMin1 = 1; input bool InpShowMinor2 = false; input ENUM_TIMEFRAMES InpMinor2TF = PERIOD_M15; input color InpColorMin2 = clrYellow; input int InpWidthMin2 = 1;
2) Variables globales y copias en tiempo de ejecución: ¿por qué separar las entradas y el estado en tiempo de ejecución?
Declaramos matrices y variables g_ para el estado mutable en tiempo de ejecución, paletas de colores, infraestructura de controles deslizantes y cadenas de nombres de la interfaz de usuario. Esta separación es clave: las operaciones de la interfaz de usuario escriben en los valores g_*; RefreshLines() los lee. También preparamos matrices que almacenan los metadatos de los controles deslizantes para que cada uno de ellos pueda crearse o actualizarse de forma genérica.
// --------------------------- GLOBALS ------------------------------- enum SliderIndex { SLIDER_MAJ_WIDTH = 0, SLIDER_REFRESH = 1 }; const int SLIDER_COUNT = 2; int Y_OFFSET = 50; // top offset for UI container // runtime (mutable) copies of inputs (dashboard will change these) ENUM_TIMEFRAMES g_HigherTF; int g_Lookback; color g_ColorMajor; int g_WidthMajor; int g_RefreshSec; // ... (other g_ variables for toggles & minors) // slider infrastructure (vertical sliders) string g_slider_track_names[]; string g_slider_knob_names[]; int g_slider_min[]; int g_slider_max[]; int g_slider_left_x[]; int g_slider_top_y[]; int g_vslider_height_px = 110; int g_vslider_width_px = 14; int g_slider_knob_w = 12; bool g_slider_drag = false; int g_current_slider = -1;
3) Utilidades auxiliares de TF: etiquetas consistentes y ciclo
TFToString() centraliza el etiquetado de los intervalos de tiempo para los botones y los nombres de los objetos (de modo que los menús desplegables y las etiquetas siempre coincidan). FindNextTFIndex() implementa un ciclo simple para pasar al siguiente intervalo de tiempo en tf_list, útil para alternar rápidamente entre botones.
string TFToString(ENUM_TIMEFRAMES tf) { switch(tf) { case PERIOD_M1: return "M1"; case PERIOD_M5: return "M5"; case PERIOD_M15: return "M15"; case PERIOD_M30: return "M30"; case PERIOD_H1: return "H1"; case PERIOD_H4: return "H4"; case PERIOD_D1: return "D1"; case PERIOD_W1: return "W1"; case PERIOD_MN1: return "MN"; } return IntegerToString((int)tf); } int FindNextTFIndex(ENUM_TIMEFRAMES current) { int n = ArraySize(tf_list); for(int i=0;i<n;i++) if(tf_list[i] == current) return (i+1)%n; return 0; }
4) OnInit(): prepara el estado de ejecución, los nombres de la interfaz de usuario, el lienzo y el dibujo inicial.
OnInit es el paso de orquestación: copia los valores de entrada en g_*, calcula los tiempos de la última barra para la detección de cambios, crea las cadenas de nombres de la interfaz de usuario, prepara las matrices de deslizadores, crea el fondo del lienzo, crea los widgets de la interfaz de usuario (botones, etiquetas, botones de color), crea los deslizadores verticales y las etiquetas, y luego inicia el temporizador. Observe cómo llamamos a RefreshLines() al final para que el gráfico se rellene inmediatamente con objetos HTF.
Tras compilar y adjuntar el EA, esperamos ver un panel de interfaz de usuario semitransparente con controles; las líneas y rellenos HTF aparecerán en el gráfico reflejando los valores predeterminados de entrada.
int OnInit() { main_chart_id = ChartID(); // copy inputs -> runtime g_HigherTF = InpHigherTF; g_Lookback = MathMax(10, InpLookback); g_ColorMajor = InpColorMajor; g_WidthMajor = MathMax(1, InpWidthMajor); g_RefreshSec = MathMax(1, InpRefreshSec); // ... (copy the rest of Inp->g_ variables) // initialize last bar times g_last_major_time = iTime(_Symbol, g_HigherTF, 0); if(g_ShowMinor1) g_last_minor1_time = iTime(_Symbol, g_Minor1TF, 0); if(g_ShowMinor2) g_last_minor2_time = iTime(_Symbol, g_Minor2TF, 0); // UI prefix and object names UI_PREFIX = StringFormat("MPS_UI_%d_", main_chart_id); lbl_title = UI_PREFIX + "LBL_TITLE"; btn_major_tf = UI_PREFIX + "BTN_MAJ_TF"; // ... (initialize other UI name strings) // prepare slider arrays ArrayResize(g_slider_track_names, SLIDER_COUNT); ArrayResize(g_slider_knob_names, SLIDER_COUNT); ArrayResize(g_slider_min, SLIDER_COUNT); ArrayResize(g_slider_max, SLIDER_COUNT); ArrayResize(g_slider_left_x, SLIDER_COUNT); ArrayResize(g_slider_top_y, SLIDER_COUNT); for(int i=0;i<SLIDER_COUNT;i++) { g_slider_track_names[i] = UI_PREFIX + StringFormat("SL_TRK_%d", i); g_slider_knob_names[i] = UI_PREFIX + StringFormat("SL_KNB_%d", i); } // background area coords and create UI g_bg_y = Y_OFFSET - 6; g_bg_h = 250; CreateUIBackground(); CreateLabel(lbl_title, 12, 4 + Y_OFFSET, "Market Periods Synchronizer Control Utility", 12); ObjectSetInteger(main_chart_id, lbl_title, OBJPROP_COLOR, XRGB(230,230,230)); // create many UI buttons/labels and sliders... CreateButton(btn_major_tf, 12, 34 + Y_OFFSET, 70, 24, TFToString(g_HigherTF)); CreateLabel(lbl_major_tf, 92, 36 + Y_OFFSET, "Major TF"); // ... more buttons and color swatches // Major width slider (vertical) g_slider_left_x[SLIDER_MAJ_WIDTH] = 190; g_slider_top_y[SLIDER_MAJ_WIDTH] = slider_base_top; g_slider_min[SLIDER_MAJ_WIDTH] = 1; g_slider_max[SLIDER_MAJ_WIDTH] = 10; CreateVerticalSliderAt(SLIDER_MAJ_WIDTH, g_slider_left_x[SLIDER_MAJ_WIDTH], g_slider_top_y[SLIDER_MAJ_WIDTH], g_slider_track_names[SLIDER_MAJ_WIDTH], g_slider_knob_names[SLIDER_MAJ_WIDTH], g_WidthMajor, g_slider_min[SLIDER_MAJ_WIDTH], g_slider_max[SLIDER_MAJ_WIDTH], g_vslider_height_px); // start events & timer ChartSetInteger(main_chart_id, CHART_EVENT_MOUSE_MOVE, true); EventSetTimer(g_RefreshSec); // initial drawing of HTF objects RefreshLines(); return INIT_SUCCEEDED; }
5) Creación de fondo de lienzo: agrupación visual y comportamiento al hacer clic.
CreateUIBackground() utiliza la función auxiliar CCanvas para crear una etiqueta de mapa de bits que actúa como el panel oscuro y semitransparente que se encuentra detrás de los elementos de la interfaz de usuario. Decisión de diseño importante: hemos configurado el lienzo con OBJPROP_BACK = false y OBJPROP_SELECTABLE = false, de modo que el lienzo sea visible pero no intercepte los clics; los botones dibujados delante siguen recibiendo los eventos del ratón.
Un panel oscuro y pulido mejora la legibilidad de los símbolos y los temas de los gráficos.
void CreateUIBackground()
{
g_bg_name = UI_PREFIX + "BG";
ObjectDelete(main_chart_id, g_bg_name);
bool ok = g_bgCanvas.CreateBitmapLabel(main_chart_id, 0, g_bg_name, g_bg_x, g_bg_y, g_bg_w, g_bg_h, COLOR_FORMAT_ARGB_RAW);
if(!ok) { PrintFormat("CreateUIBackground: CreateBitmapLabel failed err=%d", GetLastError()); return; }
uint dark_grey = ARGB(180, 30, 30, 30);
uint border_col = XRGB(80, 80, 80);
uint top_strip = ARGB(210, 24, 24, 24);
g_bgCanvas.FillRectangle(0, 0, g_bg_w - 1, g_bg_h - 1, dark_grey);
g_bgCanvas.Rectangle(0, 0, g_bg_w - 1, g_bg_h - 1, border_col);
g_bgCanvas.FillRectangle(0, 0, g_bg_w - 1, 28, top_strip);
g_bgCanvas.Update(true);
ObjectSetInteger(main_chart_id, g_bg_name, OBJPROP_BACK, false);
ObjectSetInteger(main_chart_id, g_bg_name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(main_chart_id, g_bg_name, OBJPROP_HIDDEN, false);
}
6) Herramientas para la creación de interfaces de usuario: estilo uniforme para botones y etiquetas.
CreateButton, CreateLabel y CreateColorButton centralizan la creación de la interfaz de usuario para que el diseño y el estilo se mantengan coherentes. Los botones se crean como seleccionables y se dibujan en primer plano (OBJPROP_BACK=false), las etiquetas no son seleccionables. Esto garantiza un modelo de eventos predecible y una apariencia uniforme.
void CreateButton(string name,int x,int y,int w,int h,string text) { if(StringLen(name) == 0) return; ObjectDelete(main_chart_id, name); if(!ObjectCreate(main_chart_id, name, OBJ_BUTTON, 0, 0, 0)) { PrintFormat("CreateButton: failed to create %s err=%d", name, GetLastError()); return; } ObjectSetInteger(main_chart_id, name, OBJPROP_BACK, false); // draw in front ObjectSetInteger(main_chart_id, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(main_chart_id, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(main_chart_id, name, OBJPROP_YDISTANCE, y); ObjectSetInteger(main_chart_id, name, OBJPROP_XSIZE, w); ObjectSetInteger(main_chart_id, name, OBJPROP_YSIZE, h); ObjectSetString(main_chart_id, name, OBJPROP_TEXT, text); ObjectSetInteger(main_chart_id, name, OBJPROP_FONTSIZE, 10); ObjectSetInteger(main_chart_id, name, OBJPROP_SELECTABLE, true); ObjectSetInteger(main_chart_id, name, OBJPROP_HIDDEN, false); } void CreateLabel(string name,int x,int y,string text, int fontsize=9) { if(StringLen(name) == 0) return; ObjectDelete(main_chart_id, name); if(!ObjectCreate(main_chart_id, name, OBJ_LABEL, 0, 0, 0)) { PrintFormat("CreateLabel: failed to create %s err=%d", name, GetLastError()); return; } ObjectSetInteger(main_chart_id, name, OBJPROP_BACK, false); ObjectSetInteger(main_chart_id, name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(main_chart_id, name, OBJPROP_XDISTANCE, x); ObjectSetInteger(main_chart_id, name, OBJPROP_YDISTANCE, y); ObjectSetString(main_chart_id, name, OBJPROP_TEXT, text); ObjectSetInteger(main_chart_id, name, OBJPROP_FONTSIZE, fontsize); ObjectSetInteger(main_chart_id, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(main_chart_id, name, OBJPROP_HIDDEN, false); } void CreateColorButton(string name, int x, int y, int w, int h, color col) { CreateButton(name, x, y, w, h, ""); ObjectSetInteger(main_chart_id, name, OBJPROP_BGCOLOR, col); ObjectSetInteger(main_chart_id, name, OBJPROP_FONTSIZE, 8); }
7) Controles deslizantes verticales: arquitectura y arrastre en tiempo real
Los controles deslizantes verticales se componen de una pista no seleccionable (visual) y un botón seleccionable (control deslizante móvil). La función CreateVerticalSliderAt calcula la posición del control deslizante a partir del rango de valores, almacena los metadatos del control deslizante en matrices y coloca el control. La lógica de arrastre (gestionada en OnChartEvent) utiliza CHARTEVENT_MOUSE_MOVE para establecer el eje Y del mando, calcular una relación y asignarla de nuevo al rango de valores. Cuando cambia un control deslizante, el código actualiza las variables g_ correspondientes (por ejemplo, g_WidthMajor, g_RefreshSec) e inmediatamente llama a RefreshLines().
La coordenada Y se invierte para la asignación de valores: arriba = valor máximo.
void CreateVerticalSliderAt(int id, int base_x, int base_y, string track_name, string knob_name, int current_value, int min_val, int max_val, int track_height) { // track ObjectDelete(main_chart_id, track_name); if(!ObjectCreate(main_chart_id, track_name, OBJ_BUTTON, 0, 0, 0)) { PrintFormat("CreateVerticalSliderAt: failed track %s err=%d", track_name, GetLastError()); } ObjectSetInteger(main_chart_id, track_name, OBJPROP_BACK, false); ObjectSetInteger(main_chart_id, track_name, OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetInteger(main_chart_id, track_name, OBJPROP_XDISTANCE, base_x); ObjectSetInteger(main_chart_id, track_name, OBJPROP_YDISTANCE, base_y); ObjectSetInteger(main_chart_id, track_name, OBJPROP_XSIZE, g_vslider_width_px); ObjectSetInteger(main_chart_id, track_name, OBJPROP_YSIZE, track_height); ObjectSetString(main_chart_id, track_name, OBJPROP_TEXT, ""); ObjectSetInteger(main_chart_id, track_name, OBJPROP_SELECTABLE, false); ObjectSetInteger(main_chart_id, track_name, OBJPROP_HIDDEN, false); // knob ObjectDelete(main_chart_id, knob_name); if(!ObjectCreate(main_chart_id, knob_name, OBJ_BUTTON, 0, 0, 0)) { PrintFormat("CreateVerticalSliderAt: failed knob %s err=%d", knob_name, GetLastError()); } ObjectSetInteger(main_chart_id, knob_name, OBJPROP_BACK, false); ObjectSetInteger(main_chart_id, knob_name, OBJPROP_CORNER, CORNER_LEFT_UPPER); double ratio = 0.0; if(max_val > min_val) ratio = double(current_value - min_val) / double(max_val - min_val); int knob_y = base_y + (int)MathRound((1.0 - ratio) * (track_height - g_slider_knob_w)); int knob_x = base_x - (g_slider_knob_w/2) + (g_vslider_width_px/2); ObjectSetInteger(main_chart_id, knob_name, OBJPROP_XDISTANCE, knob_x); ObjectSetInteger(main_chart_id, knob_name, OBJPROP_YDISTANCE, knob_y); ObjectSetInteger(main_chart_id, knob_name, OBJPROP_XSIZE, g_slider_knob_w); ObjectSetInteger(main_chart_id, knob_name, OBJPROP_YSIZE, g_slider_knob_w); ObjectSetString(main_chart_id, knob_name, OBJPROP_TEXT, ""); ObjectSetInteger(main_chart_id, knob_name, OBJPROP_SELECTABLE, true); ObjectSetInteger(main_chart_id, knob_name, OBJPROP_HIDDEN, false); ObjectSetInteger(main_chart_id, knob_name, OBJPROP_FONTSIZE, 1); // store slider params g_slider_min[id] = min_val; g_slider_max[id] = max_val; g_slider_left_x[id] = base_x; g_slider_top_y[id] = base_y; }
8) Actualización de la etiqueta del control deslizante: mantenga la interfaz de usuario sincronizada.
La función UpdateLabelsAfterSliderChange() actualiza las etiquetas de texto debajo de los controles deslizantes cada vez que cambia el valor, manteniendo la información visual coherente.
void UpdateLabelsAfterSliderChange() { if(ObjectFind(main_chart_id, lbl_major_width) >= 0) ObjectSetString(main_chart_id, lbl_major_width, OBJPROP_TEXT, StringFormat("Maj W:%d", g_WidthMajor)); if(ObjectFind(main_chart_id, lbl_refresh_label) >= 0) ObjectSetString(main_chart_id, lbl_refresh_label, OBJPROP_TEXT, StringFormat("Refresh:%ds", g_RefreshSec)); }
9) Lógica desplegable de TF: listas emergentes pequeñas y fiables.
ShowTFDropdownFor() crea una pila de botones pequeños inmediatamente debajo del botón TF solicitado (mayor / menor1 / menor2). HideTFDropdown() los elimina. El menú desplegable utiliza un prefijo compuesto para que los eventos de selección se puedan analizar fácilmente en OnChartEvent.
Este diseño es intencionalmente simple y robusto: el uso de botones en el gráfico evita la creación de una clase de cuadro combinado totalmente personalizada, al tiempo que proporciona el comportamiento desplegable esperado.
void ShowTFDropdownFor(int target) { if(g_tf_dropdown_visible && g_tf_dropdown_target == target) { HideTFDropdown(); return; } if(g_tf_dropdown_visible) HideTFDropdown(); string target_btn = (target == 0 ? btn_major_tf : (target == 1 ? btn_minor1_tf : btn_minor2_tf)); if(ObjectFind(main_chart_id, target_btn) < 0) return; int bx = (int)ObjectGetInteger(main_chart_id, target_btn, OBJPROP_XDISTANCE); int by = (int)ObjectGetInteger(main_chart_id, target_btn, OBJPROP_YDISTANCE); int bh = (int)ObjectGetInteger(main_chart_id, target_btn, OBJPROP_YSIZE); int base_x = bx; int base_y = by + bh + 4; int w = 60; int h = 20; for(int i=0;i<ArraySize(tf_list);i++) { string oname = TF_DROPDOWN_PREFIX + IntegerToString(target) + "_" + IntegerToString(i); CreateButton(oname, base_x, base_y + i*(h+2), w, h, TFToString(tf_list[i])); } g_tf_dropdown_visible = true; g_tf_dropdown_target = target; } void HideTFDropdown() { if(!g_tf_dropdown_visible) return; for(int i=0; i<ArraySize(tf_list); i++) { string oname = TF_DROPDOWN_PREFIX + IntegerToString(g_tf_dropdown_target) + "_" + IntegerToString(i); ObjectDelete(main_chart_id, oname); } g_tf_dropdown_visible = false; g_tf_dropdown_target = -1; }
10) OnChartEvent(): modelo de evento y manejo de arrastre
Este es el cerebro de interacción. Se encarga de:
- Iniciar y detener el arrastre con el control deslizante móvil (los clics en el control deslizante móvil inician el arrastre; cualquier clic posterior en un objeto lo finaliza).
- Movimiento del ratón mientras se arrastra: calcular la posición del píxel del mando, asignarla a un valor numérico, actualizar la variable g_ y llamar a RefreshLines() si ha cambiado.
- Pulsaciones de botones: interruptores, botones de retroceso +/-, selección de color, mostrar/ocultar menú desplegable TF.
- Selecciones desplegables de TF: analiza el nombre del botón pulsado y aplica el período de tiempo elegido a mayor/menor según corresponda, y luego vuelve a dibujar.
- Decisiones de diseño: detener el arrastre al hacer clic en cualquier objeto es un patrón sencillo multiplataforma que evita tener que detectar eventos globales de liberación del botón del ratón (que no siempre están disponibles como un evento de gráfico dedicado en MQL5).
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // stop dragging if an object click happens if(id == CHARTEVENT_OBJECT_CLICK && g_slider_drag) { g_slider_drag = false; g_current_slider = -1; return; } // dragging motion: use mouse Y from dparam if(id == CHARTEVENT_MOUSE_MOVE && g_slider_drag && g_current_slider >= 0) { int my = (int)dparam; int s = g_current_slider; string track_name = g_slider_track_names[s]; string knob_name = g_slider_knob_names[s]; int track_top = (int)ObjectGetInteger(main_chart_id, track_name, OBJPROP_YDISTANCE); int track_h = (int)ObjectGetInteger(main_chart_id, track_name, OBJPROP_YSIZE); int track_bottom = track_top + track_h - g_slider_knob_w; int new_knob_y = my; if(new_knob_y < track_top) new_knob_y = track_top; if(new_knob_y > track_bottom) new_knob_y = track_bottom; ObjectSetInteger(main_chart_id, knob_name, OBJPROP_YDISTANCE, new_knob_y); double ratio = 1.0 - double(new_knob_y - track_top) / double(track_h - g_slider_knob_w); int new_val = g_slider_min[s] + (int)MathRound(ratio * (g_slider_max[s] - g_slider_min[s])); if(new_val < g_slider_min[s]) new_val = g_slider_min[s]; if(new_val > g_slider_max[s]) new_val = g_slider_max[s]; bool changed = false; if(s == SLIDER_MAJ_WIDTH) { if(g_WidthMajor != new_val) { g_WidthMajor = new_val; changed = true; } } else if(s == SLIDER_REFRESH) { if(g_RefreshSec != new_val) { g_RefreshSec = new_val; EventSetTimer(g_RefreshSec); changed = true; } } if(changed) { UpdateLabelsAfterSliderChange(); RefreshLines(); } return; } // TF dropdown option clicked if(StringFind(sparam, TF_DROPDOWN_PREFIX) == 0 && id == CHARTEVENT_OBJECT_CLICK) { string rest = StringSubstr(sparam, StringLen(TF_DROPDOWN_PREFIX)); int sep = StringFind(rest, "_"); if(sep >= 0) { int target = (int)StringToInteger(StringSubstr(rest, 0, sep)); int idx = (int)StringToInteger(StringSubstr(rest, sep+1)); if(idx >= 0 && idx < ArraySize(tf_list)) { ENUM_TIMEFRAMES chosen = tf_list[idx]; if(target == 0) { g_HigherTF = chosen; g_last_major_time = iTime(_Symbol, g_HigherTF, 0); ObjectSetString(main_chart_id, btn_major_tf, OBJPROP_TEXT, TFToString(g_HigherTF)); } else if(target == 1) { g_Minor1TF = chosen; if(g_ShowMinor1) g_last_minor1_time = iTime(_Symbol, g_Minor1TF, 0); ObjectSetString(main_chart_id, btn_minor1_tf, OBJPROP_TEXT, TFToString(g_Minor1TF)); } else if(target == 2) { g_Minor2TF = chosen; if(g_ShowMinor2) g_last_minor2_time = iTime(_Symbol, g_Minor2TF, 0); ObjectSetString(main_chart_id, btn_minor2_tf, OBJPROP_TEXT, TFToString(g_Minor2TF)); } HideTFDropdown(); RefreshLines(); } } return; } // If dropdown visible and user clicked elsewhere => hide if(g_tf_dropdown_visible && id == CHARTEVENT_OBJECT_CLICK && StringFind(sparam, TF_DROPDOWN_PREFIX) != 0) { HideTFDropdown(); } // Handle other button clicks & knob starts... if(id == CHARTEVENT_OBJECT_CLICK) { string obj = sparam; if(obj == btn_major_tf) { ShowTFDropdownFor(0); return; } if(obj == btn_lookback_minus) { g_Lookback = MathMax(10, g_Lookback - 10); ObjectSetString(main_chart_id, lbl_lookback, OBJPROP_TEXT, StringFormat("Lookback:%d", g_Lookback)); RefreshLines(); return; } if(obj == btn_lookback_plus) { g_Lookback += 10; ObjectSetString(main_chart_id, lbl_lookback, OBJPROP_TEXT, StringFormat("Lookback:%d", g_Lookback)); RefreshLines(); return; } if(obj == btn_toggle_openclose) { g_ShowOpenClose = !g_ShowOpenClose; ObjectSetString(main_chart_id, btn_toggle_openclose, OBJPROP_TEXT, g_ShowOpenClose ? "Open/Close: ON" : "Open/Close: OFF"); RefreshLines(); return; } if(obj == btn_toggle_fill) { g_ShowFill = !g_ShowFill; ObjectSetString(main_chart_id, btn_toggle_fill, OBJPROP_TEXT, g_ShowFill ? "Fill: ON" : "Fill: OFF"); RefreshLines(); return; } // major colors if(obj == btn_major_col1) { g_ColorMajor = major_colors[0]; RefreshLines(); return; } // ... other colors // minors toggles / tf dropdown if(obj == btn_minor1_toggle) { g_ShowMinor1 = !g_ShowMinor1; if(g_ShowMinor1) g_last_minor1_time = iTime(_Symbol, g_Minor1TF, 0); ObjectSetString(main_chart_id, btn_minor1_toggle, OBJPROP_TEXT, g_ShowMinor1 ? "Min1: ON" : "Min1: OFF"); RefreshLines(); return; } if(obj == btn_minor1_tf) { ShowTFDropdownFor(1); return; } if(obj == btn_minor2_toggle) { g_ShowMinor2 = !g_ShowMinor2; if(g_ShowMinor2) g_last_minor2_time = iTime(_Symbol, g_Minor2TF, 0); ObjectSetString(main_chart_id, btn_minor2_toggle, OBJPROP_TEXT, g_ShowMinor2 ? "Min2: ON" : "Min2: OFF"); RefreshLines(); return; } if(obj == btn_minor2_tf) { ShowTFDropdownFor(2); return; } if(obj == btn_clear_all) { DeleteAllHTFLines(); return; } // slider knob clicked -> begin dragging for(int s=0; s<SLIDER_COUNT; s++) { if(obj == g_slider_knob_names[s]) { g_current_slider = s; g_slider_drag = true; return; } } } }
11) OnTick() y OnTimer(): activadores de actualización eficientes
OnTick() comprueba el iTime más reciente para los marcos de tiempo superior e inferior configurados y establece need_refresh solo cuando aparece una nueva barra; esto evita la renovación innecesaria de objetos. OnTimer() simplemente llama a RefreshLines() en el intervalo g_RefreshSec (que el control deslizante puede modificar en tiempo real).
Actualizaciones rápidas pero eficientes; los cambios controlados por la interfaz de usuario (por ejemplo, alternar el relleno) llamarán a RefreshLines() inmediatamente, mientras que las comprobaciones periódicas gestionan los casos en los que aparecen nuevas barras HTF.
void OnTimer() { RefreshLines(); } void OnTick() { bool need_refresh = false; datetime curr; curr = iTime(_Symbol, g_HigherTF, 0); if(curr != g_last_major_time && curr != 0) { g_last_major_time = curr; need_refresh = true; } if(g_ShowMinor1) { curr = iTime(_Symbol, g_Minor1TF, 0); if(curr != g_last_minor1_time && curr != 0) { g_last_minor1_time = curr; need_refresh = true; } } if(g_ShowMinor2) { curr = iTime(_Symbol, g_Minor2TF, 0); if(curr != g_last_minor2_time && curr != 0) { g_last_minor2_time = curr; need_refresh = true; } } if(need_refresh) RefreshLines(); }
12) RefreshLines(): dibujo principal y recolección de basura
Esta es la rutina principal. Hace lo siguiente:
- Copia los tiempos de HTF, abre y cierra para el período de revisión solicitado.
- Invierte el orden de los arreglos para que se ordenen de forma ascendente, lo que permite realizar comparaciones de intervalos sencillas.
- Para cada período de tiempo importante, crea o actualiza una línea vertical, un rectángulo de relleno opcional, abre/cierra líneas horizontales y etiquetas.
- Para los menores, se llama a DrawMinorsBetweenIntervals() que dibuja verticales menores solo si caen estrictamente entre mayores consecutivos (más el intervalo actual en curso).
- Crea keepNames[] que enumera todos los objetos HTF deseados; al final, itera a través de todos los objetos del gráfico y elimina cualquier objeto HTF que no esté en keepNames[] (recolección de basura).
En cada pasada actualizamos las propiedades de los objetos existentes (color, ancho, etc.), de modo que los cambios en la interfaz de usuario surten efecto de inmediato sin necesidad de eliminar o recrear todo.
void RefreshLines() { datetime major_times[]; ArrayFree(major_times); double major_opens[]; ArrayFree(major_opens); double major_closes[]; ArrayFree(major_closes); int copiedMaj = CopyTime(_Symbol, g_HigherTF, 0, g_Lookback, major_times); if(copiedMaj <= 0) { PrintFormat("RefreshLines: CopyTime majors returned %d for %s", copiedMaj, TFToString(g_HigherTF)); return; } if(CopyOpen(_Symbol, g_HigherTF, 0, copiedMaj, major_opens) != copiedMaj) { Print("RefreshLines: CopyOpen failed"); return; } if(CopyClose(_Symbol, g_HigherTF, 0, copiedMaj, major_closes) != copiedMaj) { Print("RefreshLines: CopyClose failed"); return; } // reverse to ascending order (oldest first) int n = copiedMaj; datetime sorted_times[]; ArrayResize(sorted_times, n); double sorted_opens[]; ArrayResize(sorted_opens, n); double sorted_closes[]; ArrayResize(sorted_closes, n); for(int k = 0; k < n; k++) { sorted_times[k] = major_times[n - 1 - k]; sorted_opens[k] = major_opens[n - 1 - k]; sorted_closes[k] = major_closes[n - 1 - k]; } string keepNames[]; ArrayResize(keepNames, 0); // create/update major objects for(int i = 0; i < n; ++i) { datetime t = sorted_times[i]; double p_open = sorted_opens[i]; double p_close = sorted_closes[i]; // Major vertical string name = PREFIX_MAJ + TFToString(g_HigherTF) + "_" + IntegerToString((int)t); if(ObjectFind(0, name) == -1) { double dummy_price = 0.0; if(!ObjectCreate(0, name, OBJ_VLINE, 0, t, dummy_price)) PrintFormat("Failed to create major %s error %d", name, GetLastError()); } // update props each pass so UI changes apply immediately ObjectSetInteger(0, name, OBJPROP_COLOR, g_ColorMajor); ObjectSetInteger(0, name, OBJPROP_WIDTH, g_WidthMajor); ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, false); ObjectSetInteger(0, name, OBJPROP_BACK, true); // draw in background int sz = ArraySize(keepNames); ArrayResize(keepNames, sz + 1); keepNames[sz] = name; // Optional fill / open-close code follows (omitted for brevity) } if(n >= 2) { if(g_ShowMinor1) DrawMinorsBetweenIntervals(PREFIX_MIN1, g_Minor1TF, g_ColorMin1, g_WidthMin1, sorted_times, keepNames); if(g_ShowMinor2) DrawMinorsBetweenIntervals(PREFIX_MIN2, g_Minor2TF, g_ColorMin2, g_WidthMin2, sorted_times, keepNames); } // cleanup old HTF objects not in keepNames int total = ObjectsTotal(0); for(int idx = total - 1; idx >= 0; --idx) { string oname = ObjectName(0, idx); if(StringFind(oname, "HTF_") != -1) { bool found = false; for(int k=0;k<ArraySize(keepNames);k++) if(oname == keepNames[k]) { found = true; break; } if(!found) ObjectDelete(0, oname); } } UpdateLabelsAfterSliderChange(); ChartRedraw(main_chart_id); }
13) DrawMinorsBetweenIntervals(): lógica precisa para la colocación de menores.
Esta función calcula cuántas barras menores se deben solicitar (en función del intervalo de tiempo entre la primera barra mayor y el momento actual), copia los tiempos menores, los invierte a orden ascendente y coloca líneas menores verticales solo cuando un tiempo menor cae estrictamente entre tiempos mayores consecutivos; esto refleja el comportamiento del indicador. También trata los tiempos menores que caen dentro del intervalo mayor actual (después del último tiempo mayor).
Detalle de diseño: agregamos un margen (+20) a approx_bars para que sea robusto contra la alineación; también solicitamos al menos g_Lookback bars.
void DrawMinorsBetweenIntervals(const string prefix, const ENUM_TIMEFRAMES minorTF, const color c, const int width, const datetime &major_times[], string &keepNames[]) { datetime current_minor_time = iTime(_Symbol, minorTF, 0); if(current_minor_time == 0) return; int time_span = (int)(current_minor_time - major_times[0]); int minor_sec = PeriodSeconds(minorTF); int approx_bars = (time_span / minor_sec) + 20; if(approx_bars < g_Lookback) approx_bars = g_Lookback; datetime minor_times[]; ArrayFree(minor_times); int copiedMin = CopyTime(_Symbol, minorTF, 0, approx_bars, minor_times); PrintFormat("DrawMinorsBetweenIntervals: TF=%s copiedMin=%d majors=%d", TFToString(minorTF), copiedMin, ArraySize(major_times)); if(copiedMin <= 0) return; datetime sorted_minor_times[]; ArrayResize(sorted_minor_times, copiedMin); for(int k = 0; k < copiedMin; k++) sorted_minor_times[k] = minor_times[copiedMin - 1 - k]; int num_maj = ArraySize(major_times); for(int m = 0; m < copiedMin; ++m) { datetime mt = sorted_minor_times[m]; bool equals_major = false; for(int kk = 0; kk < num_maj; ++kk) if(major_times[kk] == mt) { equals_major = true; break; } if(equals_major) continue; bool placed = false; for(int j = 0; j < num_maj - 1; ++j) { if( major_times[j] < mt && mt < major_times[j+1] ) { string name = prefix + TFToString(minorTF) + "_" + IntegerToString((int)mt); if(ObjectFind(0, name) == -1) { double dummy_price = 0.0; if(!ObjectCreate(0, name, OBJ_VLINE, 0, mt, dummy_price)) PrintFormat("Failed to create minor %s error %d", name, GetLastError()); else { ObjectSetInteger(0, name, OBJPROP_COLOR, c); ObjectSetInteger(0, name, OBJPROP_WIDTH, width); ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, false); ObjectSetInteger(0, name, OBJPROP_BACK, true); } } int sz = ArraySize(keepNames); ArrayResize(keepNames, sz + 1); keepNames[sz] = name; placed = true; break; } } if(!placed && mt > major_times[num_maj - 1]) { // create a minor in the ongoing major interval string name = prefix + TFToString(minorTF) + "_" + IntegerToString((int)mt); if(ObjectFind(0, name) == -1) { double dummy_price = 0.0; if(!ObjectCreate(0, name, OBJ_VLINE, 0, mt, dummy_price)) PrintFormat("Failed to create minor (current) %s error %d", name, GetLastError()); else { ObjectSetInteger(0, name, OBJPROP_COLOR, c); ObjectSetInteger(0, name, OBJPROP_WIDTH, width); ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DOT); ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false); ObjectSetInteger(0, name, OBJPROP_HIDDEN, false); ObjectSetInteger(0, name, OBJPROP_BACK, true); } } int sz = ArraySize(keepNames); ArrayResize(keepNames, sz + 1); keepNames[sz] = name; placed = true; } } ChartRedraw(main_chart_id); }
14) Eliminación y limpieza: DeleteAllHTFLines() y OnDeinit()
DeleteAllHTFLines() elimina solo los objetos HTF; OnDeinit() elimina los objetos de la interfaz de usuario y los componentes del control deslizante, oculta los menús desplegables y destruye el lienzo. Intencionadamente, no eliminamos los objetos HTF por defecto al desinicializarlos (excepto cuando se pulsa Borrar HTF), para que el usuario pueda conservar los dibujos si lo prefiere.
void DeleteAllHTFLines() { int total = ObjectsTotal(0); for(int idx = total - 1; idx >= 0; --idx) { string oname = ObjectName(0, idx); if(StringFind(oname, "HTF_") != -1) ObjectDelete(0, oname); } } void OnDeinit(const int reason) { EventKillTimer(); string names[] = { lbl_title, btn_major_tf, lbl_major_tf, btn_lookback_minus, lbl_lookback, btn_lookback_plus, btn_toggle_openclose, btn_toggle_fill, btn_major_col1, btn_major_col2, btn_major_col3, btn_major_col4, btn_minor1_toggle, btn_minor1_tf, btn_minor2_toggle, btn_minor2_tf, btn_clear_all, lbl_major_width, lbl_refresh_label }; for(int i=0;i<ArraySize(names);i++) if(StringLen(names[i])>0) ObjectDelete(main_chart_id, names[i]); for(int s=0; s<SLIDER_COUNT; s++) { if(StringLen(g_slider_track_names[s])>0) ObjectDelete(main_chart_id, g_slider_track_names[s]); if(StringLen(g_slider_knob_names[s])>0) ObjectDelete(main_chart_id, g_slider_knob_names[s]); } if(g_tf_dropdown_visible) HideTFDropdown(); // destroy canvas g_bgCanvas.Destroy(); }
Pruebas
Tras una compilación exitosa, llegó el momento de probar el asesor experto en un gráfico real de MetaTrader 5. Es importante tener en cuenta que la clase CCanvas forma parte de la biblioteca estándar MQL5, por lo que es fundamental definir correctamente la ruta de inclusión para evitar errores de compilación. Una vez que el EA se adjuntó al gráfico, se inicializó sin problemas y el panel de control apareció como se esperaba.
La interfaz se visualizó de forma impecable, con todos los interruptores, controles deslizantes y etiquetas correctamente alineados sobre el fondo del lienzo. Durante las pruebas, cada control respondió en tiempo real: los deslizadores ajustaron dinámicamente parámetros visuales como el ancho y el intervalo de actualización, mientras que los interruptores alternaron instantáneamente la visibilidad y las funciones de relleno. El resultado fue una experiencia interactiva y de gran capacidad de respuesta que dio vida al concepto de control de parámetros en tiempo real.
A continuación se muestra una imagen que ilustra la implementación exitosa y los procesos de control activo de la EA, lo que confirma la estabilidad y la precisión del diseño en la ejecución.

Figura 3: Probando los controles en un gráfico en vivo
Un resultado particularmente interesante de este proyecto fue observar el efecto de relleno del cuerpo en tiempo real de las velas de marcos temporales superiores que se forman directamente en el gráfico de marcos temporales inferiores. A diferencia de la versión anterior, que requería reinicializar el indicador para actualizar los elementos visuales, esta implementación permite a los traders observar cómo evolucionan dinámicamente las estructuras de marcos temporales superiores. Ofrece una visión clara y continua de cómo cada movimiento en un marco temporal inferior contribuye a la formación de una barra en un marco temporal superior, proporcionando una visión más profunda de la estructura y el impulso del mercado a medida que se desarrolla.
Conclusión
Esta exploración ha supuesto un hito tanto técnico como creativo en nuestro continuo recorrido con el lenguaje MQL5. Lo que comenzó como una simple curiosidad por mejorar la interacción con los gráficos se convirtió en un sistema completo que redefine nuestra forma de abordar el ajuste y la visualización de los parámetros de entrada. Gracias a este desarrollo, hemos aprendido que MQL5 no es solo un lenguaje para la automatización de operaciones, sino también un lienzo para el diseño de la experiencia del usuario, que nos permite transformar parámetros de entrada estáticos en entornos de control dinámicos y con respuesta inmediata.
Aprovechando la manipulación de objetos y las interfaces gráficas mediante Asesores Expertos, demostramos que es totalmente posible controlar los componentes visuales y lógicos de un sistema directamente desde el gráfico. Este avance tiende un puente entre el análisis y la interacción, brindando a los traders y desarrolladores una nueva dimensión de velocidad, precisión y creatividad. Gracias al control en tiempo real, probar múltiples configuraciones, periodos de tiempo y modos de visualización resulta fluido, lo que ayuda a los usuarios a tomar decisiones más rápidamente sin perder el contexto.
La utilidad de control del sincronizador de períodos de mercado lleva esta filosofía más allá, permitiendo a los traders observar cómo los comportamientos en marcos temporales más pequeños influyen en las estructuras de marcos temporales más grandes. Aporta profundidad al análisis de múltiples marcos temporales, lo que permite a los usuarios estudiar el pulso del mercado en diferentes escalas y comprender mejor cómo las pequeñas fluctuaciones de precios contribuyen a la forma general de las velas principales. En esencia, fomenta un enfoque más científico para la interpretación de la evolución de los precios: no solo hay que ver qué sucedió, sino también por qué y cómo se produjo.
Sin embargo, esto es solo el principio. Las ideas exploradas aquí pueden ampliarse mucho más allá del ámbito de esta utilidad. Las mejoras futuras podrían incluir controles de gráficos personalizados, sincronización de múltiples símbolos, exportación de datos para análisis o ajuste de la visualización mediante inteligencia artificial. La belleza de MQL5 reside en su apertura: recompensa la experimentación, la creatividad y la voluntad de explorar lo poco convencional.
Anímate, experimenta con esta idea en tus propios proyectos. Adáptalo, modifícalo, intégralo con tus herramientas existentes y descubre qué nuevas posibilidades puede abrir para tus sistemas de negociación. Los comentarios, las ideas y las aportaciones de todos vosotros son muy valiosos, así que no dudéis en compartir vuestras opiniones y sugerencias en los comentarios que aparecen a continuación. Juntos, podemos seguir haciendo de la comunidad MQL5 un espacio más dinámico, innovador y colaborativo para el aprendizaje y el crecimiento.
Por último, a continuación encontrarás una tabla resumen con las principales conclusiones de esta exploración, junto con los archivos fuente adjuntos para que podáis descargarlos, estudiarlos y ampliarlos. Recuerda: la mejor manera de dominar algo es construir sobre ello. Experimenta con audacia, estudia a fondo y deja que tu trabajo inspire a otros.
Lecciones clave
| Lección | Descripción |
|---|---|
| 1. Es posible el control en tiempo real. | Mediante el manejo estructurado de eventos y las actualizaciones de objetos, es posible crear interfaces que reaccionan instantáneamente a la entrada del usuario, lo que permite obtener retroalimentación inmediata y actualizaciones visuales en el gráfico. |
| 2. CCanvas libera la creatividad visual | La clase Canvas ofrece la posibilidad de diseñar paneles de control profesionales y visualmente atractivos. Permite pintar el fondo, aplicar transparencia y crear capas para mejorar la interacción del usuario. |
| 3. La capacidad de respuesta de la interfaz de usuario depende de la gestión de objetos. | Gestionar adecuadamente la creación, actualización y eliminación de objetos de gráficos garantiza un rendimiento fluido y evita la saturación o la ralentización durante las actualizaciones en tiempo real. |
| 4. Los parámetros dinámicos mejoran la experimentación: | Permitir a los traders ajustar configuraciones como marcos temporales, colores y anchos sin necesidad de volver a abrir la ventana de propiedades, acelera la experimentación y la eficiencia del análisis. |
| 5. La sincronización en múltiples marcos temporales añade profundidad. | La combinación visual de datos de marcos temporales superiores e inferiores ayuda a los traders a comprender la estructura interna del mercado, identificar micromovimientos y vincular tendencias más pequeñas con formaciones más grandes. |
| 6. El diseño basado en eventos es fundamental para las herramientas interactivas. | La creación de sistemas interactivos requiere un sólido conocimiento del manejo de eventos en gráficos. Para lograr un control intuitivo, cada acción del usuario debe estar asociada a una respuesta específica del programa. |
| 7. La superposición de objetos mejora la usabilidad. | Al configurar cuidadosamente las propiedades de fondo y primer plano, se puede garantizar que la interfaz siga respondiendo y que los objetos nunca bloqueen las interacciones esenciales del usuario. |
| 8. El código modular aumenta la facilidad de mantenimiento. | Dividir el proyecto en secciones lógicas, como la creación de la interfaz de usuario, el manejo de eventos y las funciones de dibujo, facilita la expansión, la depuración y la reutilización del sistema en proyectos futuros. |
| 9. Pruebas y depuración de herramientas de visualización. | Las pruebas visuales en tiempo real revelaron problemas como objetos que no se actualizaban y conflictos de superposición, lo que nos enseñó métodos sistemáticos de depuración mediante el uso de registros y el seguimiento de eventos. |
| 10. Optimización de los intervalos de actualización y dibujo. | Aprendimos cómo ajustar los temporizadores de actualización y las estrategias de redibujado puede mejorar el rendimiento y la capacidad de respuesta, especialmente para las actualizaciones de gráficos en tiempo real. |
| 11. La retroalimentación visual mejora la confianza analítica. | Los ajustes visuales en tiempo real ayudan a los traders a observar la relación causa-efecto al instante, lo que genera confianza en la precisión y la interpretación de los datos de los gráficos. |
| 12. La experimentación impulsa la innovación. | Este proyecto demuestra que explorar enfoques no convencionales en MQL5 da lugar a nuevas herramientas e ideas. La creatividad combinada con el conocimiento técnico genera progreso para toda la comunidad. |
Archivos adjuntos
| Nombre del archivo | Versión | Descripción |
|---|---|---|
| MarketPeriodsSynchronizer_EA.mq5 | 1.00 | Este asesor experto proporciona un panel de control en tiempo real para sincronizar y visualizar múltiples períodos de tiempo directamente en el gráfico. Amplía la funcionalidad del indicador original Market Periods Synchronizer al integrar controles interactivos, deslizadores e interruptores para el ajuste instantáneo de parámetros sin necesidad de volver a abrir la configuración de entrada. |
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/19918
Advertencia: todos los derechos de estos materiales pertenecen a MetaQuotes Ltd. Queda totalmente prohibido el copiado total o parcial.
Este artículo ha sido escrito por un usuario del sitio web y refleja su punto de vista personal. MetaQuotes Ltd. no se responsabiliza de la exactitud de la información ofrecida, ni de las posibles consecuencias del uso de las soluciones, estrategias o recomendaciones descritas.
Utilizando redes neuronales en MetaTrader
Características del Wizard MQL5 que debe conocer (Parte 69): Uso de patrones del SAR y del RVI
Particularidades del trabajo con números del tipo double en MQL4
Introducción a MQL5 (Parte 23): Automatización de la estrategia de ruptura del rango de apertura (ORB)
- 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