English 中文 Deutsch 日本語
preview
Разработка инструментария для анализа Price Action (Часть 30): Советник CCI Zero Line

Разработка инструментария для анализа Price Action (Часть 30): Советник CCI Zero Line

MetaTrader 5Торговые системы |
78 0
Christian Benjamin
Christian Benjamin

Введение

В этой статье мы создаем инструмент на MQL5, который упрощает торговлю по Price Action. Система автоматизирует стратегию, основанную на четырех элементах: двойном CCI (Commodity Channel Index), экспоненциальной скользящей средней (EMA) с периодом 34, Average True Range (ATR) и чистом анализе Price Action. После обзора каждого индикатора и описания полной торговой стратегии мы шаг за шагом разберем разработку советника, протестируем его, проанализируем результаты и сделаем выводы. Сводка других разработанных нами инструментов приведена в таблице на последней странице статьи.

Ознакомьтесь с оглавлением ниже.


Обзор индикатора

Commodity Channel Index (CCI)

Индекс товарного канала (CCI) – это осциллятор моментума, который сравнивает текущую цену с ее недавним средним значением, чтобы выявлять потенциальные состояния перекупленности, перепроданности или разворота тренда. Формула Commodity Channel Index (CCI) выглядит так:

CI = (Типичная цена - SMA) / (0,015 × Среднее отклонение)

Типичная цена (TP) вычисляется как сумма максимума, минимума и цены закрытия за период, деленная на три:

           h + l + c  
TP =       ---------
               3

SMA – простая скользящая средняя
Простая скользящая средняя (SMA) берет типичные цены за заданный период, например за последние 20 сессий, суммирует их и делит на число периодов. Этот процесс сглаживает ежедневные колебания и делает основной тренд более заметным.

Среднее отклонение
Это среднее значение абсолютных отклонений типичных цен от их простой скользящей средней.

Эта статистика показывает, насколько в среднем каждая типичная цена отклоняется от SMA, и дает практичную оценку изменчивости цены, то есть волатильности, за выбранный период.

Постоянный множитель 0,015

Этот фиксированный коэффициент масштабирует значения CCI так, что подавляющее большинство из них оказывается примерно в диапазоне от -100 до +100. Это делает индикатор более наглядным и позволяет резким отклонениям, указывающим на выраженную перекупленность или перепроданность, лучше выделяться.

Разработанный Дональдом Ламбертом и впервые опубликованный в 1980 году для анализа циклических движений на товарных рынках, этот индикатор теперь широко применяется к акциям, валютам и другим классам активов. В этой стратегии мы одновременно используем два CCI: быстрый CCI (25) для оценки краткосрочного моментума и медленный CCI (50) для оценки общей силы рынка. Большинство значений CCI находится в диапазоне от -100 до +100, а выход за его пределы часто указывает на необычно сильное или слабое движение. Хотя эти крайние уровни могут служить фильтром волатильности, основным триггером в нашей стратегии остается именно нулевая линия. Переход из отрицательной области в положительную сигнализирует о возможном переходе к бычьему моментуму; движение в противоположном направлении указывает на нарастающее давление продавцов.

Поскольку пересечения нулевой линии возникают раньше, чем пробои уровней ±100, они дают советнику более ранние входы, хотя иногда это приводит к ложным сигналам. Чтобы ослабить этот шум, советник при необходимости ищет дополнительное подтверждение: устойчивое движение выше +100 усиливает сигналы на покупку, а падение ниже -100 усиливает сигналы на продажу. Иными словами, пересечения нулевой линии формируют основные сигналы, а уровни ±100 выступают вторичным фильтром, а не их заменой.

Рис. 2. Индикатор CCI

Ниже показано, как создать в MQL5 два хэндла CCI, корректно ими управлять и получать их значения. 

  • Создание хэндлов CCI в OnInit() с помощью:

handleCCI_Long  = iCCI(_Symbol, _Period, CCI_LongPeriod, PRICE_TYPICAL);
handleCCI_Short = iCCI(_Symbol, _Period, CCI_ShortPeriod, PRICE_TYPICAL);

  • Копирование значений буфера с помощью CopyBuffer() в OnTick():

CopyBuffer(handleCCI_Long,  0, 0, 2, cciL);
CopyBuffer(handleCCI_Short, 0, 0, 1, cciS);

Экспоненциальная скользящая средняя (EMA) с периодом 34

Экспоненциальная скользящая средняя (EMA) – это трендовый индикатор, который быстро реагирует на новую ценовую информацию, придавая больший вес последним свечам. Такое экспоненциальное взвешивание отфильтровывает краткосрочный шум и делает преобладающий тренд более заметным. На часовом графике часто используется EMA с периодом 34, поскольку она охватывает примерно одну торговую неделю.  Торговать следует только по направлению EMA 34: открывать длинные позиции, когда цена выше EMA, а короткие – когда цена ниже нее. Это правило служит непрерывным фильтром тренда и не позволяет стратегии входить в сделки против тренда.

Рис. 3. EMA с периодом 34

Ниже показано, как создать в MQL5 хэндл для EMA 34.

  • Объявление:

input int EMAPeriod = 34;

  • Инициализация:

handleEMA = iMA(_Symbol, _Period, EMAPeriod, 0, MODE_EMA, PRICE_CLOSE);

  • Копирование буфера (та же логика):

double ema34[1];
if(CopyBuffer(handleEMA, 0, 0, 1, ema34) < 1) return;
double ema_now = ema34[0];

Средний истинный диапазон (ATR)

Средний истинный диапазон (ATR) – это индикатор волатильности, разработанный Дж. Уэллсом Уайлдером-младшим и описанный в его книге 1978 года "New Concepts in Technical Trading Systems". Он усредняет True Range (истинный диапазон) за выбранный период (по умолчанию 14), поэтому отражает естественные ценовые колебания рынка.

В этой статье мы используем средний истинный диапазон (ATR), чтобы вычислить и установить уровни стоп-лосса и тейк-профита. Ниже показано, как реализовать это в MQL5:

  • Объявление

int handleATR = INVALID_HANDLE;
input int ATR_Period = 14;

  • Инициализация

handleATR = iATR(_Symbol, _Period, ATR_Period);
if(handleATR == INVALID_HANDLE) return INIT_FAILED;

  • Копирование буфера

double atrBuf[1];
if(CopyBuffer(handleATR, 0, 0, 1, atrBuf) > 0)
{
   double atrValue = atrBuf[0];
   // use atrValue here…
}

  • Очистка

if(handleATR != INVALID_HANDLE)
    IndicatorRelease(handleATR);


Логика стратегии

В этом разделе мы объясним, как наш советник CCI Zero Line генерирует сигналы на вход с помощью двух индикаторов индекса товарного канала (CCI) и экспоненциальной скользящей средней (EMA). Когда значение CCI пересекает нулевую линию снизу вверх, это указывает на усиление восходящего моментума и на возможный бычий сигнал либо усиление давления покупателей. И наоборот, когда значение CCI пересекает нулевую линию сверху вниз, это сигнализирует о развороте рыночного моментума вниз и указывает на возможный медвежий тренд или усиление давления продавцов. 

Когда цена поднимается выше EMA 34, это обычно указывает на усиление восходящего моментума и на возможный бычий тренд или возможность покупки. С другой стороны, если цена опустится ниже EMA 34, это указывает на снижение моментума и потенциальное начало медвежьего тренда, что может быть сигналом к продаже.

Используемые индикаторы

  • CCI 25 – быстрый индекс товарного канала
  • CCI 50 – медленный индекс товарного канала
  • EMA 34 – экспоненциальная скользящая средняя с периодом 34 по ценам закрытия

Критерии сигнала на покупку

  • CCI 25 выше нулевой линии: CCI 25 должен находиться выше нулевой линии, указывая на краткосрочный бычий моментум.
  • CCI 50 пересекает нулевую линию: CCI 50 должен пересечь нулевую линию снизу вверх, подтверждая смену более долгосрочного тренда.
  • Подтверждение ценой: текущая свеча должна закрыться выше EMA 34, подтверждая бычий сценарий.

Рис. 4. Условия сигнала на покупку

Когда все три условия выполняются на новом баре, советник фиксирует сигнал на покупку.

Критерии сигнала на продажу

  • CCI 25 ниже нулевой линии: CCI 25 должен находиться ниже нулевой линии, указывая на краткосрочный медвежий моментум.
  • CCI 50 пересекает нулевую линию: CCI 50 должен пересечь нулевую линию сверху вниз, подтверждая смену более долгосрочного тренда на медвежий.
  • Подтверждение ценой: текущая свеча должна закрыться ниже EMA 34, подтверждая медвежий сценарий.

Рис. 5. Условия сигнала на продажу

Когда все три условия выполняются на новом баре, советник фиксирует сигнал на продажу.


Разработка советника

При запуске советник сначала создает необходимые хэндлы индикаторов, которые позволяют ему получать с графика данные в реальном времени. Это происходит в функции OnInit(). Хэндлы создаются для двух CCI, один с большим периодом (50) для определения более масштабного тренда и второй с меньшим периодом (25) в качестве подтверждающего индикатора, а также для EMA с периодом 34, которая выступает фильтром тренда. В частности, код создает эти хэндлы с помощью таких функций, как iCCI() и iMA(), которые запрашивают индикаторы у внутреннего движка MetaTrader.

Корректное создание этих хэндлов критически важно: если какой-либо из них не будет создан и функция вернет INVALID_HANDLE, советник остановится, предотвращая ложные сигналы и ошибки во время работы. Кроме того, предусмотрен необязательный хэндл ATR, который создается только в том случае, если включено управление стоп-лоссом и тейк-профитом на основе ATR (параметр UseATR).

handleCCI_Long  = iCCI(_Symbol, _Period, CCI_LongPeriod, PRICE_TYPICAL);
handleCCI_Short = iCCI(_Symbol, _Period, CCI_ShortPeriod, PRICE_TYPICAL);
handleEMA       = iMA(_Symbol, _Period, EMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(UseATR)
    handleATR = iATR(_Symbol, _Period, ATR_Period);

  • Этот подход гарантирует, что у советника будут все необходимые источники данных для принятия обоснованных торговых решений на каждом тике.

В советнике предусмотрена возможность ограничивать торговую активность определенными часами, например токийской сессией для USDJPY, что помогает избегать торговли в периоды низкого объема, когда движение цены может быть непредсказуемым. Это реализуется проверкой текущего серверного времени внутри функции OnTick(). Если фильтр сессии включен, код преобразует текущее время в структуру MqlDateTime и сравнивает значение часа с заданными пользователем начальным и конечным часами. Если текущий час выходит за пределы этого окна, советник просто завершает обработку, пропуская дальнейший анализ и торговлю.

if(UseTokyoSessionFilter && _Symbol == "USDJPY")
{
    datetime now = TimeTradeServer();
    MqlDateTime tm;
    TimeToStruct(now, tm);
    int hr = tm.hour;
    if(hr < TokyoStartHour || hr >= TokyoEndHour)
        return;
}

  • Торговля только в заданные часы помогает снизить риск проскальзывания и ложных сигналов, которые часто возникают на тонком рынке вне пиковых периодов.

Чтобы не выполнять проверку несколько раз в пределах одной свечи, советник использует простую, но эффективную логику: он запоминает временную метку последнего обработанного бара и продолжает работу только тогда, когда сформировался новый бар. Это достигается с помощью статической переменной lastTime, которая сохраняет свое значение между вызовами функции. Когда поступает новый тик, код сравнивает текущее время бара из iTime() с этим сохраненным значением. Если они совпадают, функция завершается досрочно, и все расчеты и решения выполняются только один раз на бар.

static datetime lastTime=0;
datetime t = iTime(_Symbol, _Period, 0);
if(t == lastTime) return; // same bar, skip
lastTime = t;

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

На каждом новом баре советник вызывает CopyBuffer(), чтобы получить последние значения CCI 25, CCI 50 и EMA 34, включая предыдущий бар для обнаружения пересечений. Медленный CCI 50 определяет более долгосрочный тренд, а быстрый CCI 25 подтверждает моментум для входа. EMA фильтрует шум и помогает убедиться, что цена движется по тренду. Если включены стопы на основе ATR, советник также получает текущее значение ATR 14.

double cciL[2], cciS[1], emaVal[1];
if(CopyBuffer(handleCCI_Long, 0, 0, 2, cciL) < 2) return;
if(CopyBuffer(handleCCI_Short, 0, 0, 1, cciS) < 1) return;
if(CopyBuffer(handleEMA, 0, 0, 1, emaVal) < 1) return;

double atrValue = 0.0;
if(UseATR)
{
    double atrBuf[1];
    if(CopyBuffer(handleATR, 0, 0, 1, atrBuf) < 1) return;
    atrValue = atrBuf[0];
}

  • В совокупности эти значения определяют все торговые решения: CCI выявляют развороты и продолжения, EMA подтверждает соответствие направлению тренда, а ATR позволяет динамически рассчитывать риск.

Суть торговой логики заключается в определении момента, когда долгосрочный CCI пересекает свою нулевую линию, сигнализируя о возможном изменении тренда. Пересечение вверх, то есть переход из отрицательной области в положительную, указывает на бычье движение, тогда как пересечение вниз говорит о медвежьем моментуме. Код фиксирует эти моменты, сравнивая предыдущие и текущие значения буфера медленного CCI. Как только пересечение обнаружено, советник применяет дополнительные фильтры: короткий CCI должен подтверждать направление сигнала, то есть быть положительным для бычьего сценария и отрицательным для медвежьего, а текущая цена должна находиться соответственно выше или ниже EMA.

double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);

if(crossUp && cciS[0] > 0 && price > emaVal[0])
    RegisterSignal(true, price, atrValue);
if(crossDown && cciS[0] < 0 && price < emaVal[0])
    RegisterSignal(false, price, atrValue);

  • Такое многоуровневое подтверждение помогает отфильтровывать ложные сигналы и обеспечивает вход в сделки только тогда, когда несколько индикаторов указывают в одну сторону, а фильтр тренда поддерживает движение.

Как только найден корректный сигнал, советник рассчитывает соответствующие уровни стоп-лосса и тейк-профита. Если включено управление на основе ATR, эти уровни динамически рассчитываются умножением текущего значения ATR на заданный пользователем множитель, например 1,5. Уровень стоп-лосса устанавливается на расстоянии от цены входа: ниже нее для длинных сделок и выше – для коротких. Уровень тейк-профита устанавливается пропорционально дальше в зависимости от соотношения риск/прибыль, обычно на расстоянии, в 1,5 раза превышающем расстояние до стоп-лосса.

double dist = UseATR ? atrValue * ATR_Multiplier : SLBufferPoints * _Point;
double slPrice = isBuy ? price - dist : price + dist;
double tpPrice = isBuy ? price + dist * RiskRewardRatio : price - dist * RiskRewardRatio;

  • Затем советник создает на графике визуальные маркеры: стрелки рядом с текущей свечой для обозначения точек входа и горизонтальные линии, показывающие уровни стоп-лосса и тейк-профита.

ObjectCreate(0, name, OBJ_ARROW, 0, barTime, arrowPrice);
ObjectSetInteger(0, name, OBJPROP_ARROWCODE, arrowCode);
ObjectSetInteger(0, name, OBJPROP_COLOR, isBuy ? BuyArrowColor : SellArrowColor);
ObjectCreate(0, "SL_Line", OBJ_HLINE, 0, 0, slPrice);
ObjectCreate(0, "TP_Line", OBJ_HLINE, 0, 0, tpPrice);

  • Эти визуальные подсказки позволяют наглядно проверять сигналы и убеждаться, что логика советника соответствует реальным рыночным условиям.

После построения визуальных маркеров советник выдает оповещение с подробной информацией о сделке: ценой входа, уровнем стоп-лосса и уровнем тейк-профита.

Alert("CCI ZeroLine EMA + R:R 1:1.5 " + (isBuy ? "BUY" : "SELL") +
      StringFormat(" @%.5f | SL: %.5f | TP: %.5f", price, slPrice, tpPrice));

Он также обновляет внутренние массивы отслеживания, в которых хранятся уровни стоп-лосса и тейк-профита для каждой сделки, а также логический флаг, указывающий, была ли уже зафиксирована ее отработка. Эти массивы необходимы для отслеживания текущих сделок, особенно при тестировании на исторических данных или в реальной торговле, поскольку позволяют советнику одновременно управлять несколькими открытыми позициями и оценивать их эффективность.

signalSL[totalSignals] = slPrice;
signalTP[totalSignals] = tpPrice;
resolved[totalSignals] = false;

Функция ResolveSignals() отвечает за отслеживание активных сделок. Она проходит по всем открытым сигналам, проверяя цены максимума и минимума последних баров, чтобы определить, были ли достигнуты уровни стоп-лосса или тейк-профита. Если максимум превышает тейк-профит, сделка помечается как прибыльная; если минимум пробивает стоп-лосс, она помечается как убыточная. Этот процесс обновляет счетчики общего числа сигналов и прибыльных сделок, предоставляя статистическую обратную связь об эффективности стратегии. Такой мониторинг особенно важен при тестировании на исторических данных, где точная фиксация результатов влияет на общие метрики эффективности, такие как процент выигрышных сделок, коэффициент прибыльности и просадка.

double high = iHigh(_Symbol, _Period, idx);
double low = iLow(_Symbol, _Period, idx);
if(high >= signalTP[i]) { winSignals++; resolved[i]=true; }
if(low <= signalSL[i]) { resolved[i]=true; }

Когда советник удаляется или график закрывается, функция OnDeinit() гарантирует освобождение всех хэндлов индикаторов с помощью IndicatorRelease(). Она также удаляет все графические объекты, созданные в процессе работы, такие как стрелки и горизонтальные линии. Корректное управление ресурсами предотвращает утечки памяти и не захламляет график, благодаря чему последующие запуски этого или других советников проходят без остаточных артефактов.

IndicatorRelease(handleCCI_Long);
IndicatorRelease(handleCCI_Short);
ObjectDelete(0, "SL_Line");
ObjectDelete(0, "TP_Line");

Полный код

//+------------------------------------------------------------------+
//|                                                  CCI Zero-Line EA|
//|                                   Copyright 2025, MetaQuotes Ltd.|
//|                           https://www.mql5.com/ru/users/lynnchris|
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com/ru/users/lynnchris"
#property version   "1.0"
#property strict

#include <Trade\Trade.mqh>
#include <Tools\Datetime.mqh>

CTrade trade;

//--- Session filter for USDJPY (Tokyo session)
input bool UseTokyoSessionFilter = false;  // enable Tokyo session filter
input int  TokyoStartHour        = 0;      // session start hour (server time)
input int  TokyoEndHour          = 9;      // session end hour   (server time, exclusive)

//--- SL/TP settings
input bool   UseATR           = true;   // true = ATR-based SL/TP, false = fixed points
input int    ATR_Period       = 14;     // ATR look-back
input double ATR_Multiplier   = 1.5;    // SL/TP distance = ATR × this
input double SLBufferPoints   = 10.0;   // fallback SL offset in pips if UseATR=false
input double RiskRewardRatio  = 1.5;    // TP = SL × 1.5 (1:1.5 RR)

//--- Indicator periods
input int CCI_LongPeriod   = 50;  // CCI long period (zero-line cross)
input int CCI_ShortPeriod  = 25;  // CCI short period (confirmation)
input int EMAPeriod        = 34;  // EMA period for trend filter

//--- Arrow colors
input color BuyArrowColor  = clrLime;
input color SellArrowColor = clrRed;

//--- Indicator handles
int handleCCI_Long = INVALID_HANDLE;
int handleCCI_Short= INVALID_HANDLE;
int handleEMA      = INVALID_HANDLE;
int handleATR      = INVALID_HANDLE;

//--- Signal-tracking arrays
int    totalSignals = 0;
int    winSignals   = 0;
int    signalBar[];
double signalSL[];
double signalTP[];
bool   resolved[];

//+------------------------------------------------------------------+
//| Expert initialization                                            |
//+------------------------------------------------------------------+
int OnInit()
  {
   handleCCI_Long  = iCCI(_Symbol, _Period, CCI_LongPeriod, PRICE_TYPICAL);
   handleCCI_Short = iCCI(_Symbol, _Period, CCI_ShortPeriod, PRICE_TYPICAL);
   handleEMA       = iMA(_Symbol, _Period, EMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
   if(handleCCI_Long == INVALID_HANDLE ||
      handleCCI_Short== INVALID_HANDLE ||
      handleEMA      == INVALID_HANDLE)
      return(INIT_FAILED);

   if(UseATR)
     {
      handleATR = iATR(_Symbol, _Period, ATR_Period);
      if(handleATR == INVALID_HANDLE)
         return(INIT_FAILED);
     }

   if(RiskRewardRatio <= 0 ||
      ATR_Multiplier   <= 0 ||
      (SLBufferPoints <= 0 && !UseATR))
      return(INIT_FAILED);

   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization                                          |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   if(handleCCI_Long  != INVALID_HANDLE)
      IndicatorRelease(handleCCI_Long);
   if(handleCCI_Short != INVALID_HANDLE)
      IndicatorRelease(handleCCI_Short);
   if(handleEMA       != INVALID_HANDLE)
      IndicatorRelease(handleEMA);
   if(UseATR && handleATR != INVALID_HANDLE)
      IndicatorRelease(handleATR);

   ObjectDelete(0, "SL_Line");
   ObjectDelete(0, "TP_Line");
  }

//+------------------------------------------------------------------+
//| Strategy Tester summary                                          |
//+------------------------------------------------------------------+
double OnTester()
  {
   double winRate = totalSignals > 0
                    ? 100.0 * winSignals / totalSignals
                    : 0.0;
   PrintFormat("=== Backtest Win-Rate ===\nSignals: %d  Wins: %d  Win-Rate: %.2f%%",
               totalSignals, winSignals, winRate);
   return(winRate);
  }

//+------------------------------------------------------------------+
//| Tick handler                                                     |
//+------------------------------------------------------------------+
void OnTick()
  {
// Tokyo session filter for USDJPY
   if(UseTokyoSessionFilter && _Symbol == "USDJPY")
     {
      datetime now = TimeTradeServer();
      MqlDateTime tm;
      TimeToStruct(now, tm);
      int hr = tm.hour;
      if(hr < TokyoStartHour || hr >= TokyoEndHour)
         return;
     }

// Only act on new bar
   static datetime lastTime = 0;
   datetime t = iTime(_Symbol, _Period, 0);
   if(t == lastTime)
      return;
   lastTime = t;

// Copy indicator buffers
   double cciL[2], cciS[1], emaVal[1];
   if(CopyBuffer(handleCCI_Long,  0, 0, 2, cciL)  < 2)
      return;
   if(CopyBuffer(handleCCI_Short, 0, 0, 1, cciS)  < 1)
      return;
   if(CopyBuffer(handleEMA,       0, 0, 1, emaVal) < 1)
      return;

// ATR if needed
   double atrValue = 0.0;
   if(UseATR)
     {
      double atrBuf[1];
      if(CopyBuffer(handleATR, 0, 0, 1, atrBuf) < 1)
         return;
      atrValue = atrBuf[0];
     }

   double price = SymbolInfoDouble(_Symbol, SYMBOL_BID);

// Detect zero-line cross
   bool crossUp   = (cciL[1] < 0 && cciL[0] > 0);
   bool crossDown = (cciL[1] > 0 && cciL[0] < 0);

// Confirm with short CCI & EMA trend filter
   if(crossUp   && cciS[0] >  0 && price > emaVal[0])
      RegisterSignal(true,  price, atrValue);
   if(crossDown && cciS[0] <  0 && price < emaVal[0])
      RegisterSignal(false, price, atrValue);

// Resolve pending signals for SL/TP hits
   ResolveSignals();
  }

//+------------------------------------------------------------------+
//| Register new buy/sell signal                                     |
//+------------------------------------------------------------------+
void RegisterSignal(bool isBuy, double price, double atrVal)
  {
   double dist    = UseATR
                    ? atrVal * ATR_Multiplier
                    : SLBufferPoints * _Point;
   double slPrice = isBuy ? price - dist : price + dist;
   double tpPrice = isBuy
                    ? price + dist * RiskRewardRatio
                    : price - dist * RiskRewardRatio;

// Arrow placement just outside the candle
   datetime barTime = iTime(_Symbol, _Period, 0);
   double   barHigh = iHigh(_Symbol, _Period, 0);
   double   barLow  = iLow(_Symbol, _Period, 0);
   double   offset  = 5 * _Point;  // e.g. 5-pip offset

   double arrowPrice = isBuy
                       ? barLow  - offset
                       : barHigh + offset;

// Use numeric arrow codes directly
   int arrowCode = isBuy ? 233 : 234;

   string name = (isBuy ? "BUY_" : "SELL_")
                 + TimeToString(TimeTradeServer(), TIME_SECONDS);
   ObjectCreate(0, name, OBJ_ARROW, 0, barTime, arrowPrice);
   ObjectSetInteger(0, name, OBJPROP_ARROWCODE, arrowCode);
   ObjectSetInteger(0, name, OBJPROP_COLOR,     isBuy ? BuyArrowColor : SellArrowColor);
   ObjectSetInteger(0, name, OBJPROP_WIDTH,     2);

// Draw SL/TP lines
   DrawLine("SL_Line", slPrice, clrRed);
   DrawLine("TP_Line", tpPrice, clrLime);

// Alert
   Alert("CCI ZeroLine EMA + R:R 1:1.5 "
         + (isBuy ? "BUY" : "SELL")
         + StringFormat(" @%.5f | SL: %.5f | TP: %.5f",
                        price, slPrice, tpPrice));

// Track for backtest metrics
   totalSignals++;
   ArrayResize(signalBar,  totalSignals);
   ArrayResize(signalSL,   totalSignals);
   ArrayResize(signalTP,   totalSignals);
   ArrayResize(resolved,   totalSignals);

   signalBar[totalSignals-1] = 0;
   signalSL[totalSignals-1]  = slPrice;
   signalTP[totalSignals-1]  = tpPrice;
   resolved[totalSignals-1]  = false;
  }

//+------------------------------------------------------------------+
//| Resolve pending signals (SL/TP checks)                           |
//+------------------------------------------------------------------+
void ResolveSignals()
  {
   int bars = Bars(_Symbol, _Period);
   for(int i = 0; i < totalSignals; i++)
     {
      if(resolved[i])
         continue;
      signalBar[i]++;
      int idx = signalBar[i];
      if(idx >= bars)
         continue;

      double high = iHigh(_Symbol, _Period, idx);
      double low  = iLow(_Symbol, _Period, idx);

      if(high >= signalTP[i])
        {
         winSignals++;
         resolved[i] = true;
        }
      else
         if(low <= signalSL[i])
           {
            resolved[i] = true;
           }
     }
  }

//+------------------------------------------------------------------+
//| Draw a horizontal SL/TP line                                     |
//+------------------------------------------------------------------+
void DrawLine(string name, double price, color clr)
  {
   if(ObjectFind(0, name) >= 0)
      ObjectDelete(0, name);
   ObjectCreate(0, name, OBJ_HLINE, 0, 0, price);
   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);
   ObjectSetString(0, name, OBJPROP_TEXT, name);
  }
//+------------------------------------------------------------------+


Тестирование

Ниже приведены результаты тестирования нашего советника на исторических данных.

Я протестировал советник на USDJPY, и ниже приведены записи из журнала.

  • Входные параметры
2025.06.30 09:34:54.140   UseTokyoSessionFilter=false
2025.06.30 09:34:54.140   TokyoStartHour=0
2025.06.30 09:34:54.140   TokyoEndHour=9
2025.06.30 09:34:54.140   UseATR=true
2025.06.30 09:34:54.140   ATR_Period=14
2025.06.30 09:34:54.140   ATR_Multiplier=1.5
2025.06.30 09:34:54.140   SLBufferPoints=10.0
2025.06.30 09:34:54.140   RiskRewardRatio=1.0
2025.06.30 09:34:54.140   CCI_LongPeriod=50
2025.06.30 09:34:54.140   CCI_ShortPeriod=25
2025.06.30 09:34:54.140   EMAPeriod=34
2025.06.30 09:34:54.140   BuyArrowColor=65280
2025.06.30 09:34:54.140   SellArrowColor=255

Конфигурация показывает, что фильтр токийской сессии отключен, поэтому сделки могут совершаться вне заданных для Токио часов; однако при его включении торговля будет ограничена интервалом с 00:00 до 09:00 по токийскому времени. Советник использует индикатор ATR с периодом 14 и множителем 1,5 для корректировки уровней стоп-лосса и тейк-профита с учетом текущей рыночной волатильности. К стоп-лоссу добавляется буфер в 10 пунктов, чтобы снизить риск преждевременного выхода, а соотношение риск/прибыль установлено на уровне 1:1, что уравновешивает потенциальную прибыль и убытки.

В стратегии используются два CCI: с периодом 50 как более медленный индикатор и с периодом 25 как более быстрый, а также EMA 34. Визуальные сигналы отображаются стрелками: зеленые соответствуют сигналам на покупку, а синие – сигналам на продажу.

Входные параметры можно настроить по своему усмотрению. Приведенные настройки — это те, которые я использовал во время тестирования и которые, на мой взгляд, дали хорошие результаты.

  • Сигналы

2025.06.30 09:34:54.447 2025.01.02 15:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @157.01800 | SL: 157.45075 | TP: 156.58525
2025.06.30 09:34:56.655 2025.01.09 06:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @158.04800 | SL: 158.30343 | TP: 157.79257
2025.06.30 09:40:35.572 2025.01.29 07:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @155.21100 | SL: 155.47704 | TP: 154.94496
2025.06.30 09:40:36.759 2025.01.31 07:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 BUY @154.68200 | SL: 154.25439 | TP: 155.10961
2025.06.30 09:40:45.870 2025.02.26 06:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 BUY @149.42600 | SL: 149.05400 | TP: 149.79800
2025.06.30 09:40:46.737 2025.02.28 03:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @149.51900 | SL: 150.03650 | TP: 149.00150
2025.06.30 09:40:49.726 2025.03.06 01:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @149.14200 | SL: 149.68200 | TP: 148.60200
2025.06.30 09:40:58.522 2025.03.21 16:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @148.97200 | SL: 149.35418 | TP: 148.58982
2025.06.30 09:41:01.363 2025.04.01 00:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 BUY @149.93500 | SL: 149.57425 | TP: 150.29575
2025.06.30 09:41:16.557 2025.04.28 13:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @143.33500 | SL: 143.69232 | TP: 142.97768
2025.06.30 09:41:36.021 2025.06.18 14:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @144.85500 | SL: 145.08911 | TP: 144.62089
2025.06.30 09:41:37.454 2025.06.23 19:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @146.15000 | SL: 146.65711 | TP: 145.64289
2025.06.30 09:41:38.360 2025.06.25 23:00:00   Alert: CCI ZeroLine EMA + R:R 1:1.5 SELL @145.21700 | SL: 145.47286 | TP: 144.96114

  • Процент выигрышных сделок

2025.06.30 09:41:39.206 2025.06.29 23:59:59   Signals: 13  Wins: 10  Win-Rate: 76.92%

Ниже приведен GIF, визуализирующий результаты тестирования: красные стрелки вниз обозначают сигналы на продажу, зеленые стрелки вверх – сигналы на покупку, красные линии показывают уровни стоп-лосса, а зеленые – уровни тейк-профита.


Заключение

Подводя итог, отметим, что советник CCI Zero Line выводит на график все, что нужно для четких входов по заданным правилам:

  • Мы используем CCI 50, чтобы определить общий тренд, и CCI 25, чтобы подтвердить готовность моментума, поэтому входим в рынок только тогда, когда оба сигнала совпадают.
  • EMA с периодом 34 помогает не гнаться за случайными всплесками. Если цена закрывается по нужную сторону этой линии, это подтверждает, что тренд не случаен.
  • При использовании стоп-лосса на основе ATR расстояние до него меняется вместе с волатильностью: на спокойном рынке оно меньше, а при усилении движения – больше. Уровень тейк-профита остается пропорциональным, поддерживая стабильное соотношение прибыли к риску.

Это означает, что стрелки появляются только тогда, когда цена, моментум и волатильность указывают в одну сторону. Это означает меньше ложных сигналов, более гибкое управление рисками и полную прозрачность в том, почему был выбран каждый уровень входа и выхода. Вы сможете тонко настраивать периоды CCI, длину EMA и множитель ATR под любой символ и таймфрейм и торговать увереннее, используя инструмент, основанный на надежном многоуровневом анализе. Данный советник предназначен только для образовательных целей. Не используйте его на реальном рынке без тщательного тестирования на исторических данных или проверки на демо-счете. Прежде чем переходить к реальной торговле, убедитесь, что вас устраивают полученные результаты.





   
Chart Projector
Analytical Comment
Analytics Master
Analytics Forecaster 
Volatility Navigator
Mean Reversion Signal Reaper
Signal Pulse 
Metrics Board 
External Flow
VWAP
Heikin Ashi   FibVWAP  
RSI DIVERGENCE
Parabolic Stop and Reverse (PSAR) 
Скрипт Quarters Drawer
Intrusion Detector
TrendLoom Tool  Quarters Board 
ZigZag Analyzer  Correlation Pathfinder  Market Structure Flip Detector Tool
Correlation Dashboard   Currency Strength Meter 
PAQ Analysis Tool 
Dual EMA Fractal Breaker
Pin bar, Engulfing and RSI divergence
Liquidity Sweep Opening Range Breakout Tool Boom and Crash Interceptor CCI Zero-Line EA

Перевод с английского произведен MetaQuotes Ltd.
Оригинальная статья: https://www.mql5.com/en/articles/18551

Прикрепленные файлы |
CCI.mq5 (18.28 KB)
Торговые инструменты MQL5 (Часть 21): Добавление темы в стиле киберпанк в графики регрессии Торговые инструменты MQL5 (Часть 21): Добавление темы в стиле киберпанк в графики регрессии
В этой статье мы улучшаем инструмент построения графиков регрессии в MQL5, добавляя режим темы киберпанка с неоновым свечением, анимацией и голографическими рамками для иммерсивной визуализации. Мы интегрируем переключение тем, динамические фоны со звездами, светящимися контурами и неоновыми точками / линиями, сохраняя при этом совместимость со стандартным режимом. Эта двухтематическая система придает парному анализу футуристическую эстетику, поддерживая обновления и взаимодействия в режиме реального времени для получения полезных торговых выводов.
Создание самооптимизирующихся советников на MQL5 (Часть 14): Преобразования данных как параметры настройки регулятора с обратной связью Создание самооптимизирующихся советников на MQL5 (Часть 14): Преобразования данных как параметры настройки регулятора с обратной связью
Предварительная обработка — это мощный, но часто упускаемый из виду параметр настройки. Он находится в тени своих более крупных собратьев: оптимизаторов и блестящих архитектур моделей. Даже незначительное улучшение показателей в данном случае может иметь непропорционально значительный и кумулятивный эффект на прибыльность и риски. Слишком часто эта в значительной степени неизученная наука сводится к простой рутине, рассматриваемой лишь как средство для достижения цели, тогда как на самом деле именно здесь сигнал может быть непосредственно усилен или с такой же легкостью уничтожен.
Статистический арбитраж с использованием коинтегрированных акций (Часть 4): Обновление параметров модели в реальном времени Статистический арбитраж с использованием коинтегрированных акций (Часть 4): Обновление параметров модели в реальном времени
В данной статье описывается простой, но комплексный алгоритм статистического арбитража для торговли корзиной коинтегрированных акций. В него входит полнофункциональный скрипт на языке Python для загрузки и хранения данных; тесты на корреляцию, коинтеграцию и стационарность, а также пример реализации сервиса Metatrader 5 для обновления базы данных и соответствующий советник. Здесь приведены некоторые проектные решения для справки и в целях содействия воспроизведению эксперимента.
Разработка инструментария для анализа Price Action (Часть 29): Советник "Boom and Crash Interceptor" Разработка инструментария для анализа Price Action (Часть 29): Советник "Boom and Crash Interceptor"
Узнайте, как советник Boom & Crash Interceptor превращает ваши графики в проактивную систему оповещений, выявляющую взрывные движения с помощью быстрого анализа скорости, проверки всплесков волатильности, подтверждения тренда и фильтров пивот-зон. Четкие зеленые стрелки "Boom" и красные "Crash" помогают быстрее принимать решения: этот инструмент отсекает рыночный шум и позволяет эффективнее использовать ценовые всплески. Давайте разберем, как это работает и почему этот инструмент может стать вашим следующим важным преимуществом в торговле.