English Русский 中文 Español Deutsch 日本語
preview
Previsão de taxas de câmbio usando métodos clássicos de aprendizado de máquina: Modelos Logit e Probit

Previsão de taxas de câmbio usando métodos clássicos de aprendizado de máquina: Modelos Logit e Probit

MetaTrader 5Experts |
84 9
Evgeniy Chernish
Evgeniy Chernish

Introdução

Pesquisadores dos mercados financeiros sempre enfrentarão o desafio de escolher um modelo matemático para prever o comportamento futuro dos instrumentos de negociação. Hoje em dia, há uma enorme variedade desses modelos desenvolvidos. Por isso, surge a dúvida: como não se perder nesse mar de métodos e abordagens? Em quais modelos é melhor concentrar os esforços? Especialmente se você está apenas começando a prever com modelos de aprendizado de máquina. Se tentarmos simplificar o problema da previsão para uma resposta direta à pergunta “o preço de fechamento amanhã será maior que o preço de fechamento de hoje?”, então modelos de classificação binária seriam uma escolha lógica. Entre os mais simples e amplamente utilizados estão a regressão logística e a regressão probit. Esses modelos pertencem à forma mais comum de aprendizado de máquina, conhecida como aprendizado supervisionado.

A tarefa do aprendizado supervisionado consiste em ensinar nosso modelo a mapear o conjunto de entradas {x} (preditores ou características) para o conjunto de saídas {y} (alvos ou rótulos). Neste trabalho, vamos prever apenas dois estados de mercado: alta ou queda do preço de um par de moedas. Consequentemente, teremos apenas duas classes de rótulos y ∊ {1, 0}. Como preditores, usaremos padrões de preços, mais precisamente os incrementos padronizados dos preços com certo atraso. Esses dados formarão nosso conjunto de treinamento {x, y}, com o qual avaliaremos os parâmetros de nossos modelos. O modelo de previsão baseado nos classificadores treinados foi implementado como o EA LogitExpert.


Regressão binária Logit e Probit

Vamos passar rapidamente pela parte teórica. Vale dizer que o modelo mais simples de escolha binária é o modelo linear de probabilidade, no qual a probabilidade de ocorrência do evento de sucesso P(yn=1|xn) é uma função linear das variáveis explicativas:

P(yn=1|xn) = w0*1 + w1x1 + w2x2 + … + wkxk

Infelizmente, esse modelo tem uma grande limitação: o valor previsto pode ser maior que um ou menor que zero, o que impede interpretar o valor previsto como uma probabilidade. Para resolver esse problema, foi proposto o uso de funções conhecidas de distribuição de probabilidade, nas quais seriam inseridos os valores da função linear.

O modelo Probit é baseado na distribuição normal padrão N(0,1):

P(yn=1|xn) = F(xnw)=μn

  • n – índice inferior que indica o número da observação (exemplo),

  • yn - rótulo da classe,

  • F( ) - função de distribuição da normal (função de ativação),

  • xn – vetor de características,

  • w – vetor de parâmetros do modelo,

  • xnw – logit ou pré-ativação (representa o produto escalar entre o vetor de características e o vetor de parâmetros)

xnw = w0*1 + w1x1 + w2x2 + … + wkxk

Por sua vez, o modelo Logit é baseado na distribuição logística de probabilidades:

                                                            P(yn=1|xn) = L(xnw) = exp(xnw)/(1 + exp(xnw)) = μn

As funções de distribuição logística e normal são bastante próximas, e no intervalo [-1,2;1,2] são praticamente idênticas. Por isso, os modelos Logit e Probit frequentemente produzem resultados semelhantes, se a probabilidade não está muito próxima de zero ou de um. Assim, ao substituir o vetor de características nessas equações, permitimos calcular as probabilidades dos rótulos das classes, e, consequentemente, a probabilidade da direção futura do movimento do preço.


Preparação dos dados

Antes de estimar os parâmetros do modelo, nosso trabalho é definir as características, padronizá-las e apresentá-las no formato adequado para a função que irá encontrar os parâmetros ótimos (no sentido de minimizar a função de perda). Essa tarefa é realizada pela função GetDataset:

  • InpCount_ - define a quantidade de exemplos para treinamento

  • lag_ - número de características analisadas (incrementos de preço com defasagem)

  • string X — par de moedas pelo qual os preditores são calculados

  • string y — par de moedas pelo qual os rótulos são calculados

  • int start — número da barra a partir da qual começamos a coletar exemplos para treinamento

Como exemplo ilustrativo, usaremos como características os incrementos de preço defasados do par de moedas. Por exemplo, se o argumento da função for definido como lag_ = 4, então as características serão x{return-4,return-3,return-2,return-1} e teremos exatamente (InpCount_ - lag_) exemplos para treinamento.

//+------------------------------------------------------------------+
//|Get data for analysis: features and corresponding labels          |
//+------------------------------------------------------------------+
bool GetDataset(int InpCount_,int lag_,int start,matrix &Input_X,vector & Target_y,string X,string y)
  {

   matrix rates;
   matrix target;
   target.CopyRates(y, PERIOD_CURRENT, COPY_RATES_OHLC, start+1, InpCount_);
   rates.CopyRates(X, PERIOD_CURRENT, COPY_RATES_OHLC, start+2, InpCount_-1);
   rates = rates.Transpose();
   target = target.Transpose();
   int Class_ [];
   ArrayResize(Class_,InpCount_);
   for(int i=0; i<InpCount_; i++)
     {
      if(target[i,3] >  target[i,0])
         Class_[i] = 1;
      else
         Class_[i] = 0;
     }

   vector label=vector::Zeros(InpCount_-lag_);
   for(int i=0; i<InpCount_-lag_; i++)
     {
      label[i] = Class_[i+lag_]; // class label
     }

   matrix returns=matrix::Zeros(InpCount_-lag_, lag_);
   for(int j=0; j<lag_; j++)
     {
      for(int i=0; i<InpCount_-lag_; i++)
        {
         returns[i,j] =rates[i+j,3] - rates[i+j,0]  ; // Input Data
        }
     }

   vector cols_mean=returns.Mean(0);
   vector cols_std=returns.Std(0);

   mean_ = cols_mean[lag_-1];
   std_ = cols_std[lag_-1];

   for(int j=0; j<lag_; j++)
     {
      for(int i=0; i<InpCount_-lag_; i++)
        {
         returns[i,j] = (returns[i,j] - cols_mean[lag_-1])/cols_std[lag_-1];
        }
     }
   Input_X = returns;
   Target_y = label;

   return true;
  }

Na saída, obtemos uma matriz de características Input_X e um vetor de rótulos Target_y. Após formar o conjunto de treinamento, passamos à estimativa dos parâmetros.


Estimativa dos parâmetros do modelo

Na maioria dos casos, os parâmetros são estimados usando o método da máxima verossimilhança. No caso binário, os modelos Logit e Probit assumem que a variável dependente y segue uma distribuição de Bernoulli. Sendo assim, a função log-verossimilhança será igual a:

LLF

  • yn – rótulo da classe,

  • μn - probabilidade prevista da classe, obtida por regressão Logit ou Probit,

  • N – número de exemplos de treinamento

Para estimar os parâmetros, precisamos encontrar o máximo dessa função, mas como é comum minimizar a função de perda no aprendizado de máquina — e todos os otimizadores geralmente são ajustados para minimizar funções objetivo —, então simplesmente adicionamos um sinal de menos à função de verossimilhança. O resultado é o chamado logaritmo da verossimilhança negativa (NLL). Vamos minimizar essa função de perda utilizando o método de otimização quasi-Newtoniano de segunda ordem L-BFGS implementado na biblioteca Alglib. Esse método numérico é, geralmente, o mais usado para encontrar os parâmetros em modelos Logit e Probit. Outro método de otimização bastante conhecido é o dos mínimos quadrados com reponderação iterativa (IRLS).

