De novato a experto: Noticias animadas utilizando MQL5 (IX) Gestión de múltiples símbolos en un único gráfico para el trading de noticias
Contenido
- Introducción
- Comprender el concepto
- Implementación
- Modificación de la clase CtradingButtons para el trading con múltiples símbolos
- Integración de la funcionalidad de trading con múltiples símbolos en el EA «News Headline»
- Pruebas
- Conclusión
- Lecciones clave
- Archivos adjuntos
Introducción
En períodos de gran volatilidad —como cuando se publican noticias económicas—, los operadores suelen apostar por rupturas del precio, ya que la reacción inmediata del mercado es impredecible. Cuando se produce una noticia importante, el precio suele dispararse bruscamente, a lo que suelen seguir correcciones y posibles continuaciones de la tendencia. En estas circunstancias, es posible que los operadores deseen operar con varios instrumentos a la vez, pero esto resulta difícil de conseguir con la configuración predeterminada de MetaTrader 5. Por diseño, un gráfico solo admite un asesor experto, lo que significa que los operadores deben abrir varios gráficos y asignar un asesor experto distinto a cada símbolo.
En este artículo, presentamos una solución a esta limitación: una funcionalidad de trading multisímbolo integrada en el EA «News Headline». Gracias a esta mejora, los operadores pueden gestionar varios pares desde un único gráfico mediante botones de negociación intuitivos. Analizaremos cómo el potencial de MQL5 —aprovechando tanto la biblioteca estándar como las clases de trading personalizadas— permite crear un EA sofisticado capaz de gestionar múltiples símbolos sin problemas en un solo gráfico.

