
Gestión de Riesgo (Parte 5): Integrando la Gestión de Riesgo en un Asesor Experto
- Introducción
- Mejoras en el indicador de order blocks
- Creación del asesor y parámetros de este
- Declaración de las variables globales
- Función OnInit
- Funciones OnTick y OnTradeTransaction
- Función OnDeinit
- Pruebas
- Conclusión
Introducción
Bienvenido a esta última entrega de nuestra serie sobre gestión de riesgo. En este artículo exploraremos las diferencias en el uso de la gestión de riesgo: ¿existen variaciones significativas en su uso y no uso?, ¿Cuáles son las ventajas y desventajas de utilizar un enfoque de riesgo dinámico?, y ¿en qué casos podría ser oportuno aplicarlo?
Resolveremos estas dudas creando un asesor experto simple que emplee el indicador de order blocks desarrollado en artículos anteriores sobre gestión de riesgo.
Comenzaremos optimizando dicho indicador, lo cual nos permitirá realizar los backtest de forma más rápida y eficiente, además de facilitar la optimización del bot.
También se explicará paso a paso cómo construir el asesor, definir parámetros y cómo integrar los eventos básicos, tales como el evento de "OnTradeTransaction", que debe ejecutarse únicamente dentro de su respectiva función.
Por último, abordaremos las interrogantes planteadas en este artículo realizando cuatro backtest del bot y probando diversas combinaciones de parámetros. Con ello, analizaremos la diferencia al utilizar límites de pérdidas o ganancias y no usarlos, además, evaluaremos en que casos la gestión de riesgo dinámica podría aprovecharse de manera eficiente.
Mejoras en el indicador de order blocks
Empezaremos este artículo optimizando el indicador de order blocks. Anteriormente, su rendimiento no era el más eficiente. Después de una revisión, noté que era posible mejorarlo, tanto en el uso de los datos de las velas como en la eliminación de bucles innecesarios.
1. Optimización del bucle para encontrar order blocks:
En el primer bucle del indicador reduciremos el número de iteraciones, ya que no será necesario recorrer todas las velas en cada actualización. Al cargar el indicador, el parámetro "prev_calculated" de la función "OnCalculate" es 0; este parámetro representa, en resumen, la cantidad de velas ya calculadas. Dado que en el primer cambio la vela aún no ha sido calculada, "prev_calculated" es 0. Por lo tanto, en el primer cálculo se recorrerán las velas desde el número "Rango_universal_busqueda" hasta la vela 6; cuando "prev_calculated" sea mayor a 0, solo se verificará la vela 6.
int inicio = prev_calculated == 0 ? Rango_universal_busqueda : 6; for(int i = inicio ; i > 5 ; i--) { //---- }
2. Búsqueda de mitigaciones en order blocks
Para determinar si un Order Block ha sido "mitigado" (es decir, si el precio ha alcanzado o superado el nivel del bloque), hemos mejorado dos funciones: una para bloques alcistas y otra para bloques bajistas. Ambas se han simplificado utilizando únicamente los valores de high y low de las velas.
Mitigación en un Order Block alcista.
datetime mitigados_alcsitas(double price, const double &lowArray[], const datetime &Time[], datetime start, datetime end) { int startIndex = iBarShift(_Symbol, PERIOD_CURRENT, start); int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end); NormalizeDouble(price, _Digits); for(int i = startIndex - 2 ; i >= endIndex + 1 ; i--) { if(price > lowArray[i]) { return Time[i]; //si encuentra que si hubo retorna el tiempo de la vela donde hubo la mitigacion Print("el orderblock tuvo mitigaciones", TimeToString(end)); } } return 0; //En caso no se haya encontrado niguna mitigacion retorna 0 }
Mitigación en un Order Block bajista.
//+------------------------------------------------------------------+ datetime mitigado_bajista(double price, const double &highArray[], const datetime &Time[], datetime start, datetime end) { int startIndex = iBarShift(_Symbol, PERIOD_CURRENT, start); int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end); NormalizeDouble(price, _Digits); for(int i = startIndex - 2 ; i >= endIndex + 1 ; i--) { if(highArray[i] > price) { return Time[i]; //retorna el time de la vela encontrada } } return 0; // no se mitigo hasta el momento }
En estas dos funciones se utilizan directamente los low y high de las velas para reducir el número de parámetros y disminuir ligeramente el tiempo de ejecución del bucle.
Además, se han implementado estos cambios en las funciones "esOb_mitigado_array_...", las cuales llamaban a iOpen, iClose, etc. Esto era innecesario, ya que, por ejemplo, si el cierre de una vela es menor a un Order Block, es evidencia de que el low también lo es. Así, podemos simplificar la verificación de la mitigación basándonos únicamente en el low o en el high, según corresponda.
Order Block alcista.
//+------------------------------------------------------------------+ datetime esOb_mitigado_array_alcista(OrderBlocks &newblock, datetime end) { int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end); NormalizeDouble(newblock.price2, _Digits); for(int i = 0 ; i < endIndex - 2 ; i++) { double low = iLow(_Symbol, PERIOD_CURRENT, i); if(newblock.price2 >= low) { newblock.mitigated = true; newblock.time2 = iTime(_Symbol, _Period, i); return newblock.time2; //retorna el time de la vela encontrada } } return 0; // no se mitigo hasta el momento }
Order block bajista.
//+------------------------------------------------------------------+ datetime esOb_mitigado_array_bajista(OrderBlocks &newblock, datetime end) { int endIndex = iBarShift(_Symbol, PERIOD_CURRENT, end); NormalizeDouble(newblock.price2, _Digits); for(int i = 0 ; i < endIndex - 2 ; i++) { double high = iHigh(_Symbol, PERIOD_CURRENT, i); if(high >= newblock.price2) { newblock.mitigated = true; newblock.time2 = iTime(_Symbol, _Period, i); return newblock.time2; } } return 0; // no se mitigo hasta el momento }
3. Funciones plantilla para añadir elementos a un array
Añadimos algunas funciones plantilla que serán más eficientes a la hora de insertar un elemento en un array. Anteriormente, creábamos funciones especiales que solo recibían un parámetro específico; por ejemplo, si queríamos añadir una estructura del tipo "Order_Blocks", debíamos crear una función propia.
Las funciones plantilla permiten ejecutar la misma operación utilizando distintos tipos. En nuestro caso, creamos una función para agregar un elemento a un array.
template <typename S> bool AddArray(S &Array[], const S &Value) { for(int i = 0 ; i < ArraySize(Array) ; i++) { if(Array[i].name == Value.name) return false; } ArrayResize(Array, Array.Size() + 1); Array[Array.Size() - 1] = Value; return true; } template <typename X> void AddArrayNoVerification(X &array[], const X &value) { ArrayResize(array, array.Size() + 1); array[array.Size() - 1] = value; }
4. Función plantilla para eliminar un elemento por nombre
Ahora crearemos una función plantilla para eliminar un elemento según su nombre. Esto significa que el array en el que trabajaremos debe ser una estructura o clase que contenga un miembro público "name". Esta función nos servirá para eliminar un elemento basándonos en el parámetro "targetName", lo cual es útil para eliminar elementos innecesarios del array de order blocks mitigados.
template<typename T> bool DeleteArrayBiName(T &array[], const string targetName) { int size = ArraySize(array); int index = -1; // Buscar el índice y desplazar elementos en un solo bucle for(int i = 0; i < size; i++) { if(array[i].name == targetName) { index = i; } if(index != -1 && i < size - 1) { array[i] = array[i + 1]; // Desplaza los elementos } } if(index == -1) return false; if(size > 1) ArrayResize(array, size - 1); else ArrayFree(array); // Si el array tenía solo un elemento, se libera completamente return true; }
5. Nueva funcionalidad para "eliminar" order blocks
En versiones anteriores, cuando un Order Block se consideraba mitigado, se agregaba su nombre a un array auxiliar, pero seguía presente en el array principal. Con esta mejora, solo se conserva el nombre del Order Block mitigado en el array auxiliar, eliminándolo por completo del array principal.
Order blocks bajistas.
static bool buscar_obb = true; static datetime time_b = 0; string curr_elimiandor_obb[]; for(int i = 0; i < ArraySize(ob_bajistas); i++) { datetime mitigadoTime = esOb_mitigado_array_bajista(ob_bajistas[i], ob_bajistas[i].time1); if(ob_bajistas[i].mitigated == false) { if(ObjectFind(ChartID(), ob_bajistas[i].name) < 0) { RectangleCreate(ChartID(), ob_bajistas[i].name, 0, ob_bajistas[i].time1, ob_bajistas[i].price1, time[0], ob_bajistas[i].price2, Color_Order_Block_Bajista, Witdth_order_block, Fill_order_block, Back_order_block, STYLE_SOLID); sellOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_bajistas[i].time1)] = ob_bajistas[i].price2; } else ObjectMove(ChartID(), ob_bajistas[i].name, 1, time[0], ob_bajistas[i].price2); } else { Alert("El order block bajista esta siendo mitigado: ", TimeToString(ob_bajistas[i].time1)); AddArrayNoVerification(pricetwo_eliminados_obb, ob_bajistas[i].name); AddArrayNoVerification(curr_elimiandor_obb, ob_bajistas[i].name); if(buscar_obb == true) { time_b = iTime(_Symbol, _Period, 0); buscar_obb = false; } } } for(int i = 0; i < ArraySize(curr_elimiandor_obb) ; i++) { DeleteArrayBiName(ob_bajistas, curr_elimiandor_obb[i]); }
Order Blocks alcistas.
static bool buscar_oba = true; static datetime time_a = 0; string curr_elimiandor_oba[]; for(int i = 0; i < ArraySize(ob_alcistas); i++) { datetime mitigadoTime = esOb_mitigado_array_alcista(ob_alcistas[i], ob_alcistas[i].time1); if(ob_alcistas[i].mitigated == false) { if(ObjectFind(ChartID(), ob_alcistas[i].name) < 0) { RectangleCreate(ChartID(), ob_alcistas[i].name, 0, ob_alcistas[i].time1, ob_alcistas[i].price1, time[0], ob_alcistas[i].price2, Color_Order_Block_Alcista, Witdth_order_block, Fill_order_block, Back_order_block, STYLE_SOLID); buyOrderBlockBuffer[iBarShift(_Symbol, _Period, ob_alcistas[i].time1)] = ob_alcistas[i].price2; } else ObjectMove(ChartID(), ob_alcistas[i].name, 1, time[0], ob_alcistas[i].price2); //por el contrario si si existe el objeto lo unico que haremos es actulizarlo al tiempo actual usando el punto de anclaje 1 } else { Alert("El order block alcista esta siendo mitigado: ", TimeToString(ob_alcistas[i].time1)); AddArrayNoVerification(pricetwo_eliminados_oba, ob_alcistas[i].name); AddArrayNoVerification(curr_elimiandor_oba, ob_alcistas[i].name); if(buscar_oba == true) { buscar_oba = false; time_a = iTime(_Symbol, _Period, 1); } } } for(int i = 0; i < ArraySize(curr_elimiandor_oba) ; i++) { DeleteArrayBiName(ob_alcistas, curr_elimiandor_oba[i]); }
6. Función para eliminar objetos
Al eliminar los objetos del gráfico, es necesario hacer algunas modificaciones. Antes, se creaba un array que almacenaba el nombre de los order blocks mitigados para evitar procesarlos de nuevo. Ahora, se implementó una nueva funcionalidad que elimina estos order blocks del array principal. Por ello, se debe conservar el nombre de los order blocks mitigados en los arrays auxiliares para poder eliminarlos del gráfico después.
A continuación, se muestra la función Eliminar_Objetos modificada para que también itere sobre los arrays auxiliares y elimine su contenido:
void Eliminar_Objetos() { ObjectsDeleteAll(0, "ENTRY", -1, -1); for(int i = 0; i < ArraySize(pricetwo_eliminados_oba) ; i++) ObjectDelete(0,pricetwo_eliminados_oba[i]); for(int i = 0; i < ArraySize(pricetwo_eliminados_obb) ; i++) ObjectDelete(0,pricetwo_eliminados_obb[i]); for(int i = 0; i < ArraySize(ob_alcistas) ; i++) ObjectDelete(0,ob_alcistas[i].name); for(int i = 0; i < ArraySize(ob_bajistas) ; i++) ObjectDelete(0,ob_bajistas[i].name); ArrayFree(pricetwo_eliminados_oba); ArrayFree(pricetwo_eliminados_obb); }
7. Utilización de arrays predefinidos
Se ha optimizado la lógica aprovechando los arrays predefinidos que MQL ofrece en la función OnCalculate. A partir de ahora, usaremos estos arrays para calcular los order blocks. Los principales datos utilizados de las velas son "open", "close", "high", "low", y "tick_volume" ".
Esto facilita el manejo y la interpretación de la información al trabajar con los order blocks.
ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); ArraySetAsSeries(tick_volume, true); ArraySetAsSeries(atr, true);
Creación del asesor y parámetros de este
Empezaremos creando el asesor experto:
1. Creamos el bot como plantilla:
2. Ponemos el nombre y el autor:
3. Seleccionamos solo el evento OnTradeTransaction:
4. Finalizamos:
A continuación, crearemos los parámetros que necesitará el asesor experto para su correcto funcionamiento. Comenzaremos con los parámetros generales que tendrá el bot, como el número mágico y las configuraciones por defecto que ya traía el indicador de los order blocks.
1. Parámetros Generales
Antes de establecer los parámetros generales, definiremos una enumeración que contendrá los tipos de TP y SL aceptados por el indicador; estos serán los mismos que utiliza el indicador.
enum ENUM_TP_SL_STYLE { ATR = 0, POINT = 1 };
A continuación, se explica brevemente el significado de cada parámetro agregado:
-
Magic: Número que identifica las operaciones del bot para diferenciarlas de otras.
-
timeframe_order_block: Permite especificar el timeframe en el que se detectarán los order blocks.
-
Rango_universal_busqueda: Cantidad de velas atrás que el bot analizará para encontrar posibles order blocks.
-
Witdth_order_block: Define el grosor de la línea de los order blocks (rectángulos).
-
Back_order_block y Fill_order_block: Opciones gráficas para dibujar el fondo y el relleno en el rectángulo que representa cada order block.
-
Color_Order_Block_Bajista y Color_Order_Block_Alcista: Colores de los order blocks bajistas y alcistas, respectivamente.
-
tp_sl_style: Estilo para el Take Profit y el Stop Loss (en ATR o en puntos).
-
Atr_Multiplier_1 y Atr_Multiplier_2: Factores multiplicadores si se emplea el estilo ATR.
-
TP_POINT y SL_POINT: Valores de Take Profit y Stop Loss en el caso de emplear el estilo POINT.
sinput group "--- Order Block EA settings ---" input ulong Magic = 545244; //Magic number input ENUM_TIMEFRAMES timeframe_order_block = PERIOD_M5; //Order block timeframe sinput group "-- Order Block --" input int Rango_universal_busqueda = 500; //search range of order blocks input int Witdth_order_block = 1; //Width order block input bool Back_order_block = true; //Back order block? input bool Fill_order_block = true; //Fill order block? input color Color_Order_Block_Bajista = clrRed; //Bearish order block color input color Color_Order_Block_Alcista = clrGreen; //Bullish order block color sinput group "-- Strategy --" input ENUM_TP_SL_STYLE tp_sl_style = POINT;//tp and sl style sinput group "- ATR " input double Atr_Multiplier_1 = 1.5;//Atr multiplier 1 input double Atr_Multiplier_2 = 2.0;//Atr multiplier 2 sinput group "- POINT " input int TP_POINT = 1000; //TL in Points input int SL_POINT = 1000; //SL in Points
2. Parámetros de la gestión de riesgo
Continuando con los parámetros del asesor experto, definiremos los que utilizará la gestión de riesgo. Estos son los parámetros generales:
-
Lote_Type: Especifica si se usará un lote dinámico (basado en la gestión de riesgo por operación) o un lote fijo.
-
lote: Tamaño del lote a utilizar cuando Lote_Type sea Fixed.
-
risk_mode: Permite seleccionar si la cuenta es personal o pertenece a una prop firm (por ejemplo, propfirm_ftmo).
-
get_mode: Determina la forma de calcular el tamaño del lote; se basa en el Stop Loss y ajusta el tamaño del lote según el riesgo por operación.
-
prop_firm_balance: Si la cuenta es de FTMO (o de alguna otra prop firm con reglas similares), se define el balance inicial de la cuenta. Esto se utiliza, como se explicó en artículos anteriores, para calcular la pérdida máxima por operación y la pérdida máxima diaria.
input ENUM_LOTE_TYPE Lote_Type = Dinamico; //Lote Type input double lote = 0.1; //lot size (only for fixed lot) input ENUM_MODE_RISK_MANAGEMENT risk_mode = personal_account;//type of risk management mode input ENUM_GET_LOT get_mode = GET_LOT_BY_STOPLOSS_AND_RISK_PER_OPERATION; //How to get the lot input double prop_firm_balance = 1000; //If risk mode is Prop Firm FTMO, then put your ftmo account balance
3. Parámetros para la pérdida máxima (diaria, semanal y total)
Ahora definiremos los parámetros para controlar la pérdida máxima (ML), la pérdida máxima semanal (MWL) y la pérdida máxima diaria (MDL). Cada uno se basa en tres variables que determinan cómo se calcula y aplica la pérdida:
-
percentage_or_money_..._input: Representa el valor en porcentaje o en dinero, según el modo de cálculo seleccionado. Si se establece en 0, la pérdida no se utilizará.
-
mode_calculation_...: Indica si el parámetro se evalúa en porcentaje o en dinero.
-
applied_percentages_...: Determina sobre qué base se aplica el porcentaje (por ejemplo, el balance, el profit de la cuenta, etc.).
sinput group "- ML/Maxium loss/Maxima perdida -" input double percentage_or_money_ml_input = 0; //percentage or money (0 => not used ML) input ENUM_RISK_CALCULATION_MODE mode_calculation_ml = percentage; //Mode calculation Max Loss input ENUM_APPLIED_PERCENTAGES applied_percentages_ml = Balance; //ML percentage applies to: sinput group "- MWL/Maximum weekly loss/Perdida maxima semanal -" input double percentage_or_money_mwl_input = 0; //percentage or money (0 => not used MWL) input ENUM_RISK_CALCULATION_MODE mode_calculation_mwl = percentage; //Mode calculation Max weekly Loss input ENUM_APPLIED_PERCENTAGES applied_percentages_mwl = Balance;//MWL percentage applies to: sinput group "- MDL/Maximum daily loss/Perdida maxima diaria -" input double percentage_or_money_mdl_input = 0; //percentage or money (0 => not used MDL) input ENUM_RISK_CALCULATION_MODE mode_calculation_mdl = percentage; //Mode calculation Max daily loss input ENUM_APPLIED_PERCENTAGES applied_percentages_mdl = Balance;//MDL percentage applies to:
4. GMLPO: Riesgo máximo por operación
Continuando con la configuración de parámetros para nuestro EA, configuraremos la pérdida máxima por operación. Esta sección es más compleja, ya que no se limita a tres parámetros, sino que involucra más variables, debido a que, como se mencionó al principio, se incorporará el riesgo dinámico.
4.1 Parámetros generales de GMLPO
Empezamos definiendo cinco parámetros básicos para el riesgo dinámico. Se incluyen los tres parámetros que se usaron para la pérdida máxima diaria, semanal y total; además, se agregan dos parámetros adicionales. Estos últimos permiten configurar la frecuencia con la que se revisará si es necesario modificar el riesgo por operación y el tipo de configuración de riesgo dinámico que se utilizará. A continuación, se muestran las enumeraciones adicionales que se usaran.
//--- Enumeration of the types of dynamic operational risk enum ENUM_OF_DYNAMIC_MODES_OF_GMLPO { DYNAMIC_GMLPO_FULL_CUSTOM, //Customisable dynamic risk per operation DYNAMIC_GMLPO_FIXED_PARAMETERS,//Risk per operation with fixed parameters NO_DYNAMIC_GMLPO //No dynamic risk for risk per operation };
//--- Enumeration to determine when to review a decrease in the initial balance to modify the risk per operation enum ENUM_REVISION_TYPE { REVISION_ON_CLOSE_POSITION, //Check GMLPO only when closing positions REVISION_ON_TICK //Check GMLPO on all ticks };
sinput group "- GMLPO/Gross maximum loss per operation/Porcentaje a arriesgar por operacion -" input ENUM_OF_DYNAMIC_MODES_OF_GMLPO mode_gmlpo = DYNAMIC_GMLPO_FULL_CUSTOM; //Select GMLPO mode: input ENUM_REVISION_TYPE revision_type_gmlpo = REVISION_ON_CLOSE_POSITION; //Type revision input double percentage_or_money_gmlpo_input = 1.0; //percentage or money (0 => not used GMLPO) input ENUM_RISK_CALCULATION_MODE mode_calculation_gmlpo = percentage; //Mode calculation Max Loss per operation input ENUM_APPLIED_PERCENTAGES applied_percentages_gmlpo = Balance;//GMPLO percentage applies to:
- mode_gmlpo: Define si el riesgo por operación será dinámico. Se selecciona uno de los modos de configuración de riesgo dinámico. Si no se utiliza riesgo dinámico, se debe escoger el tipo "NO_DYNAMIC_GMLPO".
- revision_type_gmlpo: Indica cuándo se revisará la equidad de la cuenta para ajustar el riesgo por operación, ya sea al cerrar una posición o en cada tick del mercado.
- percentage_or_money_gmlpo_input: Valor en porcentaje o en dinero que se utilizará como base para el riesgo por operación. Si se establece en 0, la función de GMLPO no se activará.
- mode_calculation_gmlpo: Establece si "percentage_or_money_gmlpo_input" se interpretará como un porcentaje o como un monto fijo de dinero.
- applied_percentages_gmlpo: Determina la referencia (por ejemplo, Balance o Equity) sobre la que se calculará el porcentaje en caso de usar dicho modo de cálculo.
Nota: Cada una de las enumeraciones utilizadas, ya mencionadas en artículos anteriores, proporciona mayor detalle sobre cómo se efectúa el cálculo interno del riesgo.
4.2 Configuraciones de GMLPO dinámico
Como se mencionó anteriormente, existen dos modos para configurar el riesgo dinámico: uno totalmente personalizable y otro basado en parámetros fijos. En el artículo anterior expliqué los motivos para adoptar esta decisión. Resumidamente, el problema con el uso de strings como parámetro es que éstos no se pueden optimizar, lo que resulta desfavorable al buscar los mejores ajustes en el riesgo dinámico. Por ello, se implementó una enumeración para solucionar ese inconveniente.
4.2.1 GMLPO dinámico personalizable
En este modo, el usuario determina exactamente a partir de qué porcentajes de disminución del balance se debe modificar el riesgo y cuál será el nuevo valor de riesgo por operación. Este modo es más personalizable, aunque se basa en strings, lo que impide que se optimicen.
sinput group "-- Optional GMLPO settings, Dynamic GMLPO" sinput group "--- Full customizable dynamic GMLPO" input string note1 = "subtracted from your total balance to establish a threshold."; //This parameter determines a specific percentage that will be input string str_percentages_to_be_reviewed = "2,5,7,9"; //percentages separated by commas. input string note2 = "a new risk level will be triggered on your future trades: "; //When the current balance (equity) falls below this threshold input string str_percentages_to_apply = "1,0.7,0.5,0.33"; //percentages separated by commas. input string note3 = "0 in both parameters => do not use dynamic risk in gmlpo"; //Note:
-
str_percentages_to_be_reviewed: contiene la lista de porcentajes, ordenados de menor a mayor, que indican el umbral en el que se modificará el riesgo por operación. Por defecto se establecen "2,5,7,9", lo que significa que cuando la ganancia de la cuenta, expresada en porcentaje, caiga por debajo de 2%, se modificará el riesgo al valor correspondiente, siendo el primer valor de str_percentages_to_apply (1) el que se aplicará.
-
str_percentages_to_apply: contiene los nuevos porcentajes de riesgo; estos serán los valores a los que se ajustará el riesgo por operación.
4.2.2 GMLPO dinámico con parámetros fijos
Si se prefiere configurar el riesgo dinámico mediante parámetros fijos, se ha creado otra sección en los parámetros del EA. Su funcionamiento es similar al modo personalizable, pero en este caso se pueden establecer hasta cuatro parámetros. Si no se desean usar los cuatro, y solamente se utilizarán tres, basta con colocar dos ceros en los campos que no se usarán. Por ejemplo, si solo se usarán tres "modificadores" en la sección "-- 4 --", se deben establecer 0 en los dos parámetros restantes.
sinput group "--- Fixed dynamic GMLPO with parameters" sinput group "- 1 -" input string note1_1 = "subtracted from your total balance to establish a threshold."; //This parameter determines a specific percentage that will be input double inp_balance_percentage_to_activate_the_risk_1 = 2.0; //percentage 1 that will be exceeded to modify the risk separated by commas input string note2_1 = "a new risk level will be triggered on your future trades: "; //When the current balance (equity) falls below this threshold input double inp_percentage_to_be_modified_1 = 1.0;//new percentage 1 to which the gmlpo is modified sinput group "- 2 -" input double inp_balance_percentage_to_activate_the_risk_2 = 5.0;//percentage 2 that will be exceeded to modify the risk separated by commas input double inp_percentage_to_be_modified_2 = 0.7;//new percentage 2 to which the gmlpo is modified sinput group "- 3 -" input double inp_balance_percentage_to_activate_the_risk_3 = 7.0;//percentage 3 that will be exceeded to modify the risk separated by commas input double inp_percentage_to_be_modified_3 = 0.5;//new percentage 3 to which the gmlpo is modified sinput group "- 4 -" input double inp_balance_percentage_to_activate_the_risk_4 = 9.0;//percentage 4 that will be exceeded to modify the risk separated by commas input double inp_percentage_to_be_modified_4 = 0.33;//new percentage 4 1 to which the gmlpo is modified
5. Máxima ganancia diaria (MDP)
Para completar la lista de parámetros de gestión, se añade la Máxima Ganancia Diaria (MDP), esta contiene los 3 parametros generales para toda maxiam perida/gancias, ademas de esto se agrege un nuevo paraetro "mdp_is_strict" este paraemtro indica si el modo para verificar si la gancia maxima por operacion ha sido superada.
Si mdp_is_trict es: false, signifa que sin importar si haya habido perdidas durante el dia, solo se tiene que superar "MDP" en si, esto signifac que si mi meta de gancias es de 10 USD y durante el dia pierdo pro jeemplo 4USD, y luego digamos hago 5USD, etnocnes esto se considerar como si se hubiera superado la miaxma gancnias por operacion, por otro lado que pasa si es true?, si es true se tendra que completar la meta y aparte de eso si hay perdidas durante el dia, estas se tieen que recuperar, por eemplo si mi meta es 10USD y pierdo 5 USD tendre que recuperar los 5 USD y hacer los 10USD.
sinput group "- MDP/Maximum daily profit/Maxima ganancia diaria -" input bool mdp_is_strict = true; //MDP is strict? input double percentage_or_money_mdp_input = 0; //percentage or money (0 => not used MDP) input ENUM_RISK_CALCULATION_MODE mode_calculation_mdp = percentage; //Mode calculation Max Daily Profit input ENUM_APPLIED_PERCENTAGES applied_percentages_mdp = Balance;//MDP percentage applies to:
6. Sesión de operativa
Para evitar operar en horarios de baja volatilidad o poco adecuados, se añade la opción de definir una sesión de trading
Si el tiempo actual se encuentra dentro del rango definido el bot operara de lo contrario no hará nada.
sinput group "--- Session ---" input char hora_inicio = 16;//start hour to operate (0-23) input char min_inicio = 30;//start minute to operate (0-59) input char hora_fin = 18;//end hour to operate (1-23) input char min_fin =0;//end minute to operate (0-59)
Parámetros completos:
sinput group "--- Order Block EA settings ---" input ulong Magic = 545244; //Magic number input ENUM_TIMEFRAMES timeframe_order_block = PERIOD_M5; //Order block timeframe sinput group "-- Order Block --" input int Rango_universal_busqueda = 500; //search range of order blocks input int Witdth_order_block = 1; //Width order block input bool Back_order_block = true; //Back order block? input bool Fill_order_block = true; //Fill order block? input color Color_Order_Block_Bajista = clrRed; //Bearish order block color input color Color_Order_Block_Alcista = clrGreen; //Bullish order block color sinput group "-- Strategy --" input ENUM_TP_SL_STYLE tp_sl_style = POINT;//tp and sl style sinput group "- ATR " input double Atr_Multiplier_1 = 1.5;//Atr multiplier 1 input double Atr_Multiplier_2 = 2.0;//Atr multiplier 2 sinput group "- POINT " input int TP_POINT = 1000; //TL in Points input int SL_POINT = 1000; //SL in Points sinput group "--- Risk Management ---" input ENUM_LOTE_TYPE Lote_Type = Dinamico; //Lote Type input double lote = 0.1; //lot size (only for fixed lot) input ENUM_MODE_RISK_MANAGEMENT risk_mode = personal_account;//type of risk management mode input ENUM_GET_LOT get_mode = GET_LOT_BY_STOPLOSS_AND_RISK_PER_OPERATION; //How to get the lot input double prop_firm_balance = 1000; //If risk mode is Prop Firm FTMO, then put your ftmo account balance sinput group "- ML/Maxium loss/Maxima perdida -" input double percentage_or_money_ml_input = 0; //percentage or money (0 => not used ML) input ENUM_RISK_CALCULATION_MODE mode_calculation_ml = percentage; //Mode calculation Max Loss input ENUM_APPLIED_PERCENTAGES applied_percentages_ml = Balance; //ML percentage applies to: sinput group "- MWL/Maximum weekly loss/Perdida maxima semanal -" input double percentage_or_money_mwl_input = 0; //percentage or money (0 => not used MWL) input ENUM_RISK_CALCULATION_MODE mode_calculation_mwl = percentage; //Mode calculation Max weekly Loss input ENUM_APPLIED_PERCENTAGES applied_percentages_mwl = Balance;//MWL percentage applies to: sinput group "- MDL/Maximum daily loss/Perdida maxima diaria -" input double percentage_or_money_mdl_input = 0; //percentage or money (0 => not used MDL) input ENUM_RISK_CALCULATION_MODE mode_calculation_mdl = percentage; //Mode calculation Max daily loss input ENUM_APPLIED_PERCENTAGES applied_percentages_mdl = Balance;//MDL percentage applies to: sinput group "- GMLPO/Gross maximum loss per operation/Porcentaje a arriesgar por operacion -" input ENUM_OF_DYNAMIC_MODES_OF_GMLPO mode_gmlpo = DYNAMIC_GMLPO_FULL_CUSTOM; //Select GMLPO mode: input ENUM_REVISION_TYPE revision_type_gmlpo = REVISION_ON_CLOSE_POSITION; //Type revision input double percentage_or_money_gmlpo_input = 1.0; //percentage or money (0 => not used GMLPO) input ENUM_RISK_CALCULATION_MODE mode_calculation_gmlpo = percentage; //Mode calculation Max Loss per operation input ENUM_APPLIED_PERCENTAGES applied_percentages_gmlpo = Balance;//GMPLO percentage applies to: sinput group "-- Optional GMLPO settings, Dynamic GMLPO" sinput group "--- Full customizable dynamic GMLPO" input string note1 = "subtracted from your total balance to establish a threshold."; //This parameter determines a specific percentage that will be input string str_percentages_to_be_reviewed = "2,5,7,9"; //percentages separated by commas. input string note2 = "a new risk level will be triggered on your future trades: "; //When the current balance (equity) falls below this threshold input string str_percentages_to_apply = "1,0.7,0.5,0.33"; //percentages separated by commas. input string note3 = "0 in both parameters => do not use dynamic risk in gmlpo"; //Note: sinput group "--- Fixed dynamic GMLPO with parameters" sinput group "- 1 -" input string note1_1 = "subtracted from your total balance to establish a threshold."; //This parameter determines a specific percentage that will be input double inp_balance_percentage_to_activate_the_risk_1 = 2.0; //percentage 1 that will be exceeded to modify the risk separated by commas input string note2_1 = "a new risk level will be triggered on your future trades: "; //When the current balance (equity) falls below this threshold input double inp_percentage_to_be_modified_1 = 1.0;//new percentage 1 to which the gmlpo is modified sinput group "- 2 -" input double inp_balance_percentage_to_activate_the_risk_2 = 5.0;//percentage 2 that will be exceeded to modify the risk separated by commas input double inp_percentage_to_be_modified_2 = 0.7;//new percentage 2 to which the gmlpo is modified sinput group "- 3 -" input double inp_balance_percentage_to_activate_the_risk_3 = 7.0;//percentage 3 that will be exceeded to modify the risk separated by commas input double inp_percentage_to_be_modified_3 = 0.5;//new percentage 3 to which the gmlpo is modified sinput group "- 4 -" input double inp_balance_percentage_to_activate_the_risk_4 = 9.0;//percentage 4 that will be exceeded to modify the risk separated by commas input double inp_percentage_to_be_modified_4 = 0.33;//new percentage 4 1 to which the gmlpo is modified sinput group "- MDP/Maximum daily profit/Maxima ganancia diaria -" input bool mdp_is_strict = true; //MDP is strict? input double percentage_or_money_mdp_input = 0; //percentage or money (0 => not used MDP) input ENUM_RISK_CALCULATION_MODE mode_calculation_mdp = percentage; //Mode calculation Max Daily Profit input ENUM_APPLIED_PERCENTAGES applied_percentages_mdp = Balance;//MDP percentage applies to: sinput group "--- Session ---" input char hora_inicio = 16;//start hour to operate (0-23) input char min_inicio = 30;//start minute to operate (0-59) input char hora_fin = 18;//end hour to operate (1-23) input char min_fin =0;//end minute to operate (0-59)
Declaración de las variables globales
En este apartado, se crearán distintas variables globales para la gestión de riesgo y la operativa del EA.
1. Objetos de "CTrade" y "CRiskManagement"
Para poder ejecutar trades, incluiremos nuestra librería "Risk_Management.mqh", la cual hemos desarrollado a lo largo de toda la serie de artículos. Además, se declarará un objeto del tipo CTrade.
#include <Risk_Management.mqh>
CTrade trade;
A continuación, se instancia el objeto de CRiskManagement y se rellenan los parámetros requeridos por el constructor:
CRiskManagemet risk(mdp_is_strict, get_mode, Magic, risk_mode, prop_firm_balance);
2. Variables para almacenar los handles de los indicadores
Se crearán dos variables para almacenar los handles de los indicadores: una para el indicador de order blocks y otra para la EMA, con el fin de poder visualizar cómo el bot toma decisiones.
//--- Handles int order_block_indicator_handle; int hanlde_ma;
3. Arrays para almacenar los valores de TP y SL
Para guardar los valores que se copien de los buffers del indicador de order blocks, se crearán cuatro arrays:
double tp1[]; double tp2[]; double sl1[]; double sl2[];
4. Variables para el cierre previo (diarias, semanales y del timeframe de order blocks)
El bot necesita almacenar el timestamp o el momento en el que se cerró la última vela diaria, semanal o incluso la vela específica del timeframe donde se detecta el order block:
//--- datetime TiempoBarraApertua; datetime TiempoBarraApertua_1; datetime prev_vela;
5. Variable Booleana para habilitar/deshabilitar la operativa
Para controlar si el bot puede operar en función de haber superado ciertos límites de pérdida o ganancia, se creará una variable del tipo bool que indique si es posible operar (true) o no (false):
//--- bool opera = true;
6. Variables que almacenan el inicio y fin de la sesión
Finalmente, se definen variables que marcarán el inicio y el final de la sesión durante la cual se buscarán señales de compra o venta:
datetime start_sesion; datetime end_sesion;
Función OnInit
En esta sección, configuramos todo lo necesario para que el EA comience a funcionar correctamente. Declaramos y ajustamos los parámetros del indicador de Order Blocks, inicializamos el handle de dicho indicador y el de la EMA, y, por último, configuramos la gestión de riesgo.
1. Creación del Array MqlParam para el indicador de order blocks
El primer paso consiste en preparar un array de parámetros que corresponda a cada ajuste del indicador. Con la estructura MqlParam podemos pasar estos parámetros a la función IndicatorCreate() de manera ordenada:
//--- MqlParam param[17]; param[0].type = TYPE_STRING; param[0].string_value = "::Indicators\\Order_Block_Indicador_New_Part_2"; param[1].type = TYPE_STRING; param[1].string_value = "--- Order Block Indicator settings ---"; param[2].type = TYPE_STRING; param[2].string_value = "-- Order Block --"; param[3].type = TYPE_INT; param[3].integer_value = Rango_universal_busqueda; param[4].type = TYPE_INT; param[4].integer_value = Witdth_order_block; param[5].type = TYPE_BOOL; param[5].integer_value = Back_order_block; param[6].type = TYPE_BOOL; param[6].integer_value = Fill_order_block; param[7].type = TYPE_COLOR; param[7].integer_value = Color_Order_Block_Bajista; param[8].type = TYPE_COLOR; param[8].integer_value = Color_Order_Block_Alcista; param[9].type = TYPE_STRING; param[9].string_value = "-- Strategy --"; param[10].type = TYPE_INT; param[10].integer_value = tp_sl_style; param[11].type = TYPE_STRING; param[11].string_value = "- ATR"; param[12].type = TYPE_DOUBLE; param[12].double_value = Atr_Multiplier_1; param[13].type = TYPE_DOUBLE; param[13].double_value = Atr_Multiplier_2; param[14].type = TYPE_STRING; param[14].string_value = "- POINT"; param[15].type = TYPE_INT; param[15].integer_value = TP_POINT; param[16].type = TYPE_INT; param[16].integer_value = SL_POINT;
Los índices del array param[] se relacionan con cada parámetro que el indicador de Order Blocks necesita.
2. Creación y verificación de los dandles de los indicadores
Una vez llenado el array, utilizamos IndicatorCreate() para obtener el handle del indicador de Order Blocks. Además, creamos un handle para la EMA, que servirá como referencia adicional en la estrategia de entrada y salida.
//--- order_block_indicator_handle = IndicatorCreate(_Symbol, timeframe_order_block, IND_CUSTOM, ArraySize(param), param); hanlde_ma = iMA(_Symbol, timeframe_order_block, 30, 0, MODE_EMA, PRICE_CLOSE); trade.SetExpertMagicNumber(Magic); if(order_block_indicator_handle == INVALID_HANDLE) { Print("The order blocks indicator is not available last error: ", _LastError); return INIT_FAILED; } if(hanlde_ma == INVALID_HANDLE) { Print("The ema indicator is not available latest error: ", _LastError); return INIT_FAILED; }
3. Agregar los indicadores al gráfico
Para facilitar la depuración y visualización, se pueden añadir los indicadores al gráfico directamente. Esto es opcional, aunque al visualizar los objetos en el gráfico resulta más sencillo comprobar que los parámetros y las visualizaciones son correctos.
ChartIndicatorAdd(0, 0, order_block_indicator_handle); ChartIndicatorAdd(0, 0, hanlde_ma);
4. Configurar la gestión de riesgo
Configuramos nuestro objeto CRiskManagement, el cual se encargará de aplicar los límites de pérdidas, ganancias y el calculo del lotaje ideal.
//---
risk.SetPorcentages(percentage_or_money_mdl_input, percentage_or_money_mwl_input, percentage_or_money_gmlpo_input
, percentage_or_money_ml_input, percentage_or_money_mdp_input);
risk.SetEnums(mode_calculation_mdl, mode_calculation_mwl, mode_calculation_gmlpo, mode_calculation_ml, mode_calculation_mdp);
risk.SetApplieds(applied_percentages_mdl, applied_percentages_mwl, applied_percentages_gmlpo, applied_percentages_ml, applied_percentages_mdp);
Configurar el GMLPO dinámico
Dependiendo del modo de configuración elegido (fijo o totalmente personalizable), se indican los valores de riesgo por operación correspondientes.
if(mode_gmlpo == DYNAMIC_GMLPO_FIXED_PARAMETERS) { string percentages_to_activate, risks_to_be_applied; SetDynamicGMLPOUsingFixedParameters(inp_balance_percentage_to_activate_the_risk_1, inp_balance_percentage_to_activate_the_risk_2, inp_balance_percentage_to_activate_the_risk_3 , inp_balance_percentage_to_activate_the_risk_4, inp_percentage_to_be_modified_1, inp_percentage_to_be_modified_2, inp_percentage_to_be_modified_3, inp_percentage_to_be_modified_4 , percentages_to_activate, risks_to_be_applied); risk.SetDynamicGMLPO(percentages_to_activate, risks_to_be_applied, revision_type_gmlpo); } else if(mode_gmlpo == DYNAMIC_GMLPO_FULL_CUSTOM) risk.SetDynamicGMLPO(str_percentages_to_be_reviewed, str_percentages_to_apply, revision_type_gmlpo);
La función SetDynamicGMLPOUsingFixedParameters() convierte los parámetros fijos (inp_balance_percentage_to_activate_the_risk_X e inp_percentage_to_be_modified_X) a cadenas. Esta función es muy sencilla; básicamente, lo que hace es generar cadenas sumando los valores de las variables convertidas al tipo string:
void SetDynamicGMLPOUsingFixedParameters( double _balance_percentage_to_activate_the_risk_1, double _balance_percentage_to_activate_the_risk_2, double _balance_percentage_to_activate_the_risk_3, double _balance_percentage_to_activate_the_risk_4, double _percentage_to_be_modified_1, double _percentage_to_be_modified_2, double _percentage_to_be_modified_3, double _percentage_to_be_modified_4, string &percentages_to_activate, string &risks_to_be_applied) { percentages_to_activate = DoubleToString(_balance_percentage_to_activate_the_risk_1) + "," + DoubleToString(_balance_percentage_to_activate_the_risk_2) + "," + DoubleToString(_balance_percentage_to_activate_the_risk_3) + "," + DoubleToString(_balance_percentage_to_activate_the_risk_4); risks_to_be_applied = DoubleToString(_percentage_to_be_modified_1) + "," + DoubleToString(_percentage_to_be_modified_2) + "," + DoubleToString(_percentage_to_be_modified_3) + "," + DoubleToString(_percentage_to_be_modified_4); }
5. Configuración de arrays para TP/SL del indicador de order blocks
Finalmente, configuramos los arrays que almacenarán los datos del indicador de order blocks de forma secuencial:
//--- ArraySetAsSeries(tp1, true); ArraySetAsSeries(tp2, true); ArraySetAsSeries(sl1, true); ArraySetAsSeries(sl2, true);
Funciones OnTick y OnTradeTransaction
Las funciones OnTick y OnTradeTransaction son fundamentales para cualquier sistema de trading. En nuestro caso, OnTick se utiliza para verificar si se han superado pérdidas, el inicio de nuevas velas diarias, semanales, etc., y para ejecutar las señales basándonos en los datos del indicador de order blocks.
1. Verificación de un nuevo día y semana, configuración de la sesión
En OnTick, lo primero es determinar si ha iniciado un nuevo día o una nueva semana. Esto se logra comparando el datetime de la barra más reciente para el periodo diario (PERIOD_D1) y semanal (PERIOD_W1).
Dentro de la comprobación de la vela diaria se reinicia la variable opera a true (la cual indica si se puede operar). Además, se ejecuta la función OnNewDay del objeto risk y se calcula el horario de operación del día.
//--- if(TiempoBarraApertua != iTime(_Symbol, PERIOD_D1, 0)) { opera = true; risk.OnNewDay(); start_sesion = HoraYMinutoADatetime(hora_inicio,min_inicio); end_sesion = HoraYMinutoADatetime(hora_fin,min_fin); if(TiempoBarraApertua_1 != iTime(_Symbol, PERIOD_W1, 0)) { risk.OnNewWeek(); TiempoBarraApertua_1 = iTime(_Symbol, PERIOD_W1, 0); } TiempoBarraApertua = iTime(_Symbol, PERIOD_D1, 0); }
Nota: La función HoraYMinutoADatetime(int hora, int minuto) convierte la hora y el minuto establecidos en los inputs en una variable del tipo datetime.
datetime HoraYMinutoADatetime(int hora, int minuto) { MqlDateTime tm; TimeCurrent(tm); // Asigna la hora y el minuto deseado tm.hour = hora; tm.min = minuto; tm.sec = 0; // Puedes ajustar los segundos si es necesario return StructToTime(tm);; }
2. Verificación y lógica en cada nueva vela del timeframe de order blocks
El siguiente paso es revisar si se ha formado una nueva barra en el timeframe configurado para el indicador de order blocks. Una vez detectada la nueva vela, se copian los buffers del indicador para obtener los valores de TP y SL. Luego, se configura el SL para calcular el lote y se ejecuta la orden de compra o venta según la señal.
if(prev_vela != iTime(_Symbol, timeframe_order_block, 0)) { CopyBuffer(order_block_indicator_handle, 2, 0, 5, tp1); CopyBuffer(order_block_indicator_handle, 3, 0, 5, tp2); CopyBuffer(order_block_indicator_handle, 4, 0, 5, sl1); CopyBuffer(order_block_indicator_handle, 5, 0, 5, sl2); if(tp1[0] > 0 && tp2[0] > 0 && sl1[0] > 0 && sl2[0] > 0) { if(tp2[0] > sl2[0] && risk.GetPositionsTotal() == 0) //compras { double ASK = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_ASK), _Digits); risk.SetStopLoss(ASK - sl1[0]); double lot = (Lote_Type == Dinamico ? risk.GetLote(ORDER_TYPE_BUY) : lote); if(lot > 0.0) trade.Buy(lot, _Symbol, ASK, sl1[0], tp1[0], "Order Block EA Buy"); } else if(sl2[0] > tp2[0] && risk.GetPositionsTotal() == 0) //venta { double BID = NormalizeDouble(SymbolInfoDouble(_Symbol, SYMBOL_BID), _Digits); risk.SetStopLoss(sl1[0] - BID); double lot = (Lote_Type == Dinamico ? risk.GetLote(ORDER_TYPE_SELL) : lote); if(lot > 0.0) trade.Sell(lot, _Symbol, BID, sl1[0], tp1[0], "Order Block EA Sell"); } } prev_vela = iTime(_Symbol, timeframe_order_block, 0); }
Nota: La función risk.GetPositionsTotal() limita el número de operaciones simultáneas. En este ejemplo se comprueba que no existan posiciones abiertas antes de lanzar una nueva orden.
Se usan los valores de sl1 y tp1, pero también se podría utilizar tp2 si se desea establecer otra relación, por ejemplo 1:2, según lo configurado en los parámetros.
3. Verificación final de la gestión de riesgo
Al final de cada OnTick, se comprueba si se han superado los límites de pérdida o ganancia. En caso que se haya superado una perdida o ganancia, se cierran todas las posiciones abiertas por el EA y se cambia el estado de "opera" a false.
risk.OnTickEvent(); if(risk.ML_IsSuperated(CLOSE_POSITION_AND_EQUITY) == true) { if(risk_mode == propfirm_ftmo) { Print("The expert advisor lost the funding test"); ExpertRemove(); } else { risk.CloseAllPositions(); Print("Maximum loss exceeded now"); opera = false; } } if(risk.MDL_IsSuperated(CLOSE_POSITION_AND_EQUITY) == true) { risk.CloseAllPositions(); Print("Maximum daily loss exceeded now"); opera = false; } if(risk.MDP_IsSuperated(CLOSE_POSITION_AND_EQUITY) == true) { risk.CloseAllPositions(); Print("Excellent Maximum daily profit achieved"); opera = false; }
Nota: Se pueden incorporar verificaciones adicionales para controlar otras pérdidas, como la pérdida máxima semanal.
if(risk.MWL_IsSuperated(CLOSE_POSITION_AND_EQUITY) == true) { risk.CloseAllPositions(); Print("The maximum weekly loss has been exceeded"); opera = false; extra = false; }
4. Evento OnTradeTransaction
Finalmente, para recibir eventos de apertura, cierre o modificación de transacciones, se implementa la función OnTradeTransaction. Esta función llama al método correspondiente en la clase de riesgo para gestionar dichos eventos.
//+------------------------------------------------------------------+ //| TradeTransaction function | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { risk.OnTradeTransactionEvent(trans); }
Función OnDeinit
La función OnDeinit se ejecuta justo antes de que el EA (Expert Advisor) sea desactivado o removido del gráfico. En este punto, lo ideal es liberar todos los recursos y eliminar cualquier indicador u objeto que hayamos agregado durante la ejecución, para así dejar el gráfico en un estado limpio y evitar fugas de memoria.
A continuación se muestra un ejemplo de cómo realizar esta limpieza.
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- ChartIndicatorDelete(0, 0, ChartIndicatorName(0, 0, GetMovingAverageIndex())); ChartIndicatorDelete(0, 0, "Order Block Indicator"); if(hanlde_ma != INVALID_HANDLE) IndicatorRelease(hanlde_ma); if(order_block_indicator_handle != INVALID_HANDLE) IndicatorRelease(order_block_indicator_handle); }
Encontrar y eliminar la media móvil (EMA) del gráfico
Para eliminar un indicador de tipo Moving Average, necesitamos encontrar primero su nombre tal como se registra en la pestaña de indicadores del gráfico. A veces, la única forma de hacerlo es buscarlo de manera iterativa con su nombre o parte de él.
//+------------------------------------------------------------------+ //| Extra Functions | //+------------------------------------------------------------------+ int GetMovingAverageIndex(long chart_id = 0) { int total_indicators = ChartIndicatorsTotal(chart_id, 0); for(int i = 0; i < total_indicators; i++) { string indicator_name = ChartIndicatorName(chart_id, 0, i); if(StringFind(indicator_name, "MA") >= 0) return i; } return -1; } //+------------------------------------------------------------------+
ChartIndicatorsTotal(chart_id, 0): Devuelve el número total de indicadores anexados a la ventana principal del gráfico.
ChartIndicatorName(chart_id, 0, i): Retorna el nombre de cada indicador en la ventana principal.
StringFind(indicator_name, "MA"): Comprueba si dentro del nombre existe la palabra “MA” (podría ser “EMA”, “MA”, etc.). Si la encuentra, retorna el índice.
Una vez que obtenemos el índice en la lista de indicadores, podemos usar ese nombre para su eliminación con ChartIndicatorDelete.
Liberar los handles de los indicadores
La llamada a IndicatorRelease() sirve para garantizar que el indicador queda completamente descargado de la memoria, sobre todo en indicadores personalizados o cuando manejamos sus datos a través de handles. De no hacerlo, podrían quedar restos en memoria tras cerrar el EA.- hanlde_ma: handle para la media móvil exponencial (EMA).
- order_block_indicator_handle: handle para el indicador de Order Blocks.
Pruebas
Finalmente, en la última sección llevaremos a cabo pruebas con este bot y evaluaremos los pros y los contras del uso de la gestión de riesgo.
Nuestro primer backtest se efectuará utilizando datos del último año.
El test se llevará a cabo desde el 01.01.2024 hasta el 28.03.2025.
-
Símbolo: Oro
-
Timeframe: M5
-
Modelo: Se ejecutará en cada tick, basado en ticks reales
-
Apalancamiento: 1:100
Configuración:
Gráfico:
Backtest 1
Nota: Recordemos que este back test no ha sido optimizado.
Ahora optimizaremos el bot y estos han sido los resultados:
- Nuevo timeframe: M3
- Se usa la máxima ganancia diaria y la máxima pérdida diaria
Backtest 2
Hemos optimizado el horario, la pérdida máxima, la ganancia máxima y el riesgo por operación, además del multiplicador del ATR y el timeframe, que idealmente es de 3 minutos.
Nota: En los dos backtests realizados se utilizó el ATR para calcular el stop loss y el take profit.
Para finalizar, y demostrar que la gestión de riesgo puede tener un impacto en el backtest, eliminaremos la pérdida máxima diaria y la ganancia máxima, manteniendo el resto de los parámetros.
Este ha sido el gráfico:
Backtest 3
Como podemos observar, en el gráfico del backtest 3 se aprecia un crecimiento inicial más acelerado, que posteriormente se ve afectado por diversos factores y comienza a declinar. En contraste, el gráfico del backtest 2 muestra un crecimiento gradual y sostenido, sin picos pronunciados. Esto sugiere que, al establecer un límite de ganancias, podríamos evitar sobre operar y protegernos de rachas negativas y escenarios inesperados; mientras que un límite máximo de pérdida ayuda a restringir las perdidas que puede llegar, por ejemplo en una racha perdedora, limitar la perdida máxima diaria, puede hacer que la parida que se obtuvo el EA .
Realizaremos una última prueba utilizando los mismos parámetros del backtest 3, pero con la diferencia de activar el riesgo dinámico cuando la cuenta comience a presentar resultados negativos.
Backtest 4
Observamos que cuando el porcentaje de ganancia de la cuenta es negativo, el riesgo por operación se reduce drásticamente, limitando las posibles pérdidas. Existen dos diferencias principales entre los backtests 3 y 4:
- Racha ganadora:
En el backtest 3, durante una serie de operaciones positivas, no solo se superaron las pérdidas, sino que la cuenta se incrementó en un 10%. En una prueba de fondeo, este escenario habría dado como resultado pérdidas, ya que el balance descendió hasta alrededor de 8600. En contraste, en el backtest 4, aun presentándose una racha ganadora, la cuenta se mantuvo en el valor inicial (10000) sin alcanzar ganancias significativas.
- Recuperación de pérdidas:
En el backtest 4, recuperar lo perdido requiere generalmente más operaciones y mayor tiempo. La ventaja del riesgo dinámico es que, incluso en una prueba de fondeo, la cuenta se protege mejor, manteniéndose en un balance mínimo de aproximadamente 9086.
La conclusión que podemos extraer de estos resultados es que el riesgo dinámico resulta recomendable en PropFirms, ya que limita de manera notable la pérdida que puede generar el bot. En cambio, en una cuenta normal, el uso del riesgo dinámico podría extender el tiempo necesario para recuperarse, lo cual puede no ser un inconveniente en una prop firm, donde el objetivo es pasar la cuenta y no obtener beneficios de forma rápida.
Conclusión
En este artículo implementamos la gestión de riesgo en un EA de order blocks. Como se pudo observar, existen varias diferencias en el uso de las limitaciones de ganancias y pérdidas, así como en el manejo del riesgo dinámico. Al final del artículo se presentó de forma gráfica el desempeño del sistema, evidenciando la efectividad de las medidas de protección implementadas.
Archivos usados/mejorados en este artículo:
Nombre del archivo | Tipo | Descripción |
---|---|---|
Risk_Management.mqh | .mqh (archivo incluido) | Archivo principal que contiene funciones generales y la implementación de la clase CRiskManagement, responsable de gestionar el riesgo dentro del sistema. En este archivo se definen, desarrollan y amplían todas las funcionalidades relacionadas con la gestión de pérdidas y ganancias. |
Order_Block_Indicador_New_Part_2.mq5 | .mq5 | Archivo que contiene el código del indicador de Order Blocks. |
Order Block EA MT5.mq5 | .mq5 | Archivo que contiene el código del bot de Order Blocks. |
Set 1% Risk.set | .set | Configuraciones utilizadas para el backtest 1. |
Set Order Block EA.set | .set | Configuraciones utilizadas en el backtest 2, con límites máximos de pérdida y ganancia diarios. |
Set Dynamic Risk.set | .set | Configuraciones empleadas para probar el riesgo dinámico (backtest 3), sin límites diarios de pérdidas ni ganancias. |
Set No Dynamic Risk.set | .set | Configuraciones empleadas sin el riesgo dinámico (backtest 4), también sin límites diarios de pérdidas ni ganancias. |





- 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