Classificador Bayesiano Ingênuo para sinais de um conjunto de indicadores

Stanislav Korotky | 11 setembro, 2017

Quer queiramos ou não, as estatísticas de negociação desempenham um papel importante. Começando com notícias fundamentais repletas de números, terminando com registros de negociação e relatórios de teste, não se pode escapar dos indicadores estatísticos. Por enquanto, a noção de aplicabilidade da estatística na tomada de decisões de negociação vira um dos temas mais controversos. Será que o mercado é casual, as cotações são permanentes, a abordagem probabilística para a sua análise é aplicável? Pode-se entrar numa discussão sem fim sobre esse tema. Tanto na Internet quanto na mql5.com, é fácil encontrar materiais e discussões com diferentes pontos de vista, estudos rigorosos e gráficos bem elaborados. No entanto, os traders estão geralmente interessados ​​no aspecto aplicado, ou seja, como tudo funciona na prática, no terminal. Este artigo abrange uma tentativa de mostrar uma abordagem pragmática para o modelo probabilístico de toma de decisões de negociação com a ajuda de um conjunto de indicadores técnicos. Mínimo de teoria, máximo de prática.

A ideia é avaliar o potencial de vários indicadores a partir da perspectiva da teoria da probabilidade e testar a capacidade dos indicadores para aumentar a taxa de vitoria do sistema de negociação.

Isso exigirá a criação de uma estrutura para processar os sinais de indicadores exploratórios e de um EA simples, com base nela, para testes.

Como indicadores de trabalho sugere-se utilizar indicadores padrão, no entanto a estrutura permite por si mesma conectar e analisar outros indicadores personalizados.

Mas, antes de proceder à projeção e implementação de algoritmos, teremos que mergulhar um pouco na teoria.


Introdução ao modelo de probabilidade condicionada

O título do artigo menciona um classificador Bayesiano ingênuo. Ele se baseia na famosa Teorema de Bayes, que será aqui considerada de forma sucinta. É chamado de "ingênuo" devido a uma suposição necessária sobre a independência das variáveis ​​aleatórias descritas pela fórmula. A independência dos indicadores será discutida mais tarde, mas, por enquanto, falaremos sobre a fórmula em si.

   (1)

onde H é um tipo de hipótese sobre o estado interno do sistema (no nosso caso, é uma hipótese sobre o estado do mercado e do sistema de negociação), E é o evento observado (no nosso caso, são os sinais dos indicadores), bem como as probabilidades que os descrevem:

Considere o sistema de negociação mais simples. Como hipótese H geralmente são tomados tais estados do mercado como: movimento para cima (compra), movimento para baixo (venda) e flutuações laterais (expectativa). Como evento E, que descreve a estado provável do mercado, são usados os sinais dos indicadores.

Para os sinais de um indicador específico, é fácil calcular as probabilidades do lado direito da fórmula (1) para o histórico disponível e depois encontrar o estado mais provável do mercado P(H|E).

No entanto, o cálculo requer uma definição mais clara da hipótese e a metodologia de coleta de estatísticas, que serão utilizadas como base para a obtenção das probabilidades.

Primeiro, suponhamos que a negociação é realizada em barras (não em ticks). A eficiência da negociação pode ser avaliada pela quantidade de lucro, fator de lucro ou outras características. Mas, por uma questão de simplicidade, usaremos a proporção de entradas lucrativas e desfavoráveis no mercado. Isso liga diretamente a avaliação do sistema à probabilidade de negociações bem-sucedidas (sinais usados).

Também nos limitaremos a um sistema de negociação sem níveis de Take-Profit e Stop-Loss, sem acompanhamento de Stop-Loss e nem mudanças no tamanho do lote. Todos esses parâmetros podem ser introduzidos no modelo, mas eles complicariam significativamente o cálculo das probabilidades, transformando-as em distribuições multidimensionais. O único parâmetro do sistema de negociação será o tempo de retenção de posição em barras. Em outras palavras, após a abertura de um posição em certa direção usando os indicadores de direção, a saída é executada automaticamente após um tempo predeterminado. Esta abordagem é boa na medida em que enfatiza a precisão ou a falsidade da hipótese sobre o crescimento ou a queda das cotações. Desta forma, a hipótese é testada em sua forma pura, sem salvaguardas e almofadas.

Para finalizar o tema das simplificações, faremos dois movimentos radicais.

Acima foi mencionado que "compra", "venda" e "expectativa" são tomadas como hipóteses de negociação. Descartando a "expectativa", nós reduziríamos significativamente os cálculos sem perder a síntese geral de ideias. Pode parecer que tais simplificações afetariam negativamente a aplicabilidade do resultado obtido, e, pois, até certo ponto isso é verdade. No entanto, se você prestar atenção à quantidade de material, mesmo com tais simplificações, ainda fica muito por ler. Em vista disso, você pode concordar que primeiro seria bom obter um modelo de trabalho e complementá-lo com detalhes mais tarde, gradualmente. Os interessados em construir modelos mais complexos considerando a função densidade de probabilidade podem encontrar os trabalhos apropriados na Internet, inclusive em inglês, como Reasoning Methods for Merging Financial Technical Indicators, onde é descrito um sistema probabilístico híbrido de toma de decisões.

Finalmente, o segundo e último movimento radical consolida os estados "comprar" e "vender" em um único, mas com um significado universal, isto é, "entrada no mercado". Geralmente, os sinais multidirecionais são usados simetricamente, de maneira semelhante, ou seja, por exemplo, conforme o indicador, o sinal de venda vira sobrecompra, enquanto o sinal de compra se torna sobrevenda.

Em outras palavras, a hipótese H é agora uma entrada bem-sucedida no mercado em qualquer uma das duas direções (compra ou venda).

Nessas condições, as probabilidades no lado direito da fórmula (1) podem ser calculadas no histórico de cotações selecionado da seguinte maneira.


Como, em qualquer barra, é possível fazer uma entrada bem-sucedida, uma das direções se torna lucrativa (aqui ignoramos o spread, porque o período selecionado será D1, conforme descrito em mais detalhes abaixo).

P(E) = número de barras com os sinais do indicador / número total de barras

P(E|H) = número de barras com os sinais do indicador que coincidem com a direção rentável da negociação / número total de barras

Após a simplificação, a probabilidade de que o sinal do indicador selecionado aponte nas condições para abrir uma transação bem-sucedida pode ser calculada no histórico usando uma fórmula obtida após a simplificação.

   (2)

onde Nok é o número de sinais certos, Ntotal é o número total de sinais.

A estrutura para calcular essa probabilidade para qualquer indicador será implementada um pouco mais tarde. Como veremos, esta probabilidade é geralmente próxima de 0,5, e é necessário fazer certas pesquisas para encontrar as condições em que ela excede de forma estável 0,5. No entanto, os indicadores com valores grandes são raros. Para os indicadores padrão, que serão estudados em primeiro lugar, essa probabilidade varia no intervalo de 0,51-0,55. É claro que esses valores são muito pequenos e são mais propensos a "pender", ao invés de aumentar constantemente o depósito.

Para resolver este problema, é necessário usar vários indicadores em vez de um. Por si só, esta solução não é nova, é utilizada pela maioria dos traders. A teoria da probabilidade permite realizar uma análise quantitativa da eficiência dos indicadores em diferentes combinações e avaliar o efeito potencial.

Fórmula (1) para o caso de três indicadores (A, B, C) será a seguinte:

  (3)

Nós precisamos de torná-la conveniente para o cálculo algorítmico. Felizmente, a teoria Bayesiana é aplicada em muitas indústrias e, portanto, é possível encontrar uma receita pronta para o nosso caso.

Em particular, existe o Filtro Bayesiano. Não há necessidade de estudá-lo minuciosamente. Somente os conceitos básicos são relevantes. Um documento (por exemplo, uma mensagem de e-mail) é marcado como spam se contiver certas palavras características. A ocorrência geral de palavras e a probabilidade de encontrá-las em spam são conhecidas da mesma forma como conhecemos as probabilidades gerais dos sinais dos indicadores e sua porcentagem de "acertos no alvo". Em outras palavras, para a teoria de processamento de spam encaixar plenamente em nossa teoria de negociação probabilística, basta substituir a hipótese de "spam" pela de "transação bem-sucedida" e o evento "palavra" pelo de "sinal do indicador".

Assim, a fórmula (3) pode ser estendida através das probabilidades de indicadores individuais da seguinte maneira (veja os cálculos acima):

   (4)

Os cálculos de P(H|A), P(H|B), P(H|C) são realizados de acordo com a fórmula (2) para cada indicador individualmente.

Naturalmente, se necessário, a fórmula (4) pode facilmente ser estendido a qualquer número de indicadores. Para ter a ideia de como o número de indicadores afeta a probabilidade de uma decisão de negociação certa, suponhamos que todos os indicadores tenham o mesmo valor de probabilidade:



A fórmula (4) vira:

   (5)

onde N é o número de indicadores.

O gráfico desta função para diferentes valores de n é mostrado na figura 1.

Aparência de probabilidade conjunta com diferente número de variáveis ​​aleatórias

Fig. 1 Aparência de probabilidade conjunta com diferente número de variáveis ​​aleatórias

Assim, quando p = 0.51 obtemos P(3) = 0.53, o que não é impressionante, mas quando p = 0.55 - P(3) = 0.65 já há uma melhoria notável.


Independência dos indicadores

As fórmulas consideradas acima são baseadas na suposição de independência dos processos aleatórios analisados, que são, neste caso, os sinais dos indicadores. Mas será que esta condição é atendida?

Obviamente, certos indicadores, incluindo muitos dos que são padrão, têm muito em comum. A figura 2 mostra alguns dos indicadores internos.

Grupos de indicadores padrão similares

Fig. 2 Grupos de indicadores padrão similares

É fácil ver que os indicadores Stochastic e WPR para o mesmo período, superpostos um sobre o outro, na última janela, quase se repetem. Isso não é surpreendente, já que suas fórmulas são aritmeticamente equivalentes.

Um pouco mais acima, na captura de tela, os indicadores MACD e Awesome Oscillator são idênticos, ajustados ao tipo de média móvel. Além disso, uma vez que ambos são plotados com base em médias móveis (MA), eles não podem ser chamados de independentes das próprias MA.

RSI, RVI, CCI também estão fortemente correlacionados. Repare que praticamente todos os osciladores padrão são semelhantes, os coeficientes de correlação serão próximos de 1.

Também há uma coincidência notável entre os indicadores de volatilidade, ATR e StdDev em particular.

Tudo isso deve ser considerado quando se forma o conjunto de indicadores para o sistema de negociação, já que o efeito real do comitê de indicadores dependentes será muito menor do que o efeito teórico esperado na prática.

Aliás, acontece uma situação semelhante durante a formação de redes neurais. Os traders costumam usá-los para tentar processar os dados de muitos indicadores selecionados por vontade própria. No entanto, o abastecimento de vetores dependentes para a entrada de rede reduz significativamente a eficácia do treinamento, uma vez que o poder de processamento da rede é desperdiçado. O volume de dados analisados ​​pode parecer grande, mas a informação contida neles é duplicada, sem sentido.

Uma abordagem rigorosa para este problema requer o cálculo da correlação entre os indicadores e as combinações de conjuntos com os valores pares mínimos. Sendo assim, uma área de pesquisa separada e extensa. Os interessados ​​podem encontrar artigos relacionados na Internet. Aqui, seguiremos as considerações gerais com base nas observações acima. Por exemplo, um dos conjuntos pode ser: Stochastic, ATR, AC (Acceleration/Deceleration) ou WPR, Bollinger Bands, Momentum.

Repare que o indicador de Aceleração/Desaceleração (AC) é essencialmente uma derivada do oscilador. E, então, por que ele é adequado para ser incluído no grupo?

Representaremos uma série de cotações (ou um oscilador derivado delas), numa forma simplificada, como uma oscilação periódica, por exemplo, cosseno ou seno. As correlações dessas funções e seus derivados são zero:

   (6)



As correlações dessas funções e seus derivados são zero.

    (7)


Portanto, o uso da primeira derivada de um indicador geralmente é um bom candidato para ser considerado como um indicador independente adicional.

Por outro lado, a segunda derivada é um candidato questionável em tais processos oscilatórios, porque as chances de obter uma réplica do sinal original são altas.

Para resumir a discussão sobre a independência dos indicadores, é sensato verificar se as cópias do indicador, calculadas com diferentes períodos, podem ser consideradas independentes.

Pode-se supor que a resposta depende da proporção dos períodos. Uma pequena diferença, obviamente, preserva a dependência dos indicadores e, portanto, é necessária uma diferença notável. Isso é em parte consistente com métodos clássicos, como o método de tela tripla de Elder, em que os períodos que geralmente diferem pelo menos 5 vezes são equivalentes a analisar indicadores com diferentes períodos de tempo.

Note-se que no sistema considerado, as variáveis ​​independentes não devem ser os valores dos indicadores, mas sim os sinais de negociação gerados por eles. No entanto, para a maioria dos indicadores do mesmo tipo (por exemplo, osciladores), os princípios da geração de sinal de negociação são semelhantes. Portanto, a dependência forte ou fraca das séries temporais é equivalente a uma dependência forte ou fraca dos sinais.


Desenho

Bem, entendemos a teoria e agora estamos prontos para descobrir o quê e como codificar.

As estatísticas dos sinais de negociação dos indicadores serão coletadas num EA especial. Para que o EA possa negociar com base nos valores de indicadores arbitrários, é necessário implementar uma estrutura (essencialmente, um arquivo de cabeçalho mqh), que obtém a descrição dos indicadores usados ​​e dos métodos de geração de sinal baseados nos parâmetros de entrada. Por exemplo, devemos ter uma opção para definir duas médias móveis de diferentes períodos nos parâmetros e gerar sinais de compra e venda quando a MA rápida cruzar a MA mais lenta para cima e para baixo, respectivamente.

A EA terá um controle explícito da abertura de barras negociará somente nos preços de abertura. Este não é um EA real, mas sim uma ferramenta para calcular as probabilidades e testar as hipóteses. É importante que o teste seja rápido, porque existem infinitas opções para conjuntos de indicadores.

D1 será usado como o período de trabalho padrão. Claro, nada o impede de realizar análises em qualquer outro período, no entanto, D1 é o menos suscetível ao ruído aleatório, enquanto que a análise dos padrões, existentes há vários anos, atende mais plenamente às especificidades da abordagem probabilística. Além disso, para estratégias de negociação em D1, geralmente pode ser ignorado o spread, o que nivela nossa rejeição da sustentação do estado intermediário do sistema "expectativa". Porém, para a negociação intradiária, essa suposição não pode ser feita, e é necessário calcular a probabilidade de um maior número de hipóteses.

Conforme mencionado anteriormente, o EA abrirá posições com base em sinais dos indicadores e os fechará após um tempo predeterminado. Para isso, introduzimos um parâmetro de entrada correspondente. Seu valor padrão é de 5 dias. Este é um período característico para o timeframe D1, ele é usado em muitas pesquisas - usando D1 - sobre negociação.

O EA e a intra-estrutura serão destinadas a funcionar em todas as plataformas, ou seja, você poderá compilá-los e executá-los tanto no MetaTrader 4 quanto no MetaTrader 5. Esta possibilidade será fornecida pelos arquivos-encapsulamento de cabeçalho existentes e publicamente disponíveis, que permitem utilizar a sintaxe MQL API MetaTrader 4 no ambiente MetaTrader 5. Além disso, usaremos a compilação condicional em alguns casos: partes específicas dos códigos serão encapsuladas nas diretivas do pré-processador processador #ifdef __MQL4__ e #ifdef __MQL5__.


Implementação em MQL

Estrutura para os indicadores

Começaremos a exploração da estrutura para o processamento de sinais dos indicadores com uma discussão sobre dos tipos de indicadores que necessitaremos. A enumeração mais óbvia inclui todos os indicadores internos, bem como o item iCustom para indicadores personalizados. A enumeração será necessária para selecionar os indicadores através dos parâmetros de entrada da estrutura.

enum IndicatorType
{
  iCustom,

  iAC,
  iAD,
  tADX_period_price,
  tAlligator_jawP_jawS_teethP_teethS_lipsP_lipsS_method_price,
  iAO,
  iATR_period,
  tBands_period_deviation_shift_price,
  iBearsPower_period_price,
  iBullsPower_period_price,
  iBWMFI,
  iCCI_period_price,
  iDeMarker_period,
  tEnvelopes_period_method_shift_price_deviation,
  iForce_period_method_price,
  dFractals,
  dGator_jawP_jawS_teethP_teethS_lipsP_lipsS_method_price,
  fIchimoku_tenkan_kijun_senkou,
  iMomentum_period_price,
  iMFI_period,
  iMA_period_shift_method_price,
  dMACD_fast_slow_signal_price,
  iOBV_price,
  iOsMA_fast_slow_signal_price,
  iRSI_period_price,
  dRVI_period,
  iSAR_step_maximum,
  iStdDev_period_shift_method_price,
  dStochastic_K_D_slowing_method_price,
  iWPR_period

};

O nome de cada indicador interno contém um sufixo com informações sobre os parâmetros do próprio indicador. O primeiro caractere de um elemento indica o número de buffers disponíveis, por exemplo: i — um buffer, d — dois, t — três. Tudo isso é apenas uma dica para o usuário. Se ele especificar um número incorreto de parâmetros ou um índice de buffer inexistente, o a estrutura [framework] emitirá um erro no log.

Naturalmente, nos parâmetros de entrada, para cada indicador, é necessário especificar não apenas seu tipo, mas também os parâmetros reais como uma cadeia de caracteres [string], o número do buffer e o número da barra para começar a ler os dados.

Os valores dos indicadores devem ser usados ​​para gerar sinais. Em princípio, pode haver qualquer número de sinais diferentes, mas as principais variações serão reunidas em outra enumeração.

enum SignalCondition
{
  Disabled,
  NotEmptyIndicatorX,
  SignOfValueIndicatorX,
  IndicatorXcrossesIndicatorY,
  IndicatorXcrossesLevelX,
  IndicatorXrelatesToIndicatorY,
  IndicatorXrelatesToLevelX
};

Assim, os sinais podem ser formados:

O primeiro elemento - 'Disabled' - permite desativar qualquer condição para gerar sinais. Nós forneceremos vários grupos idênticos de parâmetros de entrada para descrever os sinais, e cada sinal será desativado por padrão.

É fácil adivinhar com os nomes dos itens da enumeração anterior que é necessário, de algum modo, definir o sinal de valores e a posição das linhas em relação umas às outras. Para este fim, adicionaremos outra enumeração.

enum UpZeroDown
{
  EqualOrNone,
  UpSideOrAboveOrPositve,
  DownSideOrBelowOrNegative,
  NotEqual
};

EqualOrNone permite verificar:

UpSideOrAboveOrPositve permite verificar:

DownSideOrBelowOrNegative permite verificar:

NotEqual permite verificar:

Quando um sinal é acionado, ele deve ser processado. Para isso, definimos uma enumeração especial.

enum SignalType
{
  Alert,
  Buy,
  Sell,
  CloseBuy,
  CloseSell,
  CloseAll,
  BuyAndCloseSell,
  SellAndCloseBuy,
  ModifyStopLoss,
  ModifyTakeProfit,
  ProceedToNextCondition
};

Aqui são exibidas as principais ações para o processamento de sinais: saída de mensagem, compra, venda, fechamento de todas as posições abertas (para compra, para venda, ou ambos), reversão de posição de vender para comprar, reversão de posição de comprar para vender, modificação dos níveis Stop-Loss e Take-Profit, bem como a transição para a verificação do seguinte nível (sinal). O último item permite encadear as verificações de sinal (por exemplo, verificar se o buffer principal cruza a linha de sinal e, em caso afirmativo, verificar se aconteceu acima ou abaixo de um determinado nível).

Pode se ver que a lista de ações não contém a colocação de ordens pendentes. Isso é deixado fora do escopo deste trabalho. Os interessados ​​podem expandir a estrutura.

Com todas essas enumerações disponíveis, é possível descrever certos grupos de atributos, que são usados ​​para definir os indicadores de trabalho. Um grupo tem a seguinte aparência:

input IndicatorType Indicator1Selector = iCustom; // ·     Selector
input string Indicator1Name = ""; // ·     Name
input string Parameter1List = "" /*1.0,value:t,value:t*/; // ·     Parameters
input string Indicator1Buffer = ""; // ·     Buffer
input int Indicator1Bar = 1; // ·     Bar

O parâmetro Indicator1Name é projetado para definir o nome do indicador personalizado, quando Indicator1Selector está configurado para iCustom.

O parâmetro Parameter1List permite definir os parâmetros do indicador como uma cadeia de caracteres separada por vírgulas. O tipo de cada parâmetro de entrada será detectado automaticamente, por exemplo, 11.0 — double, 11 — int, 2015.01.01 20:00 — date/time, true/false — bool, "text" — string. Certos parâmetros (como tipos de médias móveis ou tipos de preços) podem ser definidos não por um número, mas sim por uma cadeia de caracteres sem aspas (sma, ema, smma, lwma, close, open, high, low, median, typical, weighted, lowhigh, closeclose).

Indicator1Buffer é o número ou nome do buffer sem aspas. Nomes de buffer suportados: main, signal, upper, lower, jaw, teeth, lips, tenkan, kijun, senkouA, senkouB, chikou, +di, -di.

Indicator1Bar é o número da barra, por padrão é 1.

Uma vez que todos os indicadores são definidos, eles podem ser usados ​​como base para formar sinais, isto é, condições para desencadear eventos. Cada sinal é definido por um grupo de parâmetros de entrada.

input string __SIGNAL_A = "";
input SignalCondition ConditionA = Disabled; // ·     Condition A
input string IndicatorA1 = ""; // ·     Indicator X for signal A
input string IndicatorA2 = ""; // ·     Indicator Y for signal A
input double LevelA1 = 0; // ·     Level X for signal A
input double LevelA2 = 0; // ·     Level Y for signal A
input UpZeroDown DirectionA = EqualOrNone; // ·     Direction or sign A
input SignalType ExecutionA = Alert; // ·     Action A

É possível definir um identificador para cada sinal no parâmetro __SIGNAL_.

Usando Condition é selecionada a condição de verificação do sinal. Em seguida, são definidos um ou dois indicadores e um ou dois valores de níveis ​​(o segundo nível é reservado para o futuro e não será usado nesta experiência). Os indicadores nos parâmetros 'Indicator' são o número do indicador do grupo de atributos correspondente ou um protótipo de indicador na forma de:

indicatorName@buffer(param1,param2,...)[bar]

Esta notação permite determinar o indicador usado rapidamente, sem sua descrição detalhada, usando o grupo de atributos. Por exemplo,

iMA@0(1,0,sma,high)[1]

retorna os valores dos preços high, e, em cada barra atual do EA em andamento, é tomada a barra número 1 (a última barra concluída, para a qual o preço high é conhecido definitivamente).

Assim, os indicadores podem ser definidos tanto em grupos selecionados de atributos (para referência subsequente a eles a partir dos sinais - por número), e diretamente nos sinais no parâmetro 'Indicador' (X ou Y). O primeiro método é conveniente quando o mesmo indicador deve ser usado em diferentes sinais ou como X e Y dentro de um sinal.

O parâmetro Direction indica a direção ou o sinal do valor para desencadear uma condição. Quando o sinal é disparado, a ação correspondente é realizada de acordo com 'Execution'.

Em seguida, veremos os exemplos de determinação de indicadores e sinais por sua base.

Atualmente, na estrutura, está definido que um indicador não pode ter mais de 20 parâmetros, o número máximo de grupos selecionados com atributos dos indicadores é 6 (mas, como foi dito anteriormente, os indicadores podem ser adicionalmente definidos diretamente no sinal) e 8 sinais no máximo. Tudo isso pode ser alterado no código-fonte. O arquivo IndicatN.mqh está anexado no final do artigo.

Este arquivo também implementa várias classes que contêm toda a lógica para analisar os parâmetros do indicador, chamando-os, verificando as condições e retornando os resultados da verificação ao código de chamada (que será o nosso expert).

Em particular, para transmitir as instruções sobre a necessidade de executar uma determinada ação da enumeração SignalType considerada acima, é usado uma classe simples TradeSignals pública, que contém o campo booleano correspondente aos itens de enumeração:

class TradeSignals
{
  public:
    bool alert;
    bool buy;
    bool sell;
    bool buyExit;
    bool sellExit;
    bool ModifySL;
    bool ModifyTP;
    
    int index;
    double value;
    
    string message;
  
    TradeSignals(): alert(false), buy(false), sell(false), buyExit(false), sellExit(false), ModifySL(false), ModifyTP(false), value(EMPTY_VALUE), message(""){}
};

Quando as condições necessárias são atendidas, os campos são definidos como true. Por exemplo, se for selecionada a ação CloseAll, no objeto TradeSignals, serão definidos os sinalizadores buyExit e sellExit.

O campo 'index' contém o número de série da condição desencadeada.

O campo 'value' pode ser usado para transferir um valor personalizado, por exemplo, um novo nível Stop-Loss obtido a partir dos valores dos indicadores.

Finalmente, o campo 'message' contém uma mensagem - para o usuário - descrevendo a situação.

Os detalhes sobre a implementação de todas as classes podem ser encontrados no código-fonte. Ele usa os arquivos de cabeçalho auxiliares fmtprnt2.mqh (formato de saída para o log) e RubbArray.mqh (matriz de "borracha"), que também estão anexados.

O arquivo de cabeçalho da estrutura IndicatN.mqh deve ser incluído no código do EA usando a diretiva #include. Como resultado, uma vez compilado, na caixa de diálogo de configuração do EA, pode ser visto o grupo de parâmetros de entrada com os atributos do indicador:

Configurações do indicador

Fig.3 Configurações do indicador

e com definições de sinal:

Configurações de sinais de negociação

Fig.4 Configurações de sinais de negociação

As capturas de tela mostram valores já predefinidos. Eles serão considerados com mais detalhes uma vez que avançarmos para o conceito de EA e começaremos a configurar estratégias de negociação específicas. Aqui também é importante notar que, ao definir os atributos dos indicadores, quaisquer parâmetros numéricos podem ser substituídos por expressões do tipo =var1, =var2 e assim por diante até 9. Elas fazem referência a parâmetros de entrada - especiais com mesmo nome - da estrutura var1, var2, etc. destinados para otimização. Por exemplo, a entrada:

iMACD@main(=var4,=var5,=var6,open)[0]

significa que os parâmetros dos períodos das médias móveis rápidas, lentas e de sinal do MACD podem ser otimizados através dos parâmetros de entrada var4, var5 e var6, respectivamente. E mesmo com a otimização desativada, durante uma única execução de teste, os valores dos atributos correspondentes de um indicador são lidos a partir dos parâmetros de entrada especificados da estrutura.

Expert Advisor de teste

Para facilitar a codificação, movemos todas as funções de negociação para uma classe especial e organizá-la como um arquivo de cabeçalho Expert0.mqh separado. Como devemos testar sistemas de negociação bastante simples, a classe permitirá apenas abrir e fechar posições.

Assim, todas as operações de rotina com indicadores e aquelas ligadas à negociação são movidas para arquivos de cabeçalho.

#include <IndicatN.mqh>
#include <Expert0.mqh>

O arquivo indstats.mq4 em si terá apenas algumas linhas de código e uma lógica simples.

Como, após mudar a extensão para mq5, o EA deve se compilar e trabalhar no MetaTrader 5, adicionaremos arquivos de cabeçalho fornecendo a transição dos códigos para um novo ambiente.

#ifdef __MQL5__
  #include <MarketMQL4.mqh>
  #include <ind4to5.mqh>
  #include <mt4orders.mqh>
#endif

Agora acedemos aos parâmetros de entrada do EA.

input int ConsistentSignalNumber = 1;
input int Magic = 0;
input float Lot = 0.01f;
input int TradeDuration = 1;

Magic e Lot são necessários para criar um objeto Expert a partir do arquivo Expert0.mqh.

Expert e(Magic, Lot);

O parâmetro ConsistentSignalNumber conterá o número de sinais de negociação a ser combinado para aumentar a confiabilidade.

O parâmetro TradeDuration definirá o número de barras para manter uma posição aberta. Como foi mencionado anteriormente, as transações serão abertas de acordo com os sinais e sairão após 5 barras, ou seja, dias, porque estará sendo usado o período D1.

No manipulador de eventos OnInit, inicializaremos a estrutura de indicador.

int OnInit()
{
  return IndicatN::handleInit();
}

No manipulador OnTick, forneceremos controle sobre a abertura da barra.

void OnTick()
{
  static datetime lastBar;
  
  if(lastBar != Time[0])
  {
    const RubbArray<TradeSignals> *ts = IndicatN::handleStart();
    ...
    lastBar = Time[0];
  }
}

Durante a formação de uma nova barra, verificaremos todos os indicadores e condições relacionadas com eles, chamando a estrutura de indicador novamente. Isso resulta num conjunto de sinais desencadeados, isto é, os objetos TradeSignals.

Agora é hora de falar sobre a acumulação de estatísticas.

Uma vez atendida, cada condição (evento) da estrutura gera um sinal com o sinalizador 'alert' por padrão. Nós usaremos isto para contar o número de sinais dos indicadores, bem como o número de estados realizados do sistema, isto é, os casos (barras) que, ao comprar ou vender, seriam bem-sucedidos.

Para calcular as estatísticas, descreveremos as matrizes [arrays].

int bars = 0; // total count of bars/samples
int bull = 0, bear = 0; // number of bars/samples per trade type
int buy[MAX_SIGNAL_NUM] = {0}, sell[MAX_SIGNAL_NUM] = {0};  // unconditional signals arrays
int buyOnBull[MAX_SIGNAL_NUM] = {0}, sellOnBear[MAX_SIGNAL_NUM] = {0}; // conditional (successful) signals arrays

Em nosso caso de negociação em barra, cada barra é uma nova entrada potencial numa transação de 5 barras. Cada um desses segmentos é caracterizado por um aumento ou queda das cotações e é marcado como de alta ou de baixa, respectivamente.

Todos os sinais de compra e venda serão somados nas matrizes buy e sell. Se o sinal respectivo estiver em conformidade com a "touricidade" ou "ursicidade" do segmento, ou seja, é bem-sucedido, ele também será acumulado na matriz buyOnBull ou sellOnBear, dependendo do tipo.

Para preencher as matrizes, escrevemos o seguinte código dentro do OnTick.

    const RubbArray<TradeSignals> *ts = IndicatN::handleStart();
    bool up = false, down = false;
    int buySignalCount = 0, sellSignalCount = 0;
    
    for(int i = 0; i < ts.size(); i++)
    {
      // alerts are used to collect statistics
      if(ts[i].alert)
      {
        // while setting up events, enumerated by i,
        // hypothesis H_xxx should come first, before signals S_xxx,
        // because we assign up or down marks here
        if(IndicatN::GetSignal(ts[i].index) == "H_BULL")
        {
          bull++;
          buy[ts[i].index]++;
          up = true;
        }
        else if(IndicatN::GetSignal(ts[i].index) == "H_BEAR")
        {
          bear++;
          sell[ts[i].index]++;
          down = true;
        }
        else if(StringFind(IndicatN::GetSignal(ts[i].index), "S_BUY") == 0)
        {
          buy[ts[i].index]++;
          if(up)
          {
            if(PrintDetails) Print("buyOk ", IndicatN::GetSignal(ts[i].index));
            buyOnBull[ts[i].index]++;
          }
        }
        else if(StringFind(IndicatN::GetSignal(ts[i].index), "S_SELL") == 0)
        {
          sell[ts[i].index]++;
          if(down)
          {
            if(PrintDetails) Print("sellOk ", IndicatN::GetSignal(ts[i].index));
            sellOnBear[ts[i].index]++;
          }
        }
        
        if(PrintDetails) Print(ts[i].message);
      }
    }

Depois de obter a matriz de sinais desencadeados, iteramos sobre seus elementos num ciclo. O sinalizador 'alert' habilitado indica a coleta de estatísticas.

Antes de analisar o código em maior profundidade, apresentamos uma convenção especial sobre a nomenclatura dos sinais (eventos). Hipóteses sobre o estado de alta ou de baixa do mercado serão marcadas com H_BULL e H_BEAR. Esses eventos devem ser definidos usando os parâmetros de entrada da estrutura primeiro, quer dizer, antes de outros eventos (dos sinais dos indicadores). Isso é necessário para definir as características apropriadas com base nas hipóteses confirmadas, isto é, as variáveis ​​booleanas 'up' e 'down'.

Os sinais dos indicadores devem ter identificadores que começam com S_BUY ou S_SELL.

Como pode ser visto, através da referência ao número do evento ativado ts[i].index, nós obtemos seu identificador chamando à função GetSignal. Caso as hipóteses sejam cumpridas, atualizamos os contadores gerais de segmentos de alta ou baixa. Se sinais forem gerados, para cada tipo de sinal, seu número total será contado, bem como o índice de seu sucesso, ou seja, o número de correspondências com as hipóteses atuais.

Lembre-se que a hipótese H_BULL, ou a hipótese H_BEAR, é verdadeira em cada barra.

Além da coleta de estatísticas, o EA deve suportar a negociação por sinais. Para este fim, complementamos o corpo do ciclo verificando os sinalizadores buy e sell.

      if(ts[i].buy)
      {
        buySignalCount++;
      }
      else
      if(ts[i].sell)
      {
        sellSignalCount++;
      }

Após o ciclo, implementaremos a funcionalidade de negociação. Em primeiro lugar, fecharemos as posições abertas (se houver) após o período especificado.

    if(e.getLastOrderBar() >= TradeDuration)
    {
      e.closeMarketOrders();
    }

Em seguida, realizaremos uma compra ou venda dependendo dos sinais conjuntos.

    if(buySignalCount >= ConsistentSignalNumber
    && sellSignalCount >= ConsistentSignalNumber)
    {
      Print("Signal collision");
    }
    else
    if(buySignalCount >= ConsistentSignalNumber)
    {
      e.closeMarketOrders(e.mask(OP_SELL));
      
      if(e.getOrderCount(e.mask(OP_BUY)) == 0)
      {
        e.placeMarketOrder(OP_BUY);
      }
    }
    else
    if(sellSignalCount >= ConsistentSignalNumber)
    {
      e.closeMarketOrders(e.mask(OP_BUY));
      
      if(e.getOrderCount(e.mask(OP_SELL)) == 0)
      {
        e.placeMarketOrder(OP_SELL);
      }
    }

Se os sinais de compra e venda se contradizem, ignoramos esse estado. Se o número de sinais de compra e venda for igual ou superior ao número predefinido ConsistentSignalNumber, abriremos a ordem correspondente.

Repare que, após definir um valor para ConsistentSignalNumber menor que o número de sinais configurados, será possível testar o sistema de negociação num modo que combina todas ou a maioria das estratégias. No modo de operação normal, o EA usará cruzamento, mas não união, porque, para encontrar eventos conjuntos, ConsistentSignalNumber deve ser exatamente igual à quantidade de sinais. Por exemplo, com 3 sinais configurados e ConsistentSignalNumber igual a 3, a negociação será realizada somente quando os três eventos ocorrerem simultaneamente. Se ConsistentSignalNumber for definido como 1, as transações serão abertas quando qualquer (pelo menos um) dos 3 sinais for recebido.

No manipulador OnDeinit, emitimos as estatísticas coletadas segundo os alertas ou segundo o histórico de ordens para o log.

O código-fonte completo do especialista pode ser visto no arquivo indstats.mq4.


Configurações de sinais de negociação

Todos os outros sinais devem ser verificados no que diz respeito às duas hipóteses de compra ou venda. Para fazer isso, configuramos os sinais H_BULL e H_BEAR, bem como seus indicadores.

Para obter os preços das barras, utilizamos o indicador iMA com período 1. No grupo __INDICATOR_1 definimos:

Selector = iMA_period_shift_method_price

Parameters = 1,0,sma,open

Buffer = 0

Bar = 0

No grupo __INDICATOR_2, definimos configurações semelhantes, exceto o número da barra, uma vez que ali deve ser estabelecido 5, o número de barras que serão usadas no parâmetro TradeDuration.

Em outras palavras, o EA não opera em modo de coleta de estatísticas. Em vez disso, analisa a mudança de cotações entre as barras 5 e 0, bem como os sinais indicadores na 5ª ou 6ª barra, dependendo do tipo de preço utilizado: para os indicadores que funcionam com base em preços open, os valores podem ser obtidos na quinta barra, e para todos os outros, a partir da 6º. No modo de coleta de estatísticas, a barra número 5 é a barra atual virtual e todas as barras subsequentes fornecem informações sobre o cumprimento "futuro" das hipóteses no mercado de alta ou baixa.

A razão para que, no modo de negociação, os sinais serão retirados da barra 0 (se o indicador se basear nos preços open) ou da barra 1 (em outros casos). Se o Expert Advisor não operasse usando os preços Open e analisasse os tick, seria necessário verificar os valores dos indicadores na barra 0.

A presença desses dois modos - coleta de estatísticas e negociação - implica a necessidade de criar vários conjuntos de parâmetros que diferem no número de barras de trabalho. Começaremos com um conjunto para coletar estatísticas e, em seguida, iremos convertê-lo facilmente num conjunto de negociação real.

Estas duas cópias do indicador MA serão usadas para configurar as hipóteses. No grupo __SIGNAL_A, digitamos:

__SIGNAL_A = H_BULL
Condition = IndicatorXrelatesToIndicatorY Indicator X = 1 Indicator Y = 2 Direction or sign = UpSideOrAboveOrPositve Action = Alert

No grupo __SIGNAL_B, configuramos de forma semelhante, exceto a direção:

__SIGNAL_B = H_BEAR
Direction or sign = DownSideOrBelowOrNegative

Para testar o modelo probabilístico de negociação, utilizaremos 3 estratégias padrão baseadas em indicadores:

Deve ter sido previamente declarado que os parâmetros de todos os indicadores foram otimizados, alguns deles intencionalmente deixados como referências aos parâmetros de entrada var1, var2, etc., para demonstrar essa característica da estrutura. Para recriar os resultados positivos nos dados do seu fornecedor, cada estratégia provavelmente terá que ser re-optimizada.

Estratégia Stochastic consiste em comprar quando o indicador cruzar o nível 20 de baixo para cima e em vender quando ele ultrapassa o nível 80 de cima para baixo. Para fazer isto, definimos o grupo __INDICATOR_3:

Selector = dStochastic_K_D_slowing_method_price
Parameters = 14,3,3,sma,lowhigh Buffer = main Bar = 6

Como, para o indicador, são usados os preços high e low, é necessário pegar a barra número 6, isto é, a última totalmente formada antes da barra 5, onde a negociação virtual começa no caso de um sinal ser disparado.

Os sinais de compra e venda são ajustados de acordo com o indicador Stochastic. Grupo para compra:

__SIGNAL_C = S_BUY stochastic
Condition = IndicatorXcrossesLevelX Level X = 20 Direction or sign = UpSideOrAboveOrPositve

Grupo para a venda:

__SIGNAL_D = S_SELL stochastic
Condition = IndicatorXcrossesLevelX Level X = 80 Direction or sign = DownSideOrBelowOrNegative

Estratégia segundo o MACD consiste na compra quando a linha main cruza para cima a linha de sinal e na venda quando ele cruza para baixo.

Configuramos o grupo do indicador __INDICATOR_4:

Selector = dMACD_fast_slow_signal_price
Parameters = =var4,=var5,=var6,open Buffer = signal Bar = 5

Consideraremos os períodos fast, slow, signal a partir dos parâmetros var4, var5, var6, disponíveis para otimização. Atualmente estão configurados como 6, 21, 6, respectivamente. Usaremos a barra 5, porque construiremos o indicador de acordo com o preço open.

Como o número de grupos para a configuração dos indicadores é limitado, descreveremos o buffer main diretamente nos sinais. Grupo para compra:

__SIGNAL_E = S_BUY macd
Condition = IndicatorXcrossesIndicatorY Indicator X = iMACD@main(=var4,=var5,=var6,open)[5] Indicator Y = 4 Direction or sign = UpSideOrAboveOrPositve

Grupo para a venda: 

__SIGNAL_F = S_SELL macd
Condition = IndicatorXcrossesIndicatorY Indicator X = iMACD@main(=var4,=var5,=var6,open)[5] Indicator Y = 4 Direction or sign = DownSideOrBelowOrNegative

Estratégia com base no BollingerBands consiste na compra quando o high da barra anterior rompe a linha superior do indicador deslocado 2 barras para a direita, e na venda quando o low da barra anterior rompe a linha inferior do indicador deslocado 2 barras para a direita. Abaixo estão as configurações das duas linhas do indicador.

__INDICATOR_5:

Selector = tBands_period_deviation_shift_price

Parameters = =var1,=var2,2,typical
Buffer = upper Bar = 5

__INDICATOR_6:

Selector = tBands_period_deviation_shift_price
Parameters = =var1,=var2,2,typical Buffer = lower Bar = 5

Período e desvio são definidos em var1 e var2 como 7 e 1, respectivamente. A barra 5 pode ser usada em ambos os casos, apesar do tipo de preço typical, porque as linhas do indicador são deslocadas 2 barras para a direita, ou seja, são realmente calculadas em dados passados.

Finalmente, os grupos para configurar sinais são os seguintes.

__SIGNAL_G = S_BUY bands
Condition = IndicatorXcrossesIndicatorY Indicator X = iMA@0(1,0,sma,high)[6] Indicator Y = 5 Direction or sign = UpSideOrAboveOrPositve
__SIGNAL_H = S_SELL bands
Condition = IndicatorXcrossesIndicatorY Indicator X = iMA@0(1,0,sma,low)[6] Indicator Y = 6 Direction or sign = DownSideOrBelowOrNegative

Todas as configurações são anexadas como set-files no final do artigo.


Resultados

Estatísticas por indicadores

Para calcular as probabilidades. utilizamos a estatística para o período 2014.01.01-2017.01.01, par EURUSD, D1. As configurações do EA para o modo de coleta de estatísticas estão contidas no arquivo indstats-stats-all.set.

Os dados coletados são exibidos no log. Aqui está um exemplo:

: bars=778
: bull=328 bear=449
:    buy:    328      0     30      0     50      0     58      0 
:  buyOk:      0      0     18      0     29      0     30      0 
:   sell:      0    449      0     22      0     49      0     67 
: sellOk:      0      0      0     14      0     28      0     41 
: totals:   0.00   0.00   0.60   0.64   0.58   0.57   0.52   0.61 
: Stats by name:
:  macd=0.576 [57/99]
:  bands=0.568 [71/125]
:  stochastic=0.615 [32/52]

O número total de barras é de 778, das quais 328 eram adequadas para um transação de compra de 5 dias bem-sucedida e 449 eram apropriadas para uma venda de 5 dias bem-sucedida. As primeiras 2 colunas contêm os contadores de hipóteses, isto é, os mesmos 2 números, e os próximos pares de colunas estão relacionadas às estratégias de negociação correspondentes, cada uma das quais é representada por uma coluna para negociações de compra e uma coluna para negociações de venda. Por exemplo, uma estratégia baseada em estocástico gera 30 sinais de compra, 18 dos quais são lucrativos e também 22 sinais de venda, 14 dos quais são lucrativos. Somando o número total de sinais bem-sucedidos e dividindo-os pelo número de sinais gerados obtemos a eficiência (probabilidade de sucesso com base em dados do histórico) para cada um deles.


Negociação de teste

Para garantir que as estatísticas sejam calculadas corretamente, é necessário executar o EA no modo de negociação. Para fazer isso, edite os números das barras nas configurações substituindo 5 por 0 e 6 por 1. Além disso, as estratégias de negociação devem ser ativadas uma após a outra definindo o parâmetro Action para Buy e Sell em vez de Alert. Por exemplo, para verificar a negociação baseada em estocástico, no grupo __SIGNAL_C (S_BUY stochastic), substituímos, no parâmetro Action, o valor Alert pelo valor Buy, enquanto, no grupo __SIGNAL_D (S_SELL stochastic), o valor Alert pelo valor Sell.

As configurações correspondentes para todas as 3 estratégias são fornecidas nos arquivos indstats-trade-stoch.set, indstats-trade-macd.set, indstats-trade-bands.set.

Executado 3 vezes o EA com esses conjuntos de parâmetros, obtemos 3 logs com relatórios resumidos sobre a negociação. As estatísticas estão no final. Por exemplo, a seguinte linha é obtida para estocástico:

: Buys: 18/29 0.62 Sells: 14/22 0.64 Total: 0.63

Esses números são de transações reais: 18 compras de 29 são rentáveis, 14 vendas de 22 são rentáveis, a eficiência total do sinal é de 0,63.

Os resultados das estratégias baseadas em MACD e BollingerBands são fornecidos abaixo.

: Buys: 29/49 0.59 Sells: 28/49 0.57 Total: 0.58

: Buys: 29/51 0.57 Sells: 34/59 0.58 Totals: 0.57

Resumimos os valores de todas as estratégias numa lista.

Aqui vemos uma conformidade quase completa com a teoria da subseção anterior. A ligeira diferença é explicada pelo fato de que os sinais de negociação podem se sobrepor se estiverem na faixa de 5 barras, e, nesse caso, não será aberta uma transação reiterada.

Naturalmente, é possível analisar os relatórios de negociação para cada estratégia individual.

Relatório sobre a estratégia com base no indicador Stochastic

Fig.5 Relatório sobre a estratégia com base no indicador Stochastic


Relatório sobre a estratégia com base no indicador MACD

Fig.6 Relatório sobre a estratégia com base no indicador MACD


Relatório sobre a estratégia com base no indicador BollingerBands

Fig.7 Fig.5 Relatório sobre a estratégia com base no indicador BollingerBands

A probabilidade teórica de uma transação ter sucesso ao entrar com os sinais síncronos dos três indicadores é calculada usando a fórmula (4).

P(H|ABC) = 0.63 * 0.58 * 0.57 / (0.63 * 0.58 * 0.57 + 0.37 * 0.42 * 0.43) = 0.208278 / (0.208278 + 0.066822) = 0.208278 / 0.2751 = 0.757

Para testar esta situação, é necessário levar em consideração todos os três sinais e alterar o valor do parâmetro ConsistentSignalNumber de 1 para 3. As configurações correspondentes estão localizadas no arquivo indstats-trade-all.set.

De acordo com a negociação no testador, a eficiência total desse sistema na prática é igual a 0,75:

: Buys: 4/7 0.57 Sells: 5/5 1.00 Total: 0.75

Aqui está o relatório de teste:

Relatório sobre a combinação de estratégias baseadas em 3 indicadores

Fig.8 Relatório sobre a combinação de estratégias baseadas em 3 indicadores

Poderá ver em baixo uma uma tabela de desempenho de negociação para cada um dos indicadores separadamente e para sua superposição.


Profit,$ PF N DD,$
Stochastic 204 2.36 51 41
MACD 159 1.39 98 76
Bands 132 1.29 110 64
Total 68 3.18 12 30

Como pode ser observado, um aumento na probabilidade de sucesso é alcançado devido a entradas menos frequentes, mas mais precisas. O número de transações e o lucro total diminuem, embora o fator de lucro e a redução máxima tenham melhorado em pelo menos 35%, e mais de duas vezes em alguns casos.


Fim do artigo

No artigo, examinamos a variação mais simples para implementar a abordagem probabilística a fim de tomar decisões de negociação com base nos sinais dos indicadores. Através de um Expert Advisor especial foi demonstrado que os cálculos teóricos sobre o aumento da probabilidade de transações bem-sucedidas segundo o teorema de Bayes corresponde aos resultados obtidos na prática.

Como a geração de sinal é discreta, os sinais de diferentes indicadores podem não coincidir. Pode-se dar que a superposição de indicadores não forneça sinais comuns confirmados por todos os indicadores. Uma das possíveis soluções para este problema consiste na introdução de uma tolerância de tempo entre os sinais.

Em um caso mais geral, é possível calcular a função densidade de probabilidade da implementação de hipóteses de negociação dependendo do estado (e não dos sinais) dos indicadores. Por exemplo, o valor de sobrecompra ou sobrevenda determinado com base num valor específico do oscilador dá a porcentagem (probabilidade) de entradas bem-sucedidas. Além disso, a probabilidade de uma negociação bem-sucedida é obviamente dependente dos parâmetros selecionados de Stop-Loss e Take-Profit, do sistema de gerenciamento de lotes e de muitos outros parâmetros do sistema. Tudo isso pode ser analisado do ponto de vista da teoria da probabilidade e usado para um cálculo mais preciso e complexo das decisões de negociação.

Os seguintes são arquivos anexados: