English Deutsch 日本語
preview
Переосмысливаем классические стратегии (Часть 19): Подробный разбор стратегий на пересечении скользящих средних

Переосмысливаем классические стратегии (Часть 19): Подробный разбор стратегий на пересечении скользящих средних

MetaTrader 5Примеры |
89 0
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

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

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

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


Начинаем на MQL5

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

Рисунок 1: Визуализация всех версий стратегии пересечения скользящих средних, которые мы оценим

Даты проведения тестирования, которые мы будем использовать во всех тестах, установлены с января 2022 года до момента написания статьи в 2025 году, соответственно. 

Рисунок 2: Даты тестирования, которые мы выбрали для всех версий нашей торговой стратегии

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

Рисунок 3: Выбранные нами настройки для бэктеста



Установление базового уровня

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

Она предлагала нам правила входа в сделки и выхода из них, а затем правила выхода были определены ATR для установки наших позиций стоп-лоссов и тейк-профитов. Мы использовали равноудаленные тейк-профиты и стоп-лоссы без каких-либо настроек трейлинга, чтобы гарантировать, что изменения в прибыльности происходят в результате улучшения правил принятия решений для наших входов в сделки.

//+------------------------------------------------------------------+
//|                                              MA Crossover V1.mq5 |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

//+------------------------------------------------------------------+
//| Technical Indicators                                             |
//+------------------------------------------------------------------+
int      ma_fast_handler,ma_slow_handler,atr_handler;
double   ma_fast_reading[],ma_slow_reading[],atr_reading[];

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
double ask,bid;

//+------------------------------------------------------------------+
//| Libraries                                                        |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
CTrade Trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setup our indicators
   ma_fast_handler = iMA("EURUSD",PERIOD_D1,30,0,MODE_SMA,PRICE_CLOSE);
   ma_slow_handler = iMA("EURUSD",PERIOD_D1,60,0,MODE_SMA,PRICE_CLOSE);
   atr_handler     = iATR("EURUSD",PERIOD_D1,14);
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Free up memory we are no longer using when the application is off
   IndicatorRelease(ma_fast_handler);
   IndicatorRelease(ma_slow_handler);
   IndicatorRelease(atr_handler);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- When price levels change

   datetime current_time = iTime("EURUSD",PERIOD_D1,0);
   static datetime  time_stamp;

//--- Update the time
   if(current_time != time_stamp)
     {
      time_stamp = current_time;

      //--- Fetch indicator current readings
      CopyBuffer(ma_fast_handler,0,0,1,ma_fast_reading);
      CopyBuffer(ma_slow_handler,0,0,1,ma_slow_reading);
      CopyBuffer(atr_handler,0,0,1,atr_reading);

      ask = SymbolInfoDouble("EURUSD",SYMBOL_ASK);
      bid = SymbolInfoDouble("EURUSD",SYMBOL_BID);

      //--- If we have no open positions
      if(PositionsTotal() == 0)
        {
         //--- Trading rules
         if(ma_fast_reading[0] > ma_slow_reading[0])
           {
            //--- Buy signal
            Trade.Buy(0.01,"EURUSD",ask,ask-(atr_reading[0] * 2),ask+(atr_reading[0] * 2),"");
           }

         else
            if(ma_fast_reading[0] < ma_slow_reading[0])
              {
               //--- Sell signal
               Trade.Sell(0.01,"EURUSD",bid,bid+(atr_reading[0] * 2),bid-(atr_reading[0] * 2),"");
              }
        }

     }

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

Как объяснялось во введении к статье, первоначальная версия стратегии не приносила прибыли и давала шумные сигналы, что объясняет плохую кривую эквити, которую мы наблюдали при бэктестинге в течение всего периода тестирования. 

Рисунок 4: Визуализация кривой эквити, полученной в результате работы классической версии торговой стратегии

При переходе к детальному анализу стратегии, мы замечаем, что большинство сделок, открытых по этой стратегии, были убыточными, что нежелательно. Кроме того, профит-фактор этой стратегии меньше 1, а ожидаемая прибыль на сделку меньше 0, что означает, что со временем она приведет к уменьшению капитала инвестора. В большинстве случаев от такой стратегии пришлось бы отказаться и полностью заменить ее. Однако давайте поищем способы научить "старых собак новым трюкам".

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

Рисунок 5: Результаты бэктеста, полученные с помощью исходной версии нашей торговой стратегии


Начальная попытка улучшения базового уровня

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

      //--- If we have no open positions
      if(PositionsTotal() == 0)
        {
         //--- Trading rules
         if((ma_fast_reading[0] > ma_slow_reading[0]) && (low > ma_fast_reading[0]))
           {
            //--- Buy signal
            Trade.Buy(0.01,"EURUSD",ask,ask-(atr_reading[0] * 2),ask+(atr_reading[0] * 2),"");
           }

         else
            if((ma_fast_reading[0] < ma_slow_reading[0]) && (high < ma_slow_reading[0]))
              {
               //--- Sell signal
               Trade.Sell(0.01,"EURUSD",bid,bid+(atr_reading[0] * 2),bid-(atr_reading[0] * 2),"");
              }
        }

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

Рисунок 6: Изменения, внесенные в нашу торговую систему, привели к желаемым изменениям в кривой эквити, которую мы получили из торговой логики

При рассмотрении подробной статистики нашей торговой стратегии мы видим значительные улучшения. Начнем с того, что общая чистая прибыль наконец-то стала положительной после того, как изначально была значительно отрицательной. Кроме того, общий убыток, накопленный за все время бэктеста, снизился. Это означает, что наша торговая стратегия подвержена меньшему риску, чем первоначальная версия. Кроме того, в "шумной" версии стратегии было открыто 135 сделок, в то время как в прибыльной версии открыто меньше сделок — в частности, 107 сделок — для получения большей прибыли. Точность торговли по нашей стратегии повысилась с преимущественно убыточной до незначительно прибыльной, а ожидаемая прибыль на сделку увеличилась по сравнению с первоначальной версией.

 

Рисунок 7: Наши ручные усовершенствования стратегии устранили проблему с отрицательным балансом счета, возникшую при проведении первоначального тестирования


Вторая попытка превзойти базовый уровень

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

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

//--- Define our moving average indicator
#define MA_PERIOD_FAST     30                   //--- Moving Average Fast Period
#define MA_PERIOD_SLOW     60                   //--- Moving Average Slow Period
#define MA_TYPE            MODE_SMA             //--- Type of moving average we have
#define HORIZON            5                    //--- Forecast horizon

//--- Our handlers for our indicators
int ma_fast_handle,ma_slow_handle;

//--- Data structures to store the readings from our indicators
double ma_fast_reading[],ma_slow_reading[];

//--- File name
string file_name = Symbol() + " Cross Over Data.csv";

//--- Amount of data requested
input int size = 3000;

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
   int fetch = size + (HORIZON * 2);
//---Setup our technical indicators
   ma_fast_handle        = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD_FAST,0,MA_TYPE,PRICE_CLOSE);
   ma_slow_handle        = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD_SLOW,0,MA_TYPE,PRICE_OPEN);

//---Set the values as series
   CopyBuffer(ma_fast_handle,0,0,fetch,ma_fast_reading);
   ArraySetAsSeries(ma_fast_reading,true);
   CopyBuffer(ma_slow_handle,0,0,fetch,ma_slow_reading);
   ArraySetAsSeries(ma_slow_reading,true);
   
//---Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i=size;i>=1;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,
                  //--- Time
                  "Time",
                   //--- OHLC
                   "Open",
                   "High",
                   "Low",
                   "Close",
                   "MA F",
                   "MA S"
                  );
        }

      else
        {
         FileWrite(file_handle,
                   iTime(_Symbol,PERIOD_CURRENT,i),
                   //--- OHLC
                   iOpen(_Symbol,PERIOD_CURRENT,i),
                   iHigh(_Symbol,PERIOD_CURRENT,i),
                   iLow(_Symbol,PERIOD_CURRENT,i),
                   iClose(_Symbol,PERIOD_CURRENT,i),
                   ma_fast_reading[i],
                   ma_slow_reading[i]
                   );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef HORIZON
#undef MA_PERIOD_FAST
#undef MA_PERIOD_SLOW
#undef MA_TYPE
//+------------------------------------------------------------------+


Анализ наших рыночных данных на Python

После этого мы запустили скрипт на Python, чтобы проанализировать данные и изучить на их основе торговые правила. Мы начали с импорта стандартных библиотек Python.

#Import the standard python libraries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns

Затем мы считали CSV-файл, сгенерированный скриптом. 

#Read in the data
data = pd.read_csv("/content/EURUSD Cross Over Data.csv")

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

#Label the data
#Define the forecast horizon
HORIZON = 5

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

#Define targets
data['Target']   = data['Close'].shift(-HORIZON) - data['Close']
data['Target 2'] = data['MA F'].shift(-HORIZON) - data['MA F']
data['Target 3'] = data['MA S'].shift(-HORIZON) - data['MA S']

#Drop missing rows of data
data = data.iloc[:-HORIZON,:]
Затем мы создали две отдельные выборки: обучающую и тестовую. 
#Separate the test dates
train = data.iloc[:(-365*4),:]
test  = data.iloc[(-365*4):,:]
Затем загрузили выбранную статистическую модель. 
from sklearn.linear_model import LinearRegression
Теперь определим наши инструменты кросс-валидации временных рядов. 
tscv = TimeSeriesSplit(n_splits=5,gap=HORIZON)
Мы разделили признаки и целевые переменные.
X = train.iloc[:,1:-3]
y = train.iloc[:,-3:]
На этом этапе мы были почти готовы обучать нашу модель и экспортировать ее в формат ONNX. ONNX (Open Neural Network Exchange) - это общепринятый формат для обмена моделями машинного обучения независимо от среды, в которой они были обучены.
import onnx
from skl2onnx.common.data_types import FloatTensorType
from skl2onnx import convert_sklearn

Затем мы инициализировали модель и обучили ее на исторических рыночных данных. 

model = LinearRegression()
model.fit(X,y)

После этого мы определили размерность входа модели.

initial_types = [('float_input',FloatTensorType([1,X.shape[1]]))]

Затем определили размерность выхода модели. 

final_types = [('float_output',FloatTensorType([1,3]))]

Наконец, мы сохранили модель как прототип ONNX. 

onnx_proto = convert_sklearn(model,initial_types=initial_types,final_types=final_types,target_opset=12)
onnx.save(onnx_proto,'EURUSD Detailed RF.onnx')


Реализация усовершенствований на MQL5

После сохранения модели в качестве прототипа ONNX ее можно импортировать в торговую систему.

//+------------------------------------------------------------------+
//| Resources                                                        |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD MA.onnx" as const uchar onnx_proto[];

Начнём с определения новых глобальных переменных, которые будут связаны с нашими ONNX-моделями. Эти новые переменные отвечают за обработку входных и выходных параметров, которые дает нам наша модель. Мы также определим обработчик, который позволит нам запускать прогнозы на основе нашей модели.

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
vectorf model_inputs,model_outputs;
long    model;

После инициализации необходимо предпринять несколько важных шагов, чтобы подготовить нашу модель к использованию на рынке. Во-первых, мы должны настроить модель из буфера ONNX, который мы импортировали. Далее определим размерности входа и выхода нашей модели. Наша модель принимает 6 входных параметров и выдает 3 прогноза. Отсюда мы определяем размерности входа и выхода для модели, а затем обеспечиваем успешное создание модели. Если наша торговая система больше не используется, мы освобождаем модель ONNX для освобождения ресурсов памяти. Всякий раз, когда мы получаем новые уровни цен, большая часть нашей торговой логики остается неизменной. Несколько важных усовершенствований, которые необходимо внести, заключаются в том, что мы сначала сохраняем все необходимые нам входные параметры модели в float-векторе.

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setup the ONNX model
   model = OnnxCreateFromBuffer(onnx_proto,ONNX_DATA_TYPE_FLOAT);

//--- Define the model parameter shape
   ulong input_shape[] = {1,6};
   ulong output_shape[] = {1,3};

   OnnxSetInputShape(model,0,input_shape);
   OnnxSetOutputShape(model,0,output_shape);

   model_inputs = vectorf::Zeros(6);
   model_outputs = vectorf::Zeros(3);

   if(model != INVALID_HANDLE)
     {
      return(INIT_SUCCEEDED);
     }

//---
   return(INIT_FAILED);
  }

Если модель ONNX больше не используется, мы освобождаем модель для освобождения ресурсов памяти.

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Free up memory we are no longer using when the application is off
   OnnxRelease(model);
  }

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

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//--- When price levels change

   datetime current_time = iTime("EURUSD",PERIOD_D1,0);
   static datetime  time_stamp;

//--- Update the time
   if(current_time != time_stamp)
     {
      time_stamp = current_time;

      //--- Fetch indicator current readings
      CopyBuffer(ma_fast_handler,0,0,1,ma_fast_reading);
      CopyBuffer(ma_slow_handler,0,0,1,ma_slow_reading);
      CopyBuffer(atr_handler,0,0,1,atr_reading);

      double open = iOpen("EURUSD",PERIOD_D1,0);
      double close = iClose("EURUSD",PERIOD_D1,0);
      double high = iHigh("EURUSD",PERIOD_D1,0);
      double low = iLow("EURUSD",PERIOD_D1,0);

      model_inputs[0] = (float) open;
      model_inputs[1] = (float) high;
      model_inputs[2] = (float) low;
      model_inputs[3] = (float) close;
      model_inputs[4] = (float) ma_fast_reading[0];
      model_inputs[5] = (float) ma_slow_reading[0];

      ask = SymbolInfoDouble("EURUSD",SYMBOL_ASK);
      bid = SymbolInfoDouble("EURUSD",SYMBOL_BID);

      //--- If we have no open positions
      if(PositionsTotal() == 0)
        {

         if(!(OnnxRun(model,ONNX_DATA_TYPE_FLOAT,model_inputs,model_outputs)))
           {
            Comment("Failed to obtain a forecast from our model: ",GetLastError());
           }

         else
           {
            Comment("Forecast: ",model_outputs);

            //--- Trading rules
            if((ma_fast_reading[0] > ma_slow_reading[0]) && (low > ma_fast_reading[0]) && (model_outputs[0] > 0))
              {
               //--- Buy signal
               Trade.Buy(0.01,"EURUSD",ask,ask-(atr_reading[0] * 2),ask+(atr_reading[0] * 2),"");
              }

            else
               if((ma_fast_reading[0] < ma_slow_reading[0]) && (high < ma_slow_reading[0]) && (model_outputs[0] < 0))
                 {
                  //--- Sell signal
                  Trade.Sell(0.01,"EURUSD",bid,bid+(atr_reading[0] * 2),bid-(atr_reading[0] * 2),"");
                 }
           }
        }
     }
  }
//+------------------------------------------------------------------+

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

Рисунок 8: Наша торговая стратегия в настоящее время демонстрирует устойчивый и здоровый рост баланса с течением времени

Если посмотреть на подробные результаты, видно, что общая чистая прибыль удвоилась по сравнению с первоначальной версией, которая была у нас незадолго до этого. Чистая прибыль сейчас составляет 120,00 долларов. В дополнение к этому валовой убыток, накопленный за этот тестовый период, незначительно снизился. Первоначальная версия нашей стратегии принесла валовой убыток в размере 900 долларов, тогда как новая версия показала убыток всего в 300 долларов. И все же наша общая чистая прибыль увеличилась более чем вдвое, а это значит, что этот вариант нашей стратегии определенно гораздо эффективнее. Наши показатели ожидаемой прибыли на сделку и прибыли наконец-то оказались лучше первоначальных значений. Что действительно впечатляет, так это то, что общее количество сделок сократилось почти вдвое по сравнению с количеством сделок, которые мы открывали ранее. Это означает, что мы получаем значительно большую прибыль при значительно меньшем количестве сделок.

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

Рисунок 9: Статистическая модель, которую мы реализовали в нашей стратегии, породила собственный набор проблем


Более глубокий анализ для внедрения усовершенствований

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

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

model = RandomForestRegressor()
model.fit(X,y)

onnx_proto = convert_sklearn(model,initial_types=initial_types,final_types=final_types,target_opset=12)
onnx.save(onnx_proto,'EURUSD Detailed RF.onnx')


Создание условий для роста в MQL5

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

//--- Trading rules
            if(((model_outputs[0] > 0) && (model_outputs[1] > 0) && (model_outputs[2] > 0)) || ((ma_fast_reading[0] > ma_slow_reading[0]) && (low > ma_fast_reading[0])))
              {
               //--- Buy signal
               Trade.Buy(0.01,"EURUSD",ask,ask-(atr_reading[0] * 2),ask+(atr_reading[0] * 2),"");
              }

            else
               if(((model_outputs[0] < 0) && (model_outputs[1] < 0) && (model_outputs[2] < 0)) || ((ma_fast_reading[0] < ma_slow_reading[0]) && (low < ma_slow_reading[0])))
                 {
                  //--- Sell signal
                  Trade.Sell(0.01,"EURUSD",bid,bid+(atr_reading[0] * 2),bid-(atr_reading[0] * 2),"");
                 }

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

Рисунок 10: Использование нелинейной статистической модели помогло нам достичь новых уровней эффективности на основе той же стратегии

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

Рисунок 11: Новая нелинейная модель с обучением под наблюдением скорректировала смещение, обнаруженное классической линейной моделью


Заключительные попытки

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

//+------------------------------------------------------------------+
//|                                                      ProjectName |
//|                                      Copyright 2020, CompanyName |
//|                                       http://www.companyname.net |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

//--- Define our moving average indicator
#define MA_PERIOD_FAST     30                   //--- Moving Average Fast Period
#define MA_PERIOD_SLOW     60                   //--- Moving Average Slow Period
#define MA_TYPE            MODE_SMA             //--- Type of moving average we have
#define HORIZON            5                    //--- Forecast horizon

//--- Our handlers for our indicators
int ma_fast_handle,ma_slow_handle;

//--- Data structures to store the readings from our indicators
double ma_fast_reading[],ma_slow_reading[];

//--- File name
string file_name = Symbol() + " Cross Over Data.csv";

//--- Amount of data requested
input int size = 3000;

//+------------------------------------------------------------------+
//| Our script execution                                             |
//+------------------------------------------------------------------+
void OnStart()
  {
   int fetch = size + (HORIZON * 2);
//---Setup our technical indicatorsa	
   ma_fast_handle        = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD_FAST,0,MA_TYPE,PRICE_CLOSE);
   ma_slow_handle        = iMA(_Symbol,PERIOD_CURRENT,MA_PERIOD_SLOW,0,MA_TYPE,PRICE_OPEN);

   
//---Set the values as series
   CopyBuffer(ma_fast_handle,0,0,fetch,ma_fast_reading);
   ArraySetAsSeries(ma_fast_reading,true);
   CopyBuffer(ma_slow_handle,0,0,fetch,ma_slow_reading);
   ArraySetAsSeries(ma_slow_reading,true);
   
//---Write to file
   int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");

   for(int i=size;i>=1;i--)
     {
      if(i == size)
        {
         FileWrite(file_handle,
                  //--- Time
                  "Time",
                   //--- OHLC
                   "Open",
                   "High",
                   "Low",
                   "Close",
                   //--- Moving averages
                   "MA F",
                   "MA S",
                   //--- Growth in OHLC channels
                   "Delta O",
                   "Delta H",
                   "Delta L",
                   "Delta C",
                   //--- Growth in MA Channels
                   "Delta MA F",
                   "Delta MA S",
                   
                   //--- Growth Across OHLC Channels
                   "Delta O - H",
                   "Delta O - L",
                   "Delta O - C",
                   "Delta H - L",
                   "Delta H - C",
                   "Delta L - C",
                   //--- Growth Between Price and the moving averages
                   "Delta C - MA F",
                   "Delta C - MA S"
                  );
        }

      else
        {
         FileWrite(file_handle,
                   iTime(_Symbol,PERIOD_CURRENT,i),
                   //--- OHLC
                   iOpen(_Symbol,PERIOD_CURRENT,i),
                   iHigh(_Symbol,PERIOD_CURRENT,i),
                   iLow(_Symbol,PERIOD_CURRENT,i),
                   iClose(_Symbol,PERIOD_CURRENT,i),
                   //--- Moving Averages
                   ma_fast_reading[i],
                   ma_slow_reading[i],
                   //--- Growth in OHLC channels
                   iOpen(_Symbol,PERIOD_CURRENT,i) - iOpen(_Symbol,PERIOD_CURRENT,i+HORIZON),
                   iHigh(_Symbol,PERIOD_CURRENT,i) - iHigh(_Symbol,PERIOD_CURRENT,i+HORIZON),
                   iLow(_Symbol,PERIOD_CURRENT,i) - iLow(_Symbol,PERIOD_CURRENT,i+HORIZON),
                   iClose(_Symbol,PERIOD_CURRENT,i) - iClose(_Symbol,PERIOD_CURRENT,i+HORIZON),
                   //--- Growth in MA Channels
                   ma_fast_reading[i] - ma_fast_reading[i+HORIZON],
                   ma_slow_reading[i] - ma_slow_reading[i+HORIZON],
                   //--- Growth across OHLC channels
                   iOpen(_Symbol,PERIOD_CURRENT,i+HORIZON) - iHigh(_Symbol,PERIOD_CURRENT,i+HORIZON),
                   iOpen(_Symbol,PERIOD_CURRENT,i+HORIZON) - iLow(_Symbol,PERIOD_CURRENT,i+HORIZON),
                   iOpen(_Symbol,PERIOD_CURRENT,i+HORIZON) - iClose(_Symbol,PERIOD_CURRENT,i+HORIZON),
                   iHigh(_Symbol,PERIOD_CURRENT,i+HORIZON) - iLow(_Symbol,PERIOD_CURRENT,i+HORIZON),
                   iHigh(_Symbol,PERIOD_CURRENT,i+HORIZON) - iClose(_Symbol,PERIOD_CURRENT,i+HORIZON),
                   iLow(_Symbol,PERIOD_CURRENT,i+HORIZON) - iClose(_Symbol,PERIOD_CURRENT,i+HORIZON), 
                   //--- Growth between price and the moving averages
                   iClose(_Symbol,PERIOD_CURRENT,i+HORIZON) - ma_fast_reading[i+HORIZON],
                   iClose(_Symbol,PERIOD_CURRENT,i+HORIZON) - ma_slow_reading[i+HORIZON]
            
                   );
        }
     }
//--- Close the file
   FileClose(file_handle);
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef HORIZON
#undef MA_PERIOD_FAST
#undef MA_PERIOD_SLOW
#undef MA_TYPE
//+------------------------------------------------------------------+


Анализ данных на Python

После этого загружаем наши стандартные библиотеки Python. 

from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import TimeSeriesSplit,cross_val_score

А затем создаем наш объект кросс-валидации временных рядов. 

tscv = TimeSeriesSplit(n_splits=5,gap=HORIZON)

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

def get_model():
  return(RandomForestRegressor())

Теперь мы хотели бы тщательно оценить усовершенствования, вносимые всеми новыми признаками, созданными нами. Поэтому определим два входных параметра: один — это все входные признаки, которые у нас есть, а второй — классические входные признаки — цена открытия, максимум, минимум, цена закрытия и скользящая средняя. Затем определим целевые значения. 

X = train.iloc[:,1:-3]
X_classic = train.iloc[:,1:7]
y = train.iloc[:,-3:]

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

target_1 = []
target_2 = []
target_3 = []

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

target_1.append(np.mean(np.abs(cross_val_score(get_model(),X_classic,y.iloc[:,0],cv=tscv,scoring='neg_mean_squared_error'))))
target_1.append(np.mean(np.abs(cross_val_score(get_model(),X,y.iloc[:,0],cv=tscv,scoring='neg_mean_squared_error'))))

Рисунок 12: Наши подробные рыночные данные помогли нам лучше спрогнозировать будущую доходность по паре EURUSD вне выборки

То же самое верно и для нашей быстрой скользящей средней, которая была 30-периодной скользящей средней. Новые рыночные данные, обработанные нами вручную, снизили уровень наших ошибок до невероятно низких значений — это значительное улучшение показателя среднеквадратичной ошибки. 

target_2.append(np.mean(np.abs(cross_val_score(get_model(),X_classic,y.iloc[:,1],cv=tscv,scoring='neg_mean_squared_error'))))
target_2.append(np.mean(np.abs(cross_val_score(get_model(),X,y.iloc[:,1],cv=tscv,scoring='neg_mean_squared_error'))))

Рисунок 13: Мы также наблюдали снижение уровня ошибок, когда использовали более подробные рыночные данные для прогнозирования будущего значения 30-периодной скользящей средней

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

target_3.append(np.mean(np.abs(cross_val_score(get_model(),X_classic,y.iloc[:,2],cv=tscv,scoring='neg_mean_squared_error'))))
target_3.append(np.mean(np.abs(cross_val_score(get_model(),X,y.iloc[:,2],cv=tscv,scoring='neg_mean_squared_error'))))

Рисунок 14: Однако прогноз для медленной скользящей средней — 60-периодной — выиграл от детализированных данных заметно меньше.


Реализация наших усовершенствований на MQL5

Теперь нам нужно внести несколько изменений в нашу торговую систему. Для начала необходимо изменить размерность входа нашей торговой системы. Изначально значение было 6, а теперь нам нужно установить его на 20. 

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Setup our indicators
   ma_fast_handler = iMA("EURUSD",PERIOD_D1,30,0,MODE_SMA,PRICE_CLOSE);
   ma_slow_handler = iMA("EURUSD",PERIOD_D1,60,0,MODE_SMA,PRICE_CLOSE);
   atr_handler     = iATR("EURUSD",PERIOD_D1,14);

//--- Setup the ONNX model
   model = OnnxCreateFromBuffer(onnx_proto,ONNX_DATA_TYPE_FLOAT);

//--- Define the model parameter shape
   ulong input_shape[] = {1,20};
   ulong output_shape[] = {1,3};

   OnnxSetInputShape(model,0,input_shape);
   OnnxSetOutputShape(model,0,output_shape);

   model_inputs = vectorf::Zeros(20);
   model_outputs = vectorf::Zeros(3);

   if(model != INVALID_HANDLE)
     {
      return(INIT_SUCCEEDED);
     }

//---
   return(INIT_FAILED);
  }

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

//--- Update the time
   if(current_time != time_stamp)
     {
      time_stamp = current_time;

      //--- Fetch indicator current readings
      CopyBuffer(ma_fast_handler,0,0,10,ma_fast_reading);
      CopyBuffer(ma_slow_handler,0,0,10,ma_slow_reading);
      CopyBuffer(atr_handler,0,0,10,atr_reading);

      double open = iOpen("EURUSD",PERIOD_D1,0);
      double close = iClose("EURUSD",PERIOD_D1,0);
      double high = iHigh("EURUSD",PERIOD_D1,0);
      double low = iLow("EURUSD",PERIOD_D1,0);

      model_inputs[0] = (float) open;
      model_inputs[1] = (float) high;
      model_inputs[2] = (float) low;
      model_inputs[3] = (float) close;
      model_inputs[4] = (float) ma_fast_reading[0];
      model_inputs[5] = (float) ma_slow_reading[0];
      model_inputs[6] = (float) (iOpen(_Symbol,PERIOD_CURRENT,0) - iOpen(_Symbol,PERIOD_CURRENT,0+HORIZON));
      model_inputs[7] = (float) (iHigh(_Symbol,PERIOD_CURRENT,0) - iHigh(_Symbol,PERIOD_CURRENT,0+HORIZON));
      model_inputs[8] = (float) (iLow(_Symbol,PERIOD_CURRENT,0) - iLow(_Symbol,PERIOD_CURRENT,0+HORIZON));
      model_inputs[9] = (float) (iClose(_Symbol,PERIOD_CURRENT,0) - iClose(_Symbol,PERIOD_CURRENT,0+HORIZON));
      model_inputs[10] = (float)  (ma_fast_reading[0] - ma_fast_reading[0+HORIZON]);
      model_inputs[11] = (float) (ma_slow_reading[0] - ma_slow_reading[0+HORIZON]);
      model_inputs[12] = (float) (iOpen(_Symbol,PERIOD_CURRENT,0+HORIZON) - iHigh(_Symbol,PERIOD_CURRENT,0+HORIZON));
      model_inputs[13] = (float)  (iOpen(_Symbol,PERIOD_CURRENT,0+HORIZON) - iLow(_Symbol,PERIOD_CURRENT,0+HORIZON));
      model_inputs[14] = (float) (iOpen(_Symbol,PERIOD_CURRENT,0+HORIZON) - iClose(_Symbol,PERIOD_CURRENT,0+HORIZON));
      model_inputs[15] = (float)  (iHigh(_Symbol,PERIOD_CURRENT,0+HORIZON) - iLow(_Symbol,PERIOD_CURRENT,0+HORIZON));
      model_inputs[16] = (float) (iHigh(_Symbol,PERIOD_CURRENT,0+HORIZON) - iClose(_Symbol,PERIOD_CURRENT,0+HORIZON));
      model_inputs[17] = (float) (iLow(_Symbol,PERIOD_CURRENT,0+HORIZON) - iClose(_Symbol,PERIOD_CURRENT,0+HORIZON));
      model_inputs[18] = (float) (iClose(_Symbol,PERIOD_CURRENT,0+HORIZON) - ma_fast_reading[0+HORIZON]);
      model_inputs[19] = (float) (iClose(_Symbol,PERIOD_CURRENT,0+HORIZON) - ma_slow_reading[0+HORIZON]);

      ask = SymbolInfoDouble("EURUSD",SYMBOL_ASK);
      bid = SymbolInfoDouble("EURUSD",SYMBOL_BID);

      //--- If we have no open positions
      if(PositionsTotal() == 0)
        {

         if(!(OnnxRun(model,ONNX_DATA_TYPE_FLOAT,model_inputs,model_outputs)))
           {
            Comment("Failed to obtain a forecast from our model: ",GetLastError());
           }

         else
           {
            Comment("Forecast: ",model_outputs);

            //--- Trading rules
            if(((model_outputs[0] > 0) && (model_outputs[1] > 0) && (model_outputs[2] > 0)) || ((ma_fast_reading[0] > ma_slow_reading[0]) && (low > ma_fast_reading[0])))
              {
               //--- Buy signal
               Trade.Buy(0.01,"EURUSD",ask,ask-(atr_reading[0] * 2),ask+(atr_reading[0] * 2),"");
              }

            else
               if(((model_outputs[0] < 0) && (model_outputs[1] < 0) && (model_outputs[2] < 0)) || ((ma_fast_reading[0] < ma_slow_reading[0]) && (low < ma_slow_reading[0])))
                 {
                  //--- Sell signal
                  Trade.Sell(0.01,"EURUSD",bid,bid+(atr_reading[0] * 2),bid-(atr_reading[0] * 2),"");
                 }
           }
        }
     }
  }

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

Рисунок 15: Новая полученная нами кривая эквити слишком волатильна

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

Рисунок 16: Подробный анализ результатов, полученных в финальной версии нашей торговой системы


Заключение

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

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

Что касается постоянных читателей, то нам становится ясна опасность слепого доверия RMSE. В ходе анализа подробных рыночных данных мы выявили значительное улучшение показателя RMSE вне выборки при прогнозировании доходности по паре EURUSD и 30-периодной простой скользящей средней. Однако эти существенные усовершенствования показателей RMSE вне выборки привели к нежелательным последствиям для нашей прибыльности. Таким образом, читатель должен быть лучше осведомлен об ограничениях классического статистического обучения под наблюдением. 

Название файла Описание файла
Fetch Data.mq5 Скрипт, который мы написали для получения базовых рыночных данных по обменным курсам по паре EURUSD (4 ряда OHLC-данных, 2 скользящие средние).
Fetch Data 2.mq5 Скрипт, который мы написали для получения подробных рыночных данных по обменным курсам по паре EURUSD (20 столбцов).
MA_Crossover_V1.mq5 Наиболее широко известная версия стратегии пересечения скользящих средних, которую мы реализовали для определения базовых уровней эффективности. 
MA_Crossover_V2.mq5
Лучшие ручные улучшения, которые мы только могли себе представить, улучшившие долгосрочную эффективность стратегии.
MA_Crossover_V3.mq5
Эта версия нашей стратегии опиралась на простую статистическую модель, но модель выучила перекос в сторону длинных входов.
MA_Crossover_V4.mq5
Лучшая версия нашей торговой стратегии, которую мы разработали вместе. Исправлено смещение предыдущей версии без потери прибыльности.
MA_Crossover_V5.mq5
Окончательная версия нашей торговой стратегии, которую мы разработали, используя обширные и подробные наблюдения за обменными курсами по паре EURUSD.
Advanced_Moving_Averages.ipynb Ноутбук Jupyter, который мы использовали для анализа рыночных данных.

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

Прикрепленные файлы |
Fetch_Data.mq5 (3.33 KB)
Fetch_Data_2.mq5 (5.63 KB)
Разработка инструментария для анализа Price Action (Часть 59): Выявление точных пробоев при фрактальной консолидации с помощью геометрической асимметрии Разработка инструментария для анализа Price Action (Часть 59): Выявление точных пробоев при фрактальной консолидации с помощью геометрической асимметрии
Изучая широкий спектр сетапов пробоя, я заметил, что неудачные пробои редко были связаны с нехваткой волатильности и гораздо чаще – со слабой внутренней структурой. Это наблюдение легло в основу подхода, представленного в этой статье. Подход выявляет паттерны, в которых последний ценовой отрезок заметно превосходит предыдущий по длине, наклону и скорости, что служит явным признаком накопления импульса перед направленным расширением. Обнаруживая эти тонкие геометрические дисбалансы внутри консолидации, трейдер может заранее распознавать пробои с более высокой вероятностью еще до выхода цены из диапазона. Далее показано, как этот геометрический подход на основе фракталов преобразует структурный дисбаланс в точные сигналы пробоя.
Разработка инструментария для анализа Price Action (Часть 58): Модуль анализа сжатия диапазона и классификации зрелости Разработка инструментария для анализа Price Action (Часть 58): Модуль анализа сжатия диапазона и классификации зрелости
В продолжение предыдущей статьи, где был представлен модуль классификации состояния рынка, в этой части мы сосредоточимся на реализации основной логики выявления и оценки зон сжатия. В статье представлена система обнаружения сжатия диапазона и оценки зрелости на языке MQL5, которая анализирует зоны рыночной консолидации, опираясь только на динамику цены.
Разработка торговой стратегии: Метод Triple Sine для возврата к среднему значению Разработка торговой стратегии: Метод Triple Sine для возврата к среднему значению
В этой статье представлен метод Triple Sine (тройного синуса) для возврата к среднему значению — торговая стратегия, опирающаяся на новый математический индикатор Triple Sine Oscillator (TSO). Индикатор TSO выводится из функции куба синуса, которая колеблется между –1 и +1, что делает его подходящим для выявления условий перекупленности и перепроданности на рынке. В целом, данное исследование демонстрирует, как математические функции можно преобразовать в практические инструменты для торговли.
Алгоритм оптимизации кита-белухи — Beluga Whale Optimization (BWO) Алгоритм оптимизации кита-белухи — Beluga Whale Optimization (BWO)
Кандидат в нашу рейтинговую таблицу — Beluga Whale Optimization, метаэвристика, построенная на трёх моделях поведения кита-белухи: парном плавании, охоте с полётом Леви и обновлении популяции через падение кита. По ходу реализации обнаружилось, что алгоритм не столько оптимизирует, сколько считывает геометрию тестового стенда, разбираем механизм этого и собираем честную перспективную модификацию BWOm.