English Русский 中文 Español Deutsch 日本語
preview
Engenharia de Recursos com Python e MQL5 (Parte III): Ângulo do Preço (2) Coordenadas Polares

Engenharia de Recursos com Python e MQL5 (Parte III): Ângulo do Preço (2) Coordenadas Polares

MetaTrader 5Exemplos |
25 5
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

O interesse em transformar variações nos níveis de preço em variações de ângulos não diminuiu. Como já discutimos em nosso artigo anterior desta série, existem muitos desafios a serem superados para converter com sucesso variações nos níveis de preço em um ângulo que represente essa variação.

Uma das limitações mais comumente citadas em discussões da comunidade e postagens em fóruns é a falta de significado interpretável por trás de tais cálculos. Membros experientes da comunidade frequentemente explicam que um ângulo existe entre duas linhas; portanto, tentar calcular o ângulo formado por uma variação de preço não possui significado físico no mundo real. 

A falta de interpretação no mundo real é apenas um dos muitos desafios a serem superados por traders interessados em calcular o ângulo criado por variações nos níveis de preço. Em nosso artigo anterior, tentamos resolver esse problema substituindo o tempo do eixo x, para que o ângulo formado fosse uma razão de níveis de preço e tivesse algum significado interpretável. Durante nossa exploração, observamos que é fácil encontrar nosso conjunto de dados repleto de valores de “infinito” após realizar essa transformação. Leitores interessados em obter uma rápida revisão do que observamos anteriormente podem encontrar um link direto para o artigo aqui.

Diante dos inúmeros desafios que surgem ao tentar transformar variações nos níveis de preço em uma variação correspondente de ângulo e da falta de um significado definitivo no mundo real, há informações limitadas e organizadas sobre o assunto. 

Abordaremos o problema da conversão de preço para ângulo a partir de uma perspectiva totalmente nova. Desta vez, utilizaremos uma abordagem matematicamente mais sofisticada e robusta em comparação com as ferramentas que criamos em nossa primeira tentativa. Leitores que já estão familiarizados com coordenadas polares podem pular diretamente para a seção “Primeiros Passos em MQL5”, para ver como essas ferramentas matemáticas são implementadas no MQL5. 

Caso contrário, prosseguiremos agora para compreender o que são coordenadas polares e construir uma intuição de como podemos aplicá-las para calcular o ângulo formado pelas variações de preço em nosso terminal MetaTrader 5 e usar esses sinais para negociar. Ou seja:

  1. Nossa solução proposta possui significado físico no mundo real.
  2. Também resolveremos o problema que enfrentamos anteriormente de valores infinitos ou indefinidos.


O Que São Coordenadas Polares e Como Elas Podem Ser Úteis?

Sempre que utilizamos tecnologia GPS ou até mesmo aplicativos simples de planilhas, estamos empregando uma tecnologia conhecida como Coordenadas Cartesianas. Este é um sistema matemático para representar pontos em um plano, normalmente com 2 eixos perpendiculares. 

Qualquer ponto único no sistema cartesiano é representado como um par de coordenadas (x, y). Onde x representa a distância horizontal a partir da origem, e y representa a distância vertical a partir da origem. 

Se quisermos estudar processos que possuem componentes periódicos ou algum tipo de movimento circular, as coordenadas polares são mais adequadas para isso do que as coordenadas cartesianas. Coordenadas polares são um sistema totalmente diferente usado para representar pontos em um plano utilizando uma distância a partir de um ponto de referência e um ângulo a partir de uma direção de referência, que aumenta no sentido anti-horário.  

Os mercados financeiros tendem a demonstrar padrões que se repetem de forma quase periódica. Portanto, eles podem ser adequados para serem representados como coordenadas polares. Parece que o ângulo que os traders desejam calcular a partir das variações nos níveis de preço pode ser obtido naturalmente ao representar os níveis de preço como pares polares. 

Coordenadas polares são representadas como um par (r, theta), onde:

  • R: Representa a distância radial a partir do ponto de referência (origem)
  • heta: Representa o ângulo medido a partir da direção de referência.

Utilizando funções trigonométricas implementadas na API de Matrizes e Vetores do MQL5, podemos converter perfeitamente variações de preço em um ângulo que representa a mudança no preço. 

Para alcançar nosso objetivo, devemos primeiro nos familiarizar com a terminologia que utilizaremos ao longo de nossa discussão de hoje. Primeiro, devemos definir nossas entradas x e y que serão convertidas. Para nossa discussão, definiremos x como sendo o preço de abertura do Símbolo, e y representará o preço de fechamento.

Captura de tela 1

Fig 1: Definindo nossos pontos cartesianos que serão convertidos em pontos polares

Agora que definimos nossas entradas x e y, precisamos calcular o primeiro elemento do par polar, a distância radial a partir da origem, r. 

Captura de tela 2

Fig 2: A fórmula fechada para calcular r a partir de (x, y)

Quando representadas geometricamente, podemos imaginar coordenadas polares como descrevendo um círculo. O ângulo formado entre r e x é theta. Portanto, as coordenadas polares simplesmente propõem que usar r e theta é tão informativo quanto usar x e y diretamente. Quando x e y são visualizados conforme ilustrado na Fig 3, então a distância radial, r, é calculada aplicando o teorema de Pitágoras nos lados x e y.

Captura de tela 3

Fig 3: Coordenadas polares podem ser visualizadas como descrevendo um ponto em um círculo

O ângulo entre r e x, theta, satisfaz nosso desejo por significado no mundo real. Neste exemplo simples, theta se correlaciona com a direção da tendência formada pelas variações entre o preço de abertura e o preço de fechamento. Theta nos é dado pelo cálculo da tangente inversa do preço de fechamento dividido pelo preço de abertura, conforme ilustrado na figura abaixo.

Captura de tela 4

Fig 4: Calculando theta a partir do nosso preço de Abertura (x) e Fechamento (y)

Dadas quaisquer coordenadas polares (r, theta), podemos facilmente convertê-las de volta para seus níveis de preço originais usando as 2 fórmulas abaixo:

Captura de tela 5

Fig 5: Como converter coordenadas polares de volta em coordenadas cartesianas

Discutimos 4 fórmulas até agora, mas apenas as últimas 3 fórmulas contêm theta. A primeira fórmula que usamos para calcular r não possui relação com theta. As últimas 3 fórmulas que discutimos contêm theta e podem ser facilmente diferenciadas. As derivadas dessas funções trigonométricas são resultados bem conhecidos que podem ser facilmente encontrados online ou em qualquer livro didático elementar de cálculo. 

Usaremos essas 3 derivadas como entradas adicionais para treinar nosso computador a aprender a relação entre variações nos ângulos e suas correspondentes variações nos níveis de preço.


Primeiros Passos em MQL5

Vamos começar. Primeiro, precisamos construir um script em MQL5 que irá buscar dados históricos de mercado do nosso Terminal MetaTrader 5 e também realizar as transformações para nos fornecer os ângulos que estão sendo criados.

Primeiro, precisamos definir o nome do arquivo CSV que estamos criando e, em seguida, especificar quantas barras de dados serão buscadas. Como o número de barras a serem buscadas pode variar dependendo do seu corretor, definimos esse parâmetro como uma entrada para o script.

#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;

Quando nosso script for executado, criaremos um manipulador de arquivo para gravar os níveis de preço e suas variações correspondentes de ângulo.

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{

Vamos prosseguir para calcular r usando a fórmula que discutimos na Fig 2.

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

Theta é calculado por meio da tangente inversa da razão entre y e x. Isso é implementado para nós na API do MQL5.

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

Lembre-se de que a fórmula para calcular x (preço de Abertura) é apresentada na Fig 5 acima. Podemos diferenciar essa fórmula em relação a theta para calcular a primeira derivada do preço de abertura. A derivada de cos(), como você já sabe, é -sin().

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

Também podemos calcular a derivada de y, uma vez que conhecemos a derivada das funções trigonométricas.

double derivative_y = r * MathCos(theta);

Por fim, conhecemos a primeira derivada de theta. No entanto, a função trigonométrica não é implementada diretamente na API do MQL5; em vez disso, utilizaremos uma identidade matemática para substituí-la usando as funções apropriadas do MQL5.

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

Agora que calculamos nossos ângulos, podemos prosseguir para gravar nossos dados.

           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);
  }
//+---------


Analisando Nossos Dados

Agora que nossos dados estão gravados em formato CSV, vamos usá-los para treinar nosso computador a negociar os ângulos formados. Utilizaremos algumas bibliotecas Python para acelerar nosso processo de desenvolvimento.

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

Rotule os dados para observar se os níveis de preço aumentaram no dia seguinte ou se caíram no dia seguinte.

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

Aqui está o sinal de negociação que estamos procurando. Observe que sempre que R e Theta aumentam em nosso conjunto de dados original, os níveis de preço nunca caem. Essa chamada ao pandas não retorna nada, e isso demonstra o significado no mundo real por trás dos pares polares. Conhecer os valores futuros de R e Theta é tão bom quanto conhecer o nível de preço futuro.

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

Da mesma forma, se realizarmos a mesma consulta, mas na direção oposta, ou seja, procurarmos instâncias em que R e Theta aumentaram, mas o preço futuro caiu, novamente veremos que o pandas retorna 0 instâncias.

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

Portanto, nossos sinais de negociação serão formados sempre que nosso computador esperar que os valores futuros de R e Theta sejam maiores do que seus valores atuais. Seguindo em frente, agora podemos visualizar nossos dados de preço como pontos em um círculo polar. Como podemos observar na Fig 6, os dados ainda são difíceis de separar de forma eficaz.

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()

Captura de tela 6

Fig 6: Visualizando nossos dados de preço como pontos polares em um círculo polar

Vamos verificar rapidamente se algum de nossos valores é nulo.

data.isna().any()

Captura de tela 7

Fig 7: Verificando se algum de nossos valores é nulo


Modelagem dos Dados

Todos os nossos valores estão definidos, ótimo. Também precisamos rotular os dados. Lembre-se de que nosso alvo é o valor futuro de theta e 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)

Tenha em mente que queremos remover os últimos 2 anos de dados para que possamos usá-los como nosso teste para a aplicação.

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

Captura de tela 8

Fig 8: Nosso conjunto de dados após remover os últimos 2 anos de dados

Vamos agora treinar o computador usando os dados disponíveis. Utilizaremos uma árvore de gradiente impulsionado como nosso modelo de escolha, pois elas são particularmente boas em aprender efeitos de interação.

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

Agora defina nosso objeto de divisão de séries temporais.

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

Defina as entradas e os alvos.

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

Particione os dados em metades de treinamento e teste.

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

Agora prepare as divisões de treino e teste.

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

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

As divisões de treinamento e teste precisam ser padronizadas.

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

Escalonando os dados.

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

Inicializar o modelo.

model = GradientBoostingRegressor()

Prepare uma tabela para armazenar os resultados.

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

Ajuste o modelo para prever 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
Treinar 0.76686
Test   0.89129

Ajuste o modelo para prever 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
Treinar 0.368166
Test 0.110126


Exportando para ONNX

Carregue as bibliotecas necessárias.

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

Inicialize os modelos.

r_model = GradientBoostingRegressor()
theta_model = GradientBoostingRegressor()

Armazene as pontuações globais de padronização para todo o conjunto de dados em formato 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")

Normalize todo o conjunto de dados.

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

Ajuste os modelos nos dados escalonados.

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

Defina o formato de entrada.

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

Prepare os protótipos ONNX a serem salvos.

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)

Salve os arquivos ONNX.

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


Primeiros Passos em MQL5

Agora estamos prontos para construir nossa aplicação de negociação.

//+------------------------------------------------------------------+
//|                                              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

Carregue os modelos ONNX como recursos do sistema.

//+------------------------------------------------------------------+
//| 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[];

Defina nossas variáveis globais. Usaremos algumas dessas variáveis para padronizar nossos dados, armazenar as previsões do nosso modelo ONNX e muito mais.

//+------------------------------------------------------------------+
//| 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[];

Carregue a biblioteca de negociação.

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

Nossa aplicação de negociação é composta principalmente por manipuladores de eventos. Durante cada estágio do ciclo de vida da aplicação, chamaremos funções dedicadas para executar tarefas que correspondem ao nosso objetivo naquele momento. Assim, durante a inicialização, configuraremos nossos indicadores técnicos e, quando novos preços estiverem disponíveis, atualizaremos nossas leituras desses indicadores.

//+------------------------------------------------------------------+
//| 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();
  }
//+------------------------------------------------------------------+

Obtendo uma previsão do nosso modelo ONNX. Observe que fazemos a conversão de tipo de todas as entradas do modelo para o tipo float, para garantir que o modelo receba dados no formato e tamanho corretos, de acordo com suas expectativas.

//+------------------------------------------------------------------+
//| 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]));
  }

Atualize o sistema sempre que novos preços forem oferecidos.

//+------------------------------------------------------------------+
//| 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();
     }
  }

Gerencie a conta. Se estivermos perdendo dinheiro em uma negociação, preferimos fechá-la prontamente. Caso contrário, se tivermos entrado em uma negociação, mas as médias móveis cruzarem de uma forma que comprometa nossa confiança nessa posição, iremos fechá-la imediatamente.

//+------------------------------------------------------------------+
//| 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());
     }
  }

Configure variáveis do sistema, como indicadores técnicos e modelos 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);
  }

Verifique se temos um sinal de negociação. Verificaremos principalmente a orientação da nossa estratégia de cruzamento de médias móveis. Em seguida, verificaremos nossas expectativas fornecidas pelos modelos ONNX. Portanto, se o cruzamento de médias móveis nos der um sentimento baixista, mas nossos modelos ONNX de r e theta nos derem um sentimento altista, não abriremos nenhuma posição até que os dois sistemas estejam em concordância.

//+------------------------------------------------------------------+
//| 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;
     }
  }

Remova a definição de constantes do sistema que não estamos utilizando.

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


Testando Nosso Sistema

Vamos agora começar a testar nosso sistema. Lembre-se de que, durante nossa etapa de preparação dos dados, removemos os dados a partir de 1º de janeiro de 2022, para que nosso backtest reflita o quão bem nossa estratégia poderia se comportar com dados que ela nunca viu.

Captura de tela 9

Fig 9: Nossas configurações de backtest

Agora especifique as configurações iniciais da conta. 



Fig 10: Nosso segundo conjunto de configurações para nosso backtest crucial sobre dados fora da amostra

Podemos observar a curva de patrimônio dos sinais de negociação produzidos pelo nosso novo sistema. Nossa estratégia começou com um saldo de $5000 e terminou com um saldo em torno de $7000; esses são bons resultados e nos incentivam a continuar explorando e redefinindo nossa estratégia.

Captura de tela 11

Fig 11: Os resultados do backtest que obtivemos ao negociar os sinais gerados por nossas transformações de ângulo

Vamos analisar nossos resultados em detalhes. Nossa estratégia teve uma taxa de acerto de 88% em dados fora da amostra. Essa é uma informação encorajadora e pode oferecer ao leitor um bom ponto de partida para construir suas próprias aplicações, estendendo a funcionalidade e a capacidade do Terminal MetaTrader 5 que demonstramos nesta aplicação. Ou o leitor pode considerar usar nosso framework apenas como um guia e substituir completamente nossa estratégia de negociação por uma própria.

Captura de tela 12

Fig 12: Analisando em detalhes os resultados do nosso backtest


Conclusão

A solução que fornecemos hoje demonstrou como realizar o potencial diferencial encontrado ao converter de forma significativa variações nos níveis de preço em variações de ângulos. Nosso procedimento fornece um framework simples e elegante para lidar com esse problema, permitindo obter a melhor combinação entre lógica de negociação e lógica matemática. Além disso, ao contrário de participantes ocasionais do mercado que ficam presos tentando prever o preço diretamente, agora você possui alvos alternativos que são tão úteis quanto conhecer o próprio preço, ao mesmo tempo em que são mais fáceis de prever de forma consistente do que o preço em si.

Arquivo Anexado Descrição
Busca de Dados Polares Nosso script personalizado para buscar dados de preço e transformá-los em coordenadas polares.
EURUSD Polar EA O Expert Advisor que negocia os sinais gerados a partir das variações de ângulo detectadas.
EURUSD D1 R Model O modelo ONNX responsável por prever nossos valores futuros de R
EURUSD D1 Theta Model O modelo ONNX responsável por prever nossos valores futuros de Theta
EURUSD Polar Coordinates O Jupyter Notebook que usamos para analisar os dados que buscamos com nosso script MQL5

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/17085

Últimos Comentários | Ir para discussão (5)
Too Chee Ng
Too Chee Ng | 27 mar. 2025 em 10:41
Como
Aleksej Poljakov
Aleksej Poljakov | 17 nov. 2025 em 15:38

Há uma falha grave em sua proposta.

A fórmula r=(x^2+y^2)^0,5 funciona somente se x e y forem comensuráveis. Ou seja, temos as mesmas unidades em ambos os eixos.

Em nosso caso, no eixo x, temos o tempo e, no eixo y, temos os pontos. Eles são incomensuráveis, não é possível converter segundos em pontos.

É por isso que você tem um valor ridículo de 180 graus. Ou seja, o preço foi na direção oposta - do presente para o passado. Se você quiser ângulos, crie uma regressão linear y = a*x+b. E deduza o ângulo a partir do valor de a. Em seguida, compare o resultado com uma distribuição normal circular. Será interessante.

Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 26 nov. 2025 em 14:58
Too Chee Ng #:
Como o
Obrigado @Too Chee Ng
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 26 nov. 2025 em 15:06
Aleksej Poljakov regressão linear y = a*x+b. E deduza o ângulo a partir do valor de a. Em seguida, compare o resultado com uma distribuição normal circular. Será interessante.
Obrigado por seu feedback @Aleksej Poljakov. Há uma coisa que não estou entendendo do que você disse: neste artigo, o eixo x não representa o tempo. O eixo x são os valores históricos do preço de abertura, e o eixo y são os valores históricos do preço de fechamento. Portanto, não entendo totalmente por que você diz que o tempo foi implementado no eixo x.

No entanto, não estou descartando seu ponto de vista, pois a Matemática Aplicada é um campo amplo e estou aberto a uma discussão com você.

Sua solução proposta para deduzir o ângulo a partir do valor de a é bastante interessante, e eu adoraria ouvir mais sobre você.

Abraços.

Gamu.
Alexandro Matos
Alexandro Matos | 21 jan. 2026 em 11:32
Seu maior erro é continuar analisando o passado.
Busca oscilatória determinística — Deterministic Oscillatory Search (DOS) Busca oscilatória determinística — Deterministic Oscillatory Search (DOS)
O algoritmo Deterministic Oscillatory Search (DOS) é um método inovador de otimização global que combina as vantagens dos algoritmos de gradiente e dos algoritmos de enxame sem o uso de números aleatórios. O mecanismo de oscilações e de inclinações de fitness permite ao DOS explorar espaços de busca complexos por meio de um método determinístico.
Desenvolvimento do Kit de Ferramentas de Análise de Price Action (Parte 11): EA de Sinal Heikin Ashi Desenvolvimento do Kit de Ferramentas de Análise de Price Action (Parte 11): EA de Sinal Heikin Ashi
O MQL5 oferece infinitas oportunidades para desenvolver sistemas de negociação automatizados adaptados às suas preferências. Você sabia que ele pode até realizar cálculos matemáticos complexos? Neste artigo, apresentamos a técnica japonesa Heikin-Ashi como uma estratégia de negociação automatizada.
Está chegando o novo MetaTrader 5 e MQL5 Está chegando o novo MetaTrader 5 e MQL5
Esta é apenas uma breve resenha do MetaTrader 5. Eu não posso descrever todos os novos recursos do sistema por um período tão curto de tempo - os testes começaram em 09.09.2009. Esta é uma data simbólica, e tenho certeza que será um número de sorte. Alguns dias passaram-se desde que eu obtive a versão beta do terminal MetaTrader 5 e MQL5. Eu ainda não consegui testar todos os seus recursos, mas já estou impressionado.
Redes neurais em trading: Identificação de anomalias no domínio da frequência (CATCH) Redes neurais em trading: Identificação de anomalias no domínio da frequência (CATCH)
O framework CATCH combina a transformada de Fourier e o patching de frequência para a identificação precisa de anomalias de mercado, inacessíveis aos métodos tradicionais. Neste trabalho, examinaremos como essa abordagem revela padrões ocultos nos dados financeiros.