English 中文 Deutsch 日本語
preview
Инженерия признаков с Python и MQL5 (Часть III): Угол наклона цены (2) Полярные координаты

Инженерия признаков с Python и MQL5 (Часть III): Угол наклона цены (2) Полярные координаты

MetaTrader 5Примеры |
113 2
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

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

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

Отсутствие реальной интерпретации — лишь одна из многих проблем, которую приходится преодолевать трейдерам, заинтересованным в расчете угла, создаваемого изменениями уровней цен. В нашей предыдущей статье мы попытались решить эту проблему, заменив время на ось x, чтобы образовавшийся угол представлял собой соотношение уровней цен и имел какое-то интерпретируемое значение. During our exploration, we observed that it is effortless to find our dataset riddled with "infinity" values after performing this transformation. Читатели, желающие быстро освежить в памяти то, что мы наблюдали ранее, могут быстро перейти по ссылке на статью, здесь.

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

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

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

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


Что такое полярные координаты и чем они могут быть полезны?

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

Любая точка в декартовой системе представлена парой координат (x, y). x - горизонтальное расстояние от начала координат, а y - вертикальное. 

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

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

Полярные координаты представлены в виде пары (r, theta), где:

  • R - радиальное расстояние от точки отсчета (начала координат)
  • Theta - угол, измеренный от опорного направления.

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

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

Скриншот 1

Рис. 1. Определение наших декартовых точек, которые будут преобразованы в полярные точки

Теперь, когда мы определили входные данные x и y, нам нужно вычислить первый элемент полярной пары — радиальное расстояние от начала координат, r. 

Скриншот 2

Рис. 2. Замкнутая формула для вычисления r по (x, y)

Геометрически полярные координаты можно представить себе как описание окружности. Угол, образованный между r и x, называется тета (theta). Таким образом, полярные координаты просто предполагают, что использование r и theta столь же информативно, как и непосредственное использование x и y. Если представить себе x и y так, как показано на рис. 3, то радиальное расстояние r рассчитывается путем применения теоремы Пифагора к сторонам x и y.

Скриншот 3

Рис. 3. Полярные координаты можно визуализировать как описание точки на окружности

Угол между r и x, тета, удовлетворяет наше стремление к реальному значению. В этом простом примере тета коррелирует с направлением тренда, формируемого изменениями цены открытия и закрытия. Тета получается путем вычисления арктангенса цены закрытия, деленного на цену открытия, как показано на рисунке ниже.

скриншот 4

Рис. 4. Расчет theta по цене открытия (x) и закрытия (y)

Учитывая любые полярные координаты (r, theta), мы можем легко преобразовать их обратно в исходные уровни цен, используя две формулы ниже:

Скриншот 5

Рис. 5. Как преобразовать полярные координаты обратно в декартовы

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

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


Начало работы в MQL5

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

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

#property copyright "Gamuchirai Zororo Ndawana"
#property link      "https://www.mql5.com"
#property version   "1.00"
#property script_show_inputs

//---File name
string file_name = _Symbol + " " + " Polar Coordinates.csv";

//---Amount of data requested
input int size = 100;
int size_fetch = size + 100;

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

void OnStart()
  {
      //---Write to file
       int file_handle=FileOpen(file_name,FILE_WRITE|FILE_ANSI|FILE_CSV,",");
       
    for(int i=size;i>0;i--){
      if(i == size){
            FileWrite(file_handle,"Time","Open","High","Low","Close","R","Theta","X Derivatie","Y Derivative","Theta Derivative");
      }
      
      else{

Перейдем к расчету r, используя формулу, которую мы обсуждали на рис. 2.

double r = MathSqrt(MathPow(iOpen(_Symbol,PERIOD_CURRENT,i),2) + MathPow(iClose(_Symbol,PERIOD_CURRENT,i),2));

Theta рассчитывается через обратный тангенс отношения y к x. Это уже реализовано в API MQL5.

double theta = MathArctan2(iClose(_Symbol,PERIOD_CURRENT,i),iOpen(_Symbol,PERIOD_CURRENT,i));

Напомним, что формула для расчета x (цены открытия) приведена на рис. 5 выше. Мы можем дифференцировать эту формулу относительно theta, чтобы вычислить первую производную цены открытия. Производная cos(), как вы уже знаете, равна -sin().

double derivative_x = r * (-(MathSin(theta)));

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

double derivative_y = r * MathCos(theta);

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

double derivative_theta = (1/MathPow(MathCos(theta),2));

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

           FileWrite(file_handle,iTime(_Symbol,PERIOD_CURRENT,i),
                                 iOpen(_Symbol,PERIOD_CURRENT,i),
                                 iHigh(_Symbol,PERIOD_CURRENT,i),
                                 iLow(_Symbol,PERIOD_CURRENT,i),
                                 iClose(_Symbol,PERIOD_CURRENT,i),
                                 r,
                                 theta,
                                 derivative_x,
                                 derivative_y,
                                 derivative_y
                                 );
      } 
    }
    
    FileClose(file_handle);
  }
//+---------


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

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

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

Пометьте данные, чтобы отметить, выросли ли уровни цен на следующий день или упали.

data = pd.read_csv("EURUSD  Polar Coordinates.csv")
data["UP DOWN"] = 0
data.loc[data["Close"] < data["Close"].shift(-1),"UP DOWN"] = 1
data

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

data.loc[(data["R"] < data["R"].shift(-1)) & (data['Theta'] < data['Theta'].shift(-1)) & (data['Close'] > data['Close'].shift(-1))

Аналогично, если мы выполним тот же запрос, но в противоположном направлении, то есть, будем искать случаи, когда R и Theta увеличились, но будущая цена упала, мы снова обнаружим, что pandas возвращает 0 случаев.

data.loc[(data["R"] > data["R"].shift(-1)) & (data['Theta'] > data['Theta'].shift(-1)) & (data['Close'] < data['Close'].shift(-1))]

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

data['Theta_rescaled'] = (data['Theta'] - data['Theta'].min()) / (data['Theta'].max() - data['Theta'].min()) * (2 * np.pi)
data['R_rescaled'] = (data['R'] - data['R'].min()) / (data['R'].max() - data['R'].min()) 

# Create the polar plot
fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})

# Plot data points on the polar axis
ax.scatter(data['Theta_rescaled'], data['R_rescaled'],c=data["UP DOWN"], cmap='viridis', edgecolor='black', s=100)

# Add plot labels
ax.set_title("Polar Plot of OHLC Points")
plt.colorbar(plt.cm.ScalarMappable(cmap='viridis'), ax=ax, label='1(UP) | O(DOWN)')

plt.show()

Скриншот 6

Рис. 6. Визуализация наших ценовых данных в виде полярных точек на полярном круге

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

data.isna().any()

Скриншот 7

Рис. 7. Проверяем, являются ли какие-либо из наших значений нулевыми


Моделирование данных

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

LOOK_AHEAD = 1
data['R Target'] = data['R'].shift(-LOOK_AHEAD)
data['Theta Target'] = data['Theta'].shift(-LOOK_AHEAD)
data.dropna(inplace=True)
data.reset_index(drop=True,inplace=True)

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

#Let's entirely drop off the last 2 years of data
_ = data.iloc[-((365 * 2) + 230):,:]
data = data.iloc[:-((365 * 2) + 230),:]
data

Скриншот 8

Рис. 8. Наш набор данных после удаления данных за последние 2 года

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

from sklearn.ensemble import GradientBoostingRegressor
from sklearn.model_selection import train_test_split,TimeSeriesSplit,cross_val_score

Теперь определим наш объект разделения временного ряда.

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

Определим входные данные и цели.

X = data.columns[1:-5]
y = data.columns[-2:]

Разделим данные на обучающую и тестовую части.

train , test = train_test_split(data,test_size=0.5,shuffle=False)

Теперь подготовим разделение на обучающую и тестовую части.

train_X = train.loc[:,X]
train_y = train.loc[:,y]

test_X = test.loc[:,X]
test_y = test.loc[:,y]

Разделение обучения и тестирования должно быть стандартизировано.

mean_scores = train_X.mean()
std_scores = train_X.std()

Масштабируем данные.

train_X = ((train_X - mean_scores) / std_scores)
test_X = ((test_X - mean_scores) / std_scores)

Инициализируем модель.

model = GradientBoostingRegressor()

Подготовим таблицу для хранения результатов.

results = pd.DataFrame(index=["Train","Test"],columns=["GBR"])

Подгоним модель под прогноз R.

results.iloc[0,0] = np.mean(np.abs(cross_val_score(model,train_X,train_y["R Target"],cv=tscv)))
results.iloc[1,0] = np.mean(np.abs(cross_val_score(model,test_X,test_y["R Target"],cv=tscv)))
results
GBR
Обучение 0.76686
Тестирование   0.89129

Подгоним модель для прогнозирования Theta.

results.iloc[0,0] = np.mean(np.abs(cross_val_score(model,train_X,train_y["Theta Target"],cv=tscv)))
results.iloc[1,0] = np.mean(np.abs(cross_val_score(model,test_X,test_y["Theta Target"],cv=tscv)))
results
GBR
Обучение 0.368166
Тестирование 0.110126


Экспорт в ONNX

Загружаем нужные нам библиотеки.

import onnx
import skl2onnx 
from skl2onnx.common.data_types import FloatTensorType

Инициализируем модели.

r_model = GradientBoostingRegressor()
theta_model = GradientBoostingRegressor()

Сохраним глобальные баллы стандартизации для всего набора данных в формате CSV.

mean_scores = data.loc[:,X].mean()
std_scores = data.loc[:,X].std()

mean_scores.to_csv("EURUSD Polar Coordinates Mean.csv")
std_scores.to_csv("EURUSD Polar Coordinates Std.csv")

Нормализуем весь набор данных.

data[X] = ((data.loc[:,X] - mean_scores) / std_scores)

Подгоним модели под масштабированные данные.

r_model.fit(data.loc[:,X],data.loc[:,'R Target'])
theta_model.fit(data.loc[:,X],data.loc[:,'Theta Target'])

Определим форму входных данных.

initial_types = [("float_input",FloatTensorType([1,len(X)]))]

Подготовим прототипы ONNX к сохранению.

r_model_proto = skl2onnx.convert_sklearn(r_model,initial_types=initial_types,target_opset=12)
theta_model_proto = skl2onnx.convert_sklearn(theta_model,initial_types=initial_types,target_opset=12)

Сохраним файлы ONNX.

onnx.save(r_model_proto,"EURUSD D1 R Model.onnx")
onnx.save(theta_model_proto,"EURUSD D1 Theta Model.onnx")


Начало работы в MQL5

Теперь мы готовы создать наше торговое приложение.

//+------------------------------------------------------------------+
//|                                              EURUSD Polar EA.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System Constants                                                 |
//+------------------------------------------------------------------+
#define ONNX_INPUTS 9                                              //The total number of inputs for our onnx model
#define ONNX_OUTPUTS 1                                             //The total number of outputs for our onnx model
#define TF_1  PERIOD_D1                                            //The system's primary time frame
#define TRADING_VOLUME 0.1                                         //The system's trading volume

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

//+------------------------------------------------------------------+
//| System Resources                                                 |
//+------------------------------------------------------------------+
#resource "\\Files\\EURUSD D1 R Model.onnx" as uchar r_model_buffer[];
#resource "\\Files\\EURUSD D1 Theta Model.onnx" as uchar theta_model_buffer[];

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

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
double mean_values[] = {1.1884188643844635,1.1920754015799868,1.1847545720868993,1.1883860236998025,1.6806588395310122,0.7853854898794739,-1.1883860236998025,1.1884188643844635,1.1884188643844635};
double std_values[]  = {0.09123896995032886,0.09116171300874902,0.0912656190371797,0.09120265318308786,0.1289537623737421,0.0021932437785043796,0.09120265318308786,0.09123896995032886,0.09123896995032886};
double current_r,current_theta;
long r_model,theta_model;
vectorf r_model_output = vectorf::Zeros(ONNX_OUTPUTS);
vectorf theta_model_output = vectorf::Zeros(ONNX_OUTPUTS);
double bid,ask;
int ma_o_handler,ma_c_handler,state;
double ma_o_buffer[],ma_c_buffer[];

Загрузим торговую библиотеку.

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

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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   if(!setup())
     {
      Comment("Failed To Load Corretly");
      return(INIT_FAILED);
     }

   Comment("Started");
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   OnnxRelease(r_model);
   OnnxRelease(theta_model);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   update();
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Get a prediction from our models                                 |
//+------------------------------------------------------------------+
void get_model_prediction(void)
  {
//Define theta and r
   double o = iOpen(_Symbol,PERIOD_CURRENT,1);
   double h = iHigh(_Symbol,PERIOD_CURRENT,1);
   double l = iLow(_Symbol,PERIOD_CURRENT,1);
   double c = iClose(_Symbol,PERIOD_CURRENT,1);
   current_r = MathSqrt(MathPow(o,2) + MathPow(c,2));
   current_theta = MathArctan2(c,o);

   vectorf model_inputs =
     {
      (float) o,
      (float) h,
      (float) l,
      (float) c,
      (float) current_r,
      (float) current_theta,
      (float)(current_r * (-(MathSin(current_theta)))),
      (float)(current_r * MathCos(current_theta)),
      (float)(1/MathPow(MathCos(current_theta),2))
     };

//Standardize the model inputs
   for(int i = 0; i < ONNX_INPUTS;i++)
     {
      model_inputs[i] = (float)((model_inputs[i] - mean_values[i]) / std_values[i]);
     }

//Get a prediction from our model
   OnnxRun(r_model,ONNX_DATA_TYPE_FLOAT,model_inputs,r_model_output);
   OnnxRun(theta_model,ONNX_DATA_TYPE_FLOAT,model_inputs,theta_model_output);

//Give our prediction
   Comment(StringFormat("R: %f \nTheta: %f\nR Forecast: %f\nTheta Forecast: %f",current_r,current_theta,r_model_output[0],theta_model_output[0]));
  }

Обновим систему по мере поступления новых цен.

//+------------------------------------------------------------------+
//| Update system state                                              |
//+------------------------------------------------------------------+
void update(void)
  {
   static datetime time_stamp;
   datetime current_time = iTime(_Symbol,TF_1,0);

   bid = SymbolInfoDouble(_Symbol,SYMBOL_BID);
   ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   if(current_time != time_stamp)
     {
      CopyBuffer(ma_o_handler,0,0,1,ma_o_buffer);
      CopyBuffer(ma_c_handler,0,0,1,ma_c_buffer);
      time_stamp = current_time;
      get_model_prediction();
      manage_account();
      if(PositionsTotal() == 0)
         get_signal();
     }
  }

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

//+------------------------------------------------------------------+
//| Manage the open positions we have in the market                  |
//+------------------------------------------------------------------+
void manage_account()
  {
   if(AccountInfoDouble(ACCOUNT_BALANCE) < AccountInfoDouble(ACCOUNT_EQUITY))
     {
      while(PositionsTotal() > 0)
         Trade.PositionClose(Symbol());
     }

   if(state == 1)
     {
      if(ma_c_buffer[0] < ma_o_buffer[0])
         Trade.PositionClose(Symbol());
     }

   if(state == -1)
     {
      if(ma_c_buffer[0] > ma_o_buffer[0])
         Trade.PositionClose(Symbol());
     }
  }

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

//+------------------------------------------------------------------+
//| Setup system variables                                           |
//+------------------------------------------------------------------+
bool setup(void)
  {
   ma_o_handler = iMA(Symbol(),TF_1,50,0,MODE_SMA,PRICE_CLOSE);
   ma_c_handler = iMA(Symbol(),TF_1,10,0,MODE_SMA,PRICE_CLOSE);

   r_model = OnnxCreateFromBuffer(r_model_buffer,ONNX_DEFAULT);
   theta_model = OnnxCreateFromBuffer(theta_model_buffer,ONNX_DEFAULT);

   if(r_model == INVALID_HANDLE)
      return(false);
   if(theta_model == INVALID_HANDLE)
      return(false);

   ulong input_shape[] = {1,ONNX_INPUTS};
   ulong output_shape[] = {1,ONNX_OUTPUTS};

   if(!OnnxSetInputShape(r_model,0,input_shape))
      return(false);
   if(!OnnxSetInputShape(theta_model,0,input_shape))
      return(false);

   if(!OnnxSetOutputShape(r_model,0,output_shape))
      return(false);
   if(!OnnxSetOutputShape(theta_model,0,output_shape))
      return(false);

   return(true);
  }

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

//+------------------------------------------------------------------+
//| Check if we have a trading signal                                |
//+------------------------------------------------------------------+
void get_signal(void)
  {
   if(ma_c_buffer[0] > ma_o_buffer[0])
     {
      if((r_model_output[0] < current_r) && (theta_model_output[0] < current_theta))
        {
         return;
        }
        
      if((r_model_output[0] > current_r) && (theta_model_output[0] > current_theta))
        {
         Trade.Buy(TRADING_VOLUME * 2,Symbol(),ask,0,0);
         Trade.Buy(TRADING_VOLUME * 2,Symbol(),ask,0,0);
         state = 1;
         return;
        }
        
      Trade.Buy(TRADING_VOLUME,Symbol(),ask,0,0);
      state = 1;
      return;
     }

   if(ma_c_buffer[0] < ma_o_buffer[0])
     {
      if((r_model_output[0] > current_r) && (theta_model_output[0] > current_theta))
        {
         return;
        }

     if((r_model_output[0] < current_r) && (theta_model_output[0] < current_theta))
        {
         
         Trade.Sell(TRADING_VOLUME * 2,Symbol(),bid,0,0);
         Trade.Sell(TRADING_VOLUME * 2,Symbol(),bid,0,0);
         state = -1;
         return;
        }

      Trade.Sell(TRADING_VOLUME,Symbol(),bid,0,0);
      state = -1;
      return;
     }
  }

Мы не используем отмену определения системных констант.

//+------------------------------------------------------------------+
//| Undefine system variables we don't need                          |
//+------------------------------------------------------------------+
#undef ONNX_INPUTS
#undef ONNX_OUTPUTS
#undef TF_1
//+------------------------------------------------------------------+


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

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

Скриншот 9

Рис. 9. Настройки нашего тестирования на истории

Теперь укажем начальные настройки аккаунта. 



Рис. 10. Второй набор настроек для нашего критического тестирования на истории на данных вне выборки

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

Скриншот 11

Рис. 11. Результаты тестирования на истории, полученные нами при торговле сигналами, сгенерированными нашими угловыми преобразованиями

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

Скриншот 12

Рис. 12. Подробный анализ результатов нашего тестирования на истории


Заключение

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

Прикрепленный файл Описание
Polar Fetch Data Наш настраиваемый скрипт для извлечения данных о ценах и преобразования их в полярные координаты.
EURUSD Polar EA Советник, торгующий на основе сигналов, полученных при обнаружении изменений угла наклона.
EURUSD D1 R Model Модель ONNX, отвечающая за прогнозирование наших будущих значений R
EURUSD D1 Theta Model Модель ONNX, отвечающая за прогнозирование наших будущих значений Theta
EURUSD Polar Coordinates Jupyter Notebook, который мы использовали для анализа данных, полученных с помощью скрипта MQL5

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

Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (2)
Too Chee Ng
Too Chee Ng | 27 мар. 2025 в 10:41
Например,
Aleksej Poljakov
Aleksej Poljakov | 17 нояб. 2025 в 15:38

В вашем предложении есть один серьезный недостаток.

Формула r=(x^2+y^2)^0.5 работает только в том случае, если x и y соизмеримы. То есть по обеим осям у нас одинаковые единицы измерения. 

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

Именно поэтому у вас получились несуразные 180 градусов. То есть, цена пошла в противоположном направлении - от настоящего к прошлому. Хотите углы, то стройте линейную регрессию y = a*x+b. И из значения a выводите угол. Далее сравните результат с круговым нормальным распределением. Будет интересно.

Моделирование рынка (Часть 13): Сокеты (VII) Моделирование рынка (Часть 13): Сокеты (VII)
Когда мы разрабатываем что-то в xlwings или в любом другом пакете, позволяющем читать и писать непосредственно в Excel, мы должны заметить, что все программы, функции или процедуры выполняются, а затем завершают свою задачу. Они не остаются в цикле, и неважно, как сильно мы стараемся сделать всё по-другому.
Нейросети в трейдинге: Рекуррентное моделирование микродвижений рынка (Окончание) Нейросети в трейдинге: Рекуррентное моделирование микродвижений рынка (Окончание)
Реализация фреймворка EV-MGRFlowNet демонстрирует его ключевые преимущества: модульность, устойчивость к рыночным колебаниям и способность к самостоятельной выработке стратегии. Эти особенности делают фреймворк мощным инструментом для анализа, прогнозирования и развития автономных торговых стратегий.
Управление рисками (Часть 5): Интегрируем систему управления рисками в советник Управление рисками (Часть 5): Интегрируем систему управления рисками в советник
В этой статье мы реализуем систему управления рисками, разработанную в предыдущих публикациях, и добавим индикатор Order Blocks, представленный в других статьях. Кроме того, будет проведено тестирование на исторических данных (backtest), чтобы можно было сравнить результаты с применением системы управления рисками и оценить влияние динамического риска.
Алгоритм эволюции элитных кристаллов — Elite Crystal Evolution Algorithm (CEO-inspired): Практика Алгоритм эволюции элитных кристаллов — Elite Crystal Evolution Algorithm (CEO-inspired): Практика
Экспериментальное исследование на стандартных бенчмарк-функциях выявляет преимущества и ограничения прямой адаптации комбинаторных алгоритмов. Статья содержит детальное описание механизмов алгоритма ECEA и результатов его тестирования.