English 中文 Deutsch 日本語
preview
Разработка динамического советника на нескольких парах (Часть 2): Диверсификация и оптимизация портфеля

Разработка динамического советника на нескольких парах (Часть 2): Диверсификация и оптимизация портфеля

MetaTrader 5Тестер |
311 8
Hlomohang John Borotho
Hlomohang John Borotho

Введение

Одной из самых настойчивых проблем в профессиональной торговле является поддержание согласованности портфеля и надежных протоколов управления рисками. Зачастую трейдеры чрезмерно полагаются на отдельные активы или стратегии, что увеличивает риск значительных просадок во время резких изменений рыночных режимов. Усугубляет этот риск преобладающая тенденция к чрезмерному использованию коррелирующих инструментов, что увеличивает вероятность одновременных убытков и подрывает стабильность доходов. При отсутствии строго диверсифицированного и оптимизированного портфеля трейдеры сталкиваются с непредсказуемыми результатами, что часто приводит к принятию решений на основе эмоций и волатильной прибыльности. Таким образом, для устойчивой долгосрочной эффективности необходима систематическая структура, которая стратегически балансирует доходность с учетом риска по спектру некоррелирующих активов.

Надежное решение для минимизации этих проблем, предлагает количественная методология, интегрирующая оптимизацию портфеля, диверсификацию на несколько активов и стратегию торговли на пробой, дополненную подтверждением на основе осцилляторов. Разворачивая стратегию пробоя на нескольких валютных парах, трейдеры могут использовать высоковероятные движения цены, движимые моментумом, одновременно распределяя риск за счет экспозиции на некоррелирующие рынки. Интеграция осцилляторного индикатора обеспечивает подтверждение сигналов на вход, минимизируя участие в ложных пробоях и сокращая неэффективные сделки. Этот подход не только увеличивает потенциал прибыли, но и укрепляет стабильность портфеля, систематически используя возможности в различных рыночных фазах. Итоговая стратегия демонстрирует повышенную устойчивость к волатильности, обеспечивая согласованность эффективности с изменяющимися макроэкономическими и техническими условиями.



Логика советника

Модель покупки:

Советник начинает с расчета диапазона цен между 10:00 и 12:00 по времени UTC+2, определяя наибольшую и наименьшую цены на данном таймфрейме. В 12:00 или в любое время после, если цена пробьет ранее установленный максимум, будет активирована потенциальная возможность для покупки. Однако для подтверждения стохастический осциллятор должен находиться на уровне 20 или ниже, что указывает на перепроданность рынка. Это гарантирует, что пробой не является чрезмерным и имеет пространство для движения вверх. Как только оба условия выполнены, советник выполняет покупку, стремясь извлечь выгоду из пробоя, при этом минимизируя ложные сигналы с помощью фильтрации осциллятором.

Модель продажи:

Для модели продажи советник следует тому же процессу расчета диапазона с 10:00 до 12:00 по времени UTC+2, определяя максимумы и минимумы сессии. Если в 12:00 или позже цена опустится ниже установленного минимума, это считается возможностью для продажи. Однако сделка выполняется только в том случае, если стохастический осциллятор находится на уровне 80 или выше, что указывает на перекупленность рынка. Это гарантирует, что пробой вниз поддерживается потенциальным давлением продаж, а не ложным движением. Как только оба условия совпадают, советник открывает сделку на продажу, используя медвежий моментум и избегая преждевременных входов.

Приступаем:

//+------------------------------------------------------------------+
//|                                                      Dyna MP.mq5 |
//|                                  Copyright 2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Trade/Trade.mqh>
CTrade trade;

enum Signal_Breakout{
   Normal_Signal,
   Reversed_Signal,
};

`#include <Trade/Trade.mqh>` – данная строка включает библиотеку MQL5 Trade. Файл заголовка `Trade.mqh` определяет классы и функции, которые упрощают торговые задачи, такие как открытие, корректировка или закрытие ордеров. Здесь объект с именем `trade` создается из класса `CTrade`. Этот класс содержит методы для выполнения торговых операций (например, подачи заявок на покупку/продажу, изменения открытых сделок или закрытия позиций) структурированным образом.

input group "--------------General Inputs--------------"
input string Symbols = "XAUUSD, GBPUSD, USDCAD, USDJPY";
input Signal_Breakout BreakOutMode = Reversed_Signal;
input double  In_Lot = 0.01;
input int TakeProfit = 500;
 double StopLoss = 500;
input bool TrailYourStop = false;
input int TrailingStop = 50;

// Stochastic
input int KPeriod = 21;
input int upprer_level = 80;

Строка `Symbols` указывает активы для торговли (например, "XAUUSD, GBPUSD"), `In_Lot` устанавливает фиксированный размер сделки (0,01 лота), `TakeProfit` (500 пунктов) и `StopLoss` (1 000 пунктов) определяют уровни взятия прибыли и ограничения риска, в то время как `TrailYourStop` и `Trailing Stop` (70 пунктов) активируют и настраивают динамический трейлинг стоп-лосс. Для стохастического осциллятора `KPeriod` (20) определяет период расчета для процентной линии, а `upper_level` (80) обозначает порог перекупленности. Эти входные данные в совокупности уравновешивают правила исполнения сделок, управление рисками и генерацию технических сигналов.

//+------------------------------------------------------------------+
//|                           Global vars                            |
//+------------------------------------------------------------------+
int handles[];
double bufferM[];


int RangeStart = 600; 
int RangeDuration = 120;
int RangeClose = 1200;

int Num_symbs = 0;
string symb_List[];
string Formatted_Symbs[];

В этом разделе определяются глобальные переменные. Массив `handles` хранит хэндлы для стохастического осциллятора, что позволяет советнику эффективно управлять несколькими активами. Массив `buffer` хранит рассчитанные значения, такие как показания индикаторов или исторические уровни цен. Переменная `RangeStart` установлена на 600 минут (10:00 AM UTC+2), что обозначает начало измерения диапазона, в то время как `RangeDuration` установлена на 120 минут, определяя 2-часовой период для захвата максимума и минимума. Как только время `RangeClose` в 1200 минут (12:00 PM UTC+2) будет достигнуто, советник прекращает вычисление диапазона и начинает мониторинг условий пробоя.

Для управления символами массив `symb_List` содержит сырой список символов, которые подлежат обработке. Кроме того, массив `Formatted_Symbs` содержит общее количество символов, которые будут использоваться после разбора входных данных `Symbols`. Эти переменные в совокупности позволяют советнику динамически выполнять сделки по нескольким активам, обеспечивая при этом точные расчеты диапазона для обнаружения пробоя.

//+------------------------------------------------------------------+
//|                    Ranger Global Vars                            |
//+------------------------------------------------------------------+
struct RANGER{
   datetime start_time;
   datetime end_time;
   datetime close_time;
   double high;
   double low;
   bool b_entry;
   bool b_high_breakout;
   bool b_low_breakout;
   
   RANGER() : start_time(0), end_time(0), close_time(0), high(0), low(999999), b_entry(false), b_high_breakout(false), b_low_breakout(false) {};
};

Структура `Ranger` хранит и управляет ключевыми данными для стратегии пробоя диапазона. Он определяет структурированный способ отслеживания временных границ диапазона, уровней цен и условий пробоя. Переменные `start_time`, `end_time` и `close_time` представляют собой время начала, окончания и закрытия расчета диапазона, обеспечивая правильное определение окна пробоя советником. Переменные `high` и `low` хранят максимальную и минимальную цены, зафиксированные в пределах диапазона, при этом `low` инициализируется очень высоким значением (999999), чтобы обеспечить точные обновления цен. `low (999999)` является стандартным паттерном для нахождения минимальных значений в ценовых рядах.

Кроме того, у нас есть три булевых флага. `b_entry` отслеживает, была ли выполнена торговая сделка, предотвращая размещение нескольких сделок в рамках одного события пробоя. `b_high_breakout` сигнализирует о том, что цена пробила верхнюю границу диапазона, подтверждая потенциальный сетап на покупку, а `b_low_breakout` указывает, если цена пробила нижнюю границу диапазона, подтверждая сетап на продажу. Конструктор `Ranger()` инициализирует все значения в состояния по умолчанию, обеспечивая, чтобы структура начиналась с чистых данных перед динамическим обновлением во время живой торговли.

RANGER rangeArray[];
MqlTick prevTick[], currTick[];

Эти переменные играют ключевую роль в отслеживании ценового движения и управлении логикой пробоя диапазона в советнике. `rangeArray` — это массив структур `RANGER`, который хранит несколько экземпляров определенных параметров пробоя диапазона. Это позволяет советнику отслеживать несколько символов одновременно, обеспечивая наличие у каждого символа своих данных диапазона, включая время начала и окончания, максимальную и минимальную цены, а также условия пробоя.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){

   string separator = ",";
   ushort usprtr;
   usprtr = StringGetCharacter(separator, 0);
   StringSplit(Symbols, usprtr, symb_List);
   Num_symbs = ArraySize(symb_List);
   ArrayResize(Formatted_Symbs, Num_symbs);
   
   for(int i = 0; i < Num_symbs; i++){
      Formatted_Symbs[i] = symb_List[i];
   }
   
   ArrayResize(rangeArray, Num_symbs);
   ArrayResize(prevTick, Num_symbs);
   ArrayResize(currTick, Num_symbs);
   ArrayResize(handles, Num_symbs);   
   ArraySetAsSeries(bufferM, true);
   
   // Calculate initial ranges for each symbol
   for (int i = 0; i < ArraySize(Formatted_Symbs); i++) {
      CalculateRange(i, Formatted_Symbs[i]);  // Pass the symbol index
      handles[i] = iStochastic(Formatted_Symbs[i], PERIOD_CURRENT, KPeriod, 1, 3, MODE_SMA, STO_LOWHIGH);
      
      if(handles[i] == INVALID_HANDLE){
         Alert("Failed to create indicator handle");
         return INIT_FAILED;
      }
      
      StopLoss = SymbolInfoDouble(Formatted_Symbs[i], SYMBOL_POINT)*TrailingStop;
   }
   return(INIT_SUCCEEDED);
}

Функция `OnInit` отвечает за инициализацию и настройку ключевых переменных, обработку символов и подготовку необходимых структур данных. Мы все еще используем разделитель `,` для разделения списка символов на отдельные торгуемые активы, которые хранятся в `symb_List`. Мы определяем общее количество символов `Num_symbs`, а затем изменяем размер массива `Formatted_Symbs` соответственно, обеспечивая правильное форматирование и доступность каждого символа для торговли.

Затем советник динамически изменяет размер нескольких массивов, включая `rangeArray` для хранения данных о пробое диапазона, `prevTick` и `currTick` для отслеживания обновлений цен, `handles` для стохастического осциллятора и `bufferM` для значений индикатора, обеспечивая правильное выделение всех необходимых структур данных перед началом выполнения советника.

После настройки массивов, советник проходит через каждый символ в `Formatted_Symbs`, чтобы рассчитать начальный диапазон с помощью функции `CalculateRange()`. Он также создает хэндл стохастического осциллятора (istochastic) для каждого символа, которую мы будем использовать позже для подтверждения пробоев, проверяя условия перекупленности и перепроданности. Если хэндл индикатора не удается инициализировать (INVALID_HANDLE), советник вызывает алерт и завершает работу с статусом INIT_FAILED. Кроме того, мы рассчитываем уровень Stop Loss для каждого символа на основе его значения пункта и параметра трейлинг-стопа. Если все успешно инициализировано, функция возвращает INIT_SUCCEEDED, позволяя советнику продолжить мониторинг движения цены и выполнение сделок.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason){
   for(int i = 0; i < ArraySize(Formatted_Symbs); i++){
      if(handles[i] != INVALID_HANDLE){
         IndicatorRelease(handles[i]);
      }   
   }
}

Функция OnDeinit() является функцией деинициализации советника, отвечающей за правильное освобождение ресурсов, когда советник удаляется с графика или прекращает работу.

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
 
   for(int i = 0; i < ArraySize(Formatted_Symbs); i++){
      string symbol = Formatted_Symbs[i];
      prevTick[i] = currTick[i];
      SymbolInfoTick(symbol, currTick[i]);
      
      // Range Cal
      if(currTick[i].time > rangeArray[i].start_time && currTick[i].time < rangeArray[i].end_time){
         // flag
         rangeArray[i].b_entry = true;
         
         // high
         if(currTick[i].ask > rangeArray[i].high){
            rangeArray[i].high = currTick[i].ask;
         }
         
         // low
         if(currTick[i].bid < rangeArray[i].low){
            rangeArray[i].low = currTick[i].bid;
         }
      }
            
      // now calculate range
      if(((RangeClose >= 0 && currTick[i].time >= rangeArray[i].close_time)
         || (rangeArray[i].b_high_breakout && rangeArray[i].b_low_breakout)
         || (rangeArray[i].end_time == 0)
         || (rangeArray[i].end_time != 0 && currTick[i].time > rangeArray[i].end_time && !rangeArray[i].b_entry))){
         CalculateRange(i, Formatted_Symbs[i]);
      }
      checkBreak(i, Formatted_Symbs[i]);
   }
   
}

Функция `OnTick` является основным циклом выполнения советника, который запускается при каждом обновлении тика, как мы все знаем. Он управляет анализом цен в реальном времени, настраивает параметры диапазона и оценивает возможности пробоя по всем настроенным торговым инструментам. Процесс начинается с последовательной оценки каждого символа в списке `Formatted_Symbs`. Для каждого символа он архивирует текущие тиковые данные в исторический буфер (prevTick) и извлекает самое последнее состояние рынка через `SymbolInfoTick`.

В течение предопределенного активного торгового окна (от start_time до end_time) система динамически отслеживает ценовые экстремумы. Он повышает максимальное значение сессии, используя пиковое значение ask, и снижает минимальное значение сессии, используя низовое значение bid. Если текущее время попадает в этот диапазон, активируется флаг b_entry, обозначая диапазон как действительный для торговли.

После торгового окна логика оценивает три триггера обновления:

  1. запланированное закрытие сессии – `(close_time)`;
  2. случаи одновременного пробоя вниз и вверх;
  3. недействительные или истекшие временные метки `end_time`.
Если выполнено любое из условий, `CalculateRange()` генерирует новые параметры диапазона. Цикл завершается вызовом функции `checkBreak()`, которая проводит сканирование на предмет нарушений цен за пределами границ диапазона и инициирует соответствующие сделки. Данная система обеспечивает непрерывный мониторинг рынка и стратегическое размещение ордеров, согласованных с динамикой пробоев, что гарантирует реакцию на движения цены в реальном времени.
//+------------------------------------------------------------------+
//|                  Range Calculation function                      |
//+------------------------------------------------------------------+
void CalculateRange(int index, string symbol) {
   for(index = 0; index < ArraySize(Formatted_Symbs); index++){
      symbol = Formatted_Symbs[index];
      
      // Reset all the range variables
      rangeArray[index].start_time = 0;
      rangeArray[index].end_time = 0;
      rangeArray[index].close_time = 0;
      rangeArray[index].high = 0.0;
      rangeArray[index].low = 999999;
      rangeArray[index].b_entry = false;
      rangeArray[index].b_high_breakout = false;
      rangeArray[index].b_low_breakout = false;
      
      // Calculate range start time
      int time_cycle = 86400;
      rangeArray[index].start_time = (currTick[index].time - (currTick[index].time % time_cycle)) + RangeStart * 60;
      for(int i = 0; i < 8; i++){
         MqlDateTime tmp;
         TimeToStruct(rangeArray[index].start_time, tmp);
         int dotw = tmp.day_of_week;
         if(currTick[index].time >= rangeArray[index].start_time || dotw == 6 || dotw == 0){
            rangeArray[index].start_time += time_cycle;
         }
      }
   
      // Calculate range end time
      rangeArray[index].end_time = rangeArray[index].start_time + RangeDuration * 60;
      for(int i = 0 ; i < 2; i++){
         MqlDateTime tmp;
         TimeToStruct(rangeArray[index].end_time, tmp);
         int dotw = tmp.day_of_week;
         if(dotw == 6 || dotw == 0){
            rangeArray[index].end_time += time_cycle;
         }
      }
      
      // Calculate range close
      rangeArray[index].close_time = (rangeArray[index].end_time - (rangeArray[index].end_time % time_cycle)) + RangeClose * 60;
      for(int i = 0; i < 3; i++){
         MqlDateTime tmp;
         TimeToStruct(rangeArray[index].close_time, tmp);
         int dotw = tmp.day_of_week;
         if(rangeArray[index].close_time <= rangeArray[index].end_time || dotw == 6 || dotw == 0){
            rangeArray[index].close_time += time_cycle;
         }
      } 
      
   }
}

Функция `CalculateRange` инициализирует и настраивает торговые диапазоны на основе времени для нескольких символов. Для каждого символа в массиве `Formatted_Symbs` сначала сбрасываются критические параметры диапазона — время начала/окончания/закрытия, пороговые значения высоких/низких цен и флаги пробоя — на значение по умолчанию. `start_time` вычисляется путем выравнивания текущего тика времени к дневной границе (с использованием 24-часового временного цикла), а затем смещения его на заданный пользователем `RangeStart` (в минутах). Цикл гарантирует, что время начала избегает выходных (суббота/воскресенье) и остается хронологически валидным, увеличивая временную метку на полные дни в случае возникновения конфликтов. Это создает основу для торгового окна, учитывая при этом периоды закрытия рынка.

После установки времени начала диапазона функция вычисляет время окончания диапазона, добавляя `RangeDuration` к `start_time`. Аналогично расчету времени начала, это гарантирует, что время окончания не попадает на выходные, пройдя через цикл валидации. Наконец, функция определяет диапазон времени закрытия, который отмечает момент, в котором советник прекращает мониторинг пробоев. Это время получено из `RangeClose` и скорректировано, чтобы избежать выходных. Поддерживая эти динамические расчеты, функция гарантирует, что советник точно настраивает условия диапазонной торговли по различным символам, избегая торговли в нерабочие часы и выходные, при этом обеспечивая точное определение пробоя.

bool CLots(double sl, double &lots){
   lots = In_Lot;

   if(!CHLots(lots)){return false;}
   return true;
}

Функция `CLots()` устанавливает и проверяет размер лота перед выполнением сделки, она принимает два параметра: ваш стоп-лосс (sl) и лоты (ссылочная переменная, которая хранит окончательный размер лота).

bool CHLots(double &lots){
   for(int i = 0; i < ArraySize(Formatted_Symbs); i++){
      string symbol = Formatted_Symbs[i];
      double min = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
      double max = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
      double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
      if(lots < min){
         lots = min;
         return true;
      }
      if(lots > max){
         return false;
      }
      
      lots = (int)MathFloor(lots / step)  * step;
      
   }
   return true;

}

Функция `CHLots()` проверяет размеры лотов на соответствие торговым правилам, устанавливаемым брокером, для каждого инструмента. Он проходит по списку символов в `Formatted_Symbs`, извлекая определенные брокером ограничения — минимальный размер лота, максимальный размер лота и допустимый шаг увеличения. Эти параметры устанавливают операционные границы для объема ордера. Когда предложенное значение лота падает ниже минимального порога, функция автоматически корректирует его до минимального допустимого объема и подтверждает действительность, возвращая true. Напротив, если запрашиваемый размер лота превышает максимальный лимит брокера, функция отменяет запрос, возвращая false, тем самым блокируя несоответствующие сделки.

Чтобы обеспечить точность, функция обеспечивает выравнивание шагов, округляя размер лота вниз с помощью формулы `MathFloor(lots/step) * step`. Это устраняет дробные или нерегулярные приросты, которые могут вызвать отказ брокера. Если размер лота соответствует всем ограничениям без корректировок, функция возвращает true, подтверждая его приемлемость. Строго соблюдая эти проверки, функция `CHLots()` служит в качестве критической меры предосторожности, предотвращая отклонение ордеров из-за нарушений объема и повышая надежность работы советника в условиях реальной торговли.

//+------------------------------------------------------------------+
//|                      Check for Breakout                          |
//+------------------------------------------------------------------+
void checkBreak(int i, string symbol) {
   for (i = 0; i < ArraySize(Formatted_Symbs); i++) {
      symbol = Formatted_Symbs[i];
      
      //get indicator vals
      if(CopyBuffer(handles[i], 0, 1, 2, bufferM) != 2){
         Print("Failed to get indicator values");
         return;
      }

      int stopLevel = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL);
      int spread = (int)SymbolInfoInteger(symbol, SYMBOL_SPREAD);
      double Bid = SymbolInfoDouble(symbol, SYMBOL_BID);
      double Ask = SymbolInfoDouble(symbol, SYMBOL_ASK);

      if (currTick[i].time >= rangeArray[i].end_time && rangeArray[i].end_time > 0 && rangeArray[i].b_entry) {
         double rangeSize = rangeArray[i].high - rangeArray[i].low;

         // High Breakout (BUY/SELL)
         bool upperBreak = bufferM[0] >= upprer_level && bufferM[1] < upprer_level;
         bool lowerBreak = bufferM[0] <= (100 - upprer_level) && bufferM[1] > (100 - upprer_level);
         bool HighSigType,LowSigType;
         
         if(BreakOutMode == Normal_Signal){
            HighSigType = upperBreak;
         }else{HighSigType = lowerBreak;}
         if (!rangeArray[i].b_high_breakout && currTick[i].ask >= rangeArray[i].high && HighSigType) {
            rangeArray[i].b_high_breakout = true;

            double entry = NormalizeDouble(Ask + 100 * _Point, _Digits);
            double sl = rangeArray[i].low;
            //sl = NormalizeDouble(sl, true);
            double tp = entry + TakeProfit * _Point;

            double lots;
            if (!CLots(entry - sl, lots)) continue;

            if (!trade.PositionOpen(symbol, ORDER_TYPE_BUY, lots, currTick[i].ask, sl, tp, "High Breakout"))
               Print("Buy Order Failed: ", GetLastError());
         }                                                                      
                                                                                
         if(BreakOutMode == Normal_Signal){
            LowSigType = upperBreak;
         }else{LowSigType = lowerBreak; }                                                                       
         // Low Breakout (SELL)
         if (!rangeArray[i].b_low_breakout && currTick[i].bid <= rangeArray[i].low  && LowSigType) {
            rangeArray[i].b_low_breakout = true;

            double entry = NormalizeDouble(Bid - 100 * _Point, _Digits);
            double sl = rangeArray[i].high;
            //sl = NormalizeDouble(sl,true);
            double tp = entry - TakeProfit * _Point;

            double lots;
            if (!CLots(sl - entry, lots)) continue;

            if (!trade.PositionOpen(symbol, ORDER_TYPE_SELL, lots, currTick[i].bid, sl, tp, "Low Breakout"))
               Print("Sell Order Failed: ", GetLastError());
         }
      }
   }
}

Эта функция отслеживает пробои цены и управляет выполнением сделок, сочетая движение цены с сигналами стохастического осциллятора. Он проходит через каждый символ в `Formatted_Symbs`, сначала пытаясь получить значения осциллятора с помощью `CopyBuffer()`. Если извлечение данных не удается, функция записывает ошибку в лог и завершает работу, чтобы избежать ошибочных решений. Для каждого символа собираются критические параметры, такие как уровни стопов, спред и текущие цены Bid/Ask. Оценка пробоя происходит только после завершения диапазона, что подтверждается проверкой, превышает ли текущее время `end_time` диапазона, и активен ли флаг `b_entry`, что гарантирует, что анализ ограничен действительными торговыми окнами.

Для пробоев вверх (сигнал на покупку) функция подтверждает два критерия: цена предложения превышает максимум сессии, а осциллятор отражает перепроданность (ниже 100 - upper_level). После валидации он инициирует ордер на покупку, рассчитывая цену входа, уровень стоп-лосса (на основе волатильности диапазона) и уровни тейк-профита, а затем проверяет соответствие размера лота через `CLots()`.

Напротив, для пробоя вниз (сигнал на продажу) требуется, чтобы цена предложения упала ниже минимума сессии, в то время как осциллятор указывает на перекупленность (выше upper_level). Если условие выполнено, создается ордер на продажу с аналогичными параметрами риска. Оба сценария включают ведение журнала ошибок для неудачных ордеров, обеспечивая прозрачность. Синхронизируя ценовые пороги с подтверждением на основе осцилляторов, функция обеспечивает дисциплинированное выполнение сделок на основе критериев.

//+------------------------------------------------------------------+
//|                      Trailing Stoploss                           |
//+------------------------------------------------------------------+
void Trailler(){
   if(!TrailYourStop) return;
   
   for(int i = PositionsTotal()-1; i >= 0; i--){
      ulong ticket = PositionGetTicket(i);
      if(ticket <= 0) continue;
      
      if(!PositionSelectByTicket(ticket)) continue;
      
      // Get position details
      string symbol = PositionGetString(POSITION_SYMBOL);
      long magic;
      if(!PositionGetInteger(POSITION_MAGIC, magic)) continue;
      if(magic != MagicNumber) continue;

      // Get current prices
      MqlTick latestTick;
      if(!SymbolInfoTick(symbol, latestTick)) continue;
      
      long type;
      double openPrice, currentSl, currentTp;
      PositionGetInteger(POSITION_TYPE, type);
      PositionGetDouble(POSITION_PRICE_OPEN, openPrice);
      PositionGetDouble(POSITION_SL, currentSl);
      PositionGetDouble(POSITION_TP, currentTp);
      
      // Calculate pip values
      double pipSize = 10 * SymbolInfoDouble(symbol, SYMBOL_POINT);
      double currentPrice = type == POSITION_TYPE_BUY ? latestTick.bid : latestTick.ask;
      double priceMove = MathAbs(currentPrice - openPrice);
      
      // Calculate required moves
      double requiredMove = 70 * pipSize; // 20 pips
      double trailAmount = 10 * pipSize;  // 10 pips
      
      // Calculate new stop loss
      double newSl = currentSl;
      bool inProfit = type == POSITION_TYPE_BUY ? 
                     (currentPrice > openPrice) : 
                     (currentPrice < openPrice);
      
      if(inProfit && priceMove >= requiredMove){
         int steps = int(priceMove / requiredMove);
         if(type == POSITION_TYPE_BUY){
             newSl = openPrice + (steps * trailAmount);
             newSl = MathMax(newSl, currentSl + trailAmount);
         }
         else{
             newSl = openPrice - (steps * trailAmount);
             newSl = MathMin(newSl, currentSl - trailAmount);
         }
      }
      
      // Validate and modify SL
      if(newSl != currentSl){
         // Check stop levels
         double minDist = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point;
         newSl = NormalizeDouble(newSl, _Digits);
         
         if(type == POSITION_TYPE_BUY && (currentPrice - newSl) >= minDist){
             if(!trade.PositionModify(ticket, newSl, currentTp))
                 Print("Buy Trailing Failed: ", GetLastError());
         }
         else if(type == POSITION_TYPE_SELL && (newSl - currentPrice) >= minDist){
             if(!trade.PositionModify(ticket, newSl, currentTp))
                 Print("Sell Trailing Failed: ", GetLastError());
         }
      }
   }
}

Функция `Trailing Stop Loss` (TSL) в торговле обеспечивает автоматическую корректировку стоп-лосса, как только сделка движется в благоприятном направлении, чтобы зафиксировать прибыль и минимизировать риски. Функция `Trailer()` пересчитывает и обновляет уровень стоп-лосса на основе движения цены и процента от диапазона максимума-минимума, предотвращая преждевременные выходы и обеспечивая прибыль.



Заключение

Подведем итог: оптимизация портфеля и диверсификация являются важными стратегиями в торговле, которые направлены на максимизацию доходности при минимизации риска за счет распределения инвестиций по нескольким активам. Традиционные методы торговли часто сосредоточены на стратегиях с одной парой, подвергая трейдеров более высокой волатильности и специфическим для рынка рискам. Диверсификация укрепляет устойчивость портфеля, в то время как методы оптимизации стратегически распределяют капитал, анализируя исторические паттерны эффективности, метрики волатильности активов и взаимосвязи между рынками для достижения эффективного баланса риска и вознаграждения.

Наконец, внедрение оптимизации и диверсификации портфеля в торговую стратегию обеспечивает более устойчивый и адаптивный подход к колебаниям рынка. Сочетая стратегию торговли на пробоях с осциллятором, трейдеры могут выявлять высоковероятные сетапы, при этом динамически управляя риском. Этот подход увеличивает шансы на стабильную прибыльность, а также повышает долгосрочную устойчивость, уменьшая просадки.

Для достижения нижеприведенных результатов советник был протестирован с использованием EURUSD в качестве базового символа с нулевыми задержками, идеальным исполнением и моделированием каждого тика для максимальной точности. Период тестирования – с 01.02.2022 по 22.03.2022. В настройках ввода переменной BreakoutMode было присвоено значение Reversed_signal, а TrailYourStop был включен (true), чтобы обеспечить динамическую корректировку стоп-лосса. Все остальные входные параметры были оставлены по умолчанию.

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/16089

Прикрепленные файлы |
Dyna_MP.mq5 (37.35 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (8)
Alberto Tortella
Alberto Tortella | 19 апр. 2025 в 13:48

Хорошо, я написал EURUSD в Input/Symbols и теперь все работает.

Спасибо

CapeCoddah
CapeCoddah | 19 апр. 2025 в 20:10

Отличная статья! Попробую завтра. Интересно, почему вы использовали такой странный временной период для тестера стратегий. Я бы ожидал полных месяцев в 2024 г. Мне нравится ваша концепция трейлинг-стоп лосса, я использую ту же технику. Одна из особенностей заключается в том, что я также пытаюсь минимизировать убытки, если сделка становится отрицательной после того, как почти достигла безубытка.


Будьте здоровы и продолжайте публиковать статьи, они великолепны.

CapeCoddah

Brian Pereira
Brian Pereira | 23 апр. 2025 в 08:07
Alberto Tortella стохастический осциллятор хорошо работает на графике.


Не могли бы вы мне помочь? Спасибо

Каждая вводимая валюта должна быть разделена только запятой. Не ставьте пробел между валютами

CapeCoddah
CapeCoddah | 30 апр. 2025 в 11:33

Привет еще раз,


Я попробовал использовать вашу систему на активном графике и нашел пару улучшений.


Проблема Альберто, вероятно, заключалась в том, что у него не было всех пар в списке символов в окне просмотра рынка, ctlM. Я получил эту ошибку также с XAUUSD

Вместо ArraySize(... для операторов For используйте Num_symbls, это немного быстрее. Также я обнаружил, что написание полных имен помогает другим лучше понять ваш код, а также предотвращает множество ошибок интаксиса, например, Number_Symbols, на мой взгляд, лучше, чем Num_symbls.

DisplayObjects не было в коде, я добавил его.

В объектах отображения я добавил условие для выбора только символа графика. Перечисление остальных не требуется и загромождает экран. Но, возможно, я что-то упускаю.

Наконец, есть проблема с расчетом диапазона. На активном графике, не в тестере стратегий, запуск советника создает луч, который выходит за пределы текущей даты в будущем. Например, запуск 4/30 создает луч, который начинается 4/30 10 утра и заканчивается 5/1. Это приводит к невидимому лучу, который не отображается на графике, но отображается в списке объектов. Я позволю вам исправить это.

Я прикрепляю свой код для вашего использования.


Будьте здоровы, CapeCoddah

CapeCoddah
CapeCoddah | 30 апр. 2025 в 13:02
Кажется, что-то напутали, так как eas из части 1 и части 2 идентичны. Похоже, что часть идентична части 1
Создание торговой панели администратора на MQL5 (Часть IX): Организация кода (II): Модуляризация Создание торговой панели администратора на MQL5 (Часть IX): Организация кода (II): Модуляризация
В этом обсуждении мы сделаем шаг вперед в разбиении нашей программы MQL5 на более мелкие и более управляемые модули. Эти модульные компоненты затем будут интегрированы в основную программу, что улучшит ее организацию и удобство обслуживания. Такой подход упрощает структуру нашей основной программы и делает отдельные компоненты пригодными для повторного использования в других советниках и индикаторах. Приняв эту модульную конструкцию, мы создаем прочную основу для будущих улучшений, что принесет пользу как нашему проекту, так и широкому сообществу разработчиков.
Команда ИИ-агентов с ротацией по прибыли: Эволюция живой торговой системы в MQL5 Команда ИИ-агентов с ротацией по прибыли: Эволюция живой торговой системы в MQL5
Управление финансами как экосистема: семь ИИ-трейдеров с разными характерами и стратегиями вместо одного алгоритма. Они конкурируют за капитал, учатся на ошибках и принимают решения коллективно. Статья раскрывает принципы работы системы Modern RL Trader, где код обладает сознанием и эмоциями, создавая живой, эволюционирующий торговый разум.
Нейросети в трейдинге: Агрегация движения по времени (Окончание) Нейросети в трейдинге: Агрегация движения по времени (Окончание)
Представляем фреймворк TMA — интеллектуальную систему, способную прогнозировать рыночную динамику с достаточной точностью. В этой статье мы собрали все компоненты в единую архитектуру и превратили её в полноценного торгового агента, который анализирует рынок и принимает решения в реальном времени.
Нейросети в трейдинге: Агрегация движения по времени (Основные компоненты) Нейросети в трейдинге: Агрегация движения по времени (Основные компоненты)
В этой статье теория встречается с практикой. Мы реализуем ключевые модули фреймворка TMA — MPE и MPA. Здесь данные обретают смысл, а кросс-внимание превращается в инструмент точного анализа рыночной динамики. Минимум избыточных операций, максимум эффективности — шаг к интеллектуальному трейдингу нового поколения.