Formulación de un Asesor Experto Multipar Dinámico (Parte 2): Diversificación y optimización de carteras
Introducción
Uno de los retos más persistentes en el comercio profesional radica en mantener la coherencia de la cartera y unos protocolos sólidos de gestión de riesgos. Los operadores suelen mostrar una dependencia excesiva de activos o estrategias concretos, lo que aumenta la exposición a pérdidas sustanciales durante cambios bruscos en los regímenes del mercado. A este riesgo se suma la tendencia predominante a sobreapalancar los instrumentos correlacionados, lo que amplifica la probabilidad de pérdidas simultáneas y socava la estabilidad de los rendimientos. Sin una cartera rigurosamente diversificada y optimizada, los operadores se enfrentan a resultados de rendimiento erráticos, lo que a menudo precipita la toma de decisiones impulsadas por las emociones y una rentabilidad volátil. Por lo tanto, un marco sistemático que equilibre estratégicamente los rendimientos ajustados al riesgo en una amplia gama de activos no correlacionados es indispensable para obtener un rendimiento sostenible a largo plazo.
Para mitigar estos retos, una metodología cuantitativa que integra la optimización de la cartera, la diversificación de múltiples activos y una estrategia de negociación de ruptura mejorada por la confirmación basada en osciladores ofrece una solución sólida. Al implementar una estrategia de ruptura en múltiples pares de divisas, los operadores pueden sacar provecho de los movimientos de precios impulsados por el impulso de alta probabilidad, al tiempo que dispersan el riesgo mediante la exposición a mercados no correlacionados. La integración de un indicador oscilador sirve para validar las señales de entrada, minimizando la participación en falsas rupturas y reduciendo las operaciones improductivas. Este enfoque no solo aumenta el potencial de beneficios, sino que también refuerza la estabilidad de la cartera al aprovechar sistemáticamente las oportunidades que surgen en las diferentes fases del mercado. La estrategia resultante demuestra una mayor resistencia a la volatilidad, lo que garantiza una alineación constante del rendimiento con las condiciones macroeconómicas y técnicas en constante evolución.
Lógica del EA
Modelo de compra:
El Asesor Experto comienza calculando el rango de precios entre las 10:00 a. m. y las 12:00 p. m. UTC+2, identificando el máximo más alto y el mínimo más bajo dentro de este intervalo de tiempo. A las 12:00 p. m. o en cualquier momento posterior, si el precio supera el máximo establecido previamente, se activa una posible oportunidad de compra. Sin embargo, para confirmarlo, el oscilador estocástico debe estar en el nivel 20 o por debajo, lo que indica una situación de sobreventa en el mercado. Esto garantiza que la ruptura no se prolongue en exceso y haya margen para un impulso alcista. Una vez que se cumplen ambas condiciones, el EA ejecuta una operación de compra, con el objetivo de aprovechar la ruptura y minimizar las señales falsas mediante un filtrado basado en osciladores.

Modelo de venta:
Para el modelo de venta, el Asesor Experto sigue el mismo proceso de cálculo del rango desde las 10:00 a. m. hasta las 12:00 p. m. UTC+2, identificando los máximos y mínimos de la sesión. Si a las 12:00 p. m. o más tarde, el precio rompe por debajo del mínimo establecido, se considera una oportunidad de venta. Sin embargo, la operación solo se ejecuta si el oscilador estocástico se encuentra en el nivel 80 o por encima, lo que indica una condición de mercado de sobrecompra. Esto garantiza que la ruptura a la baja esté respaldada por una posible presión vendedora y no por un movimiento falso. Una vez que ambas condiciones se alinean, el EA entra en una operación de venta, aprovechando el impulso bajista y evitando entradas prematuras.

Primeros pasos:
//+------------------------------------------------------------------+ //| Dyna MP.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Trade/Trade.mqh> CTrade trade; enum Signal_Breakout{ Normal_Signal, Reversed_Signal, };
`#include <Trade/Trade.mqh>` Esta línea incluye la biblioteca MQL5 Trade. El archivo de encabezado `Trade.mqh` define clases y funciones que agilizan las tareas de negociación, como iniciar, ajustar o cerrar órdenes. Aquí, se instancia un objeto llamado «trade» a partir de la clase «CTrade». Esta clase contiene métodos para ejecutar operaciones bursátiles (por ejemplo, enviar solicitudes de compra/venta, modificar operaciones abiertas o cerrar posiciones) de forma estructurada.
input group "--------------General Inputs--------------" input string Symbols = "XAUUSD, GBPUSD, USDCAD, USDJPY"; input Signal_Breakout BreakOutMode = Reversed_Signal; input double In_Lot = 0.01; input int TakeProfit = 500; double StopLoss = 500; input bool TrailYourStop = false; input int TrailingStop = 50; // Stochastic input int KPeriod = 21; input int upprer_level = 80;
`Symbols` especifica los activos que se van a negociar (por ejemplo, «XAUUSD, GBPUSD»), `In_Lot` establece el tamaño fijo de la operación (0,01 lotes), `TakeProfit` (500 puntos) y «StopLoss» (1000 puntos) definen los límites de beneficio y riesgo, mientras que «TrailYourStop» y «Trailing Stop» (70 puntos) activan y ajustan el stop loss dinámico. Para el oscilador estocástico, `KPeriod` (20) determina el período de cálculo de la línea porcentual, y`upper_level` (80) marca el umbral de sobrecompra. Estas entradas equilibran colectivamente las reglas de ejecución de operaciones, la gestión de riesgos y la generación de señales técnicas.
//+------------------------------------------------------------------+ //| Global vars | //+------------------------------------------------------------------+ int handles[]; double bufferM[]; int RangeStart = 600; int RangeDuration = 120; int RangeClose = 1200; int Num_symbs = 0; string symb_List[]; string Formatted_Symbs[];
Esta sección define las variables globales. La matriz «handles» almacena los identificadores del oscilador estocástico, lo que permite al EA gestionar múltiples activos de forma eficiente. La matriz «buffer» almacena valores calculados, como lecturas de indicadores o niveles históricos de precios. La variable `RangeStart` se establece en 600 minutos (10:00 a. m. UTC+2), lo que marca el inicio de la medición del rango, mientras que `RangeDuration` se establece en 120 minutos, lo que define un período de 2 horas para capturar los valores máximo y mínimo. Una vez alcanzada la hora `RangeClose` de 1200 minutos (12:00 p. m. UTC+2), el EA deja de calcular el rango y comienza a supervisar las condiciones de ruptura.
Para la gestión de símbolos, la matriz `symb_List` contiene la lista sin procesar de los símbolos que se van a procesar. Además, la matriz `Formatted_Symbs` contiene el número total de símbolos que se utilizarán después de analizar la entrada `Symbols`. Estas variables permiten al EA ejecutar operaciones de forma dinámica en múltiples activos, al tiempo que garantizan cálculos precisos del rango para la detección de rupturas.
//+------------------------------------------------------------------+ //| Ranger Global Vars | //+------------------------------------------------------------------+ struct RANGER{ datetime start_time; datetime end_time; datetime close_time; double high; double low; bool b_entry; bool b_high_breakout; bool b_low_breakout; RANGER() : start_time(0), end_time(0), close_time(0), high(0), low(999999), b_entry(false), b_high_breakout(false), b_low_breakout(false) {}; };
La estructura «Ranger» almacena y gestiona puntos de datos clave para la estrategia de ruptura de rango. Define una forma estructurada de realizar un seguimiento de los límites temporales, los niveles de precios y las condiciones de ruptura del rango. Las variables «start_time», «end_time» y «close_time» representan la hora de inicio, finalización y cierre del cálculo del rango, lo que garantiza que el EA identifique correctamente la ventana de ruptura. Las variables «high» y «low» almacenan los precios más altos y más bajos registrados dentro del rango, y «low» se inicializa con un valor muy alto (999999) para garantizar actualizaciones precisas de los precios. El «low (999999)» es un patrón estándar para encontrar valores mínimos en series de precios.
Además, tenemos tres indicadores booleanos. La variable `b_entry` registra si se ha ejecutado una entrada comercial, lo que evita que se realicen múltiples operaciones dentro del mismo evento de ruptura. `b_high_breakout` indica si el precio ha superado el máximo del rango, lo que confirma una posible configuración de compra, y `b_low_breakout` indica si el precio ha caído por debajo del mínimo del rango, lo que confirma una configuración de venta. El constructor `Ranger()` inicializa todos los valores a sus estados predeterminados, lo que garantiza que la estructura comience con datos limpios antes de actualizarse dinámicamente durante las operaciones en vivo.
RANGER rangeArray[];
MqlTick prevTick[], currTick[]; Estas variables desempeñan un papel crucial en el seguimiento de la evolución de los precios y en la gestión de la lógica de ruptura del rango en el Asesor Experto. `rangeArray` es una matriz de estructuras `RANGER`, almacena múltiples instancias de los parámetros de ruptura de rango definidos. Esto permite al EA realizar un seguimiento simultáneo de múltiples símbolos, garantizando que cada símbolo tenga sus propios datos de rango, incluyendo horas de inicio y finalización, precios máximos y mínimos, y condiciones de ruptura.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ string separator = ","; ushort usprtr; usprtr = StringGetCharacter(separator, 0); StringSplit(Symbols, usprtr, symb_List); Num_symbs = ArraySize(symb_List); ArrayResize(Formatted_Symbs, Num_symbs); for(int i = 0; i < Num_symbs; i++){ Formatted_Symbs[i] = symb_List[i]; } ArrayResize(rangeArray, Num_symbs); ArrayResize(prevTick, Num_symbs); ArrayResize(currTick, Num_symbs); ArrayResize(handles, Num_symbs); ArraySetAsSeries(bufferM, true); // Calculate initial ranges for each symbol for (int i = 0; i < ArraySize(Formatted_Symbs); i++) { CalculateRange(i, Formatted_Symbs[i]); // Pass the symbol index handles[i] = iStochastic(Formatted_Symbs[i], PERIOD_CURRENT, KPeriod, 1, 3, MODE_SMA, STO_LOWHIGH); if(handles[i] == INVALID_HANDLE){ Alert("Failed to create indicator handle"); return INIT_FAILED; } StopLoss = SymbolInfoDouble(Formatted_Symbs[i], SYMBOL_POINT)*TrailingStop; } return(INIT_SUCCEEDED); }
La función `OnInit` se encarga de inicializar y configurar variables clave, gestionar símbolos y preparar las estructuras de datos necesarias. Seguimos utilizando el separador «,» para dividir la lista de símbolos en activos negociables individuales, que se almacenan en «symb_List». Determinamos el número total de símbolos `Num_symbs` y, a continuación, redimensionamos la matriz `Formatted_Symbs` en consecuencia, asegurándonos de que cada símbolo esté correctamente formateado y sea accesible para la negociación.
A continuación, el EA redimensiona dinámicamente varias matrices, incluyendo `rangeArray` para almacenar datos de ruptura de rango, `prevTick` y `currTick` para rastrear actualizaciones de precios, `handles` para el oscilador estocástico y `bufferM` para valores de indicadores, asegurando que todas las estructuras de datos requeridas estén correctamente asignadas antes de que el EA comience su ejecución.
Una vez configuradas las matrices, el EA recorre cada símbolo de `Formatted_Symbs` para calcular el rango inicial utilizando la función `CalculateRange()`. También crea un indicador oscilador estocástico (istochastic) para cada símbolo, que utilizaremos más adelante para confirmar las rupturas comprobando las condiciones de sobrecompra y sobreventa. Si el indicador no se inicializa (INVALID_HANDLE), el EA activa una alerta y sale con el estado INIT_FAILED. Además, calculamos el Stop Loss para cada símbolo basándonos en su valor en puntos y un parámetro de trailing stop. Si todo se inicializa correctamente, la función devuelve INIT_SUCCEEDED, lo que permite al EA continuar con la supervisión de los movimientos de precios y la ejecución de operaciones.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason){ for(int i = 0; i < ArraySize(Formatted_Symbs); i++){ if(handles[i] != INVALID_HANDLE){ IndicatorRelease(handles[i]); } } }
La función OnDeinit() es la función de desinicialización del Asesor Experto (EA), responsable de liberar correctamente los recursos cuando el EA se elimina del gráfico o deja de ejecutarse.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(){ for(int i = 0; i < ArraySize(Formatted_Symbs); i++){ string symbol = Formatted_Symbs[i]; prevTick[i] = currTick[i]; SymbolInfoTick(symbol, currTick[i]); // Range Cal if(currTick[i].time > rangeArray[i].start_time && currTick[i].time < rangeArray[i].end_time){ // flag rangeArray[i].b_entry = true; // high if(currTick[i].ask > rangeArray[i].high){ rangeArray[i].high = currTick[i].ask; } // low if(currTick[i].bid < rangeArray[i].low){ rangeArray[i].low = currTick[i].bid; } } // now calculate range if(((RangeClose >= 0 && currTick[i].time >= rangeArray[i].close_time) || (rangeArray[i].b_high_breakout && rangeArray[i].b_low_breakout) || (rangeArray[i].end_time == 0) || (rangeArray[i].end_time != 0 && currTick[i].time > rangeArray[i].end_time && !rangeArray[i].b_entry))){ CalculateRange(i, Formatted_Symbs[i]); } checkBreak(i, Formatted_Symbs[i]); } }
La función `OnTick` es el bucle de ejecución central del Asesor Experto (EA), que se ejecuta con cada nueva actualización de ticks, como todos sabemos. Gestiona el análisis de precios en tiempo real, ajusta los parámetros de rango y evalúa las oportunidades de ruptura en todos los instrumentos de negociación configurados. El proceso se inicia evaluando secuencialmente cada símbolo de la lista `Formatted_Symbs`. Para cada símbolo, archiva los datos actuales de ticks en un búfer histórico (prevTick) y recupera el estado más reciente del mercado a través de `SymbolInfoTick`.
Durante el intervalo de negociación activo predefinido (desde start_time hasta end_time), el sistema realiza un seguimiento dinámico de los precios extremos. Eleva el máximo de la sesión utilizando el valor máximo ask y reduce el mínimo de la sesión utilizando el valor mínimo bid. Si la hora actual se encuentra dentro de este rango, se activa el indicador b_entry, lo que designa el rango como válido para la negociación.
Tras la ventana de negociación, la lógica evalúa tres desencadenantes de renovación:
- Cierre programado de la sesión `(close_time)`
- Ocurrencias simultáneas de rupturas altas y bajas
- Marcas de tiempo `end_time` no válidas o caducadas
//+------------------------------------------------------------------+ //| Range Calculation function | //+------------------------------------------------------------------+ void CalculateRange(int index, string symbol) { for(index = 0; index < ArraySize(Formatted_Symbs); index++){ symbol = Formatted_Symbs[index]; // Reset all the range variables rangeArray[index].start_time = 0; rangeArray[index].end_time = 0; rangeArray[index].close_time = 0; rangeArray[index].high = 0.0; rangeArray[index].low = 999999; rangeArray[index].b_entry = false; rangeArray[index].b_high_breakout = false; rangeArray[index].b_low_breakout = false; // Calculate range start time int time_cycle = 86400; rangeArray[index].start_time = (currTick[index].time - (currTick[index].time % time_cycle)) + RangeStart * 60; for(int i = 0; i < 8; i++){ MqlDateTime tmp; TimeToStruct(rangeArray[index].start_time, tmp); int dotw = tmp.day_of_week; if(currTick[index].time >= rangeArray[index].start_time || dotw == 6 || dotw == 0){ rangeArray[index].start_time += time_cycle; } } // Calculate range end time rangeArray[index].end_time = rangeArray[index].start_time + RangeDuration * 60; for(int i = 0 ; i < 2; i++){ MqlDateTime tmp; TimeToStruct(rangeArray[index].end_time, tmp); int dotw = tmp.day_of_week; if(dotw == 6 || dotw == 0){ rangeArray[index].end_time += time_cycle; } } // Calculate range close rangeArray[index].close_time = (rangeArray[index].end_time - (rangeArray[index].end_time % time_cycle)) + RangeClose * 60; for(int i = 0; i < 3; i++){ MqlDateTime tmp; TimeToStruct(rangeArray[index].close_time, tmp); int dotw = tmp.day_of_week; if(rangeArray[index].close_time <= rangeArray[index].end_time || dotw == 6 || dotw == 0){ rangeArray[index].close_time += time_cycle; } } } }
La función `CalculateRange` inicializa y configura rangos de negociación basados en el tiempo para múltiples símbolos. Para cada símbolo de la matriz `Formatted_Symbs`, primero restablece los parámetros críticos del rango (horas de inicio/fin/cierre, umbrales de precios máximos/mínimos e indicadores de ruptura) a sus valores predeterminados. El «start_time» se calcula alineando la hora actual con un límite diario (utilizando un ciclo de tiempo de 24 horas) y, a continuación, compensándolo con un «RangeStart» definido por el usuario (en minutos). Un bucle garantiza que la hora de inicio evite los fines de semana (sábado/domingo) y siga siendo válida cronológicamente incrementando la marca de tiempo en días completos si surgen conflictos. Esto crea una línea de base para la ventana de negociación, respetando los períodos de cierre del mercado.
Después de establecer la hora de inicio del intervalo, la función calcula la hora de finalización del intervalo añadiendo `RangeDuration` a `start_time`. Al igual que en el cálculo de la hora de inicio, se garantiza que la hora de finalización no caiga en fin de semana mediante un bucle de validación. Por último, la función determina el tiempo de cierre del rango, que marca el punto en el que el EA deja de supervisar las rupturas. Este tiempo se deriva de `RangeClose` y se ajusta para evitar los fines de semana. Al mantener estos cálculos dinámicos, la función garantiza que el EA establezca con precisión las condiciones de negociación de rango en diferentes símbolos, evitando operar fuera del horario de negociación y durante los fines de semana, al tiempo que garantiza una detección precisa de las rupturas.
bool CLots(double sl, double &lots){ lots = In_Lot; if(!CHLots(lots)){return false;} return true; }
La función `CLots()` establece y valida el tamaño del lote antes de ejecutar una operación. Toma dos parámetros: su stop loss (sl) y los lotes (una variable de referencia que almacena el tamaño final del lote).
bool CHLots(double &lots){ for(int i = 0; i < ArraySize(Formatted_Symbs); i++){ string symbol = Formatted_Symbs[i]; double min = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double max = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); if(lots < min){ lots = min; return true; } if(lots > max){ return false; } lots = (int)MathFloor(lots / step) * step; } return true; }
La función `CHLots()` valida los tamaños de los lotes para que cumplan con las reglas de negociación específicas del bróker para cada instrumento. Itera a través de la lista de símbolos en `Formatted_Symbs`, extrayendo las restricciones definidas por el bróker: tamaño mínimo del lote, tamaño máximo del lote y incremento permitido. Estos parámetros establecen los límites operativos para los volúmenes de pedidos. Cuando el valor del lote propuesto cae por debajo del umbral mínimo, la función lo corrige automáticamente al volumen mínimo permitido y confirma la validez devolviendo verdadero. Por el contrario, si el tamaño del lote solicitado supera el límite máximo del bróker, la función rechaza la solicitud devolviendo un valor falso, bloqueando así las operaciones que no cumplen los requisitos.
Para garantizar la precisión, la función aplica la alineación de pasos redondeando hacia abajo el tamaño del lote utilizando la fórmula `MathFloor(lots/step) * step`. Esto elimina incrementos fraccionales o irregulares que podrían provocar rechazos por parte de los corredores. Si el tamaño del lote cumple con todas las restricciones sin ajustes, la función devuelve verdadero, lo que confirma su aceptabilidad. Al aplicar rigurosamente estos controles, `CHLots()` actúa como una protección fundamental, evitando rechazos de órdenes debido a violaciones de volumen y reforzando la confiabilidad operativa del EA en entornos comerciales en vivo.
//+------------------------------------------------------------------+ //| Check for Breakout | //+------------------------------------------------------------------+ void checkBreak(int i, string symbol) { for (i = 0; i < ArraySize(Formatted_Symbs); i++) { symbol = Formatted_Symbs[i]; //get indicator vals if(CopyBuffer(handles[i], 0, 1, 2, bufferM) != 2){ Print("Failed to get indicator values"); return; } int stopLevel = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL); int spread = (int)SymbolInfoInteger(symbol, SYMBOL_SPREAD); double Bid = SymbolInfoDouble(symbol, SYMBOL_BID); double Ask = SymbolInfoDouble(symbol, SYMBOL_ASK); if (currTick[i].time >= rangeArray[i].end_time && rangeArray[i].end_time > 0 && rangeArray[i].b_entry) { double rangeSize = rangeArray[i].high - rangeArray[i].low; // High Breakout (BUY/SELL) bool upperBreak = bufferM[0] >= upprer_level && bufferM[1] < upprer_level; bool lowerBreak = bufferM[0] <= (100 - upprer_level) && bufferM[1] > (100 - upprer_level); bool HighSigType,LowSigType; if(BreakOutMode == Normal_Signal){ HighSigType = upperBreak; }else{HighSigType = lowerBreak;} if (!rangeArray[i].b_high_breakout && currTick[i].ask >= rangeArray[i].high && HighSigType) { rangeArray[i].b_high_breakout = true; double entry = NormalizeDouble(Ask + 100 * _Point, _Digits); double sl = rangeArray[i].low; //sl = NormalizeDouble(sl, true); double tp = entry + TakeProfit * _Point; double lots; if (!CLots(entry - sl, lots)) continue; if (!trade.PositionOpen(symbol, ORDER_TYPE_BUY, lots, currTick[i].ask, sl, tp, "High Breakout")) Print("Buy Order Failed: ", GetLastError()); } if(BreakOutMode == Normal_Signal){ LowSigType = upperBreak; }else{LowSigType = lowerBreak; } // Low Breakout (SELL) if (!rangeArray[i].b_low_breakout && currTick[i].bid <= rangeArray[i].low && LowSigType) { rangeArray[i].b_low_breakout = true; double entry = NormalizeDouble(Bid - 100 * _Point, _Digits); double sl = rangeArray[i].high; //sl = NormalizeDouble(sl,true); double tp = entry - TakeProfit * _Point; double lots; if (!CLots(sl - entry, lots)) continue; if (!trade.PositionOpen(symbol, ORDER_TYPE_SELL, lots, currTick[i].bid, sl, tp, "Low Breakout")) Print("Sell Order Failed: ", GetLastError()); } } } }
Esta función monitorea las rupturas de precios y administra la ejecución de operaciones combinando los movimientos de precios con las señales del oscilador estocástico. Recorre cada símbolo de `Formatted_Symbs`, intentando primero recuperar los valores del oscilador mediante `CopyBuffer()`. Si esta recuperación de datos falla, la función registra un error y finaliza para evitar decisiones erróneas. Para cada símbolo, se recopilan parámetros críticos como niveles de stop, spread y precios de oferta y demanda actuales. Las evaluaciones de ruptura solo se producen una vez finalizado el periodo del rango, lo cual se verifica comprobando si la hora actual supera la `end_time` del rango y si el indicador `b_entry` está activo, lo que garantiza que el análisis se limite a las ventanas de negociación válidas.
Para rupturas alcistas (señal de compra), la función confirma dos criterios: que el precio de venta supere el máximo de la sesión y que el oscilador refleje condiciones de sobreventa (por debajo de 100 - upper_level). Tras la validación, activa una orden de compra (calculando el precio de entrada, el stop-loss (basado en la volatilidad del rango) y los niveles de take-profit) y, a continuación, verifica el cumplimiento del tamaño del lote mediante `CLots()`.
Por el contrario, para una ruptura baja (señal de venta), es necesario que el precio de oferta caiga por debajo del mínimo de la sesión, mientras que el oscilador indica un estado de sobrecompra (por encima de upper_level). Si se cumple, se genera una orden de venta con parámetros de riesgo análogos. Ambos escenarios incluyen el registro de errores para los pedidos fallidos, lo que garantiza la transparencia. Al sincronizar los umbrales de precios con la confirmación basada en osciladores, la función garantiza una ejecución disciplinada de las operaciones basada en criterios.
//+------------------------------------------------------------------+ //| Trailing Stoploss | //+------------------------------------------------------------------+ void Trailler(){ if(!TrailYourStop) return; for(int i = PositionsTotal()-1; i >= 0; i--){ ulong ticket = PositionGetTicket(i); if(ticket <= 0) continue; if(!PositionSelectByTicket(ticket)) continue; // Get position details string symbol = PositionGetString(POSITION_SYMBOL); long magic; if(!PositionGetInteger(POSITION_MAGIC, magic)) continue; if(magic != MagicNumber) continue; // Get current prices MqlTick latestTick; if(!SymbolInfoTick(symbol, latestTick)) continue; long type; double openPrice, currentSl, currentTp; PositionGetInteger(POSITION_TYPE, type); PositionGetDouble(POSITION_PRICE_OPEN, openPrice); PositionGetDouble(POSITION_SL, currentSl); PositionGetDouble(POSITION_TP, currentTp); // Calculate pip values double pipSize = 10 * SymbolInfoDouble(symbol, SYMBOL_POINT); double currentPrice = type == POSITION_TYPE_BUY ? latestTick.bid : latestTick.ask; double priceMove = MathAbs(currentPrice - openPrice); // Calculate required moves double requiredMove = 70 * pipSize; // 20 pips double trailAmount = 10 * pipSize; // 10 pips // Calculate new stop loss double newSl = currentSl; bool inProfit = type == POSITION_TYPE_BUY ? (currentPrice > openPrice) : (currentPrice < openPrice); if(inProfit && priceMove >= requiredMove){ int steps = int(priceMove / requiredMove); if(type == POSITION_TYPE_BUY){ newSl = openPrice + (steps * trailAmount); newSl = MathMax(newSl, currentSl + trailAmount); } else{ newSl = openPrice - (steps * trailAmount); newSl = MathMin(newSl, currentSl - trailAmount); } } // Validate and modify SL if(newSl != currentSl){ // Check stop levels double minDist = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point; newSl = NormalizeDouble(newSl, _Digits); if(type == POSITION_TYPE_BUY && (currentPrice - newSl) >= minDist){ if(!trade.PositionModify(ticket, newSl, currentTp)) Print("Buy Trailing Failed: ", GetLastError()); } else if(type == POSITION_TYPE_SELL && (newSl - currentPrice) >= minDist){ if(!trade.PositionModify(ticket, newSl, currentTp)) Print("Sell Trailing Failed: ", GetLastError()); } } } }
La función «Trailing Stop Loss» (TSL) en el trading garantiza que, una vez que una operación evoluciona favorablemente, el stop-loss se ajusta automáticamente para asegurar las ganancias y minimizar los riesgos. La función `Trailer()` recalcula y actualiza el nivel de stop-loss basándose en el movimiento del precio y un porcentaje del rango alto-bajo, lo que evita salidas prematuras y asegura las ganancias.
Conclusión
En resumen, la optimización y diversificación de la cartera son estrategias esenciales en el trading que tienen como objetivo maximizar los rendimientos y minimizar el riesgo mediante la distribución de las inversiones entre múltiples activos. Los métodos de negociación tradicionales suelen centrarse en estrategias de un solo par, lo que expone a los operadores a una mayor volatilidad y a riesgos específicos del mercado. La diversificación refuerza la resiliencia de la cartera, mientras que los métodos de optimización despliegan estratégicamente el capital mediante el análisis de patrones de rendimiento históricos, métricas de volatilidad de los activos y relaciones entre mercados para lograr un equilibrio eficiente entre riesgo y rentabilidad.
En conclusión, incorporar la optimización y diversificación de la cartera en una estrategia de negociación proporciona un enfoque más resistente y adaptable a las fluctuaciones del mercado. Al combinar una estrategia de trading de ruptura con un indicador oscilador, los traders pueden identificar configuraciones de alta probabilidad mientras gestionan el riesgo de forma dinámica. Este enfoque aumenta las posibilidades de obtener una rentabilidad constante, pero también mejora la sostenibilidad a largo plazo al mitigar las caídas.
Para obtener los siguientes resultados, el Asesor Experto se probó utilizando el EURUSD como símbolo base con retrasos de latencia cero, ejecución ideal y modelado de cada tick para obtener la máxima precisión. El período de prueba abarcó desde el 1 de febrero de 2022 hasta el 22 de marzo de 2022. En la configuración de entrada, se estableció BreakoutMode en Reversed_signal y se habilitó TrailYourStop (establecido en verdadero) para permitir ajustes dinámicos de stop-loss. Todos los demás parámetros de entrada se dejaron en sus valores predeterminados.


Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/16089
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
Redes neuronales en el trading: Actor—Director—Crítico (Actor—Director—Critic)
Particularidades del trabajo con números del tipo double en MQL4
Pruebas retrospectivas manuales simplificadas: herramientas personalizadas en MQL5 para el Probador de Estrategias
- 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
Ok escribí EURUSD en Input/Symbols y ahora funciona.
Gracias
Gran artículo! Voy a probarlo mañana. Me interesa saber por qué has utilizado un periodo de tiempo tan extraño para el probador de estrategias. Yo habría esperado meses completos en 2024. Me gusta tu concepto del trailing stop loss, estoy utilizando la misma técnica. Una arruga que he hecho es también tratar de minimizar la pérdida si el comercio se vuelve negativo después de casi alcanzar el punto de equilibrio.
Saludos y sigue con los artículos, son geniales
CapeCoddah
¿Podría ayudarme? Gracias
Cada divisa de entrada debe estar separada sólo por una coma. No ponga un espacio entre las monedas
Hola de nuevo,
He intentado utilizar tu sistema en un gráfico activo y he encontrado un par de mejoras
El problema de Alberto era probablemente que no tenía todos los pares en la lista de símbolos de su ventana de observación del mercado, ctlM. Yo también tuve ese error con XAUUSD
En lugar de ArraySize(... para las sentencias For, usa Num_symbls, un poco más rápido. También he encontrado que deletrear los nombres completos ayuda a otros a entender mejor tu código y también previene muchos errores de yntax, por ejemplo Number_Symbols es mejor en mi opinión que Num_symbls.
DisplayObjects no estaba en el código, yo lo añadí.
En DisplayObjects, agregué una condición para seleccionar sólo el símbolo del gráfico. Enumerar a través de los otros no es necesario y desordena la pantalla. Pero tal vez me estoy perdiendo algo.
Finalmente, hay un problema con el Cálculo de Rango. En un gráfico activo, no en el Probador de Estrategias, al iniciar el EA se produce un rayo que está más allá de la fecha actual en el futuro. Por ejemplo, al iniciar el 30/4 se produce un rayo que comienza el 30/4 a las 10am y termina el 1/5. Esto resulta en un rayo no visible que no se muestra en el gráfico pero sí en la Lista de Objetos. Dejaré que arreglen esto.
Te adjunto mi código para que lo utilices
Saludos, CapeCoddah