Voldemar227
Voldemar227 личный блог
Вчера в 12:52

Торговая стратегия по RSI с выходом через сеть плюс исходный код торгового робота

Эта стратегия сочетает классическую логику осциллятора RCI/RSI (период 14, уровни 30/70) с сеточной (grid) логикой усреднения и реализована в виде эксперта под MetaTrader 5 на MQL5 с использованием объектно‑ориентированного подхода и хэндла индикатора. Она рассчитана для тестера стратегий и требует доработки по рискам, проверкам и нормализациям перед запуском на реальном счёте.

Видео описание стратегии и разработки кода

Логика классической стратегии RSI

Используется осциллятор с периодом 14 и уровнями 30 (перепроданность) и 70 (перекупленность), что является классическим набором настроек для RSI‑подобных индикаторов.

Сигнал на покупку: линия индикатора опускается ниже уровня 30, а затем пересекает его снизу вверх (RCI2 < 30 и RCI1 > 30), то есть фиксируется выход из зоны перепроданности.

Сигнал на продажу: линия индикатора поднимается выше 70 и затем пересекает этот уровень сверху вниз (RCI2 > 70 и RCI1 < 70), что трактуется как выход из зоны перекупленности.

Стратегия ориентирована на старшие таймфреймы (например, H1), где сигналы редкие, но более «чистые», и особенно эффективно работает по тренду, а не против него.

Торговая идея и управление позицией

Базовая идея: открывать позиции только по чётким пересечениям 30/70, избегая «догадочных» входов, когда индикатор ещё не дал формальный сигнал.

В классическом варианте предлагается риск‑менеджмент с соотношением стоп‑лосс/тейк‑профит порядка 1:3 и риском на сделку не более 3% капитала, чтобы выдержать серию из 30–40 сделок.

Предпочтение отдаётся трейлинг‑стопу: после прохождения ценой 150–200 пунктов в прибыль стоп перетягивается в безубыток или зону профита, а сделка удерживается несколько дней, чтобы дать тренду максимум пространства.


Идея стратегии: классика RSI + грид

  • Основа — классическая логика RSI: период 14, уровни 70/30, работа по пробою зон перекупленности/перепроданности.
  • Вход на покупку: RSI выходит из перепроданности (ниже 30 → выше 30), вход на продажу — выход из перекупленности (выше 70 → ниже 70). В коде это уточнено допусловием «ломки» локального минимума/максимума (сравнение rsi и rsi).
  • Вместо жёсткого стоп‑лосса используется сеточное усреднение: первая сделка открывается базовым лотом, последующие — увеличенным объёмом через множитель iMultiplier, по мере движения цены против позиции.

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

 

Входы по RSI: формализация сигнала

Ключевой фрагмент:

 

if(::CopyBuffer(handle, 0, 0, 5, rsi) != -1)
  {
   ::ArraySetAsSeries(rsi, true);

   if(rsi[2] < rsi[1] && rsi[2] < iRSI_Dw_Lvl && rsi[1] > iRSI_Dw_Lvl)
     { ... Buy ... }

   if(rsi[2] > rsi[1] && rsi[2] > iRSI_Up_Lvl && rsi[1] < iRSI_Up_Lvl)
     { ... Sell ... }
  }
  • CopyBuffer(handle, 0, 0, 5, rsi) берёт 5 последних значений нулевого буфера RSI‑индикатора, начиная с текущего бара (shift 0).​

  • ArraySetAsSeries(rsi, true) разворачивает массив так, что rsi[0] — текущий бар, rsi[1] — предыдущий, rsi[2] — бар двумя свечами назад, что является стандартной практикой при работе с временными рядами в MQL5.​

Сигнал на покупку:

  • rsi[2] < rsi[1]: на истории формируется локальный минимум (осциллятор «заворачивает» вверх).

  • rsi[2] < iRSI_Dw_Lvl && rsi[1] > iRSI_Dw_Lvl: классический выход из перепроданности (пересечение уровня снизу вверх).​

  • Если покупок по этому магику ещё нет (b == 0) — открывается стартовая позиция с iStartLots и фиксированным TakeProfit в пунктах. Если позиции уже есть, включается логика сетки (см. ниже).

Сигнал на продажу задаётся зеркально, через локальный максимум и выход из зоны перекупленности.​

Таким образом, стратегия не реагирует на простое нахождение RSI в зоне 30/70, а ждёт именно импульса выхода, что уменьшает число ложных входов в затяжном боковике

Архитектура эксперта: CTrade, PositionInfo и хэндл индикатора

Инициализация (OnInit)

trade.SetExpertMagicNumber(iMagicNumber);
trade.SetDeviationInPoints(iSlippage);
trade.SetTypeFillingBySymbol(_Symbol);
trade.SetMarginMode();

handle = ::iRSI(_Symbol, PERIOD_CURRENT, iRSI_Period, PRICE_CLOSE);
  • Используется стандартный класс CTrade из Trade.mqh для унифицированной отправки и модификации сделок (Buy, Sell, PositionModify и т.д.).​

  • CPositionInfo даёт удобный доступ к информации по каждой позиции (тип, объём, цена открытия, магик, символ).​

  • Через trade.SetExpertMagicNumber и SetDeviationInPoints задаются магик и допустимое проскальзывание, что избавляет от ручной передачи этих параметров при каждом вызове функций торговли.​

  • Хэндл RSI создаётся один раз через iRSI, а сам индикатор живёт в отдельной области памяти; дальнейший доступ к значениям осуществляется через CopyBuffer.​

Такой объектный подход — современный стандарт MQL5: логика торговли сосредоточена в классе, индикаторы — как отдельные «чёрные ящики», а код эксперта остаётся компактным.

Учёт позиций и подготовка данных для сетки

В начале OnTick робот обходится по всем позициям:

int total = ::PositionsTotal(), b = 0, s = 0;
double BuyPricSumm = 0, SelPricSumm = 0, BuyLotsSumm = 0, SelLotsSumm = 0;
double BuyLowPrice = 0, SelHigtPrice = 0;
double BuyLowLots  = 0, SelHigtLots  = 0;
  • Для позиций с нужным Symbol() и Magic() считается:

    • суммарный лот по покупкам и продажам;

    • сумма PriceOpen * Volume для каждой стороны;

    • самая «дальняя» от текущей цены точка входа: минимальная цена покупки (BuyLowPrice) и максимальная цена продажи (SelHigtPrice) с соответствующими объёмами (BuyLowLotsSelHigtLots).

Эти данные используются:

  • для решения, когда добавлять очередное колено сетки (через сравнение с iStep),

  • для расчёта средней цены корзины и общего тейк‑профита.​

Сеточная логика: добавление позиций и средний TP 

Добавление колен сетки

Фрагмент для покупок:

if(b == 0)
   if(trade.Buy(iStartLots, _Symbol, Ask, 0, Ask + iTakeProfit * _Point, ""))
      return;

if(b > 0)
   if((BuyLowPrice - Ask) >= iStep * _Point)
      if(trade.Buy(BuyLowLots * iMultiplier))
         return;

  • Если покупок ещё нет (b == 0), открывается первая сделка:

    • объём — iStartLots,

    • тейк‑профит — Ask + iTakeProfit * _Point.

  • Если покупки уже есть (b > 0) и цена ушла против позиции минимум на iStep пунктов от худшего входа (BuyLowPrice - Ask >= iStep * _Point), открывается новая позиция:

    • объём наращивается как BuyLowLots * iMultiplier, т.е. множится объём самого «крайнего» ордера.

Для продаж применяется зеркальная формула, используя SelHigtPrice и SelHigtLots.​

Это классический пример фиксированного шага сетки с геометрическим ростом объёма, типичной для сеточных и мартингейл‑подобных систем.​

Расчёт средней цены и целевого тейк‑профита
double BuyAwerage = 0;
double SelAwerage = 0;

if(b >= 2)
   BuyAwerage = NormalizeDouble(BuyPricSumm / BuyLotsSumm + iProfitPlus * _Point, _Digits);
if(s >= 2)
   SelAwerage = NormalizeDouble(SelPricSumm / SelLotsSumm - iProfitPlus * _Point, _Digits);

  • Средняя цена покупки:

    • PriceavgBuy=∑ (BuyPriceOpen_i * BuyLot_i) / ∑BuyLot

    • К ней добавляется iProfitPlus пунктов, чтобы общий TP находился выше нулевой точки и корзина закрывалась с небольшим плюсом.

  • Для продаж средняя цена считается аналогично, а iProfitPlus вычитается, так как прибыль по sell возникает при падении цены ниже средней.​

  • NormalizeDouble(..., _Digits) приводит результат к нужному количеству знаков после запятой, что является обязательной практикой в MQL5 перед установкой цен SL/TP.