Figura 1: Solo se permite un EA por gráfico en la terminal MetaTrader 5.
La imagen superior ilustra la limitación de la configuración de MetaTrader 5, donde solo se puede ejecutar un Asesor Experto (EA) en un único gráfico. Para operar con múltiples símbolos de manera efectiva, necesitamos un EA sofisticado capaz de gestionar tanto el par de divisas actual como otros pares simultáneamente, incluso operando desde un solo gráfico.
Al final del artículo, nuestro objetivo es lograr lo siguiente:
- Desarrollar un EA más sofisticado.
- Ampliar una clase existente definida en un archivo de cabecera de MQL5.
- Aprovechar la biblioteca estándar de MQL5 para crear nuevas clases.
- Integrar nueva funcionalidad en un EA existente.
- Aplicar la modularización y la agrupación estructurada de las entradas.
Comprender el concepto
Esta etapa comienza con una breve revisión de nuestro trabajo anterior. Comenzamos con un EA sencillo con titulares de noticias animados que obtenía datos del Calendario Económico y de API de noticias externas como Alpha Vantage. Con el tiempo, integramos modelos de IA alojados localmente, estrategias automatizadas de negociación basadas en noticias y botones de negociación manual para mejorar la fiabilidad del EA.
Si bien estas innovaciones mejoraron el sistema, no constituían una solución completa. El trading algorítmico sigue evolucionando y, con cada avance tecnológico, surgen nuevos desafíos que nos impulsan a actualizar nuestros sistemas. Hoy abordaremos uno de esos desafíos: habilitar el trading con múltiples pares de divisas dentro del mismo Asesor Experto (EA).
¿Por qué es necesario?
Una pregunta válida que uno podría hacerse es: ¿por qué necesitamos esta función?
Durante eventos de alta volatilidad, como la publicación de noticias económicas, los operadores deben reaccionar rápidamente, gestionando a menudo múltiples posiciones y símbolos en cuestión de segundos. Este avance proporciona una ventaja crucial al fusionar el trading algorítmico y manual en un solo lugar, mejorando la eficiencia y el control. Con un solo clic, un operador puede abrir operaciones en varios símbolos y gestionar múltiples posiciones simultáneamente, lo que le permite ganar en velocidad y eficiencia.
Proceso de integración de la funcionalidad
Teniendo esto en cuenta, vamos a describir brevemente cómo se añadirá la nueva función. Para ampliar nuestro EA, nos apoyamos en la inclusión de archivos de cabecera y clases de botones de negociación personalizadas, lo que mantiene el código base principal limpio y modular.
Para operar con múltiples símbolos, necesitamos la capacidad de seleccionar los pares deseados que se ejecutarán junto con el par que aparece en el gráfico actual cuando se pulsen los botones de negociación manual. Para lograr esto, utilizaremos las clases CCheckBox y CLabel de la biblioteca estándar MQL5. Estos componentes nos permitirán mostrar pares seleccionables, gestionar la entrada del usuario y vincular las selecciones directamente a los controladores de eventos de los botones.
Finalmente, nuestra clase CTradingButtons se ampliará para incorporar estas nuevas funciones sin problemas.
Implementación
Abordaremos esto en dos etapas principales. En primer lugar, modificaremos la clase CTradingButtons en el archivo de encabezado TradingButtons para implementar las funciones de negociación de múltiples símbolos descritas en nuestro diseño. La segunda fase se centrará en adaptar el EA «News Headline» para que sea compatible con estas nuevas funcionalidades.
Síguenos atentamente mientras desglosamos el código y explicamos cómo cada parte contribuye a dar vida a la idea. Para mayor claridad, cada sección del código y su explicación estarán numeradas secuencialmente de arriba abajo, haciendo hincapié en la nueva funcionalidad.
Si quieres profundizar en los aspectos fundamentales del código, te animo a que vuelvas a consultar las publicaciones anteriores de esta serie, en las que tratamos las versiones iniciales con detalle.
Modificación de la clase CtradingButtons para el trading con múltiples símbolos
Ya presentamos este encabezado en el artículo anterior, al que puedes acudir para mayor claridad. En esta sección, lo ampliamos con una nueva función.
Resumen general
Esta clase (CTradingButtons) agrupa tres funciones, por lo que actúa como un módulo de negociación multisímbolo compacto que se puede integrar en un EA: (1) una interfaz de usuario (lienzo + botones + casillas de selección creadas dinámicamente para los símbolos), (2) una pequeña capa auxiliar de trading (una instancia de CTrade que envía órdenes), y (3) un motor de resolución de símbolos y multisímbolo (asigna los nombres base solicitados, como EURUSD, a los símbolos del bróker y aplica acciones a todos los símbolos seleccionados). El diseño de alto nivel mantiene la alineación de índices entre las matrices: la lista solicitada (lo que pasa el EA), los símbolos de los brókers resueltos (lo que realmente negocia la plataforma) y las casillas de selección (lo que activa o desactiva el usuario); el índice i representa el mismo par en todas las matrices. Esto hace que la conexión entre la interfaz de usuario y las matrices de selección de EA sea sencilla y predecible.
// top-of-file: class skeleton + key members (from TradingButtons.mqh) class CTradingButtons { private: // UI & buttons CButton btnMultiToggle; CButton btnBuy, btnSell, btnCloseAll, btnDeleteOrders, btnCloseProfit, btnCloseLoss, btnBuyStop, btnSellStop; CCanvas buttonPanel; // trading CTrade trade; // multipair UI & resolution CCheckBox *pairChecks[]; // dynamic checkbox pointers, index-aligned with requested list string availablePairs[]; // resolved broker symbols (index-aligned) string resolvedBases[]; // original requested bases (for logging) bool multiEnabled; public: double LotSize; int StopLoss; int TakeProfit; int StopOrderDistancePips; double RiskRewardRatio; CTradingButtons() { /* default init inlined in full file */ } void Init(); void Deinit(); // ... other methods follow };
Campos y constructor: lo que almacena la clase
La clase almacena los parámetros de diseño (ancho, alto y espaciado de los botones), el tamaño y las coordenadas iniciales de las casillas de selección, la matriz dinámica de casillas de selección, las matrices de símbolos resueltos y la configuración de operaciones (tamaño del lote, stops, relación riesgo/recompensa). Su constructor establece unos valores predeterminados razonables (por ejemplo, LotSize=0,01, StopLoss=50, TakeProfit=100, multiEnabled=true), de modo que el EA pueda comenzar con una configuración operativa y modificar lo que sea necesario más adelante. Mantener estos campos como públicos (para los parámetros de trading) y privados (para los componentes internos de la interfaz de usuario) permite que la interfaz sea sencilla y segura.
// constructor + key field defaults (actual defaults in your file) CTradingButtons() : buttonWidth(100), buttonHeight(30), buttonSpacing(10), checkWidth(120), checkHeight(20), checkSpacing(6), checkStartX(10), LotSize(0.01), StopLoss(50), TakeProfit(100), StopOrderDistancePips(8), RiskRewardRatio(2.0), multiEnabled(true) { // constructor body intentionally minimal — Init() performs heavier setup }
Inicialización y limpieza
La función `Init()` configura la capa auxiliar de ejecución de órdenes de CTrade (número mágico, desviación) y crea la interfaz de usuario (panel, botones, conmutador múltiple). La función `Deinit()` destruye cuidadosamente todos los objetos dinámicos (casillas de selección, botones, lienzos) y libera las matrices para evitar que queden objetos de gráficos huérfanos o se produzcan fugas de memoria. La rutina de limpieza recorre la matriz pairChecks[] y llama a Destroy() y a delete en los punteros dinámicos; a continuación, libera la matriz, lo cual es fundamental cuando se ejecuta y se cierra el EA repetidamente durante el desarrollo.
// Init & Deinit excerpt void Init() { trade.SetExpertMagicNumber(123456); trade.SetDeviationInPoints(10); CreateButtonPanel(); CreateButtons(); CreateMultiToggle(); UpdateMultiToggleVisual(); } void Deinit() { // destroy checkboxes for(int i = 0; i < ArraySize(pairChecks); i++) { if(CheckPointer(pairChecks[i]) == POINTER_DYNAMIC) pairChecks[i].Destroy(); delete pairChecks[i]; } } ArrayFree(pairChecks); // destroy buttons and panel btnMultiToggle.Destroy(); btnBuy.Destroy(); btnSell.Destroy(); btnCloseAll.Destroy(); btnDeleteOrders.Destroy(); btnCloseProfit.Destroy(); btnCloseLoss.Destroy(); btnBuyStop.Destroy(); btnSellStop.Destroy(); buttonPanel.Destroy(); ObjectDelete(0, "ButtonPanel"); }
Resolución de símbolos (importante para multisímbolo)
Para operar con un nombre «fácil de recordar» como EURUSD, el EA debe asignarle la cadena de símbolo exacta del bróker (que podría ser EURUSD, EURUSD.ecn, FX.EURUSD, etc.). ResolveSymbol(base) primero busca una coincidencia exacta (primero prueba una coincidencia exacta). Si eso falla, recorre todos los símbolos de terminal, busca coincidencias que empiecen por y luego las que contengan (dando prioridad a las que empiecen por), y excluye los símbolos desactivados. Este paso de resolución genera las entradas de `availablePairs[i]` que utilizan las rutinas de negociación y las casillas de selección de la interfaz de usuario; es el nexo de unión entre los nombres solicitados por el EA y los símbolos negociables reales del bróker.
// ResolveSymbol implementation (exact + starts-with + contains search) string ResolveSymbol(const string base) { if(StringLen(base) == 0) return(""); // 1) Try exact symbol name first string baseName = base; if(SymbolInfoInteger(baseName, SYMBOL_SELECT) != 0 || SymbolSelect(baseName, false)) { if(SymbolInfoInteger(baseName, SYMBOL_TRADE_MODE) != SYMBOL_TRADE_MODE_DISABLED) { PrintFormat("ResolveSymbol: exact match found %s", baseName); return(baseName); } } // 2) search all terminal symbols int total = SymbolsTotal(false); string base_u = base; StringToUpper(base_u); string firstStarts = ""; string firstContains = ""; for(int i = 0; i < total; i++) { string sym = SymbolName(i, false); string sym_u = sym; StringToUpper(sym_u); if(sym_u == base_u) continue; if(SymbolInfoInteger(sym, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_DISABLED) continue; if(StringFind(sym_u, base_u) == 0) { if(firstStarts == "") firstStarts = sym; } else if(StringFind(sym_u, base_u) >= 0) { if(firstContains == "") firstContains = sym; } } if(firstStarts != "") { PrintFormat("ResolveSymbol: resolved %s -> %s (starts-with)", base, firstStarts); return(firstStarts); } if(firstContains != "") { PrintFormat("ResolveSymbol: resolved %s -> %s (contains)", base, firstContains); return(firstContains); } PrintFormat("ResolveSymbol: no match for %s", base); return(""); }
Creación de la interfaz de usuario multisímbolo — CreatePairCheckboxes(...)
CreatePairCheckboxes(inMajorPairs[], inPairSelected[], yPos) es la rutina que: (a) resuelve cada par de divisas solicitado en un símbolo de bróker, (b) comprueba que el símbolo esté presente en Market Watch (SymbolSelect) y sea negociable, y (c) crea dinámicamente un CCheckBox para cada símbolo resuelto, manteniendo la alineación de índices con las matrices del EA. Las entradas sin resolver o desactivadas se conservan como marcadores de posición, de modo que availablePairs[i] se corresponda correctamente con el elemento original de inMajorPairs[i]. El estado inicial de cada casilla de verificación creada se toma de inPairSelected[i], por lo que las matrices de selección de la interfaz de usuario y del EA están sincronizadas desde el principio.
// CreatePairCheckboxes: resolve requested bases -> create checkboxes aligned by index void CreatePairCheckboxes(string &inMajorPairs[], bool &inPairSelected[], int yPos) { // cleanup previous for(int i = 0; i < ArraySize(pairChecks); i++) { if(CheckPointer(pairChecks[i]) == POINTER_DYNAMIC) { pairChecks[i].Destroy(); delete pairChecks[i]; } } ArrayFree(pairChecks); ArrayResize(availablePairs, ArraySize(inMajorPairs)); ArrayResize(resolvedBases, ArraySize(inMajorPairs)); for(int k = 0; k < ArraySize(availablePairs); k++) { availablePairs[k] = ""; resolvedBases[k] = ""; } int count = ArraySize(inMajorPairs); if(count == 0) return; // Resolve each requested base for(int i = 0; i < count; i++) { string requested = inMajorPairs[i]; string resolved = ResolveSymbol(requested); if(resolved == "") { PrintFormat("CreatePairCheckboxes: could not resolve %s -> skipping checkbox", requested); availablePairs[i] = ""; resolvedBases[i] = requested; continue; } if(!SymbolSelect(resolved, true)) PrintFormat("CreatePairCheckboxes: SymbolSelect failed for %s (from %s) Err=%d", resolved, requested, GetLastError()); if(SymbolInfoInteger(resolved, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_DISABLED) { PrintFormat("CreatePairCheckboxes: resolved symbol %s is disabled (from %s) - skipping", resolved, requested); availablePairs[i] = ""; resolvedBases[i] = requested; continue; } availablePairs[i] = resolved; resolvedBases[i] = requested; } // Create checkbox controls (preserve index alignment) ArrayResize(pairChecks, count); int xPos = checkStartX; int chartW = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS); int wrapX = chartW - (buttonWidth * 3) - 30; for(int i = 0; i < count; i++) { if(StringLen(availablePairs[i]) == 0) { pairChecks[i] = NULL; continue; } pairChecks[i] = new CCheckBox(); string objName = "Chk_" + availablePairs[i]; if(!pairChecks[i].Create(ChartID(), objName, 0, xPos, yPos, xPos + checkWidth, yPos + checkHeight)) { PrintFormat("CreatePairCheckboxes: failed to create checkbox %s Err=%d", objName, GetLastError()); delete pairChecks[i]; pairChecks[i] = NULL; availablePairs[i] = ""; continue; } pairChecks[i].Text(" " + availablePairs[i]); pairChecks[i].Color(clrBlack); bool checked = false; if(i < ArraySize(inPairSelected)) checked = inPairSelected[i]; pairChecks[i].Checked(checked); xPos += checkWidth + checkSpacing; if(xPos + checkWidth > wrapX) { xPos = checkStartX; yPos += checkHeight + checkSpacing; } } ChartRedraw(); PrintFormat("CreatePairCheckboxes: created checkboxes (resolved count=%d)", CountResolvedPairs()); }
Contador/ayuda — CountResolvedPairs()
Los pequeños ayudantes mantienen la legibilidad del código. La función CountResolvedPairs() simplemente cuenta las entradas no vacías del array availablePairs[] y se utiliza para registrar datos o actualizar el texto de la interfaz de usuario. Es una línea de código, pero resulta útil durante la inicialización y la resolución de problemas.
// Count resolved availablePairs entries int CountResolvedPairs() { int c = 0; for(int i = 0; i < ArraySize(availablePairs); i++) if(StringLen(availablePairs[i]) > 0) c++; return c; }
Gestión de eventos — HandleChartEvent(...)
Todos los clics realizados en los objetos del gráfico se canalizan hacia HandleChartEvent. Reconoce tres categorías: (A) clics en casillas de selección (nombres de objetos con el prefijo Chk_ — determina qué símbolo resuelto recibió el clic y sincroniza la matriz inPairSelected[i]), (B) el botón de alternancia múltiple (alterna multiEnabled y actualiza los elementos visuales), y (C) los botones de acción (Comprar/Vender/Cerrar todo/Eliminar pendientes/Colocar stops); cada clic en un botón delega en la operación correspondiente, pasando los pares solicitados por el EA y los indicadores de selección. La función actúa como enrutador entre la interfaz de usuario y el motor, y mantiene sincronizadas las matrices de la interfaz de usuario y del motor.
// HandleChartEvent: routes object clicks to checkboxes / toggle / actions void HandleChartEvent(const int id, const string &sparam, string &inMajorPairs[], bool &inPairSelected[]) { if(id == CHARTEVENT_OBJECT_CLICK) { // Checkbox click handling if(StringFind(sparam, "Chk_") == 0) { for(int i = 0; i < ArraySize(availablePairs); i++) { if(StringLen(availablePairs[i]) == 0) continue; string expected = "Chk_" + availablePairs[i]; if(expected == sparam) { if(CheckPointer(pairChecks[i]) == POINTER_DYNAMIC) { bool current = pairChecks[i].Checked(); if(i < ArraySize(inPairSelected)) inPairSelected[i] = current; else { ArrayResize(inPairSelected, i+1); inPairSelected[i] = current; } PrintFormat("HandleChartEvent: checkbox for %s toggled -> %s", availablePairs[i], current ? "true":"false"); } break; } } return; } // Multi toggle if(sparam == btnMultiToggle.Name()) { multiEnabled = !multiEnabled; UpdateMultiToggleVisual(); PrintFormat("HandleChartEvent: Multi toggle clicked. New multiEnabled=%s", (multiEnabled ? "true":"false")); return; } // Buttons - delegate to command handlers if(sparam == btnBuy.Name()) OpenBuyOrder(inMajorPairs, inPairSelected); else if(sparam == btnSell.Name()) OpenSellOrder(inMajorPairs, inPairSelected); else if(sparam == btnCloseAll.Name()) CloseAllPositions(inMajorPairs, inPairSelected); else if(sparam == btnDeleteOrders.Name()) DeleteAllPendingOrders(inMajorPairs, inPairSelected); else if(sparam == btnCloseProfit.Name()) CloseProfitablePositions(inMajorPairs, inPairSelected); else if(sparam == btnCloseLoss.Name()) CloseLosingPositions(inMajorPairs, inPairSelected); else if(sparam == btnBuyStop.Name()) PlaceBuyStop(inMajorPairs, inPairSelected); else if(sparam == btnSellStop.Name()) PlaceSellStop(inMajorPairs, inPairSelected); } }
Herramientas de ayuda para la creación de interfaces de usuario
La creación de la interfaz de usuario se divide en pequeñas funciones auxiliares: CreateButtonPanel() genera un mapa de bits del lienzo (fondo del panel y rectángulo decorativo), CreateButtons() crea instancias de cada botón de acción y les aplica un estilo con fuente, tamaño y posiciones uniformes, y CreateMultiToggle() crea el botón de alternancia situado encima de los botones de acción principales. La función `UpdateMultiToggleVisual()` actualiza el texto y el color del conmutador para indicar si el modo multisímbolo está activo. Estos ayudantes mantienen el código visual separado de la lógica de negocio y facilitan los cambios de estilo.
// Create panel + buttons + multi-toggle visual helpers void CreateButtonPanel() { int panelWidthLocal = buttonWidth + 20; int panelHeightLocal = (buttonHeight + buttonSpacing) * 9 + buttonSpacing + 40; int x = 0, y = 40; if(!buttonPanel.CreateBitmap(0, 0, "ButtonPanel", x, y, panelWidthLocal, panelHeightLocal, COLOR_FORMAT_ARGB_NORMALIZE)) { Print("Failed to create button panel: Error=", GetLastError()); return; } ObjectSetInteger(0, "ButtonPanel", OBJPROP_ZORDER, 10); buttonPanel.FillRectangle(0, 0, panelWidthLocal, panelHeightLocal, ColorToARGB(clrDarkGray, 200)); buttonPanel.Rectangle(0, 0, panelWidthLocal - 1, panelHeightLocal - 1, ColorToARGB(clrRed, 255)); buttonPanel.Update(true); ChartRedraw(0); } void CreateMultiToggle() { int x = 10, y = 120; string font = "Calibri"; int fontSize = 8; color buttonBgColor = clrBlack; if(btnMultiToggle.Create(0, "btnMultiToggle", 0, x, y, x + buttonWidth, y + buttonHeight)) { ObjectSetString(0, "btnMultiToggle", OBJPROP_FONT, font); ObjectSetInteger(0, "btnMultiToggle", OBJPROP_FONTSIZE, fontSize); ObjectSetInteger(0, "btnMultiToggle", OBJPROP_BGCOLOR, buttonBgColor); ObjectSetInteger(0, "btnMultiToggle", OBJPROP_ZORDER, 11); } } void UpdateMultiToggleVisual() { if(multiEnabled) { btnMultiToggle.Text("MULTI:ON"); btnMultiToggle.ColorBackground(clrGreen); btnMultiToggle.Color(clrWhite); } else { btnMultiToggle.Text("MULTI:OFF"); btnMultiToggle.ColorBackground(clrRed); btnMultiToggle.Color(clrWhite); } }
Funciones auxiliares para el trading
Estos ayudantes encapsulan la mecánica de trades de bajo nivel. PipSize(symbol) devuelve el tamaño en pips (en tu código se utiliza SYMBOL_POINT * 10.0), IsSymbolValid(symbol) comprueba si existen precios de compra y venta, y TradeBuySingle()/TradeSellSingle() verifican la negociabilidad y los límites de lotes, calculan el precio, el SL y el TP utilizando el tamaño en pips, establecen el tipo de ejecución y envían la orden a través de trade.Buy()/trade.Sell(). Estas funciones auxiliares centralizan la lógica de envío de órdenes, de modo que los bucles multisímbolo solo tienen que llamarlas por símbolo.
// Pip size, validation, and single-symbol trade helpers double PipSize(string symbol) { double point = SymbolInfoDouble(symbol, SYMBOL_POINT); if(point <= 0) { Print("Invalid point size for ", symbol, ": Error=", GetLastError()); return 0; } return point * 10.0; } bool IsSymbolValid(string symbol) { bool inMarketWatch = SymbolInfoDouble(symbol, SYMBOL_BID) > 0 && SymbolInfoDouble(symbol, SYMBOL_ASK) > 0; if(!inMarketWatch) Print("Symbol ", symbol, " invalid: Not in Market Watch or no valid bid/ask price."); return inMarketWatch; } bool TradeBuySingle(const string symbol) { if(!IsSymbolValid(symbol)) return false; long tradeMode = SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE); if(tradeMode != SYMBOL_TRADE_MODE_FULL) { Print("TradeBuySingle: Skipping ", symbol, ": Trading disabled"); return false; } double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); if(LotSize < minLot || LotSize > maxLot) { Print("TradeBuySingle: invalid lot"); return false; } double price = SymbolInfoDouble(symbol, SYMBOL_ASK); double sl = StopLoss > 0 ? price - StopLoss * PipSize(symbol) : 0; double tp = TakeProfit > 0 ? price + TakeProfit * PipSize(symbol) : 0; trade.SetTypeFillingBySymbol(symbol); int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); price = NormalizeDouble(price, digits); sl = NormalizeDouble(sl, digits); tp = NormalizeDouble(tp, digits); if(trade.Buy(LotSize, symbol, price, sl, tp)) { Print("Buy order placed on ", symbol, ": Ticket #", trade.ResultOrder()); return true; } else { Print("Buy order failed on ", symbol, ": Retcode=", trade.ResultRetcode()); return false; } } bool TradeSellSingle(const string symbol) { if(!IsSymbolValid(symbol)) return false; long tradeMode = SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE); if(tradeMode != SYMBOL_TRADE_MODE_FULL) { Print("TradeSellSingle: Skipping ", symbol, ": Trading disabled"); return false; } double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); if(LotSize < minLot || LotSize > maxLot) { Print("TradeSellSingle: invalid lot"); return false; } double price = SymbolInfoDouble(symbol, SYMBOL_BID); double sl = StopLoss > 0 ? price + StopLoss * PipSize(symbol) : 0; double tp = TakeProfit > 0 ? price - TakeProfit * PipSize(symbol) : 0; trade.SetTypeFillingBySymbol(symbol); int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); price = NormalizeDouble(price, digits); sl = NormalizeDouble(sl, digits); tp = NormalizeDouble(tp, digits); if(trade.Sell(LotSize, symbol, price, sl, tp)) { Print("Sell order placed on ", symbol, ": Ticket #", trade.ResultOrder()); return true; } else { Print("Sell order failed on ", symbol, ": Retcode=", trade.ResultRetcode()); return false; } }
Operaciones principales: cómo se aplican los comandos multisímbolo
Las operaciones principales (por ejemplo, OpenBuyOrder, OpenSellOrder, CloseAllPositions, PlaceBuyStop, PlaceSellStop) aceptan las matrices inMajorPairs[] e inPairSelected[] proporcionadas por el EA. Cuando multiEnabled es true, las rutinas recorren todos los índices y llaman a las funciones auxiliares de negociación utilizando availablePairs[i] para cada índice seleccionado. Si multiEnabled es false, la rutina solo opera con el símbolo del gráfico. Las funciones OpenBuyOrder y OpenSellOrder también comprueban, mediante casillas de selección, si ya se ha negociado con el símbolo del gráfico y, en caso contrario, vuelven a negociar con dicho símbolo; esto garantiza que se cumplan las expectativas del usuario al cambiar entre los modos centrado en el gráfico y multisímbolo.
// OpenBuyOrder / OpenSellOrder excerpt (multipair iteration + fallback to chart symbol) void OpenBuyOrder(string &inMajorPairs[], bool &inPairSelected[]) { Print("Starting OpenBuyOrder"); string chartSym = Symbol(); if(!multiEnabled) { PrintFormat("OpenBuyOrder: multipair disabled => trading only chart symbol %s", chartSym); if(SymbolInfoInteger(chartSym, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_DISABLED) { Print("chart symbol not tradeable"); return; } TradeBuySingle(chartSym); return; } bool chartTraded = false; for(int i = 0; i < ArraySize(inMajorPairs); i++) { if(i < ArraySize(inPairSelected) && inPairSelected[i] && StringLen(availablePairs[i]) > 0) { string symbol = availablePairs[i]; Print("Attempting Buy order on ", symbol, " (requested ", resolvedBases[i], ")"); if(TradeBuySingle(symbol) && symbol == chartSym) chartTraded = true; } else { if(i < ArraySize(inMajorPairs)) Print("Skipping ", inMajorPairs[i], ": Not selected or unresolved"); } } if(!chartTraded && SymbolInfoInteger(chartSym, SYMBOL_TRADE_MODE) != SYMBOL_TRADE_MODE_DISABLED) { PrintFormat("OpenBuyOrder: attempting BUY on chart symbol %s", chartSym); TradeBuySingle(chartSym); } } void OpenSellOrder(string &inMajorPairs[], bool &inPairSelected[]) { // (same structure as OpenBuyOrder but calling TradeSellSingle) // Implementation mirrors the buy flow but uses SELL specifics. }
Integración de la funcionalidad de trading con múltiples símbolos en el EA «News Headline»
Dónde se instala el sistema multisímbolo (includes y entradas)
El EA incluye en la parte superior del archivo el encabezado de interfaz y trading con soporte multisímbolo, y ofrece una opción para activar o desactivar la función multisímbolo al inicio. Este es el único lugar en el que el EA indica: (a) que utilizará el objeto de interfaz de usuario/negociación multisímbolo externo, y (b) que el usuario puede configurar el modo multisímbolo inicial. Esto hace que la funcionalidad sea opcional desde el inicio y quede claramente expuesta al usuario del EA.
#include <TradingButtons.mqh> // header that implements the multipair UI & trading logic. // ... other includes ... input bool EnableMultipair = true; // initial multipair enabled state
Tenemos que incluir el encabezado y añadir un campo de entrada para que los usuarios puedan seleccionar el comportamiento deseado durante la inicialización.
Dónde se almacenan los datos de pares múltiples (majorPairs y los indicadores de selección)
El EA define una matriz de cadenas majorPairs[] con los nombres de los pares solicitados y una matriz booleana paralela pairSelected[] que registra qué pares están marcados. Estas dos matrices constituyen el acuerdo entre el EA y el encabezado: el índice i en ambas matrices hace referencia al mismo par de divisas. El encabezado crea casillas de selección y utiliza la matriz booleana para saber qué pares están seleccionados.
// MULTIPAIR arrays (provided to the header) // default major pairs (you can edit or later replace with resolved broker symbols) string majorPairs[] = {"EURUSD","GBPUSD","USDJPY","USDCHF","AUDUSD","USDCAD","NZDUSD"}; bool pairSelected[];
Mantener una lista de pares sencilla y alineada con el índice, junto con indicadores de selección. Es fácil de leer, se puede pasar por referencia sin problemas y facilita la sincronización.
Inicializar los valores predeterminados de selección y pasar los datos de entrada al encabezado (configuración de OnInit)
Durante la función OnInit(), el EA ajusta el tamaño de «pairSelected» para que coincida con «majorPairs» y establece por defecto el valor de todos los elementos en «true». A continuación, el EA configura los parámetros públicos del EA (tamaño del lote, stops, ajustes de riesgo) e inicializa el encabezado llamando a Init() y SetMultiEnabled(EnableMultipair). Esto garantiza que el header comience en el modo seleccionado por el EA y utilice los mismos parámetros de operación.
// In OnInit() ArrayResize(pairSelected, ArraySize(majorPairs)); for(int i = 0; i < ArraySize(pairSelected); i++) pairSelected[i] = true; // Initialize TradingButtons buttonsEA.LotSize = ButtonLotSize; buttonsEA.StopLoss = ButtonStopLoss; buttonsEA.TakeProfit = ButtonTakeProfit; buttonsEA.StopOrderDistancePips = StopOrderDistancePips; buttonsEA.RiskRewardRatio = RiskRewardRatio; buttonsEA.Init(); buttonsEA.SetMultiEnabled(EnableMultipair); // pass initial multipair state
Sincroniza la configuración antes de inicializar el objeto de interfaz de usuario/negociación: define primero los campos públicos y el modo, y luego llama a Init() para que el encabezado tenga los parámetros de tiempo de ejecución correctos.
Creación de casillas de selección (alineación de la interfaz de usuario) — Llamada a CreatePairCheckboxes
Aquí hacemos que el EA calcule la posición vertical de checkboxY (para que las casillas aparezcan debajo de las bandas de noticias) y llamamos a CreatePairCheckboxes(majorPairs, pairSelected, checkboxY). Esto crea las casillas de selección en el encabezado, manteniendo la alineación del índice con majorPairs. El encabezado también establecerá el estado inicial de cada casilla de verificación a partir de pairSelected[] para que la interfaz de usuario y el EA estén sincronizados. Esto permite que el componente de UI renderice los controles mientras sigue trabajando con los arrays del EA por referencia. Esto mantiene al EA como el estado canónico de qué pares existen y cuáles se seleccionan (el encabezado manipula las mismas matrices).
// create pair checkboxes aligned below the canvas lanes int checkboxY = InpTopOffset + (InpSeparateLanes ? 8 : 28) * lineH + 6; // adjust +6 px margin if needed buttonsEA.CreatePairCheckboxes(majorPairs, pairSelected, checkboxY);
Enrutamiento de eventos: reenvío de eventos del gráfico al encabezado
El EA no implementa por sí mismo la lógica de los clics en los botones, sino que reenvía todos los clics en los objetos del gráfico al encabezado llamando a buttonsEA.HandleChartEvent(...) desde OnChartEvent. Este contrato de una sola llamada simplifica la EA, ya que el encabezado se encarga de la conmutación entre pares, los clics en las casillas de selección y las acciones de los botones de operación manual.
// OnChartEvent: forward to the header with majorPairs and pairSelected arrays void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { // Header will handle multipair toggle and trade behaviour buttonsEA.HandleChartEvent(id, sparam, majorPairs, pairSelected); }
Al aplicar un enrutamiento de eventos independiente, el EA actúa como un canalizador de eventos, mientras que el encabezado gestiona de forma clara los eventos de la interfaz de usuario y las decisiones de negociación. De este modo, las responsabilidades quedan claramente delimitadas.
Cómo se activan las operaciones manuales multisímbolo (el papel del encabezado)
El encabezado es el motor de ejecución de las operaciones manuales multiplexadas; basta con proporcionar matrices simples y dejar que el encabezado las recorra y resuelva los símbolos. Esto mantiene el EA ordenado. Cuando un usuario hace clic en un botón de «Comprar/Vender» o activa la opción «multisímbolo», la función `HandleChartEvent` del encabezado utiliza las matrices `majorPairs` y `pairSelected` que se le pasan para decidir dónde actuar; por ejemplo, recorrerá los índices y operará solo en aquellos en los que `pairSelected[i] == true`. El EA solo proporciona las matrices y la configuración; el encabezado se encarga de resolver los símbolos y ejecutar operaciones sobre múltiples símbolos. (Consulte el encabezado para obtener información sobre las funciones de ayuda para operaciones por símbolo y la iteración multisímbolo.)// (conceptual) header receives arrays and performs per-index iteration: // Pseudocode excerpt of header behavior (actual code in TradingButtons.mqh) for(i = 0; i < ArraySize(majorPairs); i++) { if(i < ArraySize(pairSelected) && pairSelected[i]) { // resolve broker symbol for majorPairs[i] // call TradeBuySingle(resolvedSymbol) or TradeSellSingle(...) } }
La lógica de órdenes automatizadas sigue dependiendo del gráfico concreto (cómo se integra la automatización con el uso de varios símbolos)
La colocación automática de órdenes stop previas al evento y las órdenes posteriores al impacto en este EA se aplican al símbolo del gráfico (_Symbol) en lugar de a los pares principales. El sistema manual multisímbolo es independiente: las operaciones manuales multisímbolo (botones) y las operaciones automatizadas basadas en eventos constituyen flujos distintos. Esta separación evita que se generen órdenes automáticas accidentales con varios símbolos, a menos que se amplíe explícitamente la automatización para utilizar «majorPairs».
// Example: automated BuyStop/SellStop placement uses _Symbol (chart symbol) if(trade.BuyStop(InpOrderVolume, buyPrice, _Symbol, buySL, buyTP)) ticketBuyStop = trade.ResultOrder(); if(trade.SellStop(InpOrderVolume, sellPrice, _Symbol, sellSL, sellTP)) ticketSellStop = trade.ResultOrder();
De lo anterior se desprende que la lección clave es mantener los controles manuales de múltiples pares separados de las estrategias automatizadas específicas de cada gráfico, a menos que se desee deliberadamente que la automatización se aplique a varios símbolos. Una separación clara evita sorpresas.
Modelo de sincronización: EA gestiona los datos, mientras que el encabezado se encarga de la interfaz de usuario y la lógica
El EA define y almacena majorPairs[] y pairSelected[] (el estado oficial). El encabezado lee estos datos (crea controles, actúa sobre los elementos marcados) y vuelve a escribir los cambios (al marcar una casilla se establece pairSelected[i]), ya que las matrices se pasan por referencia. Este modelo de sincronización bidireccional es sencillo y fiable: el EA puede consultar pairSelected[] en cualquier momento (por ejemplo, en OnTimer) y el encabezado lo actualiza cuando el usuario interactúa con él.
// EA owns arrays; header is given references and updates them when checkboxes are toggled buttonsEA.CreatePairCheckboxes(majorPairs, pairSelected, checkboxY); ... // header's HandleChartEvent updates pairSelected[] in-place when checkboxes are clicked
Utilizamos el paso por referencia para el estado compartido en tiempo de ejecución. Requiere pocos recursos y mantiene sincronizadas UI y EA sin necesidad de intercambiar mensajes adicionales.
Consideraciones sobre la ubicación y el diseño visual (recuadros debajo de las franjas de noticias)
El EA calcula el valor de «checkboxY» basándose en las franjas de noticias y las opciones de configuración (InpTopOffset, InpSeparateLanes, lineH), de modo que las casillas de selección de pares múltiples aparezcan visualmente debajo de las franjas de noticias e indicadores. La integración de elementos de la interfaz de usuario procedentes de otros módulos es tanto una tarea de diseño como de lógica: el cálculo dinámico de los desplazamientos hace que la interfaz se adapte si cambian la altura o la posición de los carriles.
// compute vertical position for checkboxes so they sit below the lanes int checkboxY = InpTopOffset + (InpSeparateLanes ? 8 : 28) * lineH + 6; buttonsEA.CreatePairCheckboxes(majorPairs, pairSelected, checkboxY);
Al combinar lienzos y paneles de interfaz de usuario externos, centraliza los cálculos de diseño en el EA para que ambos sistemas compartan unas reglas de espaciado coherentes.
Pruebas
La implementación del EA en la plataforma MetaTrader 5 dio excelentes resultados. Pude seleccionar los pares con los que quería operar, y respondieron al instante a los botones de negociación. Las órdenes se ejecutaban a velocidad algorítmica y, con un solo clic, podía cerrar todas las posiciones en varios pares, una función de gran valor para el trading basado en noticias y otras estrategias de scalping de alta volatilidad.
La imagen siguiente muestra el resultado del proceso de pruebas del gráfico en tiempo real. Es importante señalar que las funciones manuales requieren una interacción en tiempo real con el gráfico, mientras que los componentes automatizados del EA pueden evaluarse a fondo en el Probador de estrategias para garantizar su eficacia.

Operaciones con múltiples símbolos mediante el EA «News Headline»
Conclusión
La Parte IX supuso otro hito importante en la evolución del EA «News Headline»: la integración del trading con múltiples símbolos. Esta novedad resuelve una limitación que existía desde hacía tiempo, ya que permite a los operadores gestionar varios pares desde un único gráfico. Aunque no se trata de un sistema de negociación totalmente automatizado, esta función actúa como una interfaz de negociación manual basada en la ejecución algorítmica, lo que garantiza rapidez y precisión, al tiempo que deja la toma de decisiones en manos del operador en situaciones de alta volatilidad.
El proceso de desarrollo en sí mismo resultó muy enriquecedor, ya que aplicamos principios de modularización y separación de responsabilidades para crear un sistema compacto pero potente. Lo que comenzó como una simple herramienta para mostrar calendarios y noticias se ha convertido ahora en un marco versátil que combina funciones manuales y automatizadas para resolver los retos prácticos a los que se enfrentan los operadores de noticias. Aunque se ha diseñado pensando en los principales pares de divisas, el sistema puede adaptarse a símbolos personalizados con unos pequeños ajustes de compatibilidad.
Uno de los principales retos con los que nos encontramos fue la nomenclatura de símbolos específica de cada bróker. Por ejemplo, las operaciones fallaron inicialmente porque la cuenta utilizaba EURUSD.0 en lugar del EURUSD estándar. Para solucionar esto, hemos mejorado el EA para que se adapte dinámicamente a la nomenclatura de los pares principales utilizada por los brókers.
Además, hemos aprovechado la clase CCheckBox de la biblioteca estándar de MQL5 para facilitar la selección de pares, lo que pone de manifiesto la flexibilidad y la capacidad de ampliación del lenguaje MQL5.
Espero que este debate haya aportado ideas útiles y lecciones prácticas. A continuación se adjunta el código fuente completo; utilízalo junto con este artículo, en el que se describe la implementación. Para mayor comodidad, también he resumido los puntos clave en una tabla a continuación. Siempre agradecemos vuestras opiniones y comentarios.
Lecciones clave
| Lección clave | Descripción |
|---|---|
| Matrices con índices alineados | Ambos proyectos utilizan las matrices `majorPairs[]` y `pairSelected[]`, alineadas por índice. De este modo, se garantiza que una casilla de selección, un nombre base solicitado y un símbolo de bróker resuelto hagan referencia al mismo par de divisas de forma coherente. |
| Sincronización por referencia | Las matrices se pasan por referencia desde el EA a TradingButtons.mqh, lo que permite que el encabezado actualice directamente los estados de selección al activar o desactivar las casillas de selección, manteniendo así el estado del EA sincronizado al instante. |
| Reenvío explícito de eventos | El EA no gestiona directamente los clics en los botones. En cambio, OnChartEvent reenvía los eventos a buttonsEA.HandleChartEvent(), donde el encabezado interpreta los cambios de configuración entre pares múltiples, los clics en las casillas de selección y las acciones de negociación. |
| Abstracción de la resolución de símbolos | La función `ResolveSymbol()` del encabezado asigna a los símbolos fáciles de recordar (por ejemplo, EURUSD) los símbolos específicos de cada bróker (por ejemplo, EURUSD.ecn). El EA puede ignorar las peculiaridades de los nombres de los brókers. |
| Flujos manuales frente a flujos automatizados | En el EA, las órdenes automáticas previas y posteriores al evento siempre se aplican al símbolo del gráfico, mientras que la funcionalidad multisímbolo se reserva para las acciones manuales mediante botones. Esta separación evita operaciones masivas inesperadas. |
| Creación y limpieza dinámicas de la interfaz de usuario | El encabezado crea y elimina dinámicamente casillas de selección y botones durante las funciones Init() y Deinit(). El EA calcula los desplazamientos de diseño (debajo de las franjas de noticias) para que los componentes encajen a la perfección en su interfaz de usuario. |
| Inicializar antes de Init() | En OnInit(), el EA establece LotSize, StopLoss, TakeProfit y EnableMultipair antes de llamar a buttonsEA.Init(). Esto garantiza que el encabezado se genere con la configuración correcta. |
| Herramientas de negociación centralizadas | La lógica de negociación está integrada en funciones auxiliares reutilizables como TradeBuySingle() y TradeSellSingle(). De este modo se evita la duplicación de código entre los bucles multisímbolo y los controladores de botones. |
| Comportamiento del interruptor de varios pares | El botón btnMultiToggle permite alternar entre los modos de un solo símbolo y de varios pares. En el modo multisímbolo, las acciones se aplican a todos los pares seleccionados; en el modo individual, las acciones se aplican únicamente al símbolo del gráfico. |
| Recurrir al símbolo del gráfico | Si el modo multisímbolo está activado pero no se ha seleccionado el símbolo del gráfico, el encabezado garantiza de todos modos que la operación se realice sobre ese símbolo. Esto ofrece resultados predecibles a los usuarios que se centran en su gráfico. |
| Coordinación de la maquetación | El EA calcula el valor de «checkboxY» para colocar las casillas de selección de varios pares de forma ordenada debajo del área de noticias desplazable. Aquí se explica cómo integrar paneles de interfaz de usuario de terceros con superposiciones de indicadores personalizados sin que se solapen. |
| Registro de errores y claridad | Ambos módulos generan registros detallados (por ejemplo, errores en la resolución de símbolos o códigos de error en la introducción de órdenes). Esta trazabilidad ayuda a los programadores y a los usuarios a diagnosticar rápidamente los problemas de configuración. |
| Modelo de sincronización bidireccional | Los estados de las casillas de selección se inicializan a partir de `pairSelected[]` (del EA a la interfaz de usuario) y, al hacer clic en ellas, se actualiza `pairSelected[]` (de la interfaz de usuario al EA). Este bucle continuo garantiza que ambos módulos compartan el mismo estado de selección. |
| Uso seguro de la memoria dinámica | El encabezado utiliza un nuevo objeto `CCheckBox()` para cada par y los elimina cuidadosamente en `Deinit()`. Esto enseña a los programadores de MQL5 a gestionar los objetos de la interfaz gráfica de usuario de forma segura en los EA de ejecución prolongada. |
| Extensibilidad gracias a la modularidad | Al integrar la negociación con múltiples pares en un encabezado independiente, la misma clase se puede reutilizar en varios EA (como el EA «News Headline») sin necesidad de reescribir la lógica de los múltiples pares, lo que constituye un patrón escalable para la reutilización del código. |
Archivos adjuntos
| Nombre del archivo | Versión | Descripción |
|---|---|---|
| News_Headline_EA.mq5 | 1.13 | Un asesor experto que integra los eventos del calendario económico y los titulares de las noticias directamente en el gráfico. Gestiona las órdenes de stop previas a los eventos, las operaciones posteriores al impacto y muestra noticias en pantalla. La versión 1.13 amplía su funcionalidad con la compatibilidad con la negociación de múltiples pares a través del módulo TradingButtons, lo que permite la ejecución manual de órdenes de múltiples pares junto con la negociación automatizada basada en eventos de los gráficos. |
| TradingButtons.mqh | 1 | Un archivo de encabezado modular que ofrece una interfaz de trading multisímbolo. Crea botones para órdenes de compra, venta, cierre, eliminación y stop, además de casillas de selección para elegir varios pares de divisas. Incluye una lógica de resolución de símbolos, funciones auxiliares para la realización de órdenes y la posibilidad de alternar entre la negociación de un solo símbolo y la de varios pares. Diseñado para su reutilización en diferentes EA, incluido el EA «News Headline». |
Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/19008
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
Motor de decisión Multi-IA para MQL5 (Parte 1): Integrar múltiples IA con votación por consenso
Particularidades del trabajo con números del tipo double en MQL4
Formulación de un Asesor Experto Multipar Dinámico (Parte 4): Ajuste de volatilidad y riesgo
- 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
¿Cuál es el verdadero sentido de estas cosas? El razonamiento proporcionado es irracional, porque los EAs son capaces de operar con muchos símbolos desde cualquier gráfico por diseño (out of the box), y puedes cambiar fácilmente de gráfico entre símbolos sin afectar a los EAs - no se recargan cuando se cambia el símbolo/marco temporal del gráfico.
PS. El comentario original está publicado en Inglés - por favor, léalo para una correcta comprensión - autotraducción puede producir textos ridículos.
¿Cuál es el verdadero sentido de estas cosas? El razonamiento proporcionado es irracional, porque los EAs son capaces de operar con muchos símbolos desde cualquier gráfico por diseño (out of the box), y se puede cambiar fácilmente de gráfico entre símbolos sin afectar a los EAs - no se recargan cuando se cambia el símbolo/marco de tiempo del gráfico.
PS. El comentario original está publicado en Inglés - por favor, léalo para una correcta comprensión - autotraducción puede producir textos ridículos.
Hola Stanislav Korotky,
Gracias por compartir su perspectiva. Entiendo completamente tu punto de vista - de hecho, un EA puede operar con múltiples símbolos desde cualquier gráfico, y cambiar de símbolo manualmente no recarga o interrumpe la ejecución del EA.
Sin embargo, mi idea se dirige específicamente a situaciones en las que se requiere la ejecución simultánea a través de múltiples pares - por ejemplo, durante los eventos de noticias de alto impacto cuando es posible que desee colocar órdenes sincronizadas en GBPUSD y EURUSD en el mismo momento exacto. En estos casos, el cambio manual de símbolos no resulta práctico.
Es por eso que hago hincapié en la gestión programática de múltiples símbolos - asegurando que el EA puede manejar y ejecutar operaciones a través de pares seleccionados de forma automática, incluso si está conectado a un gráfico con un símbolo base diferente.