//+------------------------------------------------------------------+ //| 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 &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 &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; } //+------------------------------------------------------------------+