Модификация TP по всем позициям корзины
for(int i = 0; i < total; i++)
   if(posit.SelectByIndex(i))
      if(posit.Symbol() == _Symbol)
         if(posit.Magic() == iMagicNumber)
           {
            if(BuyAwerage > 0)
               if(posit.PositionType() == POSITION_TYPE_BUY)
                  if(NormalizeDouble(posit.TakeProfit(), _Digits) != BuyAwerage)
                     trade.PositionModify(posit.Ticket(), 0, BuyAwerage);

            if(SelAwerage > 0)
               if(posit.PositionType() == POSITION_TYPE_SELL)
                  if(NormalizeDouble(posit.TakeProfit(), _Digits) != SelAwerage)
                     trade.PositionModify(posit.Ticket(), 0, SelAwerage);
           }
  • Для каждой позиции нужного символа и magic робот:

    • если есть корректно посчитанный BuyAwerage/SelAwerage, сравнивает текущий TP позиции с целевым;

    • при расхождении вызывает trade.PositionModify(ticket, sl, tp) с новым TP (SL = 0, т.е. без стоп‑лосса).​

  • Таким образом, вся сетка закрывается по единой цене: как только рынок доходит до уровня средней цены +/− iProfitPlus, закрываются все колена, и корзина фиксирует общий результат.​

Такой подход характерен для грид‑роботов: главное — не отдельный ордер, а итог всей серии, поэтому TP привязывается к средневзвешенной позиции корзины.​

Риски и направления доработки для реала

Для тестера стратегия уже рабочая, но для реального счёта есть необходимость серьёзной доработки. На что стоит обратить внимание:

  • Проверки торговых условий брокера:

    • минимальный и максимальный лот, шаг лота, минимальная дистанция до TP/SL, стоп‑уровни и freeze‑уровни.​

    • при нарушении этих ограничений PositionModify и торговые операции могут возвращать ошибки (invalid stops, volume limit и т.п.).​

  • Обработка ошибок торгового сервера: логирование GetLastError(), повторные попытки при временных ошибках, фильтр торговых сессий и новостных пауз.​

  • Управление риском на уровне счёта:

    • ограничение максимального количества колен сетки и суммарного лота;

    • контроль допустимой просадки по equity и соотношения маржи/свободных средств.​

  • Оптимизация параметров:

    • подбор iRSI_Period, уровней iRSI_Up_Lvl/iRSI_Dw_LvliStepiMultiplieriTakeProfit и iProfitPlus по отдельным инструментам и таймфреймам через тестер и оптимизацию.​

Тогда эта реализация станет не только учебным примером работы с RSI и грид‑логикой в MQL5, но и основой для более продвинутого промышленного советника с контролируемым риском и гибкой фильтрацией входов.


Исходный код прикрепить не получается, по этому просто вставлю его в текст
//+------------------------------------------------------------------+
//|                                                  VR RSI Grid.mq5 |
//|                                      Copyright 2025, Trading-Go. |
//|                                           https://trading-go.ru/ |
//+------------------------------------------------------------------+
//| 🤖 Торговые роботы, индикаторы, скрипты                          |
//| 📲 Телеграмм группа: https://t.me/tradinggoea                    |
//|                                                                  |
//| 💥 Видео уроки, примеры, коды, логика, разбор                    |
//| 📲 Телеграмм группа: https://t.me/mql_master_group               |
//+------------------------------------------------------------------+

#property copyright "Copyright 2025, Trading-Go."
#property link      "https://trading-go.ru/"
#property version   "25.120"

#include <Trade\Trade.mqh>        CTrade        trade; // --- Подключение глобального класса для торговли
#include <Trade\PositionInfo.mqh> CPositionInfo posit; // --- Подключаем библиотеку класс PositionInfo

input double  iStartLots   = 0.01; // Start Lots
input double  iMultiplier  = 2.0;  // Multiplier
input double  iProfitPlus  = 30;   // Profit Plus
input int     iStep        = 200;  // Step
input int     iTakeProfit  = 200;  // Take Profit

input int     iRSI_Period  = 14;   // RSI Period
input double  iRSI_Up_Lvl  = 70;   // RSI Up lvl
input double  iRSI_Dw_Lvl  = 30;   // RSI Dw lvl

input int     iMagicNumber = 227;  // Magic Number
input int     iSlippage    = 30;   // Magic Number

int handle = -1;
double rsi[];
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
// --- Отправляем MagicNumber в CTrade
   trade.SetExpertMagicNumber(iMagicNumber);
// --- Отправляем Slippage в CTrade
   trade.SetDeviationInPoints(iSlippage);
// --- Отправляем Symbol() в CTrade
   trade.SetTypeFillingBySymbol(_Symbol);
// --- Указываем CTrade что тип счета Хедж
   trade.SetMarginMode();
// ---
   handle = ::iRSI(_Symbol, PERIOD_CURRENT, iRSI_Period, PRICE_CLOSE);
// ---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {

   int total = ::PositionsTotal(), b = 0, s = 0;
   double   BuyPricSumm = 0,   SelPricSumm = 0, BuyLotsSumm = 0, SelLotsSumm = 0;

   double   BuyLowPrice = 0,   SelHigtPrice = 0;
   double   BuyLowLots  = 0,   SelHigtLots  = 0;

   double   Bid = ::SymbolInfoDouble(_Symbol, SYMBOL_BID);
   double   Ask = ::SymbolInfoDouble(_Symbol, SYMBOL_ASK);

   for(int i = 0; i < total; i++)
      if(posit.SelectByIndex(i))
         if(posit.Symbol() == _Symbol)
            if(posit.Magic() == iMagicNumber)
               if(posit.PositionType() == POSITION_TYPE_BUY)
                 {
                  b++;
                  BuyPricSumm += posit.PriceOpen() * posit.Volume();
                  BuyLotsSumm += posit.Volume();

                  if(posit.PriceOpen() < BuyLowPrice || BuyLowPrice == 0)
                    {
                     BuyLowPrice = posit.PriceOpen();
                     BuyLowLots = posit.Volume();
                    }

                 }
               else
                  if(posit.PositionType() == POSITION_TYPE_SELL)
                    {
                     s++;
                     SelPricSumm += posit.PriceOpen() * posit.Volume();
                     SelLotsSumm += posit.Volume();

                     if(posit.PriceOpen() > SelHigtPrice || SelHigtPrice == 0)
                       {
                        SelHigtPrice = posit.PriceOpen();
                        SelHigtLots = posit.Volume();
                       }
                    }


   if(::CopyBuffer(handle, 0, 0, 5, rsi) != -1)
     {
     
      ::ArraySetAsSeries(rsi, true);

      if(rsi[2] < rsi[1] && rsi[2] < iRSI_Dw_Lvl && rsi[1] > iRSI_Dw_Lvl)
        {
         if(b == 0)
            if(trade.Buy(iStartLots, _Symbol, Ask, 0, Ask + iTakeProfit * _Point, ""))
               return;

         if(b > 0)
            if((BuyLowPrice - Ask) >= iStep * _Point)
               if(trade.Buy(BuyLowLots * iMultiplier))
                  return;
        }

      if(rsi[2] > rsi[1] && rsi[2] > iRSI_Up_Lvl && rsi[1] < iRSI_Up_Lvl)
        {
         if(s == 0)
            if(trade.Sell(iStartLots, _Symbol, Bid, 0, Bid - iTakeProfit * _Point, ""))
               return;

         if(s > 0)
            if((Bid - SelHigtPrice) >= iStep * _Point)
               if(trade.Sell(SelHigtLots * iMultiplier))
                  return;
        }
     }
// ===
// Расчет средних цен
// ===
   double BuyAwerage = 0;
   double SelAwerage = 0;
   
   if(b >= 2)
      BuyAwerage = NormalizeDouble(BuyPricSumm / BuyLotsSumm + iProfitPlus * _Point, _Digits);
   if(s >= 2)
      SelAwerage = NormalizeDouble(SelPricSumm / SelLotsSumm - iProfitPlus * _Point, _Digits);
// ===
// Модификация позиций
// ===
   for(int i = 0; i < total; i++)
      if(posit.SelectByIndex(i))
         if(posit.Symbol() == _Symbol)
            if(posit.Magic() == iMagicNumber)
              {
               if(BuyAwerage > 0)
                  if(posit.PositionType() == POSITION_TYPE_BUY)
                     if(NormalizeDouble(posit.TakeProfit(), _Digits) != BuyAwerage)
                        trade.PositionModify(posit.Ticket(), 0, BuyAwerage);

               if(SelAwerage > 0)
                  if(posit.PositionType() == POSITION_TYPE_SELL)
                     if(NormalizeDouble(posit.TakeProfit(), _Digits) != SelAwerage)
                        trade.PositionModify(posit.Ticket(), 0, SelAwerage);
              }
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {

  }
//+------------------------------------------------------------------+

💥 Видео уроки, примеры, коды, логика, разбор
📲 Телеграмм группа: t.me/mql_master_group



1 Комментарий
  • svgr
    Вчера в 18:46
    Не взлетит.
    Подгоните под 14, будут хорошими впоследствии 4 или 40, например. А при 14 жёсткий минус.
    Ну, и подгонять вверх и вниз нужно независимо, со своими параметрами каждое направление.

Активные форумы
Что сейчас обсуждают

Старый дизайн
Старый
дизайн