333 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			MQL5
		
	
	
	
	
	
			
		
		
	
	
			333 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			MQL5
		
	
	
	
	
	
//+------------------------------------------------------------------+
 | 
						|
//|                                        SimpleCandlesStrategy.mqh |
 | 
						|
//|                                      Copyright 2025, Yuriy Bykov |
 | 
						|
//|                            https://www.mql5.com/ru/users/antekov |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
#property copyright "Copyright 2025, Yuriy Bykov"
 | 
						|
#property link      "https://www.mql5.com/ru/articles/17277"
 | 
						|
#property version   "1.03"
 | 
						|
 | 
						|
#include "../../Adwizard/Virtual/VirtualStrategy.mqh"
 | 
						|
 | 
						|
/** Описание стратегии
 | 
						|
 | 
						|
Входные параметры:
 | 
						|
 | 
						|
 - Символ
 | 
						|
 - Таймфрейм для подсчёта однонаправленных свечей
 | 
						|
 - Количество однонаправленных свечей (signalSeqLen)
 | 
						|
 - Период ATR (periodATR)
 | 
						|
 - Stop Loss (в пунктах или % ATR) (stopLevel)
 | 
						|
 - Take Profit (в пунктах или % ATR) (takeLevel)
 | 
						|
 - Максимальное количество одновременно отрытых позиций (maxCountOfOrders)
 | 
						|
 - Максимальный размер спреда (maxSpread)
 | 
						|
 - Размер позиций
 | 
						|
 | 
						|
При наступлении нового бара проверяем направления последних закрытых signalSeqLen свечей.
 | 
						|
 | 
						|
Если направления одинаковые и количество открытых позиций меньше maxCountOfOrders
 | 
						|
и текущий спред меньше maxSpread, то:
 | 
						|
 - Вычисляем StopLoss и TakeProfit. Если periodATR = 0, то просто отступаем от текущей
 | 
						|
   цены на количество пунктов, взятых из параметров stopLevel и takeLevel.
 | 
						|
   Если periodATR > 0, то рассчитываем величину ATR, используя параметр periodATR
 | 
						|
   для дневного таймфрейма.
 | 
						|
   От текущей цены отступаем на величины ATR * stopLevel и ATR * takeLevel.
 | 
						|
 | 
						|
 - Открываем позицию SELL, если направления свечей были вверх и
 | 
						|
   позицию BUY, если направления свечей были вниз.
 | 
						|
   При открытии устанавливаем рассчитанные ранее уровни StopLoss и TakeProfit.
 | 
						|
*/
 | 
						|
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Торговая стратегия c использованием однонаправленных свечей      |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
class CSimpleCandlesStrategy : public CVirtualStrategy {
 | 
						|
protected:
 | 
						|
   string            m_symbol;            // Символ (торговый инструмент)
 | 
						|
   ENUM_TIMEFRAMES   m_timeframe;         // Период графика (таймфрейм)
 | 
						|
 | 
						|
   //---  Параметры сигнала к открытию
 | 
						|
   int               m_signalSeqLen;      // Количество однонаправленных свечей
 | 
						|
   int               m_periodATR;         // Период ATR
 | 
						|
 | 
						|
   //---  Параметры позиций
 | 
						|
   double            m_stopLevel;         // Stop Loss (в пунктах или % ATR)
 | 
						|
   double            m_takeLevel;         // Take Profit (в пунктах или % ATR)
 | 
						|
 | 
						|
   //---  Параметры управление капиталом
 | 
						|
   int               m_maxCountOfOrders;  // Макс. количество одновременно отрытых позиций
 | 
						|
   int               m_maxSpread;         // Макс. допустимый спред (в пунктах)
 | 
						|
 | 
						|
   CSymbolInfo       *m_symbolInfo;       // Объект для получения информации о свойствах символа
 | 
						|
 | 
						|
   double            m_tp;                // Stop Loss в пунктах
 | 
						|
   double            m_sl;                // Take Profit в пунктах
 | 
						|
 | 
						|
   int               m_iATRHandle;        // Хендл индикатора SmartATR
 | 
						|
 | 
						|
   //--- Методы
 | 
						|
   int               SignalForOpen();     // Сигнал для открытия позиции
 | 
						|
   void              OpenBuy();           // Открытие позиции BUY
 | 
						|
   void              OpenSell();          // Открытие позиции SELL
 | 
						|
 | 
						|
   double            ChannelWidth(ENUM_TIMEFRAMES p_tf = PERIOD_D1); // Расчёт величины ATR
 | 
						|
   void              UpdateLevels();      // Обновление уровней SL и TP
 | 
						|
 | 
						|
   // Закрытый конструктор
 | 
						|
   CSimpleCandlesStrategy(string p_params);
 | 
						|
 | 
						|
public:
 | 
						|
   // Статический конструктор
 | 
						|
   STATIC_CONSTRUCTOR(CSimpleCandlesStrategy);
 | 
						|
 | 
						|
   virtual string    operator~() override;   // Преобразование объекта в строку
 | 
						|
   virtual void      Tick() override;        // Обработчик события OnTick
 | 
						|
};
 | 
						|
 | 
						|
// Регистрация класса-наследника CFactorable
 | 
						|
REGISTER_FACTORABLE_CLASS(CSimpleCandlesStrategy);
 | 
						|
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Конструктор                                                      |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
CSimpleCandlesStrategy::CSimpleCandlesStrategy(string p_params) {
 | 
						|
// Читаем параметры из строки инициализации
 | 
						|
   m_params = p_params;
 | 
						|
   m_symbol = ReadString(p_params);
 | 
						|
   m_timeframe = (ENUM_TIMEFRAMES) ReadLong(p_params);
 | 
						|
   m_signalSeqLen = (int) ReadLong(p_params);
 | 
						|
   m_periodATR = (int) ReadLong(p_params);
 | 
						|
   m_stopLevel = ReadDouble(p_params);
 | 
						|
   m_takeLevel = ReadDouble(p_params);
 | 
						|
   m_maxCountOfOrders = (int) ReadLong(p_params);
 | 
						|
   m_maxSpread = (int) ReadLong(p_params);
 | 
						|
 | 
						|
   if(IsValid()) {
 | 
						|
      // Загружаем индикатор SmartATR
 | 
						|
      m_iATRHandle = iCustom(
 | 
						|
                        m_symbol, m_timeframe,
 | 
						|
                        "Shared Projects/SmartATR/SmartATR.ex5",
 | 
						|
                        // Параметры индикатора
 | 
						|
                        m_periodATR,   // Initial ATR period (used for first calculation, adaptively changes)
 | 
						|
                        false,         // Enable adaptive period (dynamic lookback)
 | 
						|
                        7,             // Minimum ATR period (adaptive mode)
 | 
						|
                        28,            // Maximum ATR period (adaptive mode)
 | 
						|
                        false,         // Weight True Range by volume
 | 
						|
                        false,         // Weight True Range by economic news events (MT5 Calendar)
 | 
						|
                        2.0,           // Multiplier: alert if ATR exceeds this factor of average
 | 
						|
                        false          // Enable pop-up & sound alerts on high volatility
 | 
						|
                     );
 | 
						|
 | 
						|
      // Запрашиваем нужное количество объектов для виртуальных позиций
 | 
						|
      CVirtualReceiver::Get(&this, m_orders, m_maxCountOfOrders);
 | 
						|
 | 
						|
      // Добавляем отслеживание нового бара на нужном таймфрейме
 | 
						|
      IsNewBar(m_symbol, m_timeframe);
 | 
						|
 | 
						|
      // Создаём информационный объект для нужного символа
 | 
						|
      m_symbolInfo = CSymbolsMonitor::Instance()[m_symbol];
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Преобразование объекта в строку                                  |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
string CSimpleCandlesStrategy::operator~() {
 | 
						|
   return StringFormat("%s(%s)", typename(this), m_params);
 | 
						|
}
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| "Tick" event handler function                                    |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
void CSimpleCandlesStrategy::Tick() override {
 | 
						|
// Если наступил новый бар по заданному символу и таймфрейму
 | 
						|
   if(IsNewBar(m_symbol, m_timeframe)) {
 | 
						|
// Если количество открытых позиций меньше допустимого
 | 
						|
      if(m_ordersTotal < m_maxCountOfOrders) {
 | 
						|
         // Получаем сигнал на открытие
 | 
						|
         int signal = SignalForOpen();
 | 
						|
 | 
						|
         if(signal == 1) {          // Если сигнал на покупку, то
 | 
						|
            OpenBuy();              // открываем позицию BUY
 | 
						|
         } else if(signal == -1) {  // Если сигнал на продажу, то
 | 
						|
            OpenSell();             // открываем позицию SELL_STOP
 | 
						|
         }
 | 
						|
      }
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Сигнал для открытия отложенных ордеров                           |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
int CSimpleCandlesStrategy::SignalForOpen() {
 | 
						|
// По-умолчанию сигнала на открытие нет
 | 
						|
   int signal = 0;
 | 
						|
 | 
						|
   MqlRates rates[];
 | 
						|
// Копируем значения котировок (свечей) в массив-приёмник.
 | 
						|
// Для проверки сигнала нам нужно m_signalSeqLen закрытых свечей и текущая свеча,
 | 
						|
// поэтому всего m_signalSeqLen + 1
 | 
						|
   int res = CopyRates(m_symbol, m_timeframe, 0, m_signalSeqLen + 1, rates);
 | 
						|
 | 
						|
// Если скопировалось нужное количество свечей
 | 
						|
   if(res == m_signalSeqLen + 1) {
 | 
						|
      signal = 1; // сигнал на покупку
 | 
						|
 | 
						|
      // Перебираем все закрытые свечи
 | 
						|
      for(int i = 1; i <= m_signalSeqLen; i++) {
 | 
						|
         // Если встречается хоть одна свеча вверх, то отменяем сигнал
 | 
						|
         if(rates[i].open < rates[i].close ) {
 | 
						|
            signal = 0;
 | 
						|
            break;
 | 
						|
         }
 | 
						|
      }
 | 
						|
 | 
						|
      if(signal == 0) {
 | 
						|
         signal = -1; // иначе - сигнал на продажу
 | 
						|
 | 
						|
         // Перебираем все закрытые свечи
 | 
						|
         for(int i = 1; i <= m_signalSeqLen; i++) {
 | 
						|
            // Если встречается хоть одна свеча вниз, то отменяем сигнал
 | 
						|
            if(rates[i].open > rates[i].close ) {
 | 
						|
               signal = 0;
 | 
						|
               break;
 | 
						|
            }
 | 
						|
         }
 | 
						|
      }
 | 
						|
   }
 | 
						|
 | 
						|
// Если сигнал есть, то
 | 
						|
   if(signal != 0) {
 | 
						|
      // Если текущий спред больше максимально разрешённого, то
 | 
						|
      if(rates[0].spread > m_maxSpread) {
 | 
						|
         PrintFormat(__FUNCTION__" | IGNORE %s Signal, spread is too big (%d > %d)",
 | 
						|
                     (signal > 0 ? "BUY" : "SELL"),
 | 
						|
                     rates[0].spread, m_maxSpread);
 | 
						|
         signal = 0; // Отменяем сигнал
 | 
						|
      }
 | 
						|
   }
 | 
						|
 | 
						|
   return signal;
 | 
						|
}
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Открытие ордера BUY                                              |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
void CSimpleCandlesStrategy::OpenBuy() {
 | 
						|
// Берем необходимую нам информацию о символе и ценах
 | 
						|
   double point = m_symbolInfo.Point();
 | 
						|
   int digits = m_symbolInfo.Digits();
 | 
						|
 | 
						|
// Цена открытия
 | 
						|
   double price = m_symbolInfo.Ask();
 | 
						|
 | 
						|
// Обновим уровни SL и TP, рассчитав ATR
 | 
						|
   UpdateLevels();
 | 
						|
 | 
						|
// Уровни StopLoss и TakeProfit
 | 
						|
   double sl = NormalizeDouble(price - m_sl * point, digits);
 | 
						|
   double tp = NormalizeDouble(price + m_tp * point, digits);
 | 
						|
 | 
						|
   bool res = false;
 | 
						|
   for(int i = 0; i < m_maxCountOfOrders; i++) {   // Перебираем все виртуальные позиции
 | 
						|
      if(!m_orders[i].IsOpen()) {                  // Если нашли не открытую, то открываем
 | 
						|
         // Открытие виртуальной позиции SELL
 | 
						|
         res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY, m_fixedLot,
 | 
						|
                                0,
 | 
						|
                                NormalizeDouble(sl, digits),
 | 
						|
                                NormalizeDouble(tp, digits));
 | 
						|
 | 
						|
         break; // и выходим
 | 
						|
      }
 | 
						|
   }
 | 
						|
 | 
						|
   if(!res) {
 | 
						|
      PrintFormat(__FUNCTION__" | ERROR opening BUY virtual order", 0);
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Открытие ордера SELL                                             |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
void CSimpleCandlesStrategy::OpenSell() {
 | 
						|
// Берем необходимую нам информацию о символе и ценах
 | 
						|
   double point = m_symbolInfo.Point();
 | 
						|
   int digits = m_symbolInfo.Digits();
 | 
						|
 | 
						|
// Цена открытия
 | 
						|
   double price = m_symbolInfo.Bid();
 | 
						|
 | 
						|
// Обновим уровни SL и TP, рассчитав ATR
 | 
						|
   UpdateLevels();
 | 
						|
 | 
						|
// Уровни StopLoss и TakeProfit
 | 
						|
   double sl = NormalizeDouble(price + m_sl * point, digits);
 | 
						|
   double tp = NormalizeDouble(price - m_tp * point, digits);
 | 
						|
 | 
						|
   bool res = false;
 | 
						|
   for(int i = 0; i < m_maxCountOfOrders; i++) {   // Перебираем все виртуальные позиции
 | 
						|
      if(!m_orders[i].IsOpen()) {                  // Если нашли не открытую, то открываем
 | 
						|
         // Открытие виртуальной позиции SELL
 | 
						|
 | 
						|
         res = m_orders[i].Open(m_symbol, ORDER_TYPE_SELL, m_fixedLot,
 | 
						|
                                0,
 | 
						|
                                NormalizeDouble(sl, digits),
 | 
						|
                                NormalizeDouble(tp, digits));
 | 
						|
 | 
						|
         break;   // и выходим
 | 
						|
      }
 | 
						|
   }
 | 
						|
 | 
						|
   if(!res) {
 | 
						|
      PrintFormat(__FUNCTION__" | ERROR opening SELL virtual order", 0);
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Обновление уровней SL и TP по рассчитанному ATR                  |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
void CSimpleCandlesStrategy::UpdateLevels() {
 | 
						|
// Рассчитываем ATR
 | 
						|
   double channelWidth = (m_periodATR > 0 ? ChannelWidth() : 1);
 | 
						|
 | 
						|
// Обновляем уровни SL и TP
 | 
						|
   m_sl = m_stopLevel * channelWidth;
 | 
						|
   m_tp = m_takeLevel * channelWidth;
 | 
						|
}
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Расчёт величины ATR (нестандартная реализация)                   |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
double CSimpleCandlesStrategy::ChannelWidth(ENUM_TIMEFRAMES p_tf = PERIOD_D1) {
 | 
						|
   int n = m_periodATR; // Количество баров для расчёта
 | 
						|
   MqlRates rates[];    // Массив для котировок
 | 
						|
 | 
						|
// Копируем котировки дневного (по умолчанию) таймфрейма
 | 
						|
   int res = CopyRates(m_symbol, p_tf, 1, n, rates);
 | 
						|
 | 
						|
// Если скопировалось нужное количество
 | 
						|
   if(res == n) {
 | 
						|
      double tr[];         // Массив для диапазонов цены
 | 
						|
      ArrayResize(tr, n);  // Изменяем его размер
 | 
						|
 | 
						|
      double s = 0;        // Сумма для подсчёта среднего
 | 
						|
      FOREACH(rates) {
 | 
						|
         tr[i] = rates[i].high - rates[i].low; // Запоминаем размер бара
 | 
						|
      }
 | 
						|
 | 
						|
      ArraySort(tr); // Сортируем размеры
 | 
						|
 | 
						|
      // Суммируем внутренние две четверти размеров баров
 | 
						|
      for(int i = n / 4; i < n * 3 / 4; i++) {
 | 
						|
         s += tr[i];
 | 
						|
      }
 | 
						|
 | 
						|
      // Возвращаем средний размер в пунктах
 | 
						|
      return 2 * s / n / m_symbolInfo.Point();
 | 
						|
   }
 | 
						|
 | 
						|
   return 0.0;
 | 
						|
}
 | 
						|
//+------------------------------------------------------------------+
 |