//+------------------------------------------------------------------+
//| Derived class from CNDimensional_Func                            |
//+------------------------------------------------------------------+
class CNDimensional_Logit : public CNDimensional_Func
  {
public:
                     CNDimensional_Logit(void) {}
                    ~CNDimensional_Logit(void) {}
   virtual void      Func(CRowDouble &w,double &func,CObject &obj);
  };

//+------------------------------------------------------------------+
//| Objective Function: Logit Negative loglikelihood                 |
//+------------------------------------------------------------------+
void CNDimensional_Logit::Func(CRowDouble &w,double &func,CObject &obj)
  {

   double LLF[],probit[],probitact[];
   vector logitact;
   ArrayResize(LLF,Rows_);
   ArrayResize(probit,Rows_);
   vector params=vector::Zeros(Cols_);

   for(int i = 0; i<Cols_; i++)
     {
      params[i] =  w[i]; // vector of parameters
     }

   vector logit=vector::Zeros(Rows_);
   logit = Input_X_gl.MatMul(params);

   for(int i=0; i <Rows_; i++)
     {
      probit[i] = logit[i];
     }

   if(probit_)
      MathCumulativeDistributionNormal(probit,0,1,probitact); // Probit activation
   else
      logit.Activation(logitact,AF_SIGMOID); // Logit activation

//--------------------to avoid NAN error when calculating logarithm ------------------------------------
   if(probit_)
     {
      for(int i = 0; i<Rows_; i++)
        {
         if(probitact[i]==1)
            probitact[i]= 0.999;
         if(probitact[i]==0)
            probitact[i]= 0.001;
        }
     }
   else
     {
      for(int i = 0; i<Rows_; i++)
        {
         if(logitact[i]==1)
            logitact[i]= 0.999;
         if(logitact[i]==0)
            logitact[i]= 0.001;
        }
     }
//-------------------------------------------------------------------------------------------------
   double L2_reg;
   if(L2_)
      L2_reg = 0.5 * params.Dot(params); //  L2_regularization
   else
      L2_reg =0;

//------------------ calculate loss function-------------------------------------------------------------
   if(probit_)
     {
      for(int i = 0; i<Rows_; i++)
        {

         LLF[i]=target_y_gl[i]*MathLog(probitact[i]) + (1-target_y_gl[i])*MathLog(1-probitact[i]) ;

         if(!MathIsValidNumber(LLF[i]))
           {
            break;
           }
        }
     }
   else
     {
      for(int i = 0; i<Rows_; i++)
        {

         LLF[i]=target_y_gl[i]*MathLog(logitact[i]) + (1-target_y_gl[i])*MathLog(1-logitact[i]);

         if(!MathIsValidNumber(LLF[i]))
           {
            break;
           }
        }
     }

   func = -MathSum(LLF) + L2_reg/(Rows_*C_); // Negative Loglikelihood + L2_regularization
//------------------------------------------------------------------------------------------------------
   func_ = func;
  }

No entanto, apenas calcular as estimativas dos parâmetros não é suficiente, sendo importante também obter os erros padrão dessas estimativas, para que seja possível avaliar a significância dos nossos preditores.

Por exemplo, a popular biblioteca de aprendizado de máquina scikit-learn, por algum motivo, não calcula essas informações para o modelo Logit. Eu implementei o cálculo dos erros padrão como para o modelo Logit tanto quanto para o Probit, e agora é possível verificar se determinados preditores exercem influência estatisticamente significativa sobre a previsão. Essa é uma das razões pelas quais prefiro escrever o código do modelo Logit diretamente em MQL, em vez de usar modelos prontos convertidos via ONNX a partir de bibliotecas populares de aprendizado de máquina. Outra razão é a necessidade de um modelo dinâmico, que possa reotimizar os parâmetros do classificador a cada barra ou com uma frequência definida.

Mas, voltando à nossa função de perda, é preciso dizer que ela requer algum ajuste. A questão é que nossos classificadores, assim como métodos avançados baseados em redes neurais, tendem a apresentar sobreajuste. Isso se manifesta em estimativas anormalmente grandes dos parâmetros, e para evitar esse comportamento indesejado, precisamos de um método que limite tais valores. Esse método é conhecido como regularização L2:

NLL_L2

  • λ = 1/C , C = (0,1]

Aqui simplesmente somamos à função de perda existente o quadrado da norma do vetor de parâmetros multiplicado por um hiperparâmetro λ (lambda). Quanto maior o lambda, maior é a penalização para parâmetros com valores elevados, e mais intensa é a regularização.

A função responsável pela estimativa dos parâmetros do classificador é a FitLogitRegression:

  • bool L2 = false — por padrão, a regularização L2 está desativada,
  • double C=1.0 — hiperparâmetro que controla a intensidade da regularização, quanto menor o valor, mais restritos serão os valores dos parâmetros otimizados,
  • bool probit = false — por padrão, está ativado o modelo Logit,
  • double alpha — nível de significância alpha da estatística LR com distribuição qui-quadrado

Essa função recebe como argumento a matriz de características e adiciona a ela uma chamada variável condicional ou fictícia, que assume valor igual a um em todas as observações. Isso é necessário para que possamos estimar o parâmetro w0 (o viés) em nosso modelo. Além das estimativas dos parâmetros, essa função também calcula suas matrizes de covariância, utilizadas para o cálculo dos erros padrão.

//+------------------------------------------------------------------+
//| Finding the optimal parameters for the Logit or Probit model     |
//+------------------------------------------------------------------+
vector FitLogitRegression(matrix &input_X, vector &target_y,bool L2 = false, double C=1.0,bool probit = false,double alpha = 0.05)
  {
   L2_=L2;
   probit_ = probit;
   C_ = C;
   double              w[],s[];
   CObject             obj;
   CNDimensional_Logit ffunc;
   CNDimensional_Rep   frep;
   ulong Rows = input_X.Rows();
   ulong Cols = input_X.Cols();
   matrix One=matrix::Ones(int(Rows),int(Cols+1));
   for(int i=0;i<int(Cols); i++)
     {
      One.Col(input_X.Col(i),i+1);  // design matrix
     }
   input_X = One;
   Cols = input_X.Cols();
   Rows_ = int(Rows);
   Cols_ = int(Cols);
   Input_X_gl = input_X;
   target_y_gl = target_y;
   ArrayResize(w,int(Cols));
   ArrayResize(s,int(Cols));
//--- initialization
   ArrayInitialize(w,0.0);
   ArrayInitialize(s,1.0);
//--- optimization stop conditions
   double epsg=0.000001;
   double epsf=0.000001;
   double epsx=0.000001;
   double diffstep=0.000001;
   int maxits=0;
//------------------------------
   CMinLBFGSStateShell state;
   CMinLBFGSReportShell rep;
   CAlglib::MinLBFGSCreateF(1,w,diffstep,state);
   CAlglib::MinLBFGSSetCond(state,epsg,epsf,epsx,maxits);
   CAlglib::MinLBFGSSetScale(state,s);
   CAlglib::MinLBFGSOptimize(state,ffunc,frep,0,obj);
   CAlglib::MinLBFGSResults(state,w,rep);
   Print("TerminationType ="," ",rep.GetTerminationType());
   Print("IterationsCount ="," ",rep.GetIterationsCount());

   vector parameters=vector::Zeros(Cols);
   for(int i = 0; i<int(Cols); i++)
     {
      parameters[i]= w[i];
     }
   Print("Parameters = "," ",parameters);

//-------Likelihood Ratio Test LR-----------------------------------------
   double S = target_y.Sum();   // number of "success"
   ulong All = target_y.Size(); // all data
   double L0 = S*MathLog(S/All) + (All-S)*MathLog((All-S)/All); // Log-likelihood for the trivial model
 //  Print("L0 = ",L0);
 //  Print("LLF = ",func_);
   double LR;
   LR = 2*(-func_ - L0); // Likelihood Ratio Test LR
   int err;
   double Chi2 = MathQuantileChiSquare(1-alpha,Cols-1,err); // If H0 true ---> Chi2Distribution(alpha,v)
   Print("LR ",LR," ","Chi2 = ",Chi2);
//--------------------------------------------------------------------------------
//-------------- calculate if model significant or not
   if(LR > Chi2)
      ModelSignificant = true;
   else
      ModelSignificant = false;
//----------------------------------------------------

//-------------Estimation of the covariance matrix of parameters for the Probit model------------
   vector logit = input_X.MatMul(parameters);  //
   vector activation;
   logit.Activation(activation,AF_SIGMOID); // Logit activation
   double probit_SE[],probitact[];
   ArrayResize(probit_SE,Rows_);

   for(int i=0; i <Rows_; i++)
     {
      probit_SE[i] = logit[i];
     }

   if(probit_)
     {
      ulong size_parameters = parameters.Size();
      matrix CovProbit=matrix::Zeros(int(size_parameters),int(size_parameters));
      int err;
      vector a_=vector::Zeros(Rows_);
      vector b=vector::Zeros(Rows_);
      vector c=vector::Zeros(Rows_);
      vector xt=vector::Zeros(int(size_parameters));

      for(int i = 0; i<Rows_; i++)
        {
         a_[i] = MathPow((MathProbabilityDensityNormal(probit_SE[i],0,1,err)),2);
         b[i] = MathCumulativeDistributionNormal(probit_SE[i],0,1,err);
         c[i] = a_[i]/(b[i]*(1-b[i]));
         xt = input_X.Row(i);
         CovProbit = CovProbit + c[i]*xt.Outer(xt);
        }
      CovProbit = CovProbit.Inv();
      vector SE;
      SE = CovProbit.Diag(0);
      SE = MathSqrt(SE);  // standard errors of parameters
      Print("Probit_SE = ", SE);
     }
   else
     {
      //-------------Estimation of the covariance matrix of parameters for the Logit model------------
      vector v = vector::Zeros(Rows_);

      for(int i = 0; i<Rows_; i++)
        {
         v[i] = activation[i]*(1-activation[i]);
        }

      matrix R,Hesse,X,a,CovLogit;
      R.Diag(v,0);
      X = input_X.Transpose();
      a = X.MatMul(R);
      Hesse = a.MatMul(input_X);
      CovLogit = Hesse.Inv();
      vector SE;
      SE = CovLogit.Diag(0);
      SE = MathSqrt(SE); // standard errors of parameters
      Print("Logit_SE = ", SE);
      //-----------------------------------------------
     }
   return parameters;
  }

Depois que os parâmetros são encontrados e suas matrizes de covariância são calculadas, podemos partir para a etapa de previsão.


Previsão

A função responsável por prever os rótulos das classes e, consequentemente, gerar os sinais de compra ou venda é a Trade_PredictedTarget. Ela recebe os parâmetros otimizados como entrada e retorna o rótulo previsto da classe. A partir daí, o EA LogitExpert define as regras para abertura de posições. Elas são bastante simples. Se recebermos um sinal de compra (signal = 1) — abrimos uma posição longa. Se uma posição longa já estiver aberta, mantemos a posição. Ao receber um sinal de venda, a posição longa é encerrada e uma posição curta é aberta imediatamente.

O próprio código do EA LogitExpert

//+------------------------------------------------------------------+
//|                                                  LogitExpert.mq5 |
//|                                                           Eugene |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Eugene"
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <\LogitReg.mqh>
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
CTrade m_trade;
CPositionInfo m_position;

sinput string   symbol_X       = "EURUSD";    // Input symbol
sinput string   symbol_y       = "EURUSD";    // Target symbol
input bool     _probit_        = false;       // Probit model
input  int      InpCount       = 20;          // Depth of history
input  int     _lag_           = 4;           // Number of features
input bool     _L2_            = false;       // L2_regularization
input double   _C_             = 1;           // C(0,1) inverse of regularization strength
input double   alpha_          = 0.05;        // Significance level Alpha (0,1)
input int      reoptimize_step = 2;           // Reoptimize step

#define MAGIC_NUMBER 23092024

int prev_bars = 0;
MqlTick ticks;
double min_lot;
vector params_;
matrix _Input_X;
vector _Target_y;
static int count_ = 0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   m_trade.SetExpertMagicNumber(MAGIC_NUMBER);
   m_trade.SetTypeFillingBySymbol(Symbol());
   m_trade.SetMarginMode();
   min_lot = SymbolInfoDouble(Symbol(), SYMBOL_VOLUME_MIN);

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
   Print(__FUNCTION__," Deinitialization reason code = ",reason);
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   if(!isnewBar(PERIOD_CURRENT))
      return;

   double step;
   step = count_ % reoptimize_step;
//------------------------------------Train Dataset-------------------------------------------------
   int start = 0;
   if(step == 0)
     {
      GetDataset(InpCount,_lag_,start,_Input_X,_Target_y,symbol_X,symbol_y);
      params_ = FitLogitRegression(_Input_X,_Target_y,_L2_,_C_,_probit_,alpha_);
     }
   count_ = count_+1;
//--------------------------------------------------------------------------------------------------

//--- Get trade signal
   int signal = Trade_PredictedTarget(params_,start,_lag_,InpCount,symbol_X);
   Comment("Trade signal: ",signal,"  ","ModelSignificant: ",ModelSignificant);  
//---------------------------------------------

//--- Open trades based on Signals
   SymbolInfoTick(Symbol(), ticks);
   if(signal==1)
     {
      if(!PosExists(POSITION_TYPE_BUY) && ModelSignificant)
        {
         m_trade.Buy(min_lot,Symbol(), ticks.ask);
         PosClose(POSITION_TYPE_SELL);
        }
      else
        {
         PosClose(POSITION_TYPE_SELL);
        }
     }
   else
     {
      if(!PosExists(POSITION_TYPE_SELL) && ModelSignificant)
        {
         m_trade.Sell(min_lot,Symbol(), ticks.bid);
         PosClose(POSITION_TYPE_BUY);
        }
      else
        {
         PosClose(POSITION_TYPE_BUY);
        }
     }
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|   Function tracks the occurrence of a new bar event              |
//+------------------------------------------------------------------+
bool isnewBar(ENUM_TIMEFRAMES TF)
  {
   if(prev_bars == 0)
      prev_bars = Bars(Symbol(), TF);

   if(prev_bars != Bars(Symbol(), TF))
     {
      prev_bars = Bars(Symbol(), TF);
      return true;
     }

   return false;
  }

//+------------------------------------------------------------------+
//|Function determines whether there is an open buy or sell position |
//+------------------------------------------------------------------+
bool PosExists(ENUM_POSITION_TYPE type)
  {
   for(int i=PositionsTotal()-1; i>=0; i--)
      if(m_position.SelectByIndex(i))
         if(m_position.Symbol()==Symbol() && m_position.Magic() == MAGIC_NUMBER && m_position.PositionType()==type)
            return true;

   return false;
  }
//+------------------------------------------------------------------+
//|The function closes a long or short trade                         |
//+------------------------------------------------------------------+
void PosClose(ENUM_POSITION_TYPE type)
  {
   for(int i=PositionsTotal()-1; i>=0; i--)
      if(m_position.SelectByIndex(i))
         if(m_position.Symbol()==Symbol() && m_position.Magic() == MAGIC_NUMBER && m_position.PositionType()==type)
            if(!m_trade.PositionClose(m_position.Ticket()))
               printf("Failed to close position %d Err=%s",m_position.Ticket(),m_trade.ResultRetcodeDescription());
  }

O que diferencia esse EA de várias outras abordagens? Em primeiro lugar, ele permite a reotimização dos parâmetros do classificador a cada (reoptimize_step) barras. Em segundo lugar, ele não se limita a estimar os parâmetros do modelo, mas também considera os erros padrão dessas estimativas, algo que muitas vezes é negligenciado. Não basta apenas encontrar os parâmetros “ótimos” para o conjunto de dados; é necessário também verificar se esses parâmetros são realmente significativos e se o modelo como um todo é relevante. Afinal, se os parâmetros não forem significativos, faz mais sentido ignorar esse sinal de negociação, não é mesmo?

Por isso, este EA também inclui um procedimento para testar a significância da hipótese do modelo. Nesse teste, a hipótese nula afirma que todos os parâmetros do modelo são iguais a zero (H0:w1=0,w2=0,w3=0,..., wk=0), enquanto a hipótese alternativa H1 afirma que pelo menos alguns parâmetros são diferentes de zero e, portanto, o modelo é útil para previsão. Para testar essa hipótese, utiliza-se o critério de razão de verossimilhança (LR), que mede a diferença entre o modelo proposto e o modelo trivial:


LR = 2(LLF – LLF0)

  • LLF – valor obtido do logaritmo da função de verossimilhança,

  • LLF0 - log-verossimilhança sob a hipótese nula, ou seja, para o modelo trivial

p0 = ∑(yn =1)/N – frequência amostral de sucesso,

LLF0 = N(p0*Ln(p0) + (1- p0)*Ln(1 – p0))

Quanto maior for essa diferença, melhor o modelo completo em comparação ao modelo trivial. Sob a hipótese nula, a estatística LR segue uma distribuição qui-quadrado com v graus de liberdade (v igual ao número de preditores). Se o valor calculado da estatística LR cair na região crítica, ou seja, LR > X2крит (alpha; v=lag_), então a hipótese H0 é rejeitada e, consequentemente, o sinal de negociação não é ignorado e uma posição de trading é aberta.

Um dos possíveis cenários. GBPUSD, Diário

Backtest GBPUSD Daily

Hiperparâmetros

hyperparameters

Vale destacar que, além da estimativa dos parâmetros dos próprios modelos classificadores, também lidamos com um conjunto considerável de hiperparâmetros:

  • profundidade do histórico
  • quantidade de preditores
  • nível de significância alpha
  • intervalo de reotimização

A escolha desses hiperparâmetros é feita no testador de estratégias do MetaTrader 5. Uma das abordagens que pode melhorar o desempenho do EA é criar uma função que relacione o parâmetro da profundidade do histórico com o estado atual do mercado, ou seja, torná-lo dinâmico, assim como fizemos com os parâmetros dos modelos Logit e Probit. Mas isso já é outra história. Como dica, veja meu artigo Critério de homogeneidade de Smirnov como indicador de não estacionariedade de série temporal, no qual abordo a construção de um indicador de desajuste.


Considerações finais

Neste artigo, analisamos modelos de regressão com saídas binárias, aprendemos a estimar os parâmetros desses modelos e também implementamos o EA LogitExpert para testar e ajustar esses classificadores. Sua principal característica é permitir o re-treinamento dos parâmetros do classificador em tempo real, com base nos dados mais recentes e relevantes.

Foi dada atenção especial à estimativa dos erros padrão dos parâmetros, para o que foi necessário calcular as matrizes de covariância tanto para o modelo Logit quanto para o Probit.

Com base no critério de razão de verossimilhança, foi feita uma verificação da significância do modelo do classificador como um todo. Esse teste estatístico funciona como um filtro para eliminar sinais de negociação estatisticamente não confiáveis.

Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/16029

Arquivos anexados |
LogitReg.mqh (22.43 KB)
LogitExpert.mq5 (10.94 KB)
Últimos Comentários | Ir para discussão (9)
Stanislav Korotky
Stanislav Korotky | 4 out. 2024 em 16:41
Evgeniy Chernish #:
Todas as perguntas a sua majestade, o mercado forex e a hipótese do mercado eficiente.

O título é, portanto, enganoso.

Evgeniy Chernish
Evgeniy Chernish | 4 out. 2024 em 17:12
Aleksey Nikolayev #:

Obrigado, artigo muito interessante.

Na minha opinião, você já pode tentar usar dados fundamentais em viagens de um dia. Não se trata de criticar o artigo, mas de uma forma de pensar. Eu me pergunto como os dados macroeconômicos podem ser adequadamente "misturados" com os dados de preços. O problema é sua rara mudança, por exemplo. Provavelmente, a macroeconomia também pode ser usada de alguma forma no pré-processamento de preços - transição de taxas de câmbio nominais para reais, por exemplo.

Obrigado, Alexey! Falando francamente, nunca me interessei pelos fundamentos, não porque eles não possam fornecer informações adicionais, mas simplesmente porque é impossível cobrir toda a vastidão. É por isso que ainda não estou olhando nessa direção.
Evgeniy Chernish
Evgeniy Chernish | 4 out. 2024 em 17:17
Stanislav Korotky #:

O título é, portanto, enganoso.

Por quê? Ele usa um modelo preditivo de classificação que faz previsões. Ele conta corretamente o que é colocado no modelo. O que há de errado então? O fato de o modelo não conseguir superar uma previsão ingênua? Eu não prometi isso).
Stanislav Korotky
Stanislav Korotky | 4 out. 2024 em 17:48
Evgeniy Chernish #:
Por quê? Ele usa um modelo preditivo de classificação que faz previsões. Ele conta corretamente o que é colocado no modelo. O que há de errado, então? Que o modelo não consegue superar uma previsão ingênua? Eu não prometi isso).

"A impossibilidade de prever taxas de câmbio usando métodos clássicos..."

Evgeniy Chernish
Evgeniy Chernish | 4 out. 2024 em 18:20
Stanislav Korotky #:

"A impossibilidade de prever as taxas de câmbio usando métodos clássicos..."

Nem sequer me ocorreu que isso fosse impossível. Apenas fiz uma previsão e a verifiquei com a biblioteca python para verificar se havia erros. Talvez alguém adicione alguns filtros, seus próprios recursos, talvez outra pessoa faça melhor. E você imediatamente se depara com a impossibilidade.
Redes neurais em trading: Segmentação guiada Redes neurais em trading: Segmentação guiada
Vamos conhecer um método de análise multimodal integrada para interagir e compreender características.
Simulação de mercado (Parte 14): Sockets (VIII) Simulação de mercado (Parte 14): Sockets (VIII)
Muitos poderiam sugerir, que deveríamos abandonar o Excel, e usar o Python pura e simplesmente. Fazendo uso de alguns pacotes que permitiriam ao Python criar um arquivo de Excel, para que pudéssemos analisar os resultados depois. Mas como foi dito no artigo anterior, apesar desta solução ser a mais simples, pelo ponto de vista de muitos programadores. Ela de fato, não será bem vista, pelos olhos de alguns usuários. E nesta história toda, o usuário tem sempre razão. Você como programador deve, encontrar alguma forma ou alguma maneira de fazer as coisas funcionarem.
Análise de Sentimento no Twitter com Sockets Análise de Sentimento no Twitter com Sockets
Este inovador bot de negociação integra o MetaTrader 5 com Python para aproveitar a análise de sentimento em tempo real nas mídias sociais para decisões automatizadas de negociação. Ao analisar o sentimento no Twitter relacionado a instrumentos financeiros específicos, o bot traduz as tendências das mídias sociais em sinais acionáveis de negociação. Ele utiliza uma arquitetura cliente-servidor com comunicação via socket, permitindo uma interação contínua entre as capacidades de negociação do MT5 e o poder de processamento de dados do Python.
Do básico ao intermediário: Indicador (IV) Do básico ao intermediário: Indicador (IV)
Neste artigo, vermos como é fácil de criar e implementar uma metodologia operacional, visando colorir candles. Sendo este um conceito, que diversos operadores apreciam imensamente. Porém, é preciso se tomar cuidado ao implementar tal tipo de coisa. Isto para que as barras, ou candles, mantenham a sua aparência original. Visando assim não prejudicar a leitura que muitos operadores fazem candle a candle.