Управление рисками (Часть 5): Интегрируем систему управления рисками в советник
- Введение
- Улучшения в индикаторе Order Blocks
- Создание советника и его параметры
- Объявление глобальных переменных
- Функция OnInit
- Функции OnTick и OnTradeTransaction
- Функция OnDeinit
- Тестирование
- Заключение
Введение
Добро пожаловать в заключительную часть нашей серии статей об управлении рисками. В этой статье мы рассмотрим различия в применении системы управления рисками: есть ли разница между ее использованием и отсутствием, каковы преимущества и недостатки динамического подхода к управлению рисками, и в каких случаях его применение наиболее целесообразно.
Мы ответим на эти вопросы, создав простой экспертный советник, который использует индикатор Order Blocks, разработанный в предыдущих статьях об управлении рисками.
Начнем с оптимизации этого индикатора, что позволит нам быстрее и эффективнее проводить бэктесты, а также упростит оптимизацию советника.
Также мы подробно разберем пошаговый процесс создания советника, определение его параметров и интеграцию основных событий, таких как "OnTradeTransaction", которое должно выполняться только внутри соответствующей функции.
Наконец, мы ответим на вопросы, поднятые в этой статье, выполнив четыре теста советника с различными комбинациями параметров. Таким образом, мы проанализируем разницу между торговлей с ограничениями по убыткам и прибыли и без них, а также оценим, в каких случаях динамическое управление рисками может применяться наиболее эффективно.
Улучшения в индикаторе Order Blocks
Мы начнем эту статью с оптимизации индикатора Order Blocks. Раньше его производительность оставляла желать лучшего. После анализа я заметил, что есть возможности повышения эффективности, как за счет оптимизации обработки данных свечей, так и удаления ненужных циклов.
1. Оптимизация цикла для поиска блоков ордеров
В первом цикле индикатора мы сократим количество итераций, поскольку не будет необходимости перебирать все свечи при каждом обновлении. При загрузке индикатора параметр "prev_calculated" функции "OnCalculate" равен 0 — этот параметр, в сущности, указывает количество уже рассчитанных свечей. Поскольку при первом запуске свеча еще не рассчитана, значение "prev_calculated" равно 0. Таким образом, при первом расчете цикл будет проходить по свечам от номера "Rango_universal_busqueda" до свечи 6; а когда "prev_calculated" больше 0, будет проверяться только свеча 6.
int inicio = prev_calculated == 0 ? Rango_universal_busqueda : 6; for(int i = inicio ; i > 5 ; i--) { //---- }
2. Поиск смягчений в блоках ордеров
Чтобы определить, был ли Order Block "смягчен" (то есть достигла ли цена уровня блока или превысила его), мы улучшили две функции: одну для бычьих блоков и одну для медвежьих. Обе функции были упрощены за счет использования только high- и low-значений свечей.
Смягчение в бычьем Order Block.
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 }
Смягчение в медвежьем Order Block.
//+------------------------------------------------------------------+ 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 }
В этих двух функциях low- и high-значения свечей используются напрямую для уменьшения количества параметров и небольшого сокращения времени выполнения цикла.
Кроме того, эти изменения были реализованы в функциях "esOb_mitigado_array_...", которые вызывали iOpen, iClose и другие подобные функции. В этом не было необходимости, поскольку, например, если закрытие свечи меньше уровня Order Block, это свидетельствует о том, что значение low также находится ниже. Таким образом, мы можем упростить проверку смягчения, основываясь исключительно на low или high, в зависимости от типа блока.
Бычий Order Block.
//+------------------------------------------------------------------+ 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.
//+------------------------------------------------------------------+ 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. Шаблонные функции для добавления элементов в массив
Мы добавили несколько шаблонных функций, которые будут более эффективны при вставке элемента в массив. Раньше мы создавали специальные функции, которые принимали только определенный параметр: например, если мы хотели добавить структуру типа Order_Blocks, приходилось писать собственную функцию для этого.
Шаблонные функции позволяют выполнять одну и ту же операцию, используя разные типы. В нашем случае, мы создали функцию для добавления элемента в массив.
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. Шаблонная функция для удаления элемента по имени
Теперь создадим шаблонную функцию для удаления элемента на основе его имени. Это означает, что массив, с которым мы будем работать, должен быть структурой или классом, содержащим публичный член "name". Эта функция будет служить для удаления элемента на основе параметра targetName, что полезно для удаления ненужных элементов из массива смягченных блоков ордеров.
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. Новая функциональность для "удаления" блоков ордеров
В предыдущих версиях, когда Order Block считался смягченным, его имя добавлялось во вспомогательный массив, но сам блок оставался в основном массиве. Благодаря этому усовершенствованию, во вспомогательном массиве сохраняется только имя смягченного Order Block,а сам блок полностью удаляется из основного массива.
Медвежьи Order Blocks.
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.
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. Функция для удаления объектов
При удалении объектов с графика необходимо внести некоторые изменения. Ранее создавался массив, в котором сохранялись имена смягченных блоков ордеров, чтобы избежать их повторной обработки. Теперь реализована новая функция, которая удаляет эти Order Blocks из основного массива. По этой причине имена смягченных Order Blocks необходимо сохранять во вспомогательных массивах, чтобы позднее их можно было удалить с графика.
Ниже приводится функция Delete_Objects, модифицированная таким образом, чтобы она также перебирала вспомогательные массивы и удаляла их содержимое:
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. Использование предопределенных массивов
Логика была оптимизирована за счет использования предопределенных массивов, которые MQL предоставляет в функции OnCalculate. Теперь мы будем использовать эти массивы для расчета Order Blocks. Основные данные, используемые из свечей, это "open", "close", "high", "low" и "tick_volume".
Такой подход облегчает управление и интерпретацию информации при работе с Order Blocks.
ArraySetAsSeries(open, true); ArraySetAsSeries(close, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); ArraySetAsSeries(tick_volume, true); ArraySetAsSeries(atr, true);
Создание советника и его параметры
Начнем с создания советника:
1. Создаем робота по шаблону:

2. Указываем имя и автора:

3. Выбираем только событие OnTradeTransaction:

4. Завершаем:

Далее создадим параметры, необходимые советнику для корректной работы. Начнем с общих параметров робота, таких как магический номер и настройки по умолчанию, которые уже использовались в индикаторе Order Blocks.
1. Общие параметры
Перед настройкой общих параметров определим перечисление, содержащее типы TP и SL, принимаемые индикатором; они будут такими же, как в самом индикаторе.
enum ENUM_TP_SL_STYLE
{
ATR = 0,
POINT = 1
}; Ниже кратко объясняется значение каждого добавленного параметра:
-
Magic — магический номер, который идентифицирует сделки робота, позволяя отличить их от других.
-
timeframe_order_block — задает таймфрейм, на котором будет выполняться обнаружение блоков ордеров (Order Blocks).
-
Rango_universal_busqueda — количество свечей назад, которые робот будет анализировать для поиска возможных Order Blocks.
-
Witdth_order_block — задает толщину линий (границ) прямоугольников, представляющих Order Blocks.
-
Back_order_block иFill_order_block — графические параметры для отрисовки фона и заливки прямоугольника, представляющего каждый Order Block.
-
Color_Order_Block_Bassist и Color_Order_Block_Bullish — цвета медвежьих и бычьих блоков ордеров, соответственно.
-
tp_sl_style — стиль расчета Take Profit и Stop Loss (в ATR или в пунктах).
-
Atr_Multiplier_1 иAtr_Multiplier_2 — множители, если применяется стиль ATR.
-
TP_POINT иSL_POINT — значения Take Profit и Stop Loss в случае использования стиля 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. Параметры управления рисками
Продолжая описание параметров советника, определим те, которые будут использоваться в управлении рисками. Общие параметры таковы:
-
Lote_Type — определяет, будет ли использоваться динамический лот (на основе управления риском на сделку) или фиксированный лот.
-
lote — размер лота, применяемый, если Lote_Type установлен в значение Fixed.
-
risk_mode — позволяет выбрать, является ли счет личным или относится к PropFirm (например, propfirm_ftmo).
-
get_mode — определяет способ расчета размера лота: он основывается на значении Stop Loss и корректирует размер лота в зависимости от риска на сделку.
-
prop_firm_balance — если используется счет FTMO (или другой PropFirm с аналогичными правилами), определяет начальный баланс счета. Как объяснялось в предыдущих статьях, этот параметр используется для расчета максимального убытка на сделку и максимального дневного убытка.
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. Параметры для максимального убытка (дневного, недельного и общего)
Теперь определим параметры для контроля максимального убытка (ML), максимального недельного убытка (MWL) и максимального дневного убытка (MDL). Каждый из этих параметров основан на трех переменных, которые определяют, как рассчитывается и применяется лимит убытка:
-
percentage_or_money_..._input — представляет значение в процентах или в денежном выражении в зависимости от выбранного режима расчета. Если установлено значение 0, лимит убытка не будет использоваться.
-
mode_calculation_... — указывает, оценивается ли параметр в процентах или деньгах.
-
applied_percentages_... — определяет, к какой основе применяется процент (например, к балансу, прибыли счета и т. д.).
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: максимальный риск на сделку
Продолжая настройку параметров нашего советника, настроим максимальный убыток на сделку. Этот раздел более сложный, так как он не ограничивается тремя параметрами, а включает больше переменных, поскольку, как упоминалось в начале, здесь добавляется динамический риск.
4.1 Общие параметры GMLPO
Начнем с определения пяти основных параметров для динамического риска. Сюда входят три параметра, которые уже использовались для максимального дневного, недельного и общего убытка, а также два дополнительных параметра. Последние позволяют настраивать частоту проверки, определяющей необходимость изменения риска на сделку, а также тип конфигурации динамического риска, который будет использоваться. Ниже показаны дополнительные перечисления, которые будут использоваться:
//--- 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 — определяет, будет ли риск на сделку динамическим. Выбирается один из режимов настройки динамического риска. Если динамический риск не используется, необходимо выбрать тип "NO_DYNAMIC_GMLPO".
- revision_type_gmlpo — указывает, когда будет проверяться эквити счета для корректировки риска на сделку — при закрытии позиции или на каждом тике рынка.
- percentage_or_money_gmlpo_input — значение в процентах или денежном выражении, которое будет использоваться в качестве основы для определения риска на сделку. Если установлено значение 0, функция GMLPO не будет активирована.
- mode_calculation_gmlpo — определяет, будет ли "percentage_or_money_gmlpo_input" интерпретироваться как процент или фиксированная денежная сумма.
- applied_percentages_gmlpo — задает базу (например, Balance или Equity), на основе которой будет рассчитываться процент, если выбран соответствующий режим расчета.
Примечание: каждое из использованных перечислений, уже упомянутых в предыдущих статьях, дает более подробную информацию о том, как осуществляется внутренний расчет риска.
4.2 Настройки динамического GMLPO
Как уже упоминалось ранее, существует два режима настройки динамического риска — полностью настраиваемый и основанный на фиксированных параметрах. В предыдущей статье я объяснил причины принятия такого решения. Если кратко, то проблема использования строк (strings) в качестве параметра заключается в том, что их невозможно оптимизировать, что является неудобством при поиске наилучших параметров динамического риска. Поэтому для решения этой проблемы было реализовано перечисление.
4.2.1 Настраиваемый динамический GMLPO
В этом режиме пользователь точно определяет, при каком проценте уменьшения баланса необходимо изменить риск, и каким будет новое значение риска на сделку. Этот режим предоставляет максимальную гибкость, хотя он основан на строках (strings), что препятствует оптимизации.
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 — содержит список процентов, упорядоченных по возрастанию, которые указывают пороговое значение, при котором будет изменен риск на сделку. По умолчанию установлены значения «2,5,7,9», что означает, что когда прибыль счета, выраженная в процентах, падает ниже 2%, риск будет изменен до соответствующего значения, при этом будет применено первое значение str_percentages_to_apply (1).
-
str_percentages_to_apply — содержит новые проценты риска, до которых будет скорректирован риск на сделку.
4.2.2 Динамический GMLPO с фиксированными параметрами
Если вы предпочитаете настраивать динамический риск через фиксированные параметры, в параметрах советника предусмотрен отдельный раздел. Его принцип работы аналогичен настраиваемому режиму, однако в этом случае можно задать до четырех параметров. Если использование всех четырех не требуется, а нужно применить только три, достаточно указать два нуля в тех полях, которые не будут использоваться. Например, если будут использоваться только три "модификатора" в разделе «-- 4 --» два оставшихся параметра должны быть установлены на 0.
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. Максимальная дневная прибыль (MDP)
Для завершения списка параметров управления добавляется параметр Максимальная дневная прибыль (MDP). Он содержит 3 общих параметра для всех максимальных убытков/прибыли, а также новый параметр mdp_is_strict. Этот параметр указывает, используется ли режим проверки превышения максимальной прибыли на сделку.
Если mdp_is_strict равно false, это означает, что независимо от наличия убытков в течение дня, достаточно превысить только значение «MDP». Например, если цель по прибыли составляет 10 USD, и в течение дня было потеряно 4 USD, а затем заработано, скажем, 5 USD, то это будет считаться превышением максимальной прибыли на сделку. С другой стороны, что произойдет, если значение mdp_is_strict равно true? Это значит, что нужно не только достичь целевого уровня прибыли, но и восстановить все дневные убытки, прежде чем прибыль будет засчитана как превышающая MDP. Например, если цель составляет 10 USD, и было потеряно 5 USD, нужно будет сначала вернуть эти 5 USD и заработать еще 10 USD.
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. Торговая сессия
Чтобы избежать торговли в периоды низкой волатильности или неподходящее время, была добавлена возможность задавать торговую сессию.
Если текущее время находится в пределах заданного диапазона, робот будет выполнять операции; в противном случае — торговля не будет осуществляться.
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)
Полный список параметров:
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)
Объявление глобальных переменных
В этом разделе будут созданы различные глобальные переменные для управления рисками и для торговой деятельности советника.
1. Объекты "CTrade" и "CRiskManagement"
Для осуществления торговых операций мы подключим нашу библиотеку "Risk_Management.mqh", которую разрабатывали на протяжении всей серии статей. Кроме того, будет объявлен объект типа CTrade.
#include <Risk_Management.mqh>
CTrade trade; Затем создается экземпляр класса CRiskManagement и заполняются параметры, требуемые конструктором:
CRiskManagemet risk(mdp_is_strict, get_mode, Magic, risk_mode, prop_firm_balance);
2. Переменные для хранения дескрипторов индикаторов
Будут созданы две переменные для хранения дескрипторов индикаторов: одна — для индикатора Order Blocks и другая — для EMA, чтобы можно было визуализировать процесс принятия решений советником.
//--- Handles int order_block_indicator_handle; int hanlde_ma;
3. Массивы для хранения значений TP и SL
Для сохранения значений, которые копируются из буферов индикатора Order Blocks, будут созданы четыре массива:
double tp1[]; double tp2[]; double sl1[]; double sl2[];
4. Переменные для предыдущих закрытий (дневных, недельных и таймфрейма Order Blocks)
Роботу необходимо сохранять отметку времени (timestamp), то есть момент закрытия последней дневной, недельной или конкретной свечи определенного таймфрейма, на котором был обнаружен блок ордеров:
//--- datetime TiempoBarraApertua; datetime TiempoBarraApertua_1; datetime prev_vela;
5. Логическая переменная для включения/отключения торговой деятельности
Чтобы контролировать возможность работы советника после превышения определенных лимитов прибыли или убытков, будет создана переменная типа bool, указывающая, можно торговать (true) или нет (false):
//--- bool opera = true;
6. Переменные для хранения начала и конца торговой сессии
В заключение определяются переменные, которые будут отмечать начало и окончание сессии, в течение которой ведется поиск сигналов на покупку или продажу:
datetime start_sesion; datetime end_sesion;
Функция OnInit
В этом разделе мы настроим все необходимое для корректной работы советника. Мы объявляем и задаем параметры индикатора Order Blocks, инициализируем дескриптор этого индикатора и EMA и, наконец, настраиваем систему управления рисками.
1. Создание массива MqlParam для индикатора Order Blocks
Первым шагом является подготовка массива параметров, соответствующего каждой из настроек индикатора. С помощью структуры MqlParam эти параметры можно передавать в функцию IndicatorCreate() в упорядоченном виде:
//--- 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;
Индексы массива param[] соответствуют каждому параметру, необходимому для работы индикатора Order Blocks.
2. Создание и проверка дескрипторов индикаторов
После заполнения массива используется функция IndicatorCreate() для получения дескриптора индикатора Order Blocks. Кроме того, создается дескриптор EMA, который будет служить дополнительным ориентиром в стратегии входа и выхода.
//--- 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. Добавление индикаторов на график
Для упрощения отладки и визуального контроля, индикаторы можно добавить на график напрямую. Это необязательный шаг, однако при отображении объектов на графике проще убедиться в корректности заданных параметров и визуализации.
ChartIndicatorAdd(0, 0, order_block_indicator_handle); ChartIndicatorAdd(0, 0, hanlde_ma);
4. Настройка управления рисками
Настраиваем наш объект CRiskManagement, который будет отвечать за применение лимитов убытков и прибыли, а также за расчет идеального размера лота.
//---
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);
Настройка динамического GMLPO
В зависимости от выбранного режима настройки (фиксированного или полностью настраиваемого) задаются соответствующие значения риска на сделку.
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);
Функция SetDynamicGMLPOUsingFixedParameters() преобразует фиксированные параметры (inp_balance_percentage_to_activate_the_risk_X и inp_percentage_to_be_modified_X) в строки. Эта функция очень проста: по сути, она создает строки путем сложения значений переменных, преобразованных в тип 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. Настройка массива для TP/SL индикатора Order Blocks
В завершение настраиваются массивы, в которых будут последовательно сохраняться данные индикатора Order Blocks:
//--- ArraySetAsSeries(tp1, true); ArraySetAsSeries(tp2, true); ArraySetAsSeries(sl1, true); ArraySetAsSeries(sl2, true);
Функции OnTick и OnTradeTransaction
Функции OnTick и OnTradeTransaction являются основополагающими для любой торговой системы. В нашем случае OnTick используется для проверки превышения убытков, определения начала новых дневных, недельных и т. д. свечей, а также для выполнения сигналов на основе данных индикатора Order Blocks.
1. Проверка начала нового дня и недели, настройка сессии
В функции OnTick первым шагом является определение начала нового дня или новой недели. Это делается путем сравнения datetime самого последнего бара для дневного (PERIOD_D1) и недельного (PERIOD_W1) периодов.
Во время проверки дневной свечи переменная opera сбрасывается в значение true (что указывает, разрешена ли торговля). Кроме того, выполняется функция OnNewDay объекта risk и рассчитывается время торговой сессии на текущий день.
//--- 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); }
Примечание: функция HoraYMinutoADatetime(int hora, int minuto) преобразует установленные во входных параметрах часы и минуты в переменную типа 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. Проверка и логика на каждой новой свече таймфрейма Order Blocks
Следующий шаг — проверить, сформировался ли новый бар на таймфрейме, заданном для индикатора Order Blocks. После обнаружения новой свечи, копируются буферы индикатора для получения значений TP и SL. Далее устанавливается SL для расчета размера лота, и ордер на покупку или продажу исполняется в соответствии с сигналом.
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); }
Примечание: функция risk.GetPositionsTotal() ограничивает количество одновременно открытых сделок. В этом примере проверяется отсутствие открытых позиций перед размещением нового ордера.
Используются значения sl1 и tp1, но при необходимости можно использовать и tp2, чтобы задать другое соотношение, например 1:2, согласно настройке параметров.
3. Финальная проверка управления рисками
В конце каждого OnTick проверяется, были ли превышены лимиты убытков или прибыли. В случае превышения лимитов, все позиции, открытые советником, закрываются, а значение переменной «opera» меняется на 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; }
Примечание: можно добавить дополнительные проверки для контроля других типов убытков, например, максимального недельного убытка.
if(risk.MWL_IsSuperated(CLOSE_POSITION_AND_EQUITY) == true) { risk.CloseAllPositions(); Print("The maximum weekly loss has been exceeded"); opera = false; extra = false; }
4. Событие OnTradeTransaction
В завершение, для получения событий открытия, закрытия или изменения транзакций, реализуется функция OnTradeTransaction. Эта функция вызывает соответствующий метод класса управления рисками для обработки этих событий.
//+------------------------------------------------------------------+ //| TradeTransaction function | //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { risk.OnTradeTransactionEvent(trans); }
Функция OnDeinit
Функция OnDeinit запускается непосредственно перед отключением или удалением советника с графика. На этом этапе идеальным решением будет освободить все ресурсы и удалить все индикаторы или объекты, добавленные во время работы, чтобы оставить график в чистом состоянии и избежать утечек памяти.
Ниже приведен пример того, как выполнить эту очистку.
//+------------------------------------------------------------------+ //| 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); }
Поиск и удаление скользящей средней (EMA) с графика
Чтобы удалить индикатор типа Moving Average, необходимо сначала найти его имя,то, которое указано на вкладке индикаторов графика. Иногда единственный способ сделать это — выполнить итерационный поиск по его имени или части имени.
//+------------------------------------------------------------------+ //| 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) — возвращает общее количество индикаторов, прикрепленных к основному окну графика.
ChartIndicatorName(chart_id, 0, i) — возвращает имя каждого индикатора в основном окне графика.
StringFind(indicator_name, "MA") — проверяет, содержит ли имя слово "MA" (это может быть "EMA", "MA" и т. д.). Если совпадение найдено, функция возвращает индекс.
Как только мы получим индекс в списке индикаторов, мы можем использовать это имя для удаления индикатора с помощью ChartIndicatorDelete.
Освобождение дескрипторов индикаторов
Вызов функции IndicatorRelease() используется для того, чтобы гарантировать полное освобождение индикатора из памяти, особенно в случае пользовательских индикаторов, или когда их данные обрабатываются через дескрипторы. Если этого не сделать, то после закрытия советника в памяти могут остаться остаточные данные.- handle_ma — дескриптор экспоненциальной скользящей средней (EMA).
- order_block_indicator_handle — дескриптор индикатора Order Blocks.
Тестирование
Наконец, в заключительном разделе мы проведем тесты с этим советником и оценим преимущества и недостатки использования системы управления рисками.
Наш первый бэктест будет выполнен с использованием данных за последний год.
Период тестирования: с 01.01.2024 по 28.03.2025.
- Символ: золото (Gold)
- Таймфрейм: M5
- Модель: выполнение на каждом тике, основано на реальных тиках
- Кредитное плечо: 1:100
Настройки:

График:

Backtest 1
Примечание: помним, что этот бэктест не был оптимизирован.
Теперь мы выполним оптимизацию советника, и вот результаты:
- Новый таймфрейм: M3
- Используются параметры: максимальная дневная прибыль и максимальный дневной убыток

Backtest 2
Мы оптимизировали время торговли, максимальный убыток, максимальную прибыль и риск на сделку, а также множитель ATR и таймфрейм, который в идеале составляет 3 минуты.
Примечание: в двух проведенных бэктестах использовался ATR для расчета Stop Loss и Take Profit.
Наконец, чтобы продемонстрировать, что управление рисками может влиять на результаты бэктеста, мы удалим параметры максимального дневного убытка и максимальной прибыли, сохранив остальные параметры.
Вот график:

Backtest 3
Как видно, на графике бэктеста 3 наблюдается более быстрый первоначальный рост, который впоследствии подвергается влиянию различных факторов и начинает снижаться. А вот график бэктеста 2, напротив, показывает плавный и устойчивый рост, без выраженных пиков. Это говорит о том, что, установив лимит прибыли, мы можем избежать чрезмерного количества сделок и защитить себя от убыточных серий и непредвиденных ситуаций. В то время как лимит максимального убытка помогает ограничить возможные потери, которые могут возникнуть, например, во время серии убыточных сделок. Ограничение максимального дневного убытка может восстановить паритет, достигаемый советником.
Мы проведем последний тест, используя те же параметры, что и в бэктесте 3, но с одним отличием — динамический риск будет активирован, когда счет начнет показывать отрицательные результаты.

Backtest 4
Мы можем заметить, что когда процент прибыли по счету отрицательный, риск на сделку резко снижается, ограничивая возможные потери. Между бэктестами 3 и 4 есть два основных различия:
- Прибыльная серия
В ходе бэктеста 3 во время серии положительных сделок не только были перекрыты убытки, но и счет увеличился на 10%. При проверке на получение финансирования этот сценарий привел бы к убыткам, поскольку баланс снизился бы примерно до 8600. В отличие от этого, в 4-м бэктесте, даже при прибыльной серии, счет оставался на исходном уровне (10000) без достижения существенного прироста.
- Восстановление после убытков
В бэктесте 4 для восстановления убытков обычно требуется больше времени и больше сделок. Преимущество динамического риска заключается в том, что даже при проверке на финансирование счет защищен лучше, и минимальный баланс удерживается на уровне около 9086.
Вывод, который можно сделать из этих результатов, заключается в том, что динамический риск является предпочтительным для PropFirms, поскольку он существенно ограничивает потенциальные убытки, которые может сгенерировать робот. Однако на обычном счете использование динамического риска может увеличить время, необходимое для восстановления, что не является проблемой для PropFirm, где цель состоит в управлении счетом, а не в быстром получении прибыли.
Заключение
В этой статье мы реализовали систему управления рисками в советнике, основанном на Order Blocks. Как можно заметить, есть существенные различия в применении ограничений по прибыли и убыткам, так же как и в управлении динамическим риском. В финальной части статьи была графически представлена работа системы, наглядно демонстрирующая эффективность примененных защитных механизмов.
Файлы, использованные/улучшенные в этой статье:
| Имя файла | Тип | Описание |
|---|---|---|
| Risk_Management.mqh | .mqh (включаемый файл) | Основной файл, содержащий общие функции и реализацию класса CRiskManagement, отвечающего за управление рисками в системе. В этом файле определяются, разрабатываются и расширяются все функции, связанные с управлением прибылью и убытками. |
| Order_Block_Indicador_New_Part_2.mq5 | .mq5 | Файл, содержащий код индикатора Order Blocks. |
| Order Block EA MT5.mq5 | .mq5 | Файл, содержащий код советника Order Blocks. |
| Set 1% Risk.set | .set | Настройки, использованные для бэктеста 1. |
| Set Order Block EA.set | .set | Настройки, использованные в бэктесте 2, с дневными лимитами максимального убытка и прибыли. |
| Set Dynamic Risk.set | .set | Настройки, примененные для проверки динамического риска (бэктест 3) без дневных лимитов убытков и прибыли. |
| Set No Dynamic Risk.set | .set | Настройки, примененные без динамического риска (бэктест 4), также без дневных лимитов убытков и прибыли. |
Перевод с испанского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/es/articles/17640
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Введение в MQL5 (Часть 12): Руководство для начинающих по созданию пользовательских индикаторов
Моделирование рынка (Часть 13): Сокеты (VII)
Единый мультитаймфреймовый Ренко: Синтез временных измерений рынка
Инженерия признаков с Python и MQL5 (Часть III): Угол наклона цены (2) Полярные координаты
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования