Оптимизация и форвард-анализ стратегий (Часть 1): Метод Пардо — базовая модель
Введение
Вы написали стратегию, сконструировали индикатор и советника, получили красивую кривую в бэктесте — но остался главный вопрос: можно ли этому доверять в реальной торговле? Эта статья посвящена не просто реализации идей в коде MQL5, а формированию воспроизводимого рабочего пайплайна: от формализации торговой идеи до объективной пост‑оптимизационной валидации. Мы описываем конкретные артефакты и шаги, которые должен получить любой практикующий разработчик алгоритмов:
- чёткая формализация правил входа/выхода и риск‑менеджмента (индикатор + один или два советника);
- отбор значимых параметров и определение диапазонов сканирования;
- схема разбиения истории на in‑sample и out‑of‑sample (форвард) с критериями размеров окон;
- методика поиска «плоскогорных» (стабильных) настроек вместо хрупких лучших прогонов;
- регламент форвард‑тестирования, мультисредового тестирования (несколько рынков и таймфреймов) и пороги принятия решений.
Цель статьи — дать не набор волшебных фильтров, а практический тест‑план и процедуру, которая позволит понять, какие параметры действительно важны, как оценивать общую надёжность модели и как определить её «срок годности» и правила реоптимизации. В примерах показаны индикатор PardoSystem и советники PardoEA / Breakout_Bounce как учебные реализации этой методологии.
Разработка и структура торговой системы
Разработка торговой системы — это сложный процесс, состоящий из нескольких взаимосвязанных шагов. Вся эта процедура достаточно проста, если трейдер аккуратно и пунктуально выполняет каждый шаг, уделяя должное внимание его значимости.
Существует два подхода к созданию торговой системы. Первый подход использует логику и систематическую эмпирическую проверку: то есть, каждый шаг должен быть осмыслен, прежде чем начинать тщательное тестирование. Этот подход, который представлен и развит в данной статье, есть путь знания. Модели прибыльной торговли, рождающиеся в результате этого подхода, обеспечивают бесценное преимущество. Трейдер, торгующий по такой системе, обладает полным пониманием, почему и как данная торговая модель функционирует и является успешной. Простой пример этого подхода приведен в виде индикатора и двух торговых советников.
Чтобы понять результаты торговой модели, необходимо следовать семи шагам ее построения и тестирования:
1. формулировка торговой стратегии
2. запись ее правил в определенной форме
3. тестирование торговой стратегии
4. оптимизация торговой стратегии
5. торговля по этой стратегии
6. отслеживание эффективности трейдинга и ее сравнивнениес тестовой эффективностью
7. улучшение и совершенствование данной торговой стратегии
Каждый из этих шагов зависит от успешного выполнения предыдущего шага. Постоянная обратная связь, использование информации последующих шагов для возврата и корректировки более ранних шагов — это принципиальная составляющая данного подхода и одна из его самых сильных сторон. Если система хорошая, данный подход приводит к постоянной эволюции, усовершенствованию и улучшению выбранной торговой стратегии.
Формулировка торговой системы начинается с идеи. Без этого всё — просто гадание на кофейной гуще. Правила, образующие стратегию, должны быть изложены последовательно. Стратегия может быть простой или сложной, какой вы пожелаете, в конечном счете это не имеет значения. Для стратегии важны цельность и согласованность. В этой статье разберем и проведем оптимизацию нескольких торговых систем на пробой торгового диапазона.
Неполное определение всех правил стратегии — одна из самых распространенных ошибок при разработке систем. Особенно это справедливо в отношении начинающих трейдеров. Два минимальных требования для определения торговой стратегии — правило входа в рынок и правило выхода из рынка.
Обычно стратегия состоит из условий покупки и продажи, «зеркальных» по отношению друг к другу. Например, сигнал на покупку возникает при росте цены выше 3-дневного максимума, а сигнал на продажу — при пробитии ценой 3-дневного минимума. Это симметричная (зеркальная) система: условия для BUY и SELL абсолютно одинаковы, но смотрят в разные стороны (прорыв максимума/минимума за N баров).
Стратегия может также состоять из совершенно различных условий входа в покупку или продажу. Например, сигнал на покупку возникает при пробитии 5-дневного максимума, а сигнал на продажу — при падении 5-дневной скользящей средней ниже 20-дневной скользящей средней. Это асимметричная торговая система, где для BUY используется прорыв максимума, а для SELL — пересечение скользящих средних.
- Предварительное тестирование: раннее обнаружение фундаментальных недостатков, проверка базовой работоспособности, оценка первоначальной жизнеспособности идеи.
- Валидация мультитестированием: подтверждение работы в различных условиях, проверка согласованности результатов, оценка устойчивости стратегии.
- Результаты оптимизации: проверка стабильности параметров, оценка риска переоптимизации, определение надежности настроек.
- Форвардный анализ: проверка на непросмотренных данных, оценка способности к обобщению, тестирование вне выборки оптимизации.
- Реальная производительность: окончательная валидация в реальных условиях, проверка исполнения в живой торговле, оценка практической применимости.
Данный алгоритм гарантирует, что торговые системы проходят строгую валидацию перед реальным развертыванием и имеют четкий путь для непрерывного улучшения на основе реальных результатов.
Ключевые принципы:
- поэтапное развитие: от концепции к реальной торговле через проверенные этапы
- доказательный подход: каждое решение основано на тестировании и анализе
- управление рисками: минимизация потерь на каждом этапе
- адаптивность: способность совершенствоваться на основе опыта
- дисциплина: следование структурированной методологии
Разработка торговой системы — это сложный процесс, состоящий из нескольких взаимосвязанных шагов. Вся эта процедура достаточно проста, если трейдер аккуратно и пунктуально выполняет каждый шаг, уделяя должное внимание его значимости, ведь четкие правила и дисциплина — это залог успешной торговли. Мы постараемся воплотить эту философию в реальный код MQL5 и создадим не одну, а две полноценные торговые системы, каждая из которых является законченной стратегией с правилами входа и выхода.
Первая из них — трендовая торговая система, основанная на классическом индикаторе Envelopes (Конверты) и модернизированная с использованием фильтров от Роберта Пардо, автора знаменитой книги "Разработка, тестирование и оптимизация торговых систем" ("The Evaluation and Optimization of Trading Strategies"). Система разработана для торговли на открытии бара и состоит из индикатора PardoSystem.mq5 и советника PardoEA.mq5. Индикатор анализирует цены Open[0] (текущего формирующегося бара) и выставляет сигнал, а советник, синхронизированный с индикатором на баре №0, моментально открывает сделку по рыночной цене (Ask/Bid). В тестере стратегий, работающем по модели "OHLC", это идеально имитирует вход по цене открытия.
Описание и работа индикатора PardoSystem.mq5: торговля в направлении тренда
Система объединяет три мощных фильтра для отсеивания ложных входов:
- Прорыв канала Envelopes: цена открытия должна быть выше верхней полосы (для Buy) или ниже нижней полосы (для Sell).
- Подтверждение тренда: цена открытия текущего бара должна быть выше (для Buy) или ниже (для Sell) цены открытия предыдущего бара (простейший, но эффективный трендовый фильтр).
- Пробой локального экстремума (Пардо): цена открытия должна пробить максимум (для Buy) или минимум (для Sell), сформированный за последние N баров (параметр `InpLookbackBars`), исключая текущий и первый бары.
Индикатор PardoSystem
Внешние переменные (Input Parameters)
//--- INPUT PARAMETERS //--- ENVELOPES CHANNEL PARAMETERS input group "========== ENVELOPES CHANNEL PARAMETERS ==========" input int InpMAPeriod = 20; // MA Period for Envelopes input int InpMAShift = 0; // MA Shift input double InpDeviation = 0.25; // Deviation (0.25 = 25%) input ENUM_MA_METHOD InpMAMethod = MODE_SMA; // MA Method input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE; // Price for MA input color InpUpperColor = clrDodgerBlue; // Upper band color input color InpLowerColor = clrOrangeRed; // Lower band color input bool InpShowEnvelopes = true; // Show bands
Описание параметров Envelopes:
- InpMAPeriod — период скользящей средней для расчета канала: чем больше значение, тем медленнее реагирует канал
- InpDeviation — отклонение канала в процентах: например, 0.25 означает 25% от значения MA
- InpMAMethod — метод расчета скользящей средней (SMA, EMA, SMMA и др.)
- InpAppliedPrice — цена, используемая для расчета MA (CLOSE, OPEN, HIGH, LOW)
- InpUpperColor/InpLowerColor — цвета полос канала для визуального различия
- InpShowEnvelopes — можно отключить отображение полос, оставив только стрелки
Группа "Параметры системы Пардо"
//--- PARDO SYSTEM PARAMETERS input group "========== PARDO SYSTEM PARAMETERS ==========" input int InpLookbackBars = 5; // Баров для поиска MAX/MIN
Описание параметров Пардо:
-
InpLookbackBars — количество баров для поиска максимума и минимума (начиная с бара 2): определяет чувствительность системы к пробоям
Группа "Настройки отображения стрелок"
//--- SIGNAL ARROW SETTINGS input group "========== SIGNAL ARROW SETTINGS ==========" input color InpBuyArrowColor = clrBlue; // Цвет BUY стрелок input color InpSellArrowColor = clrRed; // Цвет SELL стрелок input int InpArrowSize = 2; // Размер стрелок input double InpArrowOffset = 15; // Смещение от цены OPEN
Описание настроек стрелок:
- InpBuyArrowColor/InpSellArrowColor — цвета стрелок для разных направлений
- InpArrowSize — толщина линий стрелок
- InpArrowOffset — смещение стрелки от цены OPEN в пунктах (чтобы стрелка не перекрывала цену)
//--- PERFORMANCE PARAMETERS input group "========== PERFORMANCE PARAMETERS ==========" input int InpMaxHistoryBars = 1000; // Max bars to display
Этот индикатор:
- визуализирует торговые условия на графике,
- рисует ключевые уровни и индикаторы,
- отображает сигналы стрелками на 0-м баре,
- показывает обучающий комментарий с расчетами,
- помогает понять, как работает система.
Функция OnInit() — инициализация индикатора
//+------------------------------------------------------------------+ //| Initialization function | //+------------------------------------------------------------------+ int OnInit() { Print("╔═══════════════════════════════════════════════════════════════════════╗"); Print("║ PardoSystem v8.2 ║"); Print("╠═══════════════════════════════════════════════════════════════════════╣"); Print("║ TRADING LOGIC: ║"); Print("║ 1. CHANNEL BREAKOUT: OPEN > UPPER (BUY) / OPEN < LOWER (SELL) ║"); Print("║ 2. TREND FILTER: OPEN[0] > OPEN[1] (BUY) / < (SELL) ║"); Print("║ 3. EXTREMUM BREAKOUT: MAX(High[2..", InpLookbackBars, "]) / MIN(Low[2..", InpLookbackBars, "]) ║"); Print("╠═══════════════════════════════════════════════════════════════════════╣"); Print("║ ARROWS ARE CALCULATED LIVE ON EACH TICK/NEW BAR ║"); Print("║ ONLY ONE SIGNAL PER BAR! ║"); Print("║ BUFFER 2 - BUY | BUFFER 3 - SELL ║"); Print("╚═══════════════════════════════════════════════════════════════════════╝"); //--- Bind buffers SetIndexBuffer(0, UpperBuffer, INDICATOR_DATA); SetIndexBuffer(1, LowerBuffer, INDICATOR_DATA); SetIndexBuffer(2, BuyArrowBuffer, INDICATOR_DATA); SetIndexBuffer(3, SellArrowBuffer,INDICATOR_DATA); //--- Envelopes bands plotting PlotIndexSetInteger(0, PLOT_DRAW_TYPE, InpShowEnvelopes ? DRAW_LINE : DRAW_NONE); PlotIndexSetInteger(0, PLOT_LINE_COLOR, InpUpperColor); PlotIndexSetInteger(0, PLOT_LINE_WIDTH, 2); PlotIndexSetInteger(1, PLOT_DRAW_TYPE, InpShowEnvelopes ? DRAW_LINE : DRAW_NONE); PlotIndexSetInteger(1, PLOT_LINE_COLOR, InpLowerColor); PlotIndexSetInteger(1, PLOT_LINE_WIDTH, 2); //--- BUY arrows (code 233 = up arrow) PlotIndexSetInteger(2, PLOT_DRAW_TYPE, DRAW_ARROW); PlotIndexSetInteger(2, PLOT_ARROW, 233); PlotIndexSetInteger(2, PLOT_LINE_WIDTH, InpArrowSize); PlotIndexSetInteger(2, PLOT_LINE_COLOR, InpBuyArrowColor); //--- SELL arrows (code 234 = down arrow) PlotIndexSetInteger(3, PLOT_DRAW_TYPE, DRAW_ARROW); PlotIndexSetInteger(3, PLOT_ARROW, 234); PlotIndexSetInteger(3, PLOT_LINE_WIDTH, InpArrowSize); PlotIndexSetInteger(3, PLOT_LINE_COLOR, InpSellArrowColor); //--- Create Envelopes indicator handle handleEnvelopes = iEnvelopes(_Symbol, _Period, InpMAPeriod, InpMAShift, InpMAMethod, InpAppliedPrice, InpDeviation); if(handleEnvelopes == INVALID_HANDLE) { Print("ERROR: Failed to create Envelopes handle! Error code: ", GetLastError()); return(INIT_FAILED); } IndicatorSetString(INDICATOR_SHORTNAME, indicatorName + " v8.2 FIXED"); lastBarTime = 0; return(INIT_SUCCEEDED); }Эта функция:
- выводит красивый баннер с описанием логики,
- привязывает буферы к индикатору,
- настраивает отображение (цвета, типы линий, символы стрелок),
- создает handle встроенного индикатора iEnvelopes
Функция OnCalculate() — главный мозг индикатора
//+------------------------------------------------------------------+ //| Main calculation function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { if(handleEnvelopes == INVALID_HANDLE) return(0); //--- Need at least InpLookbackBars+2 bars for extremum calculation if(rates_total < InpLookbackBars + 10) return(0); //--- Ensure Envelopes has enough data if(BarsCalculated(handleEnvelopes) < rates_total) return(0); //--- Copy Envelopes bands (both upper and lower) if(CopyBuffer(handleEnvelopes, 0, 0, rates_total, UpperBuffer) <= 0) return(0); if(CopyBuffer(handleEnvelopes, 1, 0, rates_total, LowerBuffer) <= 0) return(0); //--- Set series orientation (0 = newest bar) ArraySetAsSeries(UpperBuffer, true); ArraySetAsSeries(LowerBuffer, true); ArraySetAsSeries(BuyArrowBuffer, true); ArraySetAsSeries(SellArrowBuffer,true); ArraySetAsSeries(open, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); ArraySetAsSeries(time, true); //--- Determine calculation range // We recalculate only the last InpMaxHistoryBars for performance. // But we must clear arrows outside this range. int maxBars = MathMin(rates_total, InpMaxHistoryBars); //--- Reset all arrow buffers before recalculating (to avoid ghost signals) for(int i = 0; i < rates_total; i++) { BuyArrowBuffer[i] = EMPTY_VALUE; SellArrowBuffer[i] = EMPTY_VALUE; } //--- Main loop: calculate signals for all bars within history limit for(int bar = 0; bar < maxBars; bar++) { //--- Need enough future bars for extremum (bar+2 ... bar+InpLookbackBars) if(bar + InpLookbackBars + 2 >= rates_total) continue; //--- Get current and previous open prices double currOpen = open[bar]; double prevOpen = open[bar+1]; //--- Get Envelopes bands double upperEnv = UpperBuffer[bar]; double lowerEnv = LowerBuffer[bar]; //--- Validate bands if(upperEnv <= 0 || lowerEnv <= 0 || upperEnv <= lowerEnv) continue; //--- Calculate Pardo extremum: // MAX(High[bar+2] ... High[bar+InpLookbackBars]) // MIN(Low[bar+2] ... Low[bar+InpLookbackBars]) double maxHigh = high[bar+2]; double minLow = low[bar+2]; for(int i = 2; i <= InpLookbackBars; i++) { int idx = bar + i; if(idx >= rates_total) break; if(high[idx] > maxHigh) maxHigh = high[idx]; if(low[idx] < minLow) minLow = low[idx]; } //--- BUY signal conditions: // 1. Price OPEN above UPPER band // 2. Trend filter: current OPEN > previous OPEN // 3. Pardo breakout: current OPEN > maxHigh of lookback bars if(currOpen > upperEnv) { if(currOpen > prevOpen && currOpen > maxHigh) { // Place BUY arrow slightly below the OPEN price double arrowPrice = currOpen - (InpArrowOffset * _Point); BuyArrowBuffer[bar] = arrowPrice; } } //--- SELL signal conditions: // 1. Price OPEN below LOWER band // 2. Trend filter: current OPEN < previous OPEN // 3. Pardo breakout: current OPEN < minLow of lookback bars else if(currOpen < lowerEnv) { if(currOpen < prevOpen && currOpen < minLow) { // Place SELL arrow slightly above the OPEN price double arrowPrice = currOpen + (InpArrowOffset * _Point); SellArrowBuffer[bar] = arrowPrice; } } //--- If price is inside the channel -> no signal (already cleared by reset) } //--- Optional: log on new bar (commented to avoid spam) datetime currentBarTime = time[0]; if(currentBarTime != lastBarTime) { lastBarTime = currentBarTime; // Uncomment for debugging: // Print("New bar: ", TimeToString(currentBarTime), " | Total bars: ", rates_total); } return(rates_total); }
Эта функция:
- загружает данные Envelopes через CopyBuffer,
- устанавливает тайм серии для правильной индексации (бар 0 — текущий),
- при первом запуске очищает все буферы (чтобы не было артефактов),
- в основном цикле проходит по барам от нового к старому,
- рассчитывает новые сигналы по трем условиям,
- ограничивает отображение последними 1000 барами.
Индикатор отображает на графике три ключевых элемента:
- канал Envelopes (синяя верхняя полоса и красная нижняя) — зона нормальной волатильности
- синие стрелки вверх — сигналы на ПОКУПКУ (BUY)
- красные стрелки вниз — сигналы на ПРОДАЖУ (SELL)
ВАЖНО: На каждом баре может быть ТОЛЬКО ОДНА стрелка! Индикатор не ставит одновременно BUY и SELL на одном баре.
СИГНАЛ НА ПОКУПКУ (BUY): Цена ОТКРЫТИЯ бара > ВЕРХНЯЯ полоса Envelopes // Пробой канала вверх И OPEN[0] > OPEN[1] // Тренд вверх И OPEN[0] > MAX(High[2..N]) // Пробой максимума N баров (Пардо) СИГНАЛ НА ПРОДАЖУ (SELL): Цена ОТКРЫТИЯ бара < НИЖНЯЯ полоса Envelopes // Пробой канала вниз И OPEN[0] < OPEN[1] // Тренд вниз И OPEN[0] < MIN(Low[2..N]) // Пробой минимума N баров (Пардо)
Индикатор сначала проверяет положение цены относительно канала:
- если цена ВЫШЕ верхней полосы — проверяем условия на BUY
- если цена НИЖЕ нижней полосы — проверяем условия на SELL
- если цена ВНУТРИ канала — сигналов НЕТ
Это логично: пробой канала — отличный первичный фильтр.
/*
СТРУКТУРА БУФЕРОВ ИНДИКАТОРА:
Буфер 0 - Верхняя полоса Envelopes (UpperBuffer) - рисуется синей линией
Буфер 1 - Нижняя полоса Envelopes (LowerBuffer) - рисуется красной линией
Буфер 2 - BUY стрелки (BuyArrowBuffer) - значение != EMPTY_VALUE = сигнал
Буфер 3 - SELL стрелки (SellArrowBuffer) - значение != EMPTY_VALUE = сигнал
*/ Чёткую визуализацию индикатора можно посмотреть на графике, можно менять во внешних переменных цвета и размеры стрелок.
Торговые сигналы на открытие позиций представлены стрелками, окрашенными в синий (покупка) и красный (продажа) цвет:

Вот как индикатор отображает медвежий тренд:

Торговый советник PardoEA
Этот советник анализирует рынок по выбранной стратегии, автоматически открывает и закрывает позиции, управляет рисками (расчет лота, Stop Loss, Take Profit), ведёт журнал всех действий, а также работает на ЛЮБОМ таймфрейме (от M1 до D1).Математическая формула торговых сигналов:
- buy (покупка)
Open[0] > Open[1] // Тренд вверх И Open[0] > MAX(High[2..N]) // Прорыв максимума N баров И Open[0] > UpperEnvelopes // Выход за верхнюю полосу волатильности
- sell (продажа)
Open[0] < Open[1] // Тренд вниз И Open[0] < MIN(Low[2..N]) // Прорыв минимума N баров И Open[0] < LowerEnvelopes // Выход за нижнюю полосу волатильности
Советник работает по техническому индикатору iEnvelopes и получает с него исходные данные для обработки и укомплектования для принятия торговых решений. Он также синхронизирован для торговли по ценам открытия текущей торговой свечи (бара) с индексом "0".
Алгоритм работы советника
Внешние переменные (Input Parameters)
//--- INPUT PARAMETERS //--- ENVELOPES CHANNEL PARAMETERS input group "--- ENVELOPES CHANNEL PARAMETERS" input int InpMAPeriod = 20; // MA Period for channel input double InpDeviation = 0.25; // Channel deviation (0.25 = 25%) input ENUM_MA_METHOD InpMAMethod = MODE_SMA; // Moving average method input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE; // Price for MA calculation
Описание группы Envelopes:
- InpMAPeriod — период скользящей средней для расчета канала: чем больше значение, тем медленнее реагирует канал
- InpDeviation — отклонение канала в процентах: например, 0.25 означает 25% от значения MA; создает верхнюю и нижнюю границы
- InpMAMethod — метод расчета скользящей средней (SMA, EMA, SMMA и др.)
- InpAppliedPrice — цена, используемая для расчета MA (CLOSE, OPEN, HIGH, LOW и др.)
//--- PARDO SYSTEM PARAMETERS input group "--- PARDO SYSTEM PARAMETERS" input int InpLookbackBars = 5; // Depth for high/low search (N)
Описание группы Пардо:
-
InpLookbackBars — количество баров для поиска максимума и минимума: определяет чувствительность системы к пробоям.
//--- TRADING PARAMETERS input group "--- TRADING PARAMETERS" input bool buy = true; // Allow BUY trades input bool sell = true; // Allow SELL trades input double InpLotSize = 0.01; // Fixed position volume input int InpMagicNumber = 0; // Unique EA number input bool InpReverseClose = true; // Close on opposite signal
Описание торговых параметров:
- buy/sell — флаги разрешения торговли в каждом направлении. Можно отключать нежелательные сигналы.
- InpLotSize — фиксированный размер лота для всех сделок.
- InpMagicNumber — уникальный идентификатор советника. Позволяет ему "видеть" только свои позиции и не мешать другим советникам или ручной торговле.
- InpReverseClose — если true, при поступлении противоположного сигнала текущая позиция закрывается и открывается новая (разворот).
//--- RISK MANAGEMENT input group "--- RISK MANAGEMENT" input double InpStopLoss = 100; // Stop Loss (in points, 0 - off) input double InpTakeProfit = 200; // Take Profit (in points, 0 - off)
Описание риск-менеджмента:
- InpStopLoss — уровень защиты в пунктах от цены входа: например, 100 пунктов = 100 * Point
- InpTakeProfit — уровень фиксации прибыли в пунктах от цены входа
Функция OnInit() — инициализация советника
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { Print("PardoEA v8.4 | ", _Symbol, " ", EnumToString(_Period), " | MA=", InpMAPeriod, " Dev=", InpDeviation, " Lookback=", InpLookbackBars, " Lot=", InpLotSize, " SL/TP=", InpStopLoss, "/", InpTakeProfit); if(InpLotSize <= 0) { Print("ERROR: Lot size must be > 0"); return(INIT_PARAMETERS_INCORRECT); } trade.SetExpertMagicNumber(InpMagicNumber); trade.SetDeviationInPoints(0); ResetLastError(); handleEnvelopes = iEnvelopes(_Symbol, _Period, InpMAPeriod, 0, InpMAMethod, InpAppliedPrice, InpDeviation); if(handleEnvelopes == INVALID_HANDLE) { Print("ERROR: iEnvelopes failed, error ", GetLastError()); return(INIT_FAILED); } lastBarTime = iTime(_Symbol, _Period, 0); return(INIT_SUCCEEDED); }
Выводит подробную таблицу с параметрами советника:
- проверяет корректность входных параметров
- создает handle встроенного индикатора iEnvelopes с переданными параметрами
- инициализирует переменную последнего бара для отслеживания новых баров
- устанавливает магический номер для торговых операций
Функция OnTick() — основной цикл работы
//+------------------------------------------------------------------+ //| OnTick function | //+------------------------------------------------------------------+ void OnTick() { if(!IsNewBar()) return; int signal = GetSignal(); if(signal == 0) return; // no signal → silent string signalType = (signal == 1) ? "BUY" : "SELL"; Print("SIGNAL: ", signalType); //--- check permission if((signal == 1 && !buy) || (signal == -1 && !sell)) return; int currentPos = GetCurrentPositionType(); if(currentPos != 0) { if(InpReverseClose && currentPos != signal) { Print("Opposite signal -> closing position"); CloseAllPositions(); } else { return; // same direction or reverse disabled } } OpenPosition(signal); }
Назначение: вызывается на каждом тике, но выполняет действия только на новом баре. Получает сигнал, проверяет разрешения, управляет существующими позициями, открывает новые позиции.
Функция GetSignal() — мозг торговой системы
//+------------------------------------------------------------------+ //| Get trading signal (silent, no debug prints) | //+------------------------------------------------------------------+ int GetSignal() { int bars = iBars(_Symbol, _Period); if(bars < InpLookbackBars + 10) return(0); double open[], high[], low[]; ArraySetAsSeries(open, true); ArraySetAsSeries(high, true); ArraySetAsSeries(low, true); int needed = InpLookbackBars + 5; if(CopyOpen(_Symbol, _Period, 0, needed, open) < needed) return(0); if(CopyHigh(_Symbol, _Period, 0, needed, high) < needed) return(0); if(CopyLow(_Symbol, _Period, 0, needed, low) < needed) return(0); double open0 = open[0]; double open1 = open[1]; double upperEnv0, lowerEnv0; if(!GetEnvelopesValues(0, upperEnv0, lowerEnv0)) return(0); double maxHigh = high[2]; double minLow = low[2]; for(int i = 2; i < 2 + InpLookbackBars; i++) { if(i >= ArraySize(high)) break; if(high[i] > maxHigh) maxHigh = high[i]; if(low[i] < minLow) minLow = low[i]; } if(open0 > open1 && open0 > maxHigh && open0 > upperEnv0) return(1); // BUY if(open0 < open1 && open0 < minLow && open0 < lowerEnv0) return(-1); // SELL return(0); }
- копирует рыночные данные (OPEN, HIGH, LOW) для расчетов,
- получает значения Envelopes для текущего бара через GetEnvelopesValues(),
- рассчитывает максимум и минимум по системе Пардо (бары от 2 до 2+N),
- проверяет ВСЕ ТРИ УСЛОВИЯ ОДНОВРЕМЕННО на OPEN[0],
- возвращает: 1 — BUY, -1 — SELL, 0 — нет сигнала.
Все условия проверяются на текущем "0" баре (свече) — это обеспечивает мгновенную реакцию на торговый сигнал.
Функция GetEnvelopesValues() — работа с индикатором
//+------------------------------------------------------------------+ //| Get Envelopes values for specified bar | //+------------------------------------------------------------------+ bool GetEnvelopesValues(int barIndex, double &upperEnv, double &lowerEnv) { double upperBuffer[1], lowerBuffer[1]; ResetLastError(); int copiedUpper = CopyBuffer(handleEnvelopes, 0, barIndex, 1, upperBuffer); int copiedLower = CopyBuffer(handleEnvelopes, 1, barIndex, 1, lowerBuffer); if(copiedUpper == 1 && copiedLower == 1) { if(upperBuffer[0] != EMPTY_VALUE && lowerBuffer[0] != EMPTY_VALUE && upperBuffer[0] > 0 && lowerBuffer[0] > 0 && MathIsValidNumber(upperBuffer[0]) && MathIsValidNumber(lowerBuffer[0])) { upperEnv = upperBuffer[0]; lowerEnv = lowerBuffer[0]; return(true); } } return(false); }
Читает данные из буферов индикатора iEnvelopes через CopyBuffer:
- Буфер 0 — верхняя полоса Envelopes
- Буфер 1 — нижняя полоса Envelopes
- Проверяет корректность данных:
- Значение не равно EMPTY_VALUE
- Значение больше 0
- Значение является валидным числом (MathIsValidNumber)
- Возвращает true при успешном чтении
Функция OpenPosition() — открытие позиции
//+------------------------------------------------------------------+ //| Open position | //+------------------------------------------------------------------+ void OpenPosition(int signal) { double entryPrice = iOpen(_Symbol, _Period, 0); //--- OPEN[0] if(entryPrice <= 0) { Print("ERROR: Invalid open price"); return; } double sl, tp; string typeStr = (signal == 1) ? "BUY" : "SELL"; CalculateSLTP(entryPrice, signal, sl, tp); bool result = false; if(signal == 1) result = trade.Buy(InpLotSize, _Symbol, entryPrice, sl, tp, eaComment); else result = trade.Sell(InpLotSize, _Symbol, entryPrice, sl, tp, eaComment); if(result) { Print("SUCCESS: OPEN ", typeStr, " @ ", DoubleToString(entryPrice, _Digits), " | SL: ", DoubleToString(sl, _Digits), " | TP: ", DoubleToString(tp, _Digits)); } else { Print("ERROR: Opening ", typeStr, ": ", trade.ResultRetcodeDescription()); } return; }Эта функция:
- берет цену OPEN[0] для входа в позицию,
- рассчитывает SL и TP в пунктах от цены входа,
- открывает позицию через CTrade::Buy() или CTrade::Sell(),
- логирует результат сделки.
// СИНХРОНИЗАЦИЯ ВСЕХ ДАННЫХ ПРОИСХОДИТ АВТОМАТИЧЕСКИ: // 1. Рыночные данные (OPEN, HIGH, LOW) берутся напрямую из терминала // 2. Данные Envelopes берутся из встроенного индикатора iEnvelopes // 3. Все расчеты выполняются на ТЕКУЩЕМ БАРЕ 0 double buySignal[1], sellSignal[1]; // Получение значений Envelopes для ТЕКУЩЕГО бара (shift=0!) CopyBuffer(handleEnvelopes, 0, 0, 1, upperBuffer); // Верхняя полоса CopyBuffer(handleEnvelopes, 1, 0, 1, lowerBuffer); // Нижняя полоса // Проверка: значение != EMPTY_VALUE if(upperBuffer[0] != EMPTY_VALUE && lowerBuffer[0] != EMPTY_VALUE) { // Сигнал рассчитывается на основе OPEN[0] и полученных значений }
Характеристики работы советника:
| Характеристика | Описание |
|---|---|
| Мгновенная реакция | Программа считывает сигналы на текущем баре "0" и сразу открывает позицию |
| Технический анализ | Торгует тренд + пробой предыдущего максимума цены |
| Правильный риск - менеджмент | Настраиваемые уровни Stop Loss и Take Profit в пунктах цены от входа в позицию |
| Использование магического номера | Управляет только своими позициями, не вмешиваясь в другие |
| Безопасное закрытие позиций | Цикл от большего индекса к меньшему при итерации по списку позиций |
| Детальное логирование | Полная информация о каждой сделке в журнале "Эксперты" |
| Защита от повторных входов | Торгует только на новых барах (свечах) |
| Режим разворота | Автоматически закрывает противоположные позиции при новом торговом сигнале |
Вот пример работы торгового советника PardoEA:

Подробное логирование отображает ход торгов во вкладке "Эксперты" торгового терминала Metatrader 5.
Вы сами выбираете торговый таймфрейм и используете советника. Цель трейдинга — получение прибыли. Основными причинами, по которым торговая система помогает достигать этой цели — ее способность представить возможные количественные результаты, их проверяемость, объективность системы и ее согласованность.
Полное руководство по универсальной торговой стратегии Breakout_Bounce
Описание торгового алгоритма
ФАЗА 1: Анализ рынка и расчет уровней
- Старт на новой свече — система активируется при открытии каждого нового бара
- Расчет дневных уровней — определение High/Low за N предыдущих дней
- Получение текущих цен — актуальные значения Ask и Bid
- Анализ условий рынка — пользователь выбирает стратегию вручную
- BREAKOUT MODE — вход в направлении пробоя уровней
- BOUNCE MODE — вход в противоположном направлении (отбой)
Для BREAKOUT стратегии:
- BUY сигнал: Ask > Daily High Level (прорыв сопротивления вверх)
- SELL сигнал: Bid < Daily Low Level (прорыв поддержки вниз)
Для BOUNCE стратегии:
- BUY сигнал: Bid < Daily Low Level (отбой от поддержки вверх)
- SELL сигнал: Ask > Daily High Level (отбой от сопротивления вниз)
- Закрытие противоположных позиций — автоматическое закрытие перед открытием
- Проверка наличия открытой позиции — предотвращение дублирования
- Открытие позиции — BUY по Ask, SELL по Bid
- Установка SL/TP — расчет уровней SL и TP
- Рисование стрелок — синие для BUY, красные для SELL
- Обновление информации — отображение статистики на графике
- Непрерывный мониторинг — отслеживание открытой позиции
- Условия выхода — срабатывание TP, SL, новый противоположный сигнал
- Закрытие позиции — выполнение выхода из рынка
- Удаление графических объектов — очистка стрелок закрытых позиций
- Обновление статистики — расчет и отображение результатов
- Подготовка к следующей свече — сброс состояний и ожидание
КЛЮЧЕВЫЕ ОСОБЕННОСТИ АЛГОРИТМА
1. ДВОЙНАЯ СТРАТЕГИЧЕСКАЯ ЛОГИКА
- BREAKOUT (ПРОБОЙ УРОВНЕЙ) — для трендовых рынков, торговля по направлению прорыва уровней
- BOUNCE (ОТБОЙ ОТ УРОВНЕЙ) — для флетовых рынков, торговля на отскоке от ключевых уровней
- Ручной выбор пользователем
- Автоматический расчет SL/TP
- Предотвращение дублирования позиций
- Закрытие противоположных позиций
- Цветные стрелки входа
- Автоматическая очистка
- Детальная статистика на графике
- Четкая структура — логическое разделение на фазы
- Гибкость стратегий — две противоположные стратегии в одной системе
- Полная видимость — каждый шаг четко определен и визуализирован
- Надежность — множественные проверки перед открытием позиций
- Реализация системного подхода к управлению и мониторингу
ТОРГУЕМ ДВА "ЛИЦА" РЫНКА
| Режим1: пробой (BREAKOUT) | Режим 2: отбой (BOUNCE) |
|---|---|
| Эксплуатация фазы рынка "Сила продолжает движение" | Эксплуатация фазы рынка "Сопротивление устало" |
| Торгуем В НАПРАВЛЕНИИ пробоя | Торгуем ПРОТИВ пробоя |
| Покупаем, когда цена ВЫШЕ | Покупаем, когда цена НИЖЕ |
| Продаем, когда цена НИЖЕ | Продаем, когда цена ВЫШЕ |
Математическая модель
PERIOD_D1 в CalculateDailyLevels():
datetime today = iTime(_Symbol, PERIOD_D1, 0); CopyHigh(_Symbol, PERIOD_D1, 1, InpDayPeriod, highArray);
Уровни всегда рассчитываются по дневному таймфрейму.
InpDayPeriod = количество дней для поиска экстремума (2-60 дней).
Таймфрейм запуска (M1-H4) — только частота проверки условий.
Логика торговых сигналов:
Уровни = MAX(High[D1...DN]) и MIN(Low[D1...DN]) за N дней Где N = InpDayPeriod (оптимизируемый параметр: 3, 5, 7, 10, 14)ПРОБОЙ (breakout_bounce = true): BUY = Ask > DayHighLevel // Цена пробила дневной максимум вверх SELL = Bid < DayLowLevel // Цена пробила дневной минимум вниз ОТБОЙ (breakout_bounce = false): BUY = Bid < DayLowLevel // Цена ушла ниже минимума (ждем отскок вверх) SELL = Ask > DayHighLevel // Цена ушла выше максимума (ждем отскок вниз)
Примечание: в советнике считываем торговый сигнал на пробой/отбой от значения котировки символа с дневного таймфрейма. Переключение режимов — простота использования (одна переменная — две абсолютно разные стратегии). Внешние переменные "говорят сами за себя" (код подробно закомментирован).
//══════════════════════════════════════════════════════════════════════════════════ // GROUP 1: BASIC TRADING SETTINGS //══════════════════════════════════════════════════════════════════════════════════ input group "══════════════════ BASIC TRADING SETTINGS ═══════════════════" input double InpLotSize = 0.1; // Lot size input int InpMagicNumber = 1; // Magic number input int InpSlippage = 1000; // Slippage (points) //══════════════════════════════════════════════════════════════════════════════════ // GROUP 2: RISK MANAGEMENT //══════════════════════════════════════════════════════════════════════════════════ input group "══════════════════ RISK MANAGEMENT ═══════════════════" input double InpStopLoss = 200; // Stop Loss (points), 0 = no stop loss input double InpTakeProfit = 200; // Take Profit (points), 0 = no take profit //══════════════════════════════════════════════════════════════════════════════════ // GROUP 3: PARDO SYSTEM STRATEGY //══════════════════════════════════════════════════════════════════════════════════ input group "══════════════════ PARDO SYSTEM STRATEGY ═══════════════════" input bool breakout_bounce = true; // Strategy: true=Breakout, false=Bounce input int InpDayPeriod = 3; // Days period for extremes //══════════════════════════════════════════════════════════════════════════════════ // GROUP 4: VISUALIZATION AND LOGGING SETTINGS //══════════════════════════════════════════════════════════════════════════════════ input group "══════════════════ VISUALIZATION SETTINGS ═══════════════════" input bool InpEnableLogging = true; // Enable logging input bool InpEnableVisuals = true; // Enable visualization input color InpHighLineColor = clrDodgerBlue; // High line color input color InpLowLineColor = clrRed; // Low line color input color InpBuyArrowColor = clrBlue; // BUY arrows color input color InpSellArrowColor = clrRed; // SELL arrows color
input bool breakout_bounce = true; // true = BREAKOUT, false = BOUNCE
Математическая основа системы (расчет ключевых уровней)
DiyHighLevel = MAX(High[1..N дней]) DayLowLevel = MIN(Low[1..N дней]) Где N = InpDayPeriod (по умолчанию 3 дня)
Рассмотрим основные функции.
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize trading objects Trade.SetExpertMagicNumber(InpMagicNumber); Trade.SetDeviationInPoints(InpSlippage); Trade.SetTypeFilling(ORDER_FILLING_FOK); //--- Initialize symbol information if(!SymbolInfo.Name(_Symbol)) { Print("Error: Failed to initialize symbol " + _Symbol); return(INIT_FAILED); } SymbolInfo.RefreshRates(); //--- Initialize graphical objects (using "_" as prefix) highLineName = "_HIGH_" + _Symbol + "_" + IntegerToString(InpDayPeriod); lowLineName = "_LOW_" + _Symbol + "_" + IntegerToString(InpDayPeriod); //--- Display startup information Print(""); Print("--- BREAKOUT/BOUNCE SYSTEM v6.5 (PARDO METHODOLOGY)"); Print(""); Print("--- • Symbol: " + _Symbol); Print("--- • Timeframe: " + EnumToString(_Period)); Print("--- • Strategy Mode: " + (breakout_bounce ? "BREAKOUT" : "BOUNCE")); Print("--- • Day Period: " + IntegerToString(InpDayPeriod)); Print("--- • Lot Size: " + DoubleToString(InpLotSize, 2)); Print("--- • SL/TP: " + DoubleToString(InpStopLoss, 0) + "/" + DoubleToString(InpTakeProfit, 0) + " pts"); Print("--- • Magic Number: " + IntegerToString(InpMagicNumber)); Print("--- • Visualization: " + (InpEnableVisuals ? "ON" : "OFF")); Print("--- • Arrows: BLUE for BUY, RED for SELL"); Print(""); if(breakout_bounce) { Print("--- ENTRY CONDITIONS (BREAKOUT MODE):"); Print("--- • BUY: Ask > Daily High (Breakout upward)"); Print("--- • SELL: Bid < Daily Low (Breakout downward)"); } else { Print("--- ENTRY CONDITIONS (BOUNCE MODE):"); Print("--- • BUY: Bid < Daily Low (Expect bounce up from level)"); Print("--- • SELL: Ask > Daily High (Expect bounce down from level)"); } Print(""); return(INIT_SUCCEEDED); }Эта функция отвечает за подготовку системы к работе, настройку всех параметров. Она:
- настраивает торговый объект CTrade с магическим номером,
- создает уникальные имена для графических объектов,
- выводит подробный баннер с настройками стратегии,
- показывает условия входа для выбранного режима.
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Check for new bar (only one signal per bar) if(!IsNewBar()) return; //--- Update status currentStatus = "WAITING"; //--- Check trading context if(!CheckTradeContext()) return; //--- Calculate daily levels if(!CalculateDailyLevels()) { currentStatus = "CALC_ERROR"; UpdateChartInfo(); return; } //--- Visualize levels (only in real trading) if(InpEnableVisuals && !MQLInfoInteger(MQL_TESTER)) UpdateVisualLevels(); //--- Get current prices SymbolInfo.RefreshRates(); double currentAsk = SymbolInfo.Ask(); double currentBid = SymbolInfo.Bid(); //--- Get trading signal int signal = GetTradingSignal(currentAsk, currentBid); //--- Process signal switch(signal) { case 1: // BUY signal ProcessBuySignal(currentAsk); break; case -1: // SELL signal ProcessSellSignal(currentBid); break; default: // No signal lastBuySignal = false; lastSellSignal = false; currentStatus = "NO_SIGNAL"; break; } //--- Update chart information UpdateChartInfo(); return; }
Эта функция:
- работает ТОЛЬКО на новом "баре" (свече) — защита от множественных входов,
- проверяет возможность торговли,
- рассчитывает актуальные уровни,
- визуализирует уровни на графике,
- получает сигнал на основе выбранной стратегии,
- исполняет сделку при наличии сигнала,
- обновляет информационную панель.
Функция CalculateDailyLevels() — расчет ключевых уровней
//+------------------------------------------------------------------+ //| Daily level calculation (key function) | //+------------------------------------------------------------------+ bool CalculateDailyLevels() { //--- Check: levels already calculated for today? datetime today = iTime(_Symbol, PERIOD_D1, 0); if(dayLevelsDate == today && dayHighLevel > 0 && dayLowLevel > 0) return(true); // Levels are current //--- Copy high data for the specified number of days double highArray[]; int copiedHigh = CopyHigh(_Symbol, PERIOD_D1, 1, InpDayPeriod, highArray); //--- Copy low data for the specified number of days double lowArray[]; int copiedLow = CopyLow(_Symbol, PERIOD_D1, 1, InpDayPeriod, lowArray); //--- Check if data was copied successfully if(copiedHigh <= 0 || copiedLow <= 0) { Print("Error: Insufficient data for level calculation"); return(false); } //--- Find maximum high and minimum low over the period dayHighLevel = highArray[0]; dayLowLevel = lowArray[0]; for(int i = 1; i < copiedHigh; i++) { if(highArray[i] > dayHighLevel) dayHighLevel = highArray[i]; } for(int i = 1; i < copiedLow; i++) { if(lowArray[i] < dayLowLevel) dayLowLevel = lowArray[i]; } //--- Normalize to correct number of digits dayHighLevel = NormalizeDouble(dayHighLevel, _Digits); dayLowLevel = NormalizeDouble(dayLowLevel, _Digits); dayLevelsDate = today; //--- Log if enabled if(InpEnableLogging) { Print("--- Levels updated: High=" + DoubleToString(dayHighLevel, _Digits) + " Low=" + DoubleToString(dayLowLevel, _Digits) + " for " + IntegerToString(InpDayPeriod) + " days"); } return(true); }
Функция:
- проверяет, не рассчитаны ли уже уровни для текущего дня,
- копирует данные HIGH и LOW за указанное количество дней,
- находит МАКСИМАЛЬНЫЙ High и МИНИМАЛЬНЫЙ Low за период,
- сохраняет уровни и дату расчета.
GetTradingSignal() — интеллект системы: генерация сигналов
int GetTradingSignal(double ask, double bid) { //--- РЕЖИМ BREAKOUT (ПРОБОЙ) if(breakout_bounce) { // BREAKOUT: Вход в направлении пробоя bool buySignal = (ask > dayHighLevel); // Пробой максимума вверх с дневного таймфрейма bool sellSignal = (bid < dayLowLevel); // Пробой минимума вниз с данных с дневного таймфрейма return ProcessSignal(buySignal, sellSignal, ask, bid, "BREAKOUT"); } else //--- РЕЖИМ BOUNCE (ОТБОЙ) { // BOUNCE: Вход в противоположном направлении bool buySignal = (bid < dayLowLevel); // Цена ушла ниже минимума (ожидаем отбой вверх) bool sellSignal = (ask > dayHighLevel); // Цена ушла выше максимума (ожидаем отбой вниз) return ProcessSignal(buySignal, sellSignal, ask, bid, "BOUNCE"); } }
Генерирует сигналы в зависимости от выбранной стратегии.
- Определяет выбранный режим стратегии
- Для BREAKOUT:
- BUY при пробое максимума (Ask выше уровня)
- SELL при пробое минимума (Bid ниже уровня)
- Для BOUNCE:
- BUY при падении ниже минимума (ожидание отскока вверх)
- SELL при росте выше максимума (ожидание отскока вниз)
- Вызывает ProcessSignal для обработки условий
Функция ProcessSignal() — обработка сигнала
//+------------------------------------------------------------------+ //| Process signal | //+------------------------------------------------------------------+ int ProcessSignal(bool buyCondition, bool sellCondition, double ask, double bid, string strategy) { if(buyCondition && sellCondition) return(0); // Signal conflict //--- Process BUY signal if(buyCondition && !lastBuySignal) { totalSignals++; lastBuySignal = true; lastSellSignal = false; if(InpEnableLogging) Print("--- SIGNAL: " + strategy + " BUY | Price: " + DoubleToString(ask, _Digits) + " | Level: " + DoubleToString(dayHighLevel, _Digits)); return(1); } //--- Process SELL signal if(sellCondition && !lastSellSignal) { totalSignals++; lastSellSignal = true; lastBuySignal = false; if(InpEnableLogging) Print("--- SIGNAL: " + strategy + " SELL | Price: " + DoubleToString(bid, _Digits) + " | Level: " + DoubleToString(dayLowLevel, _Digits)); return(-1); } //--- Reset flags if conditions disappeared if(!buyCondition) lastBuySignal = false; if(!sellCondition) lastSellSignal = false; return(0); }
Эта функция:
- проверяет конфликт сигналов (одновременный BUY и SELL),
- предотвращает повторные сигналы через флаги lastBuySignal/lastSellSignal,
- увеличивает счетчик сигналов,
- логирует сигнал при включенном логировании,
- возвращает код сигнала: 1 = BUY, -1 = SELL, 0 = нет сигнала.
Функция ProcessBuySignal() — исполнение BUY сделки
//+------------------------------------------------------------------+ //| Process BUY signal | //+------------------------------------------------------------------+ void ProcessBuySignal(double entryPrice) { //--- Close opposite positions (SELL) ClosePositionsByType(POSITION_TYPE_SELL); //--- Check for existing BUY position if(CountPositionsByType(POSITION_TYPE_BUY) > 0) return; //--- Calculate SL and TP double sl = CalculateStopLoss(entryPrice, POSITION_TYPE_BUY); double tp = CalculateTakeProfit(entryPrice, POSITION_TYPE_BUY); //--- Create trade comment string strategyTag = breakout_bounce ? "BREAKOUT" : "BOUNCE"; string comment = StringFormat("%s_B|D%d|S#%d", strategyTag, InpDayPeriod, totalSignals); //--- Open position currentStatus = strategyTag + "_BUY_PROCESSING"; if(Trade.Buy(InpLotSize, _Symbol, entryPrice, sl, tp, comment)) { ulong ticket = Trade.ResultOrder(); totalTrades++; currentStatus = strategyTag + "_BUY_OPENED"; //--- Draw blue BUY arrow (using "_" prefix) if(InpEnableVisuals && !MQLInfoInteger(MQL_TESTER)) { string arrowName = "_BUY_" + IntegerToString(ticket); if(ObjectCreate(0, arrowName, OBJ_ARROW_BUY, 0, TimeCurrent(), entryPrice)) { ObjectSetInteger(0, arrowName, OBJPROP_COLOR, InpBuyArrowColor); ObjectSetInteger(0, arrowName, OBJPROP_WIDTH, 2); ObjectSetInteger(0, arrowName, OBJPROP_BACK, false); ObjectSetInteger(0, arrowName, OBJPROP_SELECTABLE, false); Print("--- BLUE BUY arrow drawn: " + arrowName); } } if(InpEnableLogging) Print("--- SUCCESS: " + strategyTag + " BUY #" + IntegerToString(ticket) + " | Price: " + DoubleToString(entryPrice, _Digits)); } else { currentStatus = "BUY_ERROR"; Print("--- Error opening BUY: " + Trade.ResultRetcodeDescription()); } return; }Функция:
- закрывает противоположные позиции (SELL), если они есть,
- проверяет, нет ли уже открытой BUY позиции,
- рассчитывает уровни Stop Loss и Take Profit,
- формирует информативный комментарий для сделки,
- открывает позицию через Trade.Buy(),
- рисует синюю стрелку BUY на графике,
- логирует успешное открытие.
UpdateVisualLevels() — визуальное воплощение
//+------------------------------------------------------------------+ //| Visualize levels on chart | //+------------------------------------------------------------------+ void UpdateVisualLevels() { if(!InpEnableVisuals) return; //--- High line if(ObjectFind(0, highLineName) < 0 || dayHighLevel != ObjectGetDouble(0, highLineName, OBJPROP_PRICE)) { ObjectDelete(0, highLineName); if(ObjectCreate(0, highLineName, OBJ_HLINE, 0, 0, dayHighLevel)) { ObjectSetInteger(0, highLineName, OBJPROP_COLOR, InpHighLineColor); ObjectSetInteger(0, highLineName, OBJPROP_STYLE, STYLE_DASH); ObjectSetInteger(0, highLineName, OBJPROP_WIDTH, 2); ObjectSetString(0, highLineName, OBJPROP_TEXT, "Daily High (" + IntegerToString(InpDayPeriod) + "d)"); ObjectSetInteger(0, highLineName, OBJPROP_BACK, true); } } //--- Low line if(ObjectFind(0, lowLineName) < 0 || dayLowLevel != ObjectGetDouble(0, lowLineName, OBJPROP_PRICE)) { ObjectDelete(0, lowLineName); if(ObjectCreate(0, lowLineName, OBJ_HLINE, 0, 0, dayLowLevel)) { ObjectSetInteger(0, lowLineName, OBJPROP_COLOR, InpLowLineColor); ObjectSetInteger(0, lowLineName, OBJPROP_STYLE, STYLE_DASH); ObjectSetInteger(0, lowLineName, OBJPROP_WIDTH, 2); ObjectSetString(0, lowLineName, OBJPROP_TEXT, "Daily Low (" + IntegerToString(InpDayPeriod) + "d)"); ObjectSetInteger(0, lowLineName, OBJPROP_BACK, true); } } return; }
Эта функция:
- рисует горизонтальную линию на уровне дневного максимума,
- рисует горизонтальную линию на уровне дневного минимума,
- автоматически обновляет линии при изменении уровней,
- добавляет подписи к линиям.
Визуальные преимущества:
- автоматическое обновление — уровни меняются каждый день
- хороший дизайн — четкие пунктирные линии
- информативные подписи — понятно даже новичкам
- фоновая отрисовка — не мешает анализу графика
ProcessBuySignal() / ProcessSellSignal() — торговые решения
//+------------------------------------------------------------------+ //| Process SELL signal | //+------------------------------------------------------------------+ void ProcessSellSignal(double entryPrice) { //--- Close opposite positions (BUY) ClosePositionsByType(POSITION_TYPE_BUY); //--- Check for existing SELL position if(CountPositionsByType(POSITION_TYPE_SELL) > 0) return; //--- Calculate SL and TP double sl = CalculateStopLoss(entryPrice, POSITION_TYPE_SELL); double tp = CalculateTakeProfit(entryPrice, POSITION_TYPE_SELL); //--- Create trade comment string strategyTag = breakout_bounce ? "BREAKOUT" : "BOUNCE"; string comment = StringFormat("%s_S|D%d|S#%d", strategyTag, InpDayPeriod, totalSignals); //--- Open position currentStatus = strategyTag + "_SELL_PROCESSING"; if(Trade.Sell(InpLotSize, _Symbol, entryPrice, sl, tp, comment)) { ulong ticket = Trade.ResultOrder(); totalTrades++; currentStatus = strategyTag + "_SELL_OPENED"; //--- Draw red SELL arrow (using "_" prefix) if(InpEnableVisuals && !MQLInfoInteger(MQL_TESTER)) { string arrowName = "_SELL_" + IntegerToString(ticket); if(ObjectCreate(0, arrowName, OBJ_ARROW_SELL, 0, TimeCurrent(), entryPrice)) { ObjectSetInteger(0, arrowName, OBJPROP_COLOR, InpSellArrowColor); ObjectSetInteger(0, arrowName, OBJPROP_WIDTH, 2); ObjectSetInteger(0, arrowName, OBJPROP_BACK, false); ObjectSetInteger(0, arrowName, OBJPROP_SELECTABLE, false); Print("--- RED SELL arrow drawn: " + arrowName); } } if(InpEnableLogging) Print("--- SUCCESS: " + strategyTag + " SELL #" + IntegerToString(ticket) + " | Price: " + DoubleToString(entryPrice, _Digits)); } else { currentStatus = "SELL_ERROR"; Print("--- Error opening SELL: " + Trade.ResultRetcodeDescription()); } return; }
Умные фичи торгового модуля:
- автоматическое закрытие противоположных позиций — хеджирование рисков
- предотвращение повторных входов — одна позиция на сигнал
- динамические SL/TP — расчет в пунктах, а не в ценах
- детальные комментарии — полная история в один клик
- визуальные метки — стрелки для ручной проверки
UpdateChartInfo() — визуальное отображение на графике
//+------------------------------------------------------------------+ //| Update chart information | //+------------------------------------------------------------------+ void UpdateChartInfo() { //--- Check and delete arrows for closed positions CheckAndDeleteArrows(); //--- Calculate current profit double currentProfit = CalculateCurrentProfit(); //--- Create information panel string modeText = breakout_bounce ? "BREAKOUT" : "BOUNCE"; string profitText = (currentProfit >= 0) ? "Profit: +$" + DoubleToString(currentProfit, 2) : "Loss: -$" + DoubleToString(MathAbs(currentProfit), 2); string info = "╔═══════════ " + modeText + " v6.5 (PARDO) ═══════════╗\n" + "║ Status: " + currentStatus + "\n" + "║ Time: " + TimeToString(TimeCurrent(), TIME_SECONDS) + "\n" + "║ Magic: " + IntegerToString(InpMagicNumber) + "\n" + "╠═══════════════════════════════════════════════╣\n" + "║ TRADING LEVELS (LAST " + IntegerToString(InpDayPeriod) + " DAYS):\n" + "║ • Daily High: " + DoubleToString(dayHighLevel, _Digits) + "\n" + "║ • Daily Low: " + DoubleToString(dayLowLevel, _Digits) + "\n" + "╠═══════════════════════════════════════════════╣\n" + "║ STATISTICS:\n" + "║ • Signals: " + IntegerToString(totalSignals) + "\n" + "║ • Trades: " + IntegerToString(totalTrades) + "\n" + "║ • Open: " + IntegerToString(CountPositions()) + "\n" + "╠═══════════════════════════════════════════════╣\n" + "║ " + profitText + "\n" + "╠═══════════════════════════════════════════════╣\n" + "║ SIGNALS FOR " + modeText + " MODE:\n" + "║ • " + (breakout_bounce ? "BUY: Ask > Daily High (BREAKOUT ↑)" : "BUY: Bid < Daily Low (BOUNCE ↑)") + "\n" + "║ • " + (breakout_bounce ? "SELL: Bid < Daily Low (BREAKOUT ↓)" : "SELL: Ask > Daily High (BOUNCE ↓)") + "\n" + "║ • ARROWS: BLUE = BUY, RED = SELL\n" + "╚═══════════════════════════════════════════════╝"; Comment(info); return; }
Она:
- вызывает CheckAndDeleteArrows() для очистки графика,
- рассчитывает текущую прибыль/убыток по открытым позициям,
- формирует красивую информационную панель с рамкой,
- показывает статус, время, уровни, статистику и прибыль,
- отображает условия входа для текущего режима,
- обновляет информацию на каждом баре.
Информационная панель:
- Режим системы — сразу видно BREAKOUT или BOUNCE
- Ключевые уровни — текущие DayHigh/DayLow
- Статистика — сигналы, сделки, открытые позиции
- Время и магик — полный контроль
- Красивый дизайн — символы псевдографики
Функция OnDeinit() — полная очистка
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Remove main graphical objects (level lines) if(ObjectFind(0, highLineName) >= 0) ObjectDelete(0, highLineName); if(ObjectFind(0, lowLineName) >= 0) ObjectDelete(0, lowLineName); //--- Remove ALL objects with "_" prefix (arrows) DeleteAllObjectsWithUnderscore(); //--- Display final statistics PrintFinalStats(reason); return; }
Удаляет горизонтальные линии уровней, а также вызывает DeleteAllPardoObjects() для удаления всех стрелок и выводит подробную финальную статистику работы.
Функция DeleteAllObjectsWithUnderscore() — массовое удаление
//+------------------------------------------------------------------+ //| Delete all objects with "_" prefix | //+------------------------------------------------------------------+ void DeleteAllObjectsWithUnderscore() { int totalObjects = ObjectsTotal(0); int deleted = 0; for(int i = totalObjects - 1; i >= 0; i--) { string objName = ObjectName(0, i); //--- Look for objects containing "_" if(StringFind(objName, "_") >= 0) { ObjectDelete(0, objName); deleted++; } } if(InpEnableLogging && deleted > 0) Print("--- Objects with '_' deleted: " + IntegerToString(deleted)); return; }Функция проходит по всем объектам на графике, находит объекты с префиксом "_" и удаляет их (стрелки и линии), логирует количество удаленных объектов.
Характеристики и преимущества торгового советника
| Характеристика | Описание |
|---|---|
| Две стратегии в одном | Переключение одной переменной между пробоем и отбоем |
| Визуализация уровней | Автоматическая отрисовка ключевых уровней на графике |
| Цветные стрелки | Синие = BUY, Красные = SELL, привязаны к тикетам |
| Авто очистка стрелок | Стрелки удаляются при закрытии позиций |
| Информационная панель | Все данные о торговле на графике |
| Защита от спама | Только один сигнал на бар |
| Работа на любых ТФ | От M1 |
Работа торгового советника с подробными комментариями представлена на следующем рисунке (синим и красным цветом горизонтальные уровни это уровни пробоя цены для входа в позицию):

Тестирование и оптимизация торговой системы по методу Роберта Прадо
Технология оптимизации относительно проста, но требует аккуратности. Она включает пять элементов: выбор параметров и их диапазонов, определение объёма выборки, метод оценки модели и критерий итогового тестирования.
В оптимизации следует использовать только значимые параметры. Если влияние параметра несущественно, его лучше зафиксировать или исключить. При неизвестной значимости параметры проверяют через сканирование диапазона: существенные изменения эффективности указывают на важность, слабые — на второстепенность.
Оптимизацию начинают с отбора ключевых переменных и задания корректных диапазонов. Также важно выбрать достаточный объём данных, чтобы охватить разные рыночные условия, и использовать адекватный метод оценки устойчивости модели.
Для проверки универсальности модель тестируют на разных рынках: чем шире охват, тем выше её устойчивость и статистическая надёжность. Модели, работающие только на одном рынке, вызывают сомнения, если они не были специально под него разработаны. Также проводится тестирование на разных временных интервалах.
Рыночные условия постоянно меняются — тренды, волатильность и ликвидность. Если модель работает нестабильно на разных периодах, это может указывать либо на неблагоприятные условия, либо на недостатки модели.
Форвардный тест
Форвардный тест состоит из двух этапов. Сначала проводится оптимизация и выбираются лучшие параметры. Затем они проверяются на новом участке данных, что имитирует реальную торговлю.
Таким образом, модель обучается на одном отрезке истории и тестируется на другом. Это вневыборочное тестирование, позволяющее объективно оценить её эффективность.
Размер оптимизационного окна подбирается эмпирически. На срок актуальности модели влияют волатильность, структура трендов и полнота модели. Обычно модель, обученная на двухлетних данных, работает 3–6 месяцев; на годовых — 1–2 месяца; на шестимесячных — 2–4 недели.
Форвардное окно обычно составляет 10–20% от оптимизационного периода.
Оптимизация в тестере торговых стратегий — от хаоса к порядку
Далее аналогично строгий тест следует применить к выбранным инструментам из тестовой корзины по различным секторам рынка. После этого можно оценить общие результаты.
Если форвардный анализ по всему набору тестов оказался убыточным, необходимо проверить структуру тестирования на наличие ошибок. При обнаружении ошибок их следует исправить и повторить анализ. Если же ошибок нет, от модели стоит отказаться — её предыдущие результаты были следствием оптимизации, а сама она неустойчива.
Если форвардный анализ показывает лишь предельно допустимую прибыльность, также следует проверить корректность теста. В случае обнаружения ошибок их нужно устранить и провести повторный анализ. Если ошибок нет, слабые результаты указывают на посредственное качество модели. Тем не менее, её характеристики могут быть полезны в составе диверсифицированного портфеля.
Если же форвардный анализ на всей корзине рынков или её части демонстрирует уверенную прибыльность, модель можно считать работоспособной. Она прошла один из самых строгих этапов проверки, и её можно использовать в торговле, сохраняя при этом необходимую осторожность.
Итак, перейдем к самому интересному в этом процессе — оптимизации значений торговых параметров внешних переменных.
Рекомендации по оптимизации
| Параметр | Диапазон | Шаг | Пояснение |
|---|---|---|---|
| InpDayPeriod | 2 — 60 | 2 | количество дней для поиска уровней (шаг 2 для ускорения) |
| InpStopLoss | 100 — 1000 | 100 | стоп лосс в пунктах (для 5-знака = 10-100 пипсов) |
| InpTakeProfit | 200 — 2000 | 200 | тейк профит в пунктах (соотношение 1:2 к SL) |
| breakout_bounce | true / false | - | тестируем отдельно два режима |
Ниже приведены три периода тестирования с пересекающимися временными окнами. Цель — отработать оптимизацию и форвард-проверку, а также выбрать лучший оптимизационный проход. При необходимости число периодов можно увеличить до 10. Для более качественной проработки, в этом примере 3 периода:
| Период | Оптимизация (In-Sample) | Форвард (Out-of-Sample) |
|---|---|---|
| 1 | 2020.01.01 - 2023.12.31 (48 мес) | 2024.01.01 - 2024.12.31 (25%) |
| 2 | 2021.01.01 - 2024.12.31 (48 мес) | 2025.01.01 - 2025.12.31 (25%) |
| 3 | 2022.01.01 - 2025.12.31 (48 мес) | 2026.01.01 - 2026.03.31 (по текущее время не более 25%) |
Теперь когда у нас есть карта переменных и календарь, мы проводим сам обряд.
ШАГ 1: Подготовка
- Убедитесь, что советник скомпилирован и в папке MQL5/Experts/Advisors, выбираем советник, торговый символ,
- Откройте тестер стратегий (Ctrl+R)
- Выберите символ, период, диапазон дат
ШАГ 2: Настройка тестирования
Режим: «По ценам открытия». Период: M15/H1. Диапазон: последние 1–2 года. Депозит: 10 000 USD. Критерии оптимизации: прибыльность (общая прибыль), фактор прибыли (Profit Factor > 1.5), максимальная просадка (< 20%), количество сделок (минимум 100 для статистики), средняя прибыль на сделку.
Валидация результатов. In-sample оптимизация: 70% данных Out-of-sample тест: до 20-30% свежих данных Forward тестирование: на текущих данных Тест на разных символах: EURUSD, GBPUSD, XAUUSD Тест на разных ТФ: M15, H1, H4.
Выбор "плоскогорного" варианта значений переменных, является ключевым после оптимизации торгового подхода и является целью оптимизации для дальнейшего использования в торговле, представлен на рисунке:

Оптимизация выглядит следующим образом:

Отчеты по оптимизации и тестированию размещены во вложении в архивах.
Тестер торговых стратегий в MetaTrader 5 предоставляет уникальную возможность проводить форвард тестирование с уже выставленными частями общего периода. Можно задать асимметричные условия входа. Например: для BUY — пробой максимума за 25 дней, для SELL — пробой минимума за 5 дней.
Я проводил оптимизацию со следующими размерами шагов и настройками:

Срок годности модели
Размер тестового окна существенно влияет на срок «жизни» торговой системы. Системы, использующие оптимизацию, требуют периодической реоптимизации для адаптации к текущим рыночным условиям. При этом модели, оптимизированные на более длинных временных интервалах, как правило, сохраняют работоспособность дольше. Напротив, системы с коротким тестовым окном нуждаются в более частой реоптимизации и обладают меньшей устойчивостью.
Обычно период между реоптимизациями составляет долю от длины тестового окна. Практическое правило — от 1/8 до 1/4. Например, при оптимизационном окне в 24 месяца система может эффективно работать от 3 до 6 месяцев.
Это связано с тем, что рынок постоянно меняется. Чем дальше от точки оптимизации, тем менее надёжны прогнозы. Если условия остаются стабильными, реоптимизация может не требоваться, но на практике это почти не происходит.
Короткое тестовое окно охватывает ограниченный набор рыночных условий. Такая модель хорошо работает только в знакомой среде и теряет эффективность при изменении рынка, поэтому требует частой перенастройки. Длинное окно, напротив, включает больше различных рыночных состояний, благодаря чему модель лучше адаптируется к новым условиям и дольше остаётся актуальной.
В итоге: короткие окна дают более чувствительные, но менее стабильные системы; длинные — более устойчивые, но менее реактивные. Оптимизация — это процесс повышения эффективности, и именно грамотный подход к ней отличает успешного трейдера от игрока в казино.
Заключение
Приведённый алгоритм разработки, тестирования и оптимизации — это практическая инструкция, а не теоретическая концепция. На выходе вы должны получить не абстрактное «улучшение», а конкретные артефакты и решения:
- набор рабочих файлов (индикатор + советник/ы) с задокументированными правилами входа/выхода и управления риском;
- тест‑план для Strategy Tester MetaTrader 5: список оптимизируемых переменных и их диапазонов, правила разбиения истории (IS/OOS), критерии отбора (например, Profit Factor, максимальная просадка, минимальное число сделок), метод выбора плато;
- регламент форвард‑проверки и таблица решений: когда модель считается годной, когда требует реоптимизации, как часто переоптимизировать (ориентир — 1/8…1/4 длины оптимизационного окна);
- практические рекомендации по мультисимвольному и мульти‑TF тестированию для повышения статистической валидности.
Ключевые предостережения: избегайте переоптимизации, не полагайтесь на единичный лучший прогон, проверяйте модель на разных рынках и в форварде, и всегда осуществляйте тестирование на демо/контролируемых счетах прежде чем переходить в реальную торговлю. Эта методика даёт управляемый путь от идеи к воспроизводимой, измеримой и адаптируемой торговой системе.
Материал и код приведены в образовательных целях. Перед использованием на реальных счетах обязательно проводите тестирование. Торговля связана с рисками.
| Наименование файла | Описание |
|---|---|
| Templates_sets_reports.zip | Отчёты тестера стратегий и установочные файлы со значениями параметров советника |
| PardoSystem.mq5 | Индикатор по торговой методике Р.Пардо |
| Breakout_Bounce.mq5 | Торговый советник по торговым условиям пробой/отбой уровня |
| PardoEA.mq5 | Торговый советник по методу Р.Пардо |
Предупреждение: все права на данные материалы принадлежат MetaQuotes Ltd. Полная или частичная перепечатка запрещена.
Данная статья написана пользователем сайта и отражает его личную точку зрения. Компания MetaQuotes Ltd не несет ответственности за достоверность представленной информации, а также за возможные последствия использования описанных решений, стратегий или рекомендаций.
Оптимизатор ястребов Харриса — Harris Hawks Optimization (HHO)
Создание самооптимизирующихся советников на MQL5 (Часть 8): Анализ нескольких стратегий (2)
Архитектура системы машинного обучения в MetaTrader5 (Часть 5): Последовательный бутстреппинг— устранение смещения меток и повышение доходности
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования