preview
Разрабатываем мультивалютный советник (Часть 5): Переменный размер позиций

Разрабатываем мультивалютный советник (Часть 5): Переменный размер позиций

MetaTrader 5Тестер | 21 марта 2024, 17:30
497 0
Yuriy Bykov
Yuriy Bykov

Введение

В предыдущей части мы добавили возможность восстановления состояния советника после перезапуска. Не важно, в чём была причина — перезагрузка терминала, смена таймфрейма на графике с советником, запуск более свежей версии советника — во всех случаях восстановление состояния позволяло не начинать советнику работу с нуля и не терять уже открытые позиции, а продолжать их обработку.

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


Понятия

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

Фиксированный размер стратегии (Fixed Lot) — некоторый размер, используемый для вычисления размеров всех открываемых позиций в торговой стратегии. В простейшем случае, все открываемые позиции могут иметь размер, равный этому значению. При использовании каких либо ухищрений по повышению или понижению размеров второй и последующих открываемых позиций серии, фиксированный размер может задавать размер первой позиции в серии, а последующие будут вычисляться исходя из него и количества уже открытых позиций. Стоит отметить, что это считается не очень хорошим приёмом. Но до единства мнений в этом вопросе ещё очень далеко, если оно вообще достижимо.

Нормированный баланс стратегии (Fitted Balance) — размер начального баланса, при котором для выбранного фиксированного размера стратегии просадка на всём периоде тестирования достигает, но не превышает 10% от начального баланса. Почему именно 10%? Это число является ещё не слишком большой просадкой, которая психологически кажется допустимой и оказалось более удобным для быстрых прикидочных расчётов в уме. В общем-то, можно было взять любое значение - хоть 1%, хоть 5%, хоть 50%. Это не более чем параметр нормировки.

Нормированная торговая стратегия — торговая стратегия, для которой выбран фиксированный размер стратегии и нормированный баланс стратегии. Следовательно, при запуске такой стратегии на выбранном периоде тестирования мы должны получить максимальное значение просадки примерно 10% от размера нормированного баланса стратегии.

Имея торговую стратегию, мы можем превратить её в нормированную торговую стратегию, выполнив следующие действия:

  • Выбираем некоторый фиксированный размер стратегии, например, 0.01.
  • Выбираем период тестирования (начальную и конечную дату)
  • Запускаем стратегию на выбранном периоде тестирования с большим начальным балансом и смотрим на значение максимальной абсолютной просадки по средствам.
  • Находим значение нормированного баланса стратегии, умножая значение максимальной абсолютной просадки по средствам на 10.

Рассмотрим пример. Предположим, что для фиксированного размера стратегии 0.01 мы получили значение максимальной абсолютной просадки равное $440. Если мы хотим, чтобы это значение составляло ровно 10% от начального баланса, то для этого можно разделить $440 на 0.10 или, что то же самое, умножить на 10:

$440 / 0.10 = $440 * 10 = $4400

Эти два значения (0.01 и 4400) мы устанавливаем в параметрах создания экземпляра торговой стратегии и получаем нормированную торговую стратегию.

Теперь мы можем для нормированной торговой стратегии вычислять размер открываемых позиций для любого значения баланса с сохранением максимальной относительной просадки по средствам равной 10%. Для этого достаточно изменять размер открываемых позиций  пропорционально отношению текущего общего баланса (Total Balance) и нормированного баланса (Fitted Balance).

CurrentLot = FixedLot * (TotalBalance / FittedBalance)

Например, для использованных выше в примере значений 0.01 и 4400, при значении баланса $10000 размер открываемых позиций должен рассчитываться исходя из базового значения:

CurrentLot = 0.01 * (10000 / 4400) = 0.0227

Открыть в точности такой размер не получится, мы вынуждены будем округлять его до 0.02,  поэтому просадка в этом случае может быть на тесте несколько меньше 10%. Если округлим в большую сторону (до 0.03), то просадка может быть несколько больше 10%. Но при увеличении баланса ошибки округления будут уменьшаться.

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

  • Фиксированный размер или отсутствие стратегии управления капиталом. Применяется заданный в стратегии фиксированный размер вне зависимости от баланса торгового счёта. Такая стратегия будет применяться при тестировании отдельного экземпляра стратегии для определения нормированного баланса стратегии.

  • Постоянный размер для заданного фиксированного баланса. При старте вычисляется пропорциональный фиксированному балансу размер для стратегии исходя из нормированного баланса стратегии и фиксированного размера стратегии. Такой размер используется на всём периоде тестирования. Эта стратегия будет применяться для проверки равномерности (линейности) роста кривой средств на всём периоде тестирования при соблюдении заявленной максимальной абсолютной просадки.

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

Приведем примеры использования данных трёх вариантов. Возьмем советник с одним экземпляром стратегии, установим большой стартовый баланс $100000 и запустим тестирование на периоде 2018-2022 годов с фиксированным размером открываемых позиций 0.01 лота. Получим такие результаты:

Рис. 1. Результаты при фиксированном размере и балансе $100000


Как видно, на данном периоде тестирования встретилась максимальная абсолютная просадка по средствам в размере около $153, что составило примерно 0.15% от баланса счета. Точнее, нам правильнее оценивать относительную просадку относительно начального баланса счёта. Но поскольку разница между начальным и конечным балансом составляет малую величину (около 1% от первоначального баланса), то просадка в 0.15% будет составлять с хорошей точностью абсолютную величину $150 в какой бы момент тестового периода она не встретилась.

Давайте рассчитаем, какой размер начального баланса можно установить, чтобы максимальная абсолютная просадка составила 10% от начального баланса:

FittedBalance = MaxDrawdown / 10% = 153 / 0.10 = 153 * 10 = $1530

Проверим наши расчёты:

Рис. 2. Результаты при фиксированном размере и балансе $1530


Видим, что абсолютная просадка по средствам составила такую же величину $153, но относительная составила не 10%, а только 7.2%. Это нормально, так как говорит только о том, что самая большая просадка случилась тогда, когда баланс счёта уже несколько подрос со своего начального значения, и величина $153 составила уже менее 10% от текущего баланса.

Теперь проверим второй вариант — постоянный размер для заданного фиксированного баланса. Зададим опять большой начальный баланс $100000, но разрешим использовать из него, например, только десятую часть, то есть $10000. Это будет значение Current Balance, которое будет одинаковым на всём периоде тестирования. При таких условиях мы получим, что размер открываемых позиций должен составить:

CurrentBalance = TotalBalance * 0.1 = 10000

CurrentLot = FixedLot *  (CurrentBalance / FittedBalance) = 0.01 * (10000 / 1530) = 0.0653

В процессе работы это значение будет округляться до величины, кратной шагу изменения лота. Получим следующие результаты:

Рис. 3. Результаты при постоянном размере для фиксированного баланса $10000 из доступных $100000


Как видно, абсолютная просадка составила $1016, что с достаточной точностью составляет 10% от выделенных этой стратегии $10000. Однако относительно всего баланса это составило только 1%.

И наконец-то посмотрим на третий вариант — переменный размер для текущего баланса. Поставим теперь начальный баланс равный $10000 и разрешим использовать его полностью. Вот что получится:


Рис. 4. Результаты при переменном размере для текущего баланса

Здесь мы видим, что значение максимальной абсолютной просадки уже превышает 10% от начального баланса, но относительная просадка продолжает оставаться в пределах допустимых 10%. Мы получили нормированную торговую стратегию, для которой значение Fitted Balance = 1530, и теперь мы можем легко вычислять размеры открываемых позиций для обеспечения заданной просадки 10%.


Расчёт размеров позиций

Рассматривая эти варианты управления капиталом, можно сделать следующие замечания:

  • Если же речь об одном экземпляре стратегии, то может ли пригодиться варианты с переменным лотом? Похоже, что нет. Нам достаточно использовать только первый вариант. Второй и третий мы можем использовать пару раз для демонстрации работоспособности, но затем они нам не понадобятся.

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

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

Исходя из схемы работы получается, что пересчитывать в реальный открываемый размер позиций должны будут объект получателя или символьных получателей. Для этого они должны будут получить от стратегии, а точнее, от виртуального ордера, который спросит у стратегии, значение нормированного баланса, при котором обеспечивается просадка в 10% от этого баланса.

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

Одним из таких способов является явное введение весового множителя, показывающего какую часть текущего баланса счёта можно использовать советнику.

Выделенный баланс (Current Balance) — часть общего баланса счёта, которая выделена для торговли данному советнику.

Множитель баланса (Depo Part) — отношение выделенного баланса стратегии к общему балансу счёта.

DepoPart =  CurrentBalance / TotalBalance

Тогда исходный размер позиций можно рассчитывать так:

CurrentLot = FixedLot * (CurrentBalance / FittedBalance)

CurrentLot =  FixedLot * (DepoPart * TotalBalance / FittedBalance)

Здесь можно сделать важное замечание, которое нам очень пригодиться для реализации. Если мы после создания экземпляра стратегии пересчитаем её нормированный баланс, то в формуле для расчёта размера позиций будет использоваться общий баланс вместо текущего баланса стратегии:

FittedBalance = FittedBalance / DepoPart

CurrentLot =  FixedLot * (TotalBalance / FittedBalance)

Такой перерасчёт нормированного баланса мы выполним один раз при инициализации советнике, после чего множитель Depo Part уже не нужен. 


Объединение нескольких стратегий

Предыдущие рассуждения были сделаны для случая использования одного экземпляра стратегии в советнике. Давайте теперь подумаем, что нам понадобиться сделать в том случае, когда мы хотим взять несколько нормированных стратегий и объединить их в одном советнике, допускающем на всём периоде тестирования просадку не более 10% (или другую, заданную заранее). Будем пока рассматривать заданное значение просадки именно 10%.

Если мы будем ориентироваться на худший случай, который может произойти при объединении стратегий, то таковым является событие одновременного достижения всеми экземплярами стратегий своей максимальной просадки 10%. В этом случае нам придётся уменьшить размер позиций каждой стратегии пропорционально числу экземпляров. Например, если объединяем три экземпляра стратегии, то надо уменьшать размер позиций в три раза. 

Добиться этого можно уменьшением в заданное число раз баланса, выделенного стратегии, или увеличением в заданное число раз нормированного баланса стратегий. Мы воспользуемся именно вторым вариантом. 

Если обозначить количество стратегий в группе через StrategiesCount, то формулы для пересчёта нормированного баланса приобретает вид:

FittedBalance = StrategiesCount * FittedBalance

Однако, вероятность возникновения такого худшего случая очень сильно снижается при увеличении количества экземпляров стратегий, если они подбираются так, чтобы были максимально непохожими друг на друга. В этом случае и просадки у них случаются в разные времена, а не одновременно. Это можно увидеть в процессе тестирования. Тогда можно ввести ещё один масштабирующий множитель (Scale), который будет по умолчанию равен единице, но при желании его можно будет сделать и больше, чтобы увеличить размер позиций за счёт уменьшения нормированного баланса стратегий:

FittedBalance = StrategiesCount * FittedBalance

FittedBalance = FittedBalance / Scale

За счёт подбора множителя Scale можно снова добиться, чтобы группа стратегий давала заданную просадку на всем периоде тестирования. В этом случае мы получим нормированную группу стратегий.

Нормированная группа стратегий — группа нормированных торговых стратегий, для которой выбран масштабирующий множитель, обеспечивающий при совместной работе группы максимальную просадку не более 10%.

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

FittedBalance = StrategiesCount1 * FittedBalance

FittedBalance = StrategiesCount2 * FittedBalance

...

FittedBalance = FittedBalance / Scale1

FittedBalance = FittedBalance / Scale2

...

Тогда итоговая формула перерасчёта нормированного баланса каждой стратегии будет выглядеть так:

FittedBalance = (StrategiesCount1 *  StrategiesCount1  * ... ) * FittedBalance / (Scale1 * Scale2 * ... )

И в конце, для возможного преобразования нормированной группы стратегий, оказавшейся на самом верхнем уровне объединения, в группу с другой заданной просадкой вместо 10%, применим последний масштабирующий множитель Depo Part в формуле расчёта размеров позиций:

CurrentLot =  FixedLot * (DepoPart * TotalBalance / FittedBalance)

Для программной реализации два новых класса. Первый класс CVirtualStrategyGroup будет отвечать за перерасчёт нормированных балансов стратегий при объединении их в группы. Второй класс CMoney будет отвечать за вычисление реальных открываемых объёмов исходя из фиксированного размера стратегии, нормированного баланса стратегии и выделенного баланса для стратегии. 


Класс группы торговых стратегий

Этот класс будет использоваться для создания объектов, представляющих собой либо группу стратегий, либо группу групп стратегий. В обоих случаях при создании группы будет применяться масштабирующий множитель через вызов единственного метода Scale().

//+------------------------------------------------------------------+
//| Класс группы торговых стратегий или групп торговых стратегий     |
//+------------------------------------------------------------------+
class CVirtualStrategyGroup {
protected:
   void              Scale(double p_scale); // Масштабирование нормированного баланса
public:
   CVirtualStrategyGroup(CVirtualStrategy *&p_strategies[],
                         double p_scale = 1);   // Конструктор для группы стратегий
   CVirtualStrategyGroup(CVirtualStrategyGroup *&p_groups[],
                         double p_scale = 1);   // Конструктор для группы групп стратегий

   CVirtualStrategy      *m_strategies[];       // Массив стратегий
   CVirtualStrategyGroup *m_groups[];           // Массив групп стратегий
};

Конструкторы будут принимать в качестве параметров масштабирующий множитель и либо массив указателей на стратегии, либо массив указателей на группы стратегий. Полученный массив будет копироваться в соответствующее свойство создаваемого объекта, и к каждому элементу массива будет применяться метод Scale(). Для объектов стратегий нам нужно будет добавить этот метод в класс стратегии.

//+------------------------------------------------------------------+
//| Конструктор для группы стратегий                                 |
//+------------------------------------------------------------------+
CVirtualStrategyGroup::CVirtualStrategyGroup(
   CVirtualStrategy *&p_strategies[],
   double p_scale
) {
   ArrayCopy(m_strategies, p_strategies);
   Scale(p_scale / ArraySize(m_strategies));
}

//+------------------------------------------------------------------+
//| Конструктор для группы групп стратегий                           |
//+------------------------------------------------------------------+
CVirtualStrategyGroup::CVirtualStrategyGroup(
   CVirtualStrategyGroup *&p_groups[],
   double p_scale
) {
   ArrayCopy(m_groups, p_groups);
   Scale(p_scale / ArraySize(m_groups));
}

//+------------------------------------------------------------------+
//| Масштабирование нормированного баланса                           |
//+------------------------------------------------------------------+
void CVirtualStrategyGroup::Scale(double p_scale) {
   FOREACH(m_groups,     m_groups[i].Scale(p_scale));
   FOREACH(m_strategies, m_strategies[i].Scale(p_scale));
}

Сохраним этот код в файле VirtualStrategyGroup.mqh в текущей папке.

Внесём необходимые дополнения в класс виртуальной стратегии. Нам понадобится добавить два новых свойства класса для хранения нормированного баланса стратегии и фиксированного размера стратегии. Так как они должны обязательно быть установлены, то понадобится конструктор, который раньше был не нужен. Публичный метод FittedBalance() будет просто возвращать значение нормированного баланса стратегии, а метод Scale() выполнять его масштабирование на заданный множитель.

//+------------------------------------------------------------------+
//| Класс торговой стратегии с виртуальными позициями                |
//+------------------------------------------------------------------+
class CVirtualStrategy : public CStrategy {
protected:
   ...
   double            m_fittedBalance;  // Нормированный баланс стратегии
   double            m_fixedLot;       // Фиксированный размер стратегии
   ...
public:
   CVirtualStrategy(double p_fittedBalance = 0, double p_fixedLot = 0.01); // Конструктор
   ...
   double            FittedBalance() {    // Нормированный баланс стратегии
      return m_fittedBalance;
   }

   void              Scale(double p_scale) { // Масштабирование нормированного баланса
      m_fittedBalance /= p_scale;
   }
};

Сохраним этот код в файле VirtualStrategy.mqh в текущей папке.

Также придётся внести небольшие изменения в класс CSimpleVolumesStrategy. Необходимо добавить в конструктор дополнительный параметр для нормированного баланса стратегии, а параметр для задания размеров виртуальных позиций — убрать. Он теперь будет всегда одинаковым и равным минимальному лоту 0.01.

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CSimpleVolumesStrategy::CSimpleVolumesStrategy(
   string           p_symbol,
   ENUM_TIMEFRAMES  p_timeframe,
   int              p_signalPeriod,
   double           p_signalDeviation,
   double           p_signaAddlDeviation,
   int              p_openDistance,
   double           p_stopLevel,
   double           p_takeLevel,
   int              p_ordersExpiration,
   int              p_maxCountOfOrders,
   double           p_fittedBalance = 0) :
// Список инициализации
   CVirtualStrategy(p_fittedBalance, 0.01),
   m_symbol(p_symbol),
   m_timeframe(p_timeframe),
   m_signalPeriod(p_signalPeriod),
   m_signalDeviation(p_signalDeviation),
   m_signaAddlDeviation(p_signaAddlDeviation),
   m_openDistance(p_openDistance),
   m_stopLevel(p_stopLevel),
   m_takeLevel(p_takeLevel),
   m_ordersExpiration(p_ordersExpiration),
   m_maxCountOfOrders(p_maxCountOfOrders) {
   ...
}

Сохраним изменения в файле SimpleVolumesStrategy.mqh в текущей папке.

Для того, чтобы можно было к объекту эксперта добавлять группы стратегий, то есть экземпляры нашего нового класса CVirtualStrategyGroup, добавим в класс эксперта перегруженный метод Add(), который будет выполнять такую работу:

//+------------------------------------------------------------------+
//| Класс эксперта, работающего с виртуальными позициями (ордерами)  |
//+------------------------------------------------------------------+
class CVirtualAdvisor : public CAdvisor {
   ...
public:
   ...
   virtual void      Add(CVirtualStrategyGroup &p_group);  // Метод добавления группы стратегий
   ...
};

//+------------------------------------------------------------------+
//| Метод добавления группы стратегий                                |
//+------------------------------------------------------------------+
void CVirtualAdvisor::Add(CVirtualStrategyGroup &p_group) {
   FOREACH(p_group.m_groups, {
      CVirtualAdvisor::Add(p_group.m_groups[i]);
      delete p_group.m_groups[i];
   });
   FOREACH(p_group.m_strategies, CAdvisor::Add(p_group.m_strategies[i]));
}

Так как группы стратегий после добавления в эксперта становятся не нужны, то в этом методе мы сразу удаляем их из динамической области памяти. Сохраним сделанные изменения в файле VirtualAdvisor.mqh в текущей папке.


Класс управления капиталом

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

Объект этого класса должен быть единственным. Поэтому либо можно использовать для него шаблон проектирования Singleton, либо, как в итоге и было реализовано, класс может содержать только статические поля и методы, доступные любым объектам.

Основным методом в данном классе является метод определения реального размера для виртуальной позиции (ордера) Volume(). И ещё два метода позволяют задать значения двух параметров, определяющих, какая часть баланса торгового счёта участвует в торговле.

//+------------------------------------------------------------------+
//| Базовый класс управления капиталом                               |
//+------------------------------------------------------------------+
class CMoney {
   static double     s_depoPart;       // Используемая часть общего баланса
   static double     s_fixedBalance;   // Используемый общий баланс
public:
   CMoney() = delete;                  // Запрещаем конструктор
   static double     Volume(CVirtualOrder *p_order); // Определение реального размера виртуальной позиции

   static void       DepoPart(double p_depoPart) {
      s_depoPart = p_depoPart;
   }
   static void       FixedBalance(double p_fixedBalance) {
      s_fixedBalance = p_fixedBalance;
   }
};

double CMoney::s_depoPart = 1.0;
double CMoney::s_fixedBalance = 0;

//+------------------------------------------------------------------+
//| Определение реального размера виртуальной позиции                |
//+------------------------------------------------------------------+
double CMoney::Volume(CVirtualOrder *p_order) {
   // Запрашиваем нормированный баланс стретегии для этой виртуальной позиции 
   double fittedBalance = p_order.FittedBalance();
   
   // Если он равен 0, то реальный объем равен виртуальному
   if(fittedBalance == 0.0) {
      return p_order.Volume();
   }
   
   // Иначе находим величину общего баланса для торговли
   double totalBalance = s_fixedBalance > 0 ? s_fixedBalance : AccountInfoDouble(ACCOUNT_BALANCE);
   
   // Возвращаем вычисленный реальный объем по виртуальному
   return p_order.Volume() * totalBalance * s_depoPart / fittedBalance ;
}
//+------------------------------------------------------------------+

Сохраним этот код в файле Money.mqh в текущей папке.


Советники для тестирования

Для тестирования внесем изменения в файлы советников. В файле SimpleVolumesExpertSingle.mq5 нам достаточно в функции инициализации советника удалить из списка параметров конструктора стратегии параметр размера позиций:

int OnInit() {
// Создаем эксперта, работающего с виртуальными позициями
   expert = new CVirtualAdvisor(magic_, "SimpleVolumesSingle");

   expert.Add(new CSimpleVolumesStrategy(
                 symbol_, timeframe_,
                 fixedLot_,
                 signalPeriod_, signalDeviation_, signaAddlDeviation_,
                 openDistance_, stopLevel_, takeLevel_, ordersExpiration_,
                 maxCountOfOrders_)
             );       // Добавляем один экземпляр стратегии

   return(INIT_SUCCEEDED);
}

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

В файле SimpleVolumesExpert.mq5 внесём уже более существенные добавления. Они нам нужны в основном для демонстрации возможностей добавленных классов, поэтому не стоит рассматривать это как окончательный код.

Прежде всего мы создадим перечисление для обозначения разных способов объединения экземпляров торговых стратегий в группы:

enum ENUM_VA_GROUP {
   VAG_EURGBP,          // Only EURGBP (3 items)
   VAG_EURUSD,          // Only EURUSD (3 items)
   VAG_GBPUSD,          // Only GBPUSD (3 items)
   VAG_EURGBPUSD_9,     // EUR-GBP-USD (9 items)
   VAG_EURGBPUSD_3_3_3  // EUR-GBP-USD (3+3+3 items)
};

Первые три значения будут соответствовать использованию трёх экземпляров торговых стратегий для одного из символов (EURGBP, EURUSD или GBPUSD). Четвёртое значение будет соответствовать использованию одной группы из всех девяти экземпляров стратегий. А пятое значение будет соответствовать использованию группы из трёх нормированных групп, в которые будет входить по три экземпляра торговых стратегий для определённого символа.

Расширим немного список входных параметров:

//+------------------------------------------------------------------+
//| Входные параметры                                                |
//+------------------------------------------------------------------+
input group "::: Группы стратегий"
input ENUM_VA_GROUP group_ = VAG_EURGBP;  // - Группа стратегий

input group "::: Управление капиталом"
input double expectedDrawdown_ = 10;      // - Максимальный риск (%)
input double fixedBalance_ = 0;           // - Используемый депозит (0 - использовать весь) в валюте счета
input double scale_ = 1.0;                // - Масштабирующий множитель для группы

input group "::: Прочие параметры"
input ulong  magic_        = 27183;       // - Magic

В функции инициализации советника мы установим параметры управления капиталом с учётом нормировки максимальной допустимой просадки в 10%, создадим девять экземпляров стратегий, сгруппируем их в соответствии с выбранной группировкой и добавим к эксперту:

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit() {
   // Устанавливаем параметры в классе управления капиталом
   CMoney::DepoPart(expectedDrawdown_ / 10.0);
   CMoney::FixedBalance(fixedBalance_);

   // Создаем эксперта, работающего с виртуальными позициями
   expert = new CVirtualAdvisor(magic_, "SimpleVolumes_" + EnumToString(group_));

   // Создаем и наполняем массив из всех экземпляров стратегий
   CVirtualStrategy *strategies[] = {
      new CSimpleVolumesStrategy("EURGBP", PERIOD_H1,  13, 0.3, 1.0, 0, 10500,  465,  1000, 3, 1600),
      new CSimpleVolumesStrategy("EURGBP", PERIOD_H1,  17, 1.7, 0.5, 0, 16500,  220,  1000, 3,  900),
      new CSimpleVolumesStrategy("EURGBP", PERIOD_H1,  51, 0.5, 1.1, 0, 19500,  370, 22000, 3, 1600),

      new CSimpleVolumesStrategy("EURUSD", PERIOD_H1,  24, 0.1, 0.3, 0,  7500, 2400, 24000, 3, 2300),
      new CSimpleVolumesStrategy("EURUSD", PERIOD_H1,  18, 0.2, 0.4, 0, 19500, 1480,  6000, 3, 2000),
      new CSimpleVolumesStrategy("EURUSD", PERIOD_H1, 128, 0.7, 0.3, 0,  3000,  170, 42000, 3, 2200),

      new CSimpleVolumesStrategy("GBPUSD", PERIOD_H1,  80, 1.1, 0.2, 0,  6000, 1190,  1000, 3, 2500),
      new CSimpleVolumesStrategy("GBPUSD", PERIOD_H1, 128, 2.0, 0.9, 0,  2000, 1170,  1000, 3,  900),
      new CSimpleVolumesStrategy("GBPUSD", PERIOD_H1,  13, 1.5, 0.8, 0,  2500, 1375,  1000, 3, 1400),
   };

   // Создаём массивы указателей на стратегии по одному символу из имеющихся стратегий
   CVirtualStrategy *strategiesEG[] = {strategies[0], strategies[1], strategies[2]};
   CVirtualStrategy *strategiesEU[] = {strategies[3], strategies[4], strategies[5]};
   CVirtualStrategy *strategiesGU[] = {strategies[6], strategies[7], strategies[8]};

   // Формируем и добавляем к эксперту выбранные группы стратегий
   switch(group_) {
   case VAG_EURGBP: {
      expert.Add(CVirtualStrategyGroup(strategiesEG, scale_));
      FOREACH(strategiesEU, delete strategiesEU[i]);
      FOREACH(strategiesGU, delete strategiesGU[i]);
      break;
   }
   case VAG_EURUSD: {
      expert.Add(CVirtualStrategyGroup(strategiesEU, scale_));
      FOREACH(strategiesEG, delete strategiesEG[i]);
      FOREACH(strategiesGU, delete strategiesGU[i]);
      break;
   }
   case VAG_GBPUSD: {
      expert.Add(CVirtualStrategyGroup(strategiesGU, scale_));
      FOREACH(strategiesEU, delete strategiesEU[i]);
      FOREACH(strategiesEG, delete strategiesEG[i]);
      break;
   }
   case VAG_EURGBPUSD_9: {
      expert.Add(CVirtualStrategyGroup(strategies, scale_));
      break;
   }
   case VAG_EURGBPUSD_3_3_3: {
      // Создаем группу из трех групп стратегий
      CVirtualStrategyGroup *groups[] = {
         new CVirtualStrategyGroup(strategiesEG, 1.25),
         new CVirtualStrategyGroup(strategiesEU, 2.24),
         new CVirtualStrategyGroup(strategiesGU, 2.64)
      };

      expert.Add(CVirtualStrategyGroup(groups, scale_));
      break;
   }
   default:
      return(INIT_FAILED);
   }

// Загружаем прошлое состояние при наличии
   expert.Load();

   return(INIT_SUCCEEDED);
}

Сохраним сделанные изменения в файле SimpleVolumesExpert.mq5 в текущей папке.


Тестирование

Протестируем первую группу — три экземпляра стратегии, работающие на символе EURGBP. Получим следующие результаты:


Рис. 5. Результаты для EURGBP с тремя стратегиями, Scale=1


Как видно, при объединении максимальная относительная просадка составила 8% вместо 10% для каждого отдельного экземпляра нормированной стратегии. Это значит, что мы можем немного увеличить размер позиций. Для достижения просадки 10% мы установим значение Scale = 10% / 8% = 1.25.


Рис. 6. Результаты для EURGBP с тремя стратегиями, Scale=1.25


Теперь значение просадки составляет примерно 10%. Проделаем подобную операцию по подбору масштабирующего множителя для второй и третьей группы. Получим следующие результаты:

Рис. 7. Результаты для EURUSD с тремя стратегиями, Scale=2.24


Рис. 8. Результаты для GBPUSD с тремя стратегиями, Scale=2.64


Подобранные значения масштабирующих множителей мы используем в коде для создания нормированной группы из трех нормированных групп стратегий:

// Создаем группу из трех групп стратегий
CVirtualStrategyGroup *groups[] = {
     new CVirtualStrategyGroup(strategiesEG, 1.25),
     new CVirtualStrategyGroup(strategiesEU, 2.24),
     new CVirtualStrategyGroup(strategiesGU, 2.64)
 };

Теперь подберем масштабирующий множитель для четвёртой группы. Если мы объединим все 9 экземпляров в одну группу, то получим такие результаты:


Рис. 9. Результаты для EURGBP, EURUSD, GBPUSD (всего 9 стратегий), Scale=1


Это позволяет нам поднять масштабирующий множитель до значения 3.3 и остаться по относительной просадке в пределах 10%:


Рис. 10. Результаты для EURGBP, EURUSD, GBPUSD (всего 9 стратегий), Scale=3.3


И наконец-то, самое интересное. Объединим те же самые 9 нормированных стратегий, но другим способом: отдельно нормируем группы по три стратегии для отдельных символов, а затем объединим в группу полученные три нормированные группы. Получим следующее:


Рис. 11. Результаты для EURGBP, EURUSD, GBPUSD (3 + 3 + 3 стратегии), Scale=1


Конечный баланс получился больше, чем для четвёртой группировки при одинаковом Scale=1, но и просадка тоже больше: 4.57% вместо 3%. Приведём пятую группировку к просадке 10% и сравним после этого конечный результат:


Рис. 12. Результаты для EURGBP, EURUSD, GBPUSD (3 + 3 + 3 стратегии), Scale=2.18


Теперь точно видно, что пятый вариант группировки стратегий даёт гораздо лучшие результаты при сохранении максимальной относительной просадки в пределах 10%. За выбранный период тестирования прибыль выросла более чем в два раза по сравнению с четвёртым вариантом группировки.

Напоследок посмотрим на линейность роста баланса для пятого варианта группировки. Это позволит оценить, нет ли каких-либо внутренних периодов, на которых советник работает заметно хуже, чем на других внутренних периодах всего периода тестирования. Для этого установим значение параметра FixedBalance = 10000, чтобы советник всегда использовал только такую величину баланса счёта для расчёта размеров позиций.



Рис. 13. Результаты для EURGBP, EURUSD, GBPUSD (3 + 3 + 3 стратегии), FixedBalance = 10000, Scale=2.18


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

Отметим также, что максимальная просадка по средствам в абсолютном выражении составила $995, то есть как раз около 10% от значения используемого для торговли баланса $10000. Это подтверждает, что сделанная реализация управления капиталом ведёт себя корректно.


Заключение

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

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

Мы пока продолжим работу по дальнейшему развитию этого проекта. Спасибо за внимание!


Прикрепленные файлы |
Advisor.mqh (4.3 KB)
Interface.mqh (3.22 KB)
Macros.mqh (2.3 KB)
Money.mqh (4.46 KB)
Receiver.mqh (1.8 KB)
Strategy.mqh (1.74 KB)
VirtualAdvisor.mqh (13.69 KB)
VirtualOrder.mqh (38.66 KB)
VirtualReceiver.mqh (17.67 KB)
Создаем простой мультивалютный советник с использованием MQL5 (Часть 4): Треугольная скользящая средняя — Сигналы индикатора Создаем простой мультивалютный советник с использованием MQL5 (Часть 4): Треугольная скользящая средняя — Сигналы индикатора
Под мультивалютным советником в этой статье понимается советник, или торговый робот, который может торговать (открывать/закрывать ордера, управлять ордерами, например, трейлинг-стоп-лоссом и трейлинг-профитом) более чем одной парой символов с одного графика. На этот раз мы будем использовать только один индикатор, а именно треугольную скользящую среднюю на одном или нескольких таймфреймах.
Шаблоны проектирования в MQL5 (Часть 2): Структурные шаблоны Шаблоны проектирования в MQL5 (Часть 2): Структурные шаблоны
В этой статье мы продолжим изучать шаблоны проектирования, которые позволяют разработчикам создавать расширяемые и надежные приложений не только на MQL5, но и на других языках программирования. В этот раз мы поговорим о другом типе — о структурных шаблонах. Будем учиться проектировать системы, используя имеющиеся классы для формирования более крупных структур.
Нейросети — это просто (Часть 82): Модели Обыкновенных Дифференциальных Уравнений (NeuralODE) Нейросети — это просто (Часть 82): Модели Обыкновенных Дифференциальных Уравнений (NeuralODE)
В данной статье я предлагаю познакомиться Вас с еще одним типом моделей, которые направлены на изучение динамики состояния окружающей среды.
Комбинаторно-симметричная перекрестная проверка в MQL5 Комбинаторно-симметричная перекрестная проверка в MQL5
В статье показана реализация комбинаторно-симметричной перекрестной проверки на чистом MQL5 для измерения степени подгонки после оптимизации стратегии с использованием медленного полного алгоритма тестера стратегий.