385 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			MQL5
		
	
	
	
	
	
			
		
		
	
	
			385 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			MQL5
		
	
	
	
	
	
//+------------------------------------------------------------------+
 | 
						|
//|                                        SimpleVolumesStrategy.mqh |
 | 
						|
//|                                      Copyright 2024, Yuriy Bykov |
 | 
						|
//|                            https://www.mql5.com/ru/users/antekov |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
#property copyright "Copyright 2024, Yuriy Bykov"
 | 
						|
#property link      "https://www.mql5.com/ru/articles/16452"
 | 
						|
#property version   "1.11"
 | 
						|
 | 
						|
#include "../Utils/NewBarEvent.mqh"
 | 
						|
#include "../Virtual/VirtualStrategy.mqh"
 | 
						|
 | 
						|
/** Описание стратегии
 | 
						|
 | 
						|
Входные параметры стратегии:
 | 
						|
   - Символ
 | 
						|
   - Период
 | 
						|
   - Количество свечей для усреднения объемов (K)
 | 
						|
   - Относительное отклонение от среднего для открытия первого ордера (D)
 | 
						|
   - Относительное отклонение от среднего для открытия второго и последующих ордеров (D_add)
 | 
						|
   - Расстояние от цены до отложенного ордера
 | 
						|
   - Stop Loss (в пунктах)
 | 
						|
   - Take Profit (в пунктах)
 | 
						|
   - Время истечения отложенных ордеров (в минутах)
 | 
						|
   - Максимальное количество одновременно отрытых ордеров (N_max)
 | 
						|
   - Объем одного ордера
 | 
						|
 | 
						|
Находим количество открытых ордеров и позиций (N).
 | 
						|
Если оно меньше N_max, то:
 | 
						|
   - вычисляем средний тиковый объём за последние K закрытых свечей, получаем величину V_avr.
 | 
						|
   - Если выполнено условие V > V_avr * (1 + D + N * D_add), то:
 | 
						|
        - определяем направление изменения цены на текущей свече: если цена увеличилась,
 | 
						|
          то будем выставлять BUY, BUY_STOP или BUY_LIMIT, а иначе - SELL, SELL_STOP или SELL_LIMIT
 | 
						|
        - выставляем рыночный или отложенный ордер исходя из заданного в параметрах расстояния
 | 
						|
          (0 - рыночный, >0 - отложенный стоповый, <0 - отложенный лимитный),
 | 
						|
          времени истечения и уровнями StopLoss и TakeProfit.
 | 
						|
*/
 | 
						|
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Торговая стратегия с использованием тиковых объемов              |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
class CSimpleVolumesStrategy : public CVirtualStrategy {
 | 
						|
protected:
 | 
						|
   string            m_symbol;               // Символ (торговый инструмент)
 | 
						|
   ENUM_TIMEFRAMES   m_timeframe;            // Период графика (таймфрейм)
 | 
						|
 | 
						|
   //---  Параметры сигнала к открытию
 | 
						|
   int               m_signalPeriod;         // Количество свечей для усреднения объемов
 | 
						|
   double            m_signalDeviation;      // Относ. откл. от среднего для открытия первого ордера
 | 
						|
   double            m_signaAddlDeviation;   // Относ. откл. от среднего для открытия второго и последующих ордеров
 | 
						|
 | 
						|
   //---  Параметры отложенных ордеров
 | 
						|
   int               m_openDistance;         // Расстояние от цены до отлож. ордера
 | 
						|
   double            m_stopLevel;            // Stop Loss (в пунктах)
 | 
						|
   double            m_takeLevel;            // Take Profit (в пунктах)
 | 
						|
   int               m_ordersExpiration;     // Время истечения отложенных ордеров (в минутах)
 | 
						|
 | 
						|
   //---  Параметры управление капиталом
 | 
						|
   int               m_maxCountOfOrders;     // Макс. количество одновременно отрытых ордеров
 | 
						|
 | 
						|
   CSymbolInfo       *m_symbolInfo;          // Объект для получения информации о свойствах символа
 | 
						|
 | 
						|
   int               m_iVolumesHandle;       // Хэндл индикатора тиковых объемов
 | 
						|
   double            m_volumes[];            // Массив-приемник значений индикатора (самих объемов)
 | 
						|
 | 
						|
   //--- Методы
 | 
						|
   int               SignalForOpen();        // Сигнал для открытия отложенных ордеров
 | 
						|
   void              OpenBuyOrder();         // Открытие ордера BUY_STOP
 | 
						|
   void              OpenSellOrder();        // Открытие ордера SELL_STOP
 | 
						|
   double            ArrayAverage(
 | 
						|
      const double &array[]);                // Среднее значение массива чисел
 | 
						|
 | 
						|
public:
 | 
						|
   //--- Публичные методы
 | 
						|
                     CSimpleVolumesStrategy(string p_params); // Конструктор
 | 
						|
 | 
						|
   virtual string    operator~() override;   // Преобразование объекта в строку
 | 
						|
 | 
						|
   virtual void      Tick() override;        // Обработчик события OnTick
 | 
						|
   virtual void      Save() override;        // Сохранение состояния
 | 
						|
   virtual bool      Load() override;        // Загрузка состояния
 | 
						|
 | 
						|
   // Замена названий символов
 | 
						|
   virtual bool      SymbolsReplace(CHashMap<string, string> &p_symbolsMap);
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Конструктор                                                      |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
CSimpleVolumesStrategy::CSimpleVolumesStrategy(string p_params) {
 | 
						|
   m_params = p_params;
 | 
						|
   m_symbol = ReadString(p_params);
 | 
						|
   m_timeframe = (ENUM_TIMEFRAMES) ReadLong(p_params);
 | 
						|
   m_signalPeriod = (int) ReadLong(p_params);
 | 
						|
   m_signalDeviation = ReadDouble(p_params);
 | 
						|
   m_signaAddlDeviation = ReadDouble(p_params);
 | 
						|
   m_openDistance = (int) ReadLong(p_params);
 | 
						|
   m_stopLevel = ReadDouble(p_params);
 | 
						|
   m_takeLevel = ReadDouble(p_params);
 | 
						|
   m_ordersExpiration = (int) ReadLong(p_params);
 | 
						|
   m_maxCountOfOrders = (int) ReadLong(p_params);
 | 
						|
 | 
						|
   if(IsValid()) {
 | 
						|
      CVirtualReceiver::Get(GetPointer(this), m_orders, m_maxCountOfOrders);
 | 
						|
 | 
						|
      // Загружаем индикатор для получения тиковых объемов
 | 
						|
      m_iVolumesHandle = iVolumes(m_symbol, m_timeframe, VOLUME_TICK);
 | 
						|
 | 
						|
      // Если индикатор загружен успешно
 | 
						|
      if(m_iVolumesHandle != INVALID_HANDLE) {
 | 
						|
 | 
						|
         // Устанавливаем размер массива-приемника тиковых объемов и нужную адресацию
 | 
						|
         ArrayResize(m_volumes, m_signalPeriod);
 | 
						|
         ArraySetAsSeries(m_volumes, true);
 | 
						|
 | 
						|
         // Регистрируем обработчик события нового бара на минимальном таймфрейме
 | 
						|
         //IsNewBar(m_symbol, PERIOD_M1);
 | 
						|
 | 
						|
         m_symbolInfo = CSymbolsMonitor::Instance()[m_symbol];
 | 
						|
      } else {
 | 
						|
         // Иначе устанавливаем ошибочное состояние объекта
 | 
						|
         SetInvalid(__FUNCTION__, "Can't load iVolumes()");
 | 
						|
      }
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Преобразование объекта в строку                                  |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
string CSimpleVolumesStrategy::operator~() {
 | 
						|
   return StringFormat("%s(%s)", typename(this), m_params);
 | 
						|
}
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| "Tick" event handler function                                    |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
void CSimpleVolumesStrategy::Tick() override {
 | 
						|
   if(IsNewBar(m_symbol, PERIOD_M1)) {
 | 
						|
// Если их количество меньше допустимого
 | 
						|
      if(m_ordersTotal < m_maxCountOfOrders) {
 | 
						|
         // Получаем сигнал на открытие
 | 
						|
         int signal = SignalForOpen();
 | 
						|
 | 
						|
         if(signal == 1 /* || m_ordersTotal < 1 */) {          // Если сигнал на покупку, то
 | 
						|
            OpenBuyOrder();         // открываем ордер BUY_STOP
 | 
						|
         } else if(signal == -1) {  // Если сигнал на продажу, то
 | 
						|
            OpenSellOrder();        // открываем ордер SELL_STOP
 | 
						|
         }
 | 
						|
      }
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Замена названий символов                                         |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
bool CSimpleVolumesStrategy::SymbolsReplace(CHashMap<string, string> &p_symbolsMap) {
 | 
						|
// Если в словаре есть ключ, совпадающий с текущим символом
 | 
						|
   if(p_symbolsMap.ContainsKey(m_symbol)) {
 | 
						|
      string targetSymbol; // Целевой символ
 | 
						|
 | 
						|
      // Если целевой символ для текущего успешно получен из словаря
 | 
						|
      if(p_symbolsMap.TryGetValue(m_symbol, targetSymbol)) {
 | 
						|
         // Обновляем текущий символ
 | 
						|
         m_symbol = targetSymbol;
 | 
						|
      }
 | 
						|
   }
 | 
						|
 | 
						|
   return true;
 | 
						|
}
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Сигнал для открытия отложенных ордеров                           |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
int CSimpleVolumesStrategy::SignalForOpen() {
 | 
						|
// По-умолчанию сигнала на открытие нет
 | 
						|
   int signal = 0;
 | 
						|
 | 
						|
// Копируем значения объемов из индикаторного буфера в массив-приёмник
 | 
						|
   int res = CopyBuffer(m_iVolumesHandle, 0, 0, m_signalPeriod, m_volumes);
 | 
						|
 | 
						|
// Если скопировалось нужное количество чисел
 | 
						|
   if(res == m_signalPeriod) {
 | 
						|
      // Вычисляем их среднее значение
 | 
						|
      double avrVolume = ArrayAverage(m_volumes);
 | 
						|
 | 
						|
      // Если текущий объем превысил заданный уровень, то
 | 
						|
      if(m_volumes[0] > avrVolume * (1 + m_signalDeviation + m_ordersTotal * m_signaAddlDeviation)) {
 | 
						|
         // если цена открытия свечи меньше текущей цены (закрытия), то
 | 
						|
         if(iOpen(m_symbol, m_timeframe, 0) < iClose(m_symbol, m_timeframe, 0)) {
 | 
						|
            signal = 1; // сигнал на покупку
 | 
						|
         } else {
 | 
						|
            signal = -1; // иначе - сигнал на продажу
 | 
						|
         }
 | 
						|
      }
 | 
						|
   }
 | 
						|
 | 
						|
   return signal;
 | 
						|
}
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Открытие ордера BUY                                              |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
void CSimpleVolumesStrategy::OpenBuyOrder() {
 | 
						|
// Обновляем информацию о текущих ценах для символа
 | 
						|
//m_symbolInfo.Name(m_symbol);
 | 
						|
//m_symbolInfo.RefreshRates();
 | 
						|
 | 
						|
// Берем необходимую нам информацию о символе и ценах
 | 
						|
   double point = m_symbolInfo.Point();
 | 
						|
   int digits = m_symbolInfo.Digits();
 | 
						|
   double bid = m_symbolInfo.Bid();
 | 
						|
   double ask = m_symbolInfo.Ask();
 | 
						|
   int spread = (int) ((ask - bid) / point); //m_symbolInfo.Spread();
 | 
						|
 | 
						|
// Сделаем, чтобы расстояние открытия было не меньше спреда
 | 
						|
   int distance = MathMax(MathAbs(m_openDistance), spread) * (m_openDistance < 0 ? -1 : 1);
 | 
						|
 | 
						|
// Цена открытия
 | 
						|
   double price = ask + distance * point;
 | 
						|
 | 
						|
// Уровни StopLoss и TakeProfit
 | 
						|
   double sl = NormalizeDouble(price - m_stopLevel * point, digits);
 | 
						|
   double tp = NormalizeDouble(price + (m_takeLevel + spread) * point, digits);
 | 
						|
 | 
						|
// Время истечения
 | 
						|
   datetime expiration = TimeCurrent() + m_ordersExpiration * 60;
 | 
						|
 | 
						|
   bool res = false;
 | 
						|
   for(int i = 0; i < m_maxCountOfOrders; i++) {   // Перебираем все виртуальные позиции
 | 
						|
      if(!m_orders[i].IsOpen()) {                  // Если нашли не открытую, то открываем
 | 
						|
         if(m_openDistance > 0) {
 | 
						|
            // Устанавливаем отложенный ордер SELL STOP
 | 
						|
            res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY_STOP, m_fixedLot,
 | 
						|
                                   NormalizeDouble(price, digits),
 | 
						|
                                   NormalizeDouble(sl, digits),
 | 
						|
                                   NormalizeDouble(tp, digits),
 | 
						|
                                   "", expiration);
 | 
						|
 | 
						|
         } else if(m_openDistance < 0) {
 | 
						|
            // Устанавливаем отложенный ордер SELL LIMIT
 | 
						|
            res = m_orders[i].Open(m_symbol, ORDER_TYPE_BUY_LIMIT, m_fixedLot,
 | 
						|
                                   NormalizeDouble(price, digits),
 | 
						|
                                   NormalizeDouble(sl, digits),
 | 
						|
                                   NormalizeDouble(tp, digits),
 | 
						|
                                   "", expiration);
 | 
						|
 | 
						|
         } else {
 | 
						|
            // Открытие виртуальной позиции 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 CSimpleVolumesStrategy::OpenSellOrder() {
 | 
						|
// Обновляем информацию о текущих ценах для символа
 | 
						|
//m_symbolInfo.Name(m_symbol);
 | 
						|
//m_symbolInfo.RefreshRates();
 | 
						|
 | 
						|
// Берем необходимую нам информацию о символе и ценах
 | 
						|
   double point = m_symbolInfo.Point();
 | 
						|
   int digits = m_symbolInfo.Digits();
 | 
						|
   double bid = m_symbolInfo.Bid();
 | 
						|
   double ask = m_symbolInfo.Ask();
 | 
						|
   int spread = (int) ((ask - bid) / point); //m_symbolInfo.Spread();
 | 
						|
 | 
						|
// Сделаем, чтобы расстояние открытия было не меньше спреда
 | 
						|
   int distance = MathMax(MathAbs(m_openDistance), spread) * (m_openDistance < 0 ? -1 : 1);
 | 
						|
 | 
						|
// Цена открытия
 | 
						|
   double price = bid - distance * point;
 | 
						|
 | 
						|
// Уровни StopLoss и TakeProfit
 | 
						|
   double sl = NormalizeDouble(price + m_stopLevel * point, digits);
 | 
						|
   double tp = NormalizeDouble(price - (m_takeLevel + spread) * point, digits);
 | 
						|
 | 
						|
// Время истечения
 | 
						|
   datetime expiration = TimeCurrent() + m_ordersExpiration * 60;
 | 
						|
 | 
						|
   bool res = false;
 | 
						|
   for(int i = 0; i < m_maxCountOfOrders; i++) {   // Перебираем все виртуальные позиции
 | 
						|
      if(!m_orders[i].IsOpen()) {                  // Если нашли не открытую, то открываем
 | 
						|
         if(m_openDistance > 0) {
 | 
						|
            // Устанавливаем отложенный ордер SELL STOP
 | 
						|
            res = m_orders[i].Open(m_symbol, ORDER_TYPE_SELL_STOP, m_fixedLot,
 | 
						|
                                   NormalizeDouble(price, digits),
 | 
						|
                                   NormalizeDouble(sl, digits),
 | 
						|
                                   NormalizeDouble(tp, digits),
 | 
						|
                                   "", expiration);
 | 
						|
 | 
						|
         } else if(m_openDistance < 0) {
 | 
						|
            // Устанавливаем отложенный ордер SELL LIMIT
 | 
						|
            res = m_orders[i].Open(m_symbol, ORDER_TYPE_SELL_LIMIT, m_fixedLot,
 | 
						|
                                   NormalizeDouble(price, digits),
 | 
						|
                                   NormalizeDouble(sl, digits),
 | 
						|
                                   NormalizeDouble(tp, digits),
 | 
						|
                                   "", expiration);
 | 
						|
 | 
						|
         } else {
 | 
						|
            // Открытие виртуальной позиции 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);
 | 
						|
   }
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Среднее значение массива чисел со второго элемента               |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
double CSimpleVolumesStrategy::ArrayAverage(const double &array[]) {
 | 
						|
   double s = 0;
 | 
						|
   int total = ArraySize(array) - 1;
 | 
						|
   for(int i = 1; i <= total; i++) {
 | 
						|
      s += array[i];
 | 
						|
   }
 | 
						|
 | 
						|
   return s / MathMax(1, total);
 | 
						|
}
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Сохранение состояния                                             |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
void CSimpleVolumesStrategy::Save() {
 | 
						|
   double avrVolume = ArrayAverage(m_volumes);
 | 
						|
 | 
						|
// Сформируем общую часть ключа с типом и хешем стратегии
 | 
						|
   string key = "CSimpleVolumesStrategy[" + this.Hash() + "]";
 | 
						|
 | 
						|
// Сохраняем средний тиковый объём
 | 
						|
   CStorage::Set(key + ".avrVolume", avrVolume);
 | 
						|
 | 
						|
// Сохраняем массив тиковых объёмов
 | 
						|
   CStorage::Set(key + ".m_volumes", m_volumes);
 | 
						|
 | 
						|
// Вызываем метод базового класса (для сохранения виртуальных позиций)
 | 
						|
   CVirtualStrategy::Save();
 | 
						|
}
 | 
						|
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
//| Загрузка состояния                                               |
 | 
						|
//+------------------------------------------------------------------+
 | 
						|
bool CSimpleVolumesStrategy::Load() {
 | 
						|
   bool res = true;
 | 
						|
 | 
						|
   double avrVolume = 0;
 | 
						|
 | 
						|
// Сформируем общую часть ключа с типом и хешем стратегии
 | 
						|
   string key = "CSimpleVolumesStrategy[" + this.Hash() + "]";
 | 
						|
 | 
						|
// Загружаем массив тиковых объёмов
 | 
						|
   res &= CStorage::Get(key + ".avrVolume", avrVolume);
 | 
						|
 | 
						|
// Загружаем массив тиковых объёмов
 | 
						|
   res &= CStorage::Get(key + ".m_volumes", m_volumes);
 | 
						|
 | 
						|
// Вызываем метод базового класса (для загрузки виртуальных позиций)
 | 
						|
   res &= CVirtualStrategy::Load();
 | 
						|
 | 
						|
   return res;
 | 
						|
}
 | 
						|
//+------------------------------------------------------------------+
 |