Эта стратегия сочетает классическую логику осциллятора RCI/RSI (период 14, уровни 30/70) с сеточной (grid) логикой усреднения и реализована в виде эксперта под MetaTrader 5 на MQL5 с использованием объектно‑ориентированного подхода и хэндла индикатора. Она рассчитана для тестера стратегий и требует доработки по рискам, проверкам и нормализациям перед запуском на реальном счёте.
Видео описание стратегии и разработки кода
Используется осциллятор с периодом 14 и уровнями 30 (перепроданность) и 70 (перекупленность), что является классическим набором настроек для RSI‑подобных индикаторов.
Сигнал на покупку: линия индикатора опускается ниже уровня 30, а затем пересекает его снизу вверх (RCI2 < 30 и RCI1 > 30), то есть фиксируется выход из зоны перепроданности.
Сигнал на продажу: линия индикатора поднимается выше 70 и затем пересекает этот уровень сверху вниз (RCI2 > 70 и RCI1 < 70), что трактуется как выход из зоны перекупленности.
Стратегия ориентирована на старшие таймфреймы (например, H1), где сигналы редкие, но более «чистые», и особенно эффективно работает по тренду, а не против него.
Базовая идея: открывать позиции только по чётким пересечениям 30/70, избегая «догадочных» входов, когда индикатор ещё не дал формальный сигнал.
В классическом варианте предлагается риск‑менеджмент с соотношением стоп‑лосс/тейк‑профит порядка 1:3 и риском на сделку не более 3% капитала, чтобы выдержать серию из 30–40 сделок.
Предпочтение отдаётся трейлинг‑стопу: после прохождения ценой 150–200 пунктов в прибыль стоп перетягивается в безубыток или зону профита, а сделка удерживается несколько дней, чтобы дать тренду максимум пространства.
Такая связка классического сигнального индикатора и грид‑логики делает стратегию наглядной для обучения: видно, где именно индикатор даёт вход и как математически вытаскивается серия убыточных ордеров.
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, а ждёт именно импульса выхода, что уменьшает число ложных входов в затяжном боковике
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) с соответствующими объёмами (BuyLowLots, SelHigtLots).
Эти данные используются:
для решения, когда добавлять очередное колено сетки (через сравнение с iStep),
для расчёта средней цены корзины и общего тейк‑профита.
Фрагмент для покупок:
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.
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_Lvl, iStep, iMultiplier, iTakeProfit и 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
Подгоните под 14, будут хорошими впоследствии 4 или 40, например. А при 14 жёсткий минус.
Ну, и подгонять вверх и вниз нужно независимо, со своими параметрами каждое направление.