Funcionalidades do assistente MQL5 que você precisa conhecer (Parte 04): Análise discriminante linear
Análise discriminante linear (LDA, Linear discriminant analysis) é uma técnica de redução de dimensionalidade amplamente utilizada em problemas de classificação, , assim como os mapas Kohonen. Ela permite transformar dados multidimensionais (com muitos atributos ou variáveis) em um subespaço dimensional inferior, tornando as classes o mais distintas possível. O LDA encontra uma projeção linear que otimiza a separação entre classes, com a dimensão do subespaço nunca sendo maior que o número de classes. Neste artigo, veremos como o LDA pode ser utilizado como sinal, indicador de rastreamento e ferramenta de gerenciamento de dinheiro. Começaremos com a teoria e depois aplicaremos praticamente.
Note que o LDA é semelhante a outros métodos como PCA, QDA e ANOVA, mas neste artigo não abordaremos a fundo esses métodos, apenas destacaremos as suas diferenças.
1) Análise de componentes principais (PCA):
O LDA é muito semelhante ao PCA. Às vezes surge a dúvida de se faz sentido realizar o PCA seguido da regularização do LDA (para evitar o ajuste da curva). Este é um tema amplo e pode ser abordado em outro artigo.
A diferença crucial entre os métodos de redução 2D é que o PCA tenta encontrar eixos com variância máxima para todo o conjunto de dados, com a suposição de que quanto mais dispersos os dados, mais separáveis eles são, enquanto o LDA tenta encontrar os eixos que realmente separam os dados com base na classificação.
A figura acima mostra claramente que o PCA nos dará LD2, enquanto o LDA nos dará LD1. Isso torna a principal diferença (e, portanto, a vantagem do LDA) entre PCA e LDA absolutamente óbvia: só porque uma função tem alta dispersão não significa que ela será útil para previsão de classes.
2) Análise discriminante quadrática (QDA):
O QDA é uma generalização do LDA como classificador. O LDA assume que as distribuições condicionais de uma classe são Gaussianas com a mesma matriz de covariâncias se quisermos que ela realize alguma classificação.
O QDA não faz suposições sobre a homocedasticidade e tenta estimar a covariância de todas as classes. Embora tal algoritmo possa parecer mais robusto (com menos suposições), ele também implica que um número muito maior de parâmetros deve ser estimado. O número de parâmetros cresce quadraticamente com o número de classes! Portanto, a menos que você possa garantir que suas estimativas de covariância sejam confiáveis, é improvável que você queira usar o QDA.
O anterior pode causar confusão sobre a relação entre LDA e QDA, como qual é melhor para redução de dimensionalidade e qual é melhor para classificação, etc. O post CrossValidated (em inglês) e todos os links nele contidos podem ajudá-lo neste caso.
3) Análise de dispersão (ANOVA):
À primeira vista, LDA e ANOVA tentam dividir a variável observada em múltiplas variáveis independentes/dependentes. No entanto, a ferramenta usada pela ANOVA, segundo a Wikipedia, é uma versão espelhada da que o LDA usa:
"LDA está intimamente relacionado com a análise de dispersão (ANOVA) e análise de regressão, que também tentam expressar uma variável dependente como uma combinação linear de outras características ou dimensões. O LDA é estreitamente relacionado ao análise de dispersão (ANOVA) e à análise de regressão, que também tentam expressar uma variável dependente como uma combinação linear de outras variáveis ou medidas. No entanto, o ANOVA usa variáveis independentes categóricas e uma variável dependente contínua, enquanto o LDA usa variáveis independentes contínuas e uma variável dependente categórica (isto é, a etiqueta da classe).
O LDA é geralmente definido da seguinte forma.
Dado que:
- n - número de classes
- μ - média de todas as observações
- N i - número de observações na classe i
- μ i - valor médio da classe i
- Σi - matriz de dispersão da classe i
SW - matriz de espalhamento intraclasse, obtida como
SW = ∑ i = 1 n Σ i
SB é a matriz de dispersão entre as classes, obtida como
SB = ∑ i = 1 n N i ( μ i − μ ) ( μ i − μ ) T
Diagonalizamos SW − 1 SB para obter valores e vetores próprios.
vamos escolher k , isto é, os maiores valores e seus vetores associados. Vamos projetar nossas observações no subespaço formado por esses vetores.
Isso significa que formamos uma matriz A cujas colunas são vetores próprios k, selecionados acima. A classe CLDA da biblioteca ALGLIB faz exatamente isso e classifica os vetores com base nos valores próprios em ordem decrescente, o que significa que precisamos apenas escolher o melhor vetor preditor para fazer uma previsão.
Como nos artigos anteriores, usaremos a biblioteca MQL durante a implementação do LDA para nosso Expert Advisor. Em particular, contaremos com a classe CLDA no arquivo dataanalysis.mqh.
Consideraremos o LDA para o par de moedas USDJPY durante 2022 no período gráfico diário. A escolha dos dados de entrada para o Expert Advisor depende em grande parte do usuário. No nosso caso, para este LDA, a entrada possui uma variável e um componente de classe. Precisamos preparar esses dados de entrada antes de executar os testes. Como estaremos lidando com preços de fechamento, por padrão o LDA irá "continuar" (em seu estado original). Aplicaremos normalização e discretização às variáveis e componentes de classe de nossos dados. A normalização significa que todos os dados estão entre o mínimo e o máximo especificados, enquanto a discretização significa que os dados são convertidos em um valor booleano (verdadeiro ou falso). Abaixo vemos os cinco conjuntos de dados preparados para o nosso sinal:
- Rastreamento de variáveis discretas de mudanças de preço de fechamento para corresponderem a categorias de classe.
- Dados de variáveis normalizadas de mudanças de preço de fechamento não processadas dentro do intervalo de -1.0 a 1.0.
- Dados de variáveis contínuas em mudanças de preço de fechamento não processadas.
- Preços de fechamento não processados.
A normalização mostrará a variação do preço de fechamento como uma proporção em relação ao intervalo das últimas 2 barras em valor decimal (-1,0 a +1,0) e a discretização mostrará se o preço subiu (índice 2), permaneceu no intervalo neutro (índice 1) ou foi rejeitado (índice 0). Vamos testar todos os tipos de dados para verificar o desempenho. Este trabalho preparatório é feito pelo método Data mostrado abaixo. Todos os quatro tipos de dados são ajustados com o parâmetro de entrada m_signal_regulizer de modo a definir uma zona neutra para nossos dados e assim reduzir o ruído branco.
//+------------------------------------------------------------------+ //| Data Set method | //| INPUT PARAMETERS | //| Index - int, read index within price buffer. | //| | //| Variables | //| - whether data component is variables or . | //| classifier. | //| OUTPUT | //| double - Data depending on data set type | //| | //| DATA SET TYPES | //| 1. Discretized variables. - 0 | //| | //| 2. Normalized variables. - 1 | //| | //| 3. Continuized variables. - 2 | //| | //| 4. Raw data variables. - 3 | //| | //+------------------------------------------------------------------+ double CSignalDA::Data(int Index,bool Variables=true) { m_close.Refresh(-1); m_low.Refresh(-1); m_high.Refresh(-1); if(Variables) { if(m_signal_type==0) { return(fabs(Close(StartIndex()+Index)-Close(StartIndex()+Index+1))<m_signal_regulizer*Range(Index)?1.0:((Close(StartIndex()+Index)>Close(StartIndex()+Index+1))?2.0:0.0)); } else if(m_signal_type==1) { if(fabs(Close(StartIndex()+Index)-Close(StartIndex()+Index+1))<m_signal_regulizer*Range(Index)) { return(0.0); } return((Close(StartIndex()+Index)-Close(StartIndex()+Index+1))/fmax(m_symbol.Point(),fmax(High(StartIndex()+Index),High(StartIndex()+Index+1))-fmin(Low(StartIndex()+Index),Low(StartIndex()+Index+1)))); } else if(m_signal_type==2) { if(fabs(Close(StartIndex()+Index)-Close(StartIndex()+Index+1))<m_signal_regulizer*Range(Index)) { return(0.0); } return(Close(StartIndex()+Index)-Close(StartIndex()+Index+1)); } else if(m_signal_type==3) { if(fabs(Close(StartIndex()+Index)-Close(StartIndex()+Index+1))<m_signal_regulizer*Range(Index)) { return(Close(StartIndex()+Index+1)); } return(Close(StartIndex()+Index)); } } return(fabs(Close(StartIndex()+Index)-Close(StartIndex()+Index+1))<m_signal_regulizer*Range(Index)?1.0:((Close(StartIndex()+Index)>Close(StartIndex()+Index+1))?2.0:0.0)); }
Usamos quatro dimensões, o que significa que cada valor de indicador terá quatro variáveis. Assim, para simplificar, em nosso caso, consideraremos os últimos quatro valores do indicador para cada conjunto de dados no treinamento. Nossa classificação também será básica, levando em consideração apenas dois classes (mínimo) na componente de classe de cada conjunto de dados. Também precisaremos definir o número de pontos de dados no nosso conjunto de treinamento, que é armazenado no parâmetro m_signal_points.
Normalmente, a saída do LDA é uma matriz de coeficientes, que são classificados por vetores e a multiplicação escalar de qualquer um destes vetores com o ponto atual de dados do indicador deve resultar em um valor que, então, é comparado com valores semelhantes obtidos pela multiplicação do conjunto de dados de treinamento, para classificar essas novas/atuais unidades de dados. Portanto, se em nosso conjunto de treinamento tivesse apenas duas pontos de dados com projeções LDA 0 e 1, e nosso novo valor resultasse em uma multiplicação escalar de 0,9, concluiríamos que ele está mais próximo da classe 1. Por outro lado, se o produto desse um valor de 0,1, consideraríamos que esse novo ponto de dados deve pertencer à mesma categoria do ponto de dados cuja projeção LDA foi igual a 0.
Conjuntos de dados de treinamento raramente consistem apenas em dois pontos de dados. Por isso, na prática, tomamos o "centroide" de cada classe como comparação com a saída do produto escalar de uma nova amostra de dados e o vetor de saída do LDA. Este "centroide" será a média da projeção LDA para cada classe.
Para classificar cada ponto de dados como de alta ou de baixa, simplesmente observamos a mudança no preço de fechamento depois dos valores do indicador. Se for positivo, a amostra de dados é de alta, se for negativo, é de baixa. Além disso, pode ser uma lateralização. Para simplificar, igualaremos as lateralizações com o sinal de alta.
A classe ExpertSignal normalmente usa valores inteiros normalizados (0-100) para ponderar decisões longas e curtas. Como as projeções do LDA serão necessariamente do tipo double, nós as normalizamos conforme mostrado abaixo para que calhem no intervalo de -1,0 a +1,0 (negativo para baixista e positivo para altista).
// best eigen vector is the first for(int v=0;v<__S_VARS;v++){ _unknown_centroid+= (_w[0][v]*_z[0][v]); } // if(fabs(_centroids[__S_BULLISH]-_unknown_centroid)<fabs(_centroids[__S_BEARISH]-_unknown_centroid) && fabs(_centroids[__S_BULLISH]-_unknown_centroid)<fabs(_centroids[__S_WHIPSAW]-_unknown_centroid)) { _da=(1.0-(fabs(_centroids[__S_BULLISH]-_unknown_centroid)/(fabs(_centroids[__S_BULLISH]-_unknown_centroid)+fabs(_centroids[__S_WHIPSAW]-_unknown_centroid)+fabs(_centroids[__S_BEARISH]-_unknown_centroid)))); } else if(fabs(_centroids[__S_BEARISH]-_unknown_centroid)<fabs(_centroids[__S_BULLISH]-_unknown_centroid) && fabs(_centroids[__S_BEARISH]-_unknown_centroid)<fabs(_centroids[__S_WHIPSAW]-_unknown_centroid)) { _da=-1.0*(1.0-(fabs(_centroids[__S_BEARISH]-_unknown_centroid)/(fabs(_centroids[__S_BULLISH]-_unknown_centroid)+fabs(_centroids[__S_WHIPSAW]-_unknown_centroid)+fabs(_centroids[__S_BEARISH]-_unknown_centroid)))); }
Este valor é facilmente normalizado para o número inteiro típico (0-100) esperado pela classe do sinal.
if(_da>0.0) { result=int(round(100.0*_da)); }
para a função long Check e,
if(_da<0.0) { result=int(round(-100.0*_da)); }
para a função short Check.
Portanto, após a corrida de testes para cada um dos tipos de dados de entrada, o testador de estratégia nós dá os seguintes relatórios.
Relatório de conjunto de dados 1
Relatório de conjunto de dados 2
Relatório de conjunto de dados 3
Relatório de conjunto de dados 4
Esses relatórios demonstram o potencial do LDA como ferramenta do trader.
A classe ExpertTrailing ajusta ou define um stop loss para uma posição aberta. O principal resultado exibido aqui é o valor double do novo stop loss. Assim, dependendo da posição aberta, consideraremos os preços máximos e mínimos como nossos conjuntos de dados primários. Eles serão preparados da seguinte forma para preços de alta e de baixa, com uma opção dependendo do tipo de posição aberta:
- Rastreamento dos dados de variáveis discretas de mudanças de preço ("High" ou "Low") para corresponder às categorias de classe.
- Dados de variáveis normalizadas de mudanças de preço brutas ("High" ou "Low") variando de -1,0 a +1,0.
- Dados variáveis contínuas de mudanças de preço brutas ("High" ou "Low").
- Preços brutos ("High" ou "Low").
Tal como acontece com a classe do sinal, o LDA será exibido como um valor double normalizado. Como tal valor é inútil para determinar o stop loss, ele será ajustado conforme mostrado abaixo, dependendo do tipo de posição aberta, para determinar o preço do stop loss.
int _index =StartIndex(); double _min_l=Low(_index),_max_l=Low(_index),_min_h=High(_index),_max_h=High(_index); for(int d=_index;d<m_trailing_points+_index;d++) { _min_l=fmin(_min_l,Low(d)); _max_l=fmax(_max_l,Low(d)); _min_h=fmin(_min_h,High(d)); _max_h=fmax(_max_h,High(d)); } if(Type==POSITION_TYPE_BUY) { _da*=(_max_l-_min_l); _da+=_min_l; } else if(Type==POSITION_TYPE_SELL) { _da*=(_max_h-_min_h); _da+=_max_h; }
Também vemos como ajustamos e definimos nossos novos níveis de stop loss. Para posições de compra:
m_long_sl=ProcessDA(StartIndex(),POSITION_TYPE_BUY); double level =NormalizeDouble(m_symbol.Bid()-m_symbol.StopsLevel()*m_symbol.Point(),m_symbol.Digits()); double new_sl=NormalizeDouble(m_long_sl,m_symbol.Digits()); double pos_sl=position.StopLoss(); double base =(pos_sl==0.0) ? position.PriceOpen() : pos_sl; //--- sl=EMPTY_VALUE; tp=EMPTY_VALUE; if(new_sl>base && new_sl<level) sl=new_sl;
Aqui determinamos a mínima provável antes da próxima barra para uma posição aberta longa ('m_long_sl') e, em seguida, nós a definimos como nosso novo stop loss se for maior que o preço de abertura da posição ou se seu stop loss atual estiver abaixo do bid menos o nível de parada. O tipo de dados usado no cálculo é preços Low.
O stop loss para posições curtas é definido de forma espelhada.
Portanto, uma corrida para cada um dos tipos de dados de entrada, usando o tipo de dados ... para o sinal, oferece os seguintes resultados.
Relatório de conjunto de dados 1
Relatório de conjunto de dados 2
Relatório de conjunto de dados 3
Relatório de conjunto de dados 4
Estes relatórios podem indicar que o conjunto de mudanças em andamento e não processadas .data é o mais adequado, levando em consideração seu coeficiente de recuperação de 6.82.
A classe ExpertMoney define o tamanho da posição. Isso pode ser resultado do desempenho anterior, por isso criamos a classe OptimizedVolume. No entanto, o LDA pode ajudar com o tamanho inicial, se considerarmos a volatilidade ou o intervalo entre preços altos e baixos. Assim, nosso conjunto principal de dados será o intervalo de preços. Verificaremos se o intervalo da barra de preço está aumentando ou diminuindo. Vamos preparar os seguintes dados:
- Rastreamento de dados de variáveis discretas de mudança do valor do intervalo para corresponder às categorias de classe.
- Dados de variáveis normalizadas das alterações brutas para o intervalo de -1,0 a +1,0.
- Dados de variáveis contínuas em mudanças brutas para o intervalo de valores.
- Valores brutos para o intervalo.
Tal como acontece com as classes de sinal e de trailing, o LDA será mostrado como um valor double normalizado. Como no caso anterior, não nos dá nada, então faremos os ajustes mostrados abaixo para projetar melhor o novo intervalo de barras.
int _index =StartIndex(); double _min_l=Low(_index),_max_h=High(_index); for(int d=_index;d<m_money_points+_index;d++) { _min_l=fmin(_min_l,Low(d)); _max_h=fmax(_max_h,High(d)); } _da*=(_max_h-_min_l); _da+=(_max_h-_min_l);
Duas funções espelhadas são responsáveis por definir o volume aberto, dependendo de se o EA abre uma posição longa ou curta. Abaixo estão os destaques para uma posição longa.
double _da=ProcessDA(StartIndex()); if(m_symbol==NULL) return(0.0); sl=m_symbol.Bid()-_da; //--- select lot size double _da_1_lot_loss=(_da/m_symbol.TickSize())*m_symbol.TickValue(); double lot=((m_percent/100.0)*m_account.FreeMargin())/_da_1_lot_loss; //--- calculate margin requirements for 1 lot if(m_account.FreeMarginCheck(m_symbol.Name(),ORDER_TYPE_BUY,lot,m_symbol.Ask())<0.0) { printf(__FUNCSIG__" insufficient margin for sl lot! "); lot=m_account.MaxLotCheck(m_symbol.Name(),ORDER_TYPE_BUY,m_symbol.Ask(),m_percent); } //--- return trading volume return(Optimize(lot));
Aqui é curioso que determinamos a variação de preço prevista e subtraímos essa previsão de nosso preço bid (deveríamos ter subtraído o nível de stop também). Isso nos dará um stop loss ajustado ao risco. E se utilizarmos o parâmetro de entrada porcentual como parâmetro de risco máximo de perda, podemos calcular um tamanho de lote que limitará nossa porcentagem de rebaixamento ao valor porcentual se encontrarmos um rebaixamento abaixo do preço bid previsto.
Portanto, uma corrida de testes para cada um dos tipos de dados de entrada, usando o tipo de dados de fechamento bruto para o sinal e ... o traling, oferece os seguintes resultados.
Relatório de conjunto de dados 1
Relatório de conjunto de dados 2
Relatório de conjunto de dados 3
Relatório de conjunto de dados 4
Parece que o conjunto de dados de mudanças discretas de valores de intervalo é o mais promissor para gerenciamento de capital. Também é importante destacar a enorme diferença nos resultados entre os conjuntos de dados de gerenciamento de capital, considerando que todos eles usam as mesmas configurações de sinal e trailing.
O artigo abordou as possibilidades de usar análise discriminante como uma ferramenta de negociação em um EA. Claro, um único artigo não pode esgotar este assunto. Uma análise mais aprofundada poderia ser realizada usando conjuntos de dados mais diversificados que abrangem períodos mais longos.
Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/11687
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso