English 中文 Español Deutsch 日本語 Português
preview
Простая торговая стратегия возврата к среднему

Простая торговая стратегия возврата к среднему

MetaTrader 5Трейдинг | 4 августа 2023, 15:47
3 580 4
Javier Santiago Gaston De Iriarte Cabrera
Javier Santiago Gaston De Iriarte Cabrera

Введение

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


Краткое введение в стратегию

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

Возврат к среднему может быть в форме скользящей средней, где, если рынок уйдет слишком далеко от нее, он, вероятно, вернется в свою область.


Что такое возврат к среднему?

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

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

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

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

Возврат к среднему кажется более научным методом выбора точек покупки и продажи активов, чем построение графиков, потому что точные числовые значения получаются из исторических данных для определения значений покупки/продажи, а не пытаются интерпретировать движение цены с помощью графиков (технический анализ), хотя индикатор RSI и средний истинный диапазон (ATR) являются относительно новыми попытками уловить такую закономерность.

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

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

Историческая модель возврата к среднему не будет полностью учитывать фактическое поведение цены актива. Например, может стать доступной новая информация, которая навсегда повлияет на долгосрочную оценку базового актива. В случае банкротства актив может прекратить торговаться и никогда не восстановиться до своего прежнего среднего исторического значения.

В финансах термин "возврат к среднему" имеет несколько иное значение, чем "возврат или регрессия к среднему" в статистике. Джереми Сигел (Jeremy Siegel) использует термин "возврат к среднему" для описания общего принципа, финансового временного ряда, в котором "доходность может быть очень нестабильной в краткосрочной перспективе, но очень стабильной в долгосрочной". В количественном отношении это стандартное отклонение среднегодовой доходности, которое уменьшается быстрее, чем обратное значение периода владения, что подразумевает, что процесс не является случайным блужданием, а периоды более низкой доходности затем сменяются компенсирующими периодами более высокой доходности, например в случае сезонного бизнеса.


Проиллюстрируем сказанное следующим примером.

покупка

Но как мы можем определить, что рынок ушел "слишком далеко"? Мы применим очень простой способ, основанный только на положении цены относительно скользящей средней.


Разработка стратегии

Теперь, когда у нас есть нормализованное расстояние за 50 периодов между рынком и его скользящей средней за 200 периодов, мы готовы воплотить в виде кода следующие торговые сигналы:

  • Сигнал на покупку генерируется всякий раз, когда нормализованный индекс падает с уровня 100 после того, как стал равным 100, в то время как текущая цена закрытия ниже цены закрытия пять периодов назад и ниже 200-периодной скользящей средней.
  • Сигнал на продажу генерируется всякий раз, когда нормализованный индекс падает с уровня 100 после того, как стал равным 100, в то время как текущая цена закрытия выше цены закрытия пять периодов назад и выше 200-периодной скользящей средней.

Так что, возможно, это не самая простая стратегия в данных условиях, но, тем не менее, она интуитивно понятна. Функция сигнала следующая:

Учитывая сведения из предыдущего раздела у нас есть четкие цели для начала разработки стратегии:

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

Я внесу изменения в стратегию, чтобы попытаться получить лучшие результаты там, где стратегия не работает (когда эквити падает). В частности, будет внесено следующее изменение:

- Вместо того, чтобы смотреть на последние закрытия, я буду искать максимумы и минимумы, чтобы попытаться отфильтровать лучшие ордера.

Ордера открываются реже.

Ниже представлен измененный код:

if(previousValue==100)
        {
         if(Normalizado<100 && array_ma[0]>tick.bid  && rates[5].high < rates[1].low )
           {
            Print("Open Order Buy");
            Alert(" Buying");
            Orden="Buy";
            sl=NormalizeDouble(tick.ask - ptsl*_Point,_Digits);
            tp=NormalizeDouble(tick.bid + pttp*_Point,_Digits);
            //trade.Buy(get_lot(tick.bid),_Symbol,tick.bid,sl,tp);
            trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,get_lot(tick.bid),tick.bid,sl,tp,"Buy");
           }
         if(Normalizado<100 && array_ma[0]<tick.ask  && rates[5].low > rates[1].high )
           {
            Print("Open Order Sell");
            Alert(" Selling");
            Orden="Sell";
            sl=NormalizeDouble(tick.bid + ptsl*_Point,_Digits);
            tp=NormalizeDouble(tick.ask - pttp*_Point,_Digits);
            //trade.Sell(get_lot_s(tick.ask),_Symbol,tick.ask,sl,tp);
            trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,get_lot(tick.ask),tick.ask,sl,tp,"Sell");

           }
        }

С этими изменениями стратегия выигрывает меньше, но выглядит стабильнее (стоп-лосс = 4000 пунктов).

4500 sl 30 мин


данные 4500 sl 30 мин


Советник должен быть оптимизирован для получения лучших результатов. Вы также можете изменить код. Это пример того, что вы можете увидеть при стратегии со стоп-лоссом 300 на периоде в 30 минут. Просто найдите наилучшее соответствие в соответствии с символом и используемым периодом времени.

350 sl 30 мин


данные 350 sl 30 мин


График кажется более стабильным при стоп-лоссе 300 пунктов, но более прибыльным при стоп-лоссе 4000 пунктов. Мы должны сделать оптимизацию для стоп-лосса, чтобы найти баланс для периода в 30 минут. Предлагаю вам сделать это самостоятельно.



Код

Ниже представлены входные параметры:


input ENUM_LOT_TYPE        inp_lot_type               = LOT_TYPE_FIX;              // type of lot
input double               inp_lot_fix                = 0.01;                        // fix lot
input double               inp_lot_risk               = 0.01;      
input bool     InpPrintLog          = false;       // Print log
ulong                    Expert_MagicNumber       =66777;            //
bool                     Expert_EveryTick         =false;            //
input ENUM_TIMEFRAMES my_timeframe=PERIOD_CURRENT;                  // Timeframe
int    handle_iMA;
input int                  Inp_MA_ma_period     = 200;          // MA: averaging period
input int                  Inp_MA_ma_shift      = 5;           // MA: horizontal shift
input ENUM_MA_METHOD       Inp_MA_ma_method     = MODE_SMA;    // MA: smoothing type
input ENUM_APPLIED_PRICE   Inp_MA_applied_price = PRICE_CLOSE; // MA: type of price
int shift = 49; // loockback normalization
input int ptsl = 350; // points for stoploss
input int pttp = 5000; // points for takeprofit

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

В OnInit():

int OnInit()
  {
//---
   handle_iMA=iMA(_Symbol,my_timeframe,Inp_MA_ma_period,Inp_MA_ma_shift,
                  Inp_MA_ma_method,Inp_MA_applied_price);

// Initialize the variable here if needed
   previousValue = 0.0;

//---
   return(INIT_SUCCEEDED);
  }

Этот код написан на языке MQL5 и используется для инициализации переменной. Первая строка кода создает хэндл функции iMA для вычисления скользящего среднего значения данного символа за указанный период времени. Параметры функции iMA устанавливаются на значения входных переменных Inp_MA_ma_period, Inp_MA_ma_shift, Inp_MA_ma_method и Inp_MA_applied_price. Вторая строка кода инициализирует переменную previousValue значением 0,0. Последняя строка кода возвращает значение INIT_SUCCEEDED, что указывает на то, что инициализация прошла успешно.

В OnTick():

MqlTick tick;
   double last_price = tick.ask;
   SymbolInfoTick(_Symbol,tick);

и

   if(SymbolInfoTick(_Symbol,tick))
      last=tick.last;

   double Last = NormalizeDouble(last,_Digits);

Этот код написан на языке MQL5 и используется для сравнения текущей цены предложения символа с последней ценой предложения. Первая строка создает переменную с именем tick типа MqlTick. Вторая строка хранит последнюю цену предложения в переменной last_price. Третья строка извлекает тиковую информацию для символа, указанного в переменной _Symbol, и сохраняет ее в переменной tick. Четвертая строка проверяет, больше ли текущая цена предложения, чем последняя цена предложения, хранящаяся в переменной last_price. Если да, то производится какое-либо действие.

Этот код используется для расчета процента спреда данного символа. Расчет начинается с получения последней цены символа с помощью функции SymbolInfoTick(). Затем последняя цена нормализуется до количества цифр, указанного параметром _Digits. Если нормализованная последняя цена больше 0, цены спроса и предложения символа извлекаются и нормализуются. Спред рассчитывается путем вычитания нормализованной цены спроса (bid) из нормализованной цены предложения (ask). Затем спред делится на значение символа в пунктах (рассчитанное с помощью функции Pow()), чтобы получить спред в пунктах. Наконец, спред в пунктах делится на нормализованную последнюю цену и умножается на 100, чтобы получить процент спреда. Если процент спреда меньше или равен параметру Max_Spread, предпринимаются те или иные действия.

Для скользящего среднего мы будем использовать следующее:

   handle_iMA=iMA(_Symbol,my_timeframe,Inp_MA_ma_period,Inp_MA_ma_shift,
                  Inp_MA_ma_method,Inp_MA_applied_price);
//---
   double array_ma[];
   ArraySetAsSeries(array_ma,true);
   int start_pos=0,count=3;
   if(!iGetArray(handle_iMA,0,start_pos,count,array_ma))
      return;
   string text="";
   for(int i=0; i<count; i++)
      text=text+IntegerToString(i)+": "+DoubleToString(array_ma[i],Digits()+1)+"\n";
//---
   Comment(text);

bool iGetArray(const int handle,const int buffer,const int start_pos,
               const int count,double &arr_buffer[])
  {
   bool result=true;
   if(!ArrayIsDynamic(arr_buffer))
     {
      //if(InpPrintLog)
      PrintFormat("ERROR! EA: %s, FUNCTION: %s, this a no dynamic array!",__FILE__,__FUNCTION__);
      return(false);
     }
   ArrayFree(arr_buffer);
//--- reset error code
   ResetLastError();
//--- fill a part of the iBands array with values from the indicator buffer
   int copied=CopyBuffer(handle,buffer,start_pos,count,arr_buffer);
   if(copied!=count)
     {
      //--- if the copying fails, tell the error code
      //if(InpPrintLog)
      PrintFormat("ERROR! EA: %s, FUNCTION: %s, amount to copy: %d, copied: %d, error code %d",
                  __FILE__,__FUNCTION__,count,copied,GetLastError());
      //--- quit with zero result - it means that the indicator is considered as not calculated
      return(false);
     }
   return(result);
  }

Этот код используется для расчета и отображения скользящей средней данного символа на заданном таймфрейме. Функция iMA() используется для расчета MA, а функция iGetArray() используется для извлечения значений скользящей средней из индикаторного буфера. Функция ArraySetAsSeries() используется для установки массива в виде серии, а функции IntegerToString() и DoubleToString() используются для преобразования значений массива в строки. Наконец, функция Comment() используется для отображения значений скользящей средней на графике.


Чтобы не получать ошибок объема при открытии ордеров, мы применим следующий код:

//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
double get_lot(double price)
  {
   if(inp_lot_type==LOT_TYPE_FIX)
      return(normalize_lot(inp_lot_fix));
   double one_lot_margin;
   if(!OrderCalcMargin(ORDER_TYPE_BUY,_Symbol,1.0,price,one_lot_margin))
      return(inp_lot_fix);
   return(normalize_lot((AccountInfoDouble(ACCOUNT_BALANCE)*(inp_lot_risk/100))/ one_lot_margin));
  }
//+------------------------------------------------------------------+
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
double normalize_lot(double lt)
  {
   double lot_step = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);
   lt = MathFloor(lt / lot_step) * lot_step;
   double lot_minimum = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
   lt = MathMax(lt, lot_minimum);
   return(lt);
  }

Этот код используется для расчета размера лота для ордера на покупку. Первая функция, get_lot(), принимает в качестве аргумента цену и возвращает размер лота. Размер лота определяется типом лота, который либо фиксирован, либо основан на проценте риска. Если тип лота фиксированный, функция возвращает нормализованный размер лота. Если тип лота основан на проценте риска, функция вычисляет маржу для одного лота, вычисляет размер лота на основе баланса и процента риска и возвращает нормализованный размер лота. Вторая функция, normalize_lot(), принимает размер лота в качестве аргумента и возвращает нормализованный размер лота. Нормализованный размер лота рассчитывается путем деления размера лота на шаг объема и последующего умножения на шаг объема. Затем нормализованный размер лота сравнивается с минимальным размером лота, и возвращается максимальный из двух.

Для открытия ордеров мы будем использовать следующий код:

trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,get_lot(tick.bid),tick.bid,sl,tp,"Buy");

Этот код написан на MQL5 и используется для открытия новой позиции на рынке. Первый параметр — это символ торгуемого актива. Второй параметр — тип ордера, в данном случае ордер на покупку. Третий параметр — это размер лота, который рассчитывается с помощью функции get_lot() и текущей цены предложения. Четвертый параметр – текущая цена предложения. Пятый и шестой параметры — уровни стоп-лосса и тейк-профита соответственно. Последний параметр — это комментарий, который будет добавлен к ордеру.

Начальный код соответствует начальной стратегии, только с изменением условий закрытия на максимум и минимум (чтобы получить более надежные результаты).

   if(0<=Normalizado<=100 )
     {
      //------------------------------------------------------------------------------
      if(previousValue==100)
        {
         if(Normalizado<100 && array_ma[0]>tick.bid  && rates[5].high < rates[1].low )
           {
            Print("Open Order Buy");
            Alert(" Buying");
            Orden="Buy";
            sl=NormalizeDouble(tick.ask - ptsl*_Point,_Digits);
            tp=NormalizeDouble(tick.bid + pttp*_Point,_Digits);
            //trade.Buy(get_lot(tick.bid),_Symbol,tick.bid,sl,tp);
            trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,get_lot(tick.bid),tick.bid,sl,tp,"Buy");
           }
         if(Normalizado<100 && array_ma[0]<tick.ask  && rates[5].low > rates[1].high )
           {
            Print("Open Order Sell");
            Alert(" Selling");
            Orden="Sell";
            sl=NormalizeDouble(tick.bid + ptsl*_Point,_Digits);
            tp=NormalizeDouble(tick.ask - pttp*_Point,_Digits);
            //trade.Sell(get_lot_s(tick.ask),_Symbol,tick.ask,sl,tp);
            trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,get_lot(tick.ask),tick.ask,sl,tp,"Sell");
           }
        }
     }
   previousValue = Normalizado;
   if(Orden=="Sell" && rates[0].low <array_ma[0])
     {
      trade.PositionClose(_Symbol,5);
      Print("cerro sell");
      return;
     }
   if(Orden=="Buy" && rates[0].high >array_ma[0])
     {
      trade.PositionClose(_Symbol,5);
      Print("cerro buy");
      return;
     }
  }

Этот код написан на языке MQL5 и используется для открытия и закрытия ордеров на рынке Форекс. Сначала код проверяет, находится ли значение переменной Normalizado в диапазоне от 0 до 100. Если это так, то он проверяет, было ли предыдущее значение Normalizado равным 100. Если это так, то он проверяет, меньше ли значение Normalizado 100 и больше ли значение array_ma[0] текущей цены предложения, а также меньше ли максимум последних 5 цен, чем минимум первой цены. Если все эти условия соблюдены, то код печатает "Open Order Buy" (открыть ордер на покупку), предупреждает пользователя о том, что совершается покупка, устанавливает для переменной Orden значение Buy, устанавливает уровни стоп-лосса и тейк-профита и открывает ордер на покупку с указанными параметрами.

Если значение Normalizado меньше 100, а значение array_ma[0] меньше текущей цены предложения, а минимум последних 5 цен больше максимума первой цены, код печатает "Open Order Sell" (открыть ордер на продажу), уведомляет пользователя о продаже, устанавливает переменную Orden в значение Sell, устанавливает уровни стоп-лосса и тейк-профита и открывает ордер на продажу с заданными параметрами. После этого код меняет предыдущее значение Normalizado на текущее. Наконец, код проверяет, установлено ли для переменной Orden значение Sell и меньше ли минимум текущей ставки, чем значение array_ma[0]. Если эти условия соблюдены, то код закрывает ордер на продажу и печатает "cerro sell". Точно так же, если для переменной Orden установлено значение Buy, а максимум текущей цены больше, чем значение array_ma[0], код закрывает ордер на покупку и печатает "cerro buy".



Заключение

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



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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (4)
Valeriy Yastremskiy
Valeriy Yastremskiy | 9 авг. 2023 в 14:49
А что только екзешник выложен, кода нет.
Alexey Petrov
Alexey Petrov | 10 авг. 2023 в 11:07
Valeriy Yastremskiy #:
А что только екзешник выложен, кода нет.

Спасибо, поправили. Теперь код есть

Valeriy Yastremskiy
Valeriy Yastremskiy | 10 авг. 2023 в 11:24
Alexey Petrov #:

Спасибо, поправили. Теперь код есть

Спасибо.

Алексей
Алексей | 10 авг. 2023 в 21:08
При тестирование выдает ошибку в 84 строке.  
Нейросети — это просто (Часть 53): Декомпозиция вознаграждения Нейросети — это просто (Часть 53): Декомпозиция вознаграждения
Мы уже не раз говорили о важности правильного подбора функции вознаграждения, которую используем для стимулирования желательного поведения Агента, добавляя вознаграждения или штрафы за отдельные действия. Но остается открытым вопрос о дешифровке наших сигналов Агентом. В данной статье мы поговорим о декомпозиции вознаграждения в части передачи отдельных сигналов обучаемому Агенту.
Торговые транзакции. Структуры запросов и ответов, описание и вывод в журнал Торговые транзакции. Структуры запросов и ответов, описание и вывод в журнал
В статье рассмотрим работу со структурами торговых запросов — для создания запроса, его предварительной проверки перед отправкой на сервер, ответ сервера на торговый запрос и структуру торговых транзакций. Создадим простые удобные функции для отправки торговых приказов на сервер и, на основе всего рассмотренного, создадим советник-информер о торговых транзакциях.
Нейросети — это просто (Часть 54): Использование случайного энкодера для эффективного исследования (RE3) Нейросети — это просто (Часть 54): Использование случайного энкодера для эффективного исследования (RE3)
Каждый раз, при рассмотрении методов обучения с подкреплением, мы сталкиваемся с вопросом эффективного исследования окружающей среды. Решение данного вопроса часто приводит к усложнению алгоритма и обучению дополнительных моделей. В данной статье мы рассмотрим альтернативный подход к решению данной проблемы.
Нейросети — это просто (Часть 52): Исследование с оптимизмом и коррекцией распределения Нейросети — это просто (Часть 52): Исследование с оптимизмом и коррекцией распределения
По мере обучения модели на базе буфера воспроизведения опыта текущая политика Актера все больше отдаляется от сохраненных примеров, что снижает эффективность обучения модели в целом. В данной статье мы рассмотрим алгоритм повышения эффективности использования образцов в алгоритмах обучения с подкреплением.