Estratégia de negociação "Momentum Pinball"

Alexander Puzanov | 19 janeiro, 2018


Introdução

Neste artigo, continuamos a falar sobre a programação de estratégias de negociação descritas no livro de Linda Raschke e Laurence Connors Street Smarts: High Probability Short-Term Trading Strategies. Ele é dedicado a testar os limites da faixa usando o preço. A última das estratégias de negociação genuínas, no livro, é a "Momentum Pinball". Ela implementa um padrão que consiste em duas barras diárias. Na primeira barra, é definida a direção da negociação para o segundo dia. O movimento dos preços no início da segunda barra deve especificar certos níveis de negociação para entradas e saídas do mercado.

O objetivo deste artigo é mostrar aos programadores, que já dominam a linguagem MQL5, uma das variantes para a realização da estratégia de negociação "Momentum Pinball", na qual serão aplicados métodos simplificados de programação orientada a objetos. O código diferirá de toda a POO pela ausência de classes, uma vez que elas serão substituídas por estruturas. Ao contrário das classes, o design do código e a aplicação de objetos deste tipo difere minimamente da programação processual conhecida pela a maioria dos programadores novatos. Por outro lado, as características fornecidas pelas estruturas são mais do que suficientes para resolver essas tarefas.

Como no artigo anterior, primeiro, criamos o módulo do bloco de sinal, logo, o indicador para negociação manual e o layout do histórico, que usa esse módulo. O expert advisor será o terceiro programa para negociação automatizada. Ele também usará o módulo de sinal. Em conclusão, testaremos o EA em cotações recentes, porque os autores do livro trabalhavam com cotações de há 20 anos.


Regras da estratégia de negociação "Momentum Pinball"

L. Raschke e L. Connors enfrentaram incerteza ao usar as técnicas de negociação descritas por George Taylor, isso se tornou o motivo para compilar as regras desta estratégia de negociação. A estratégia de Taylor, antes do início do dia seguinte, define a direção de sua negociação, seja este um dia de vendas seja um dia de compras. No entanto, a negociação real do autor muitas vezes viola essa diretiva; ela, na opinião dos autores do livro, embaraça as regras de negociação.

Para determinar definitivamente a direção da negociação do dia seguinte, Raschke e Connors implementaram o indicador ROC (Rate Of Change - Taxa de mudança do preço). O oscilador RSI (Relative Strenght Index - Índice de Força Relativa) foi aplicado aos valores do ROC, o que tornou bem visível a ciclicidade dos seus valores. Finalmente, os autores da estratégia adicionaram níveis de sinal, isto é, as bordas das áreas de sobrecompra e sobrevenda no gráfico do RSI. A presença da linha desse indicador (denominada LBR/RSI, de Linda Bradford Raschke), na respectiva área, é projetada para detectar os dias de venda e os de compra mais prováveis. Abaixo examinaremos em detalhes o LBR/RSI.

Todas as regras da estratégia de negociação Momentum Pinball, para compra, são formuladas da seguinte forma.

  1. No timeframe D1, o valor do indicador LBR/RSI, do último dia concluído, deve se encontrar na zona de sobrecompra - em baixo de 30.
  2. Após o fechamento da primeira barra horário, de um novo dia, posicione a ordem pendente para compra acima do máximo desta barra.
  3. Após a ativação da ordem pendente, coloque o Stop Loss no mínimo da primeira barra horária.
  4. Se a posição for fechada com perda, coloque novamente a ordem pendente para venda no mesmo nível.
  5. Se, no final do dia, a posição permanecer lucrativa, deixe essa ordem para o dia seguinte. No segundo dia de negociação, a posição tem que ser fechado.

Regras de entrada usando dois indicadores:

- LBR/RSI se encontra na zona de sobrevenda, no timeframe diário (veja 30 de outubro de 2017)


- indicador TS_Momentum_Pinball num período arbitrário (de M1 a D1) exibe os níveis de negociação e faixa de preço da primeira hora do dia, com base nos quais são calculados estes níveis:


As regras para a saída do mercado não estão claramente definidas no livro: os autores falam sobre o uso do trailing, o fechamento na manhã seguinte e a saída do High máximo do primeiro dia de negociação.

As regras para as entradas de venda são semelhantes: LBR/RSI deve estar dentro da área de sobrecompra (superior a 70), uma ordem pendente deve ser colocada no Low da primeira barra horária.



Indicador LBR/RSI

Claro, todos os cálculos necessários para obter um sinal podem ser realizados no módulo de sinal. Porém, além do trading automatizado, a negociação manual faz parte do plano deste artigo. Ter um indicador separado LBR/RSI com destaque de áreas de sobrecompra/sobrevenda será útil para identificar visualmente o padrão da versão manual, mais facilmente. E, para otimizar nossos esforços, não programaremos duas várias versões da estimativa do LBR/RSI ("buffer" para o indicador e "sem buffer" para o robô). Aproveitamos a oportunidade para conectar um indicador externo ao módulo de sinal através da função iCustom padrão. Este indicador não realizará cálculos intensivos em recursos, além disso, não haverá necessidade de acessá-lo em cada tick, porque na estratégia de negociação, é usado o valor do indicador na barra diária fechada. Portanto, não há obstáculos significativos para essa solução.

Neste indicador, juntamos os algoritmos de cálculo do ROC e RSI que plotam a curva resultante do oscilador. Para facilitar a identificação dos valores desejados, adicionamos o preenchimento de zonas de sobrevenda/sobrecompra com diferentes cores. Para fazer tudo isso, são necessários cinco buffers, para exibição, e mais quatro, para cálculos auxiliares.

Às configurações padrão (período do RSI e valores das borda de duas zonas) adicionamos mais uma que não é prevista pelas regras originais do sistema de negociação. Ela permitirá utilizar, para cálculos, não só o preço da barra diária, mas também o preço típico de mediana ou o preço moderado. Em suma, o usuário poderá selecionar, para suas experiências, qualquer uma das sete opções previstas pelo tipo ENUM_APPLIED_PRICE.

A declaração de buffers de campos inseridos personalizados e do bloco de inicialização terá a seguinte aparência:

#property indicator_separate_window
#property indicator_buffers  9
#property indicator_plots    3
#property indicator_label1  "Zona de sobrecompra"
#property indicator_type1   DRAW_FILLING
#property indicator_color1  C\"255,208,234\"
#property indicator_width1  1
#property indicator_label2  "Zona de sobrevenda"
#property indicator_type2   DRAW_FILLING
#property indicator_color2  C\"179,217,255\"
#property indicator_width2  1
#property indicator_label3  "RSI do ROC"
#property indicator_type3   DRAW_LINE
#property indicator_style3  STYLE_SOLID
#property indicator_color3  clrTeal
#property indicator_width3  2
#property indicator_minimum 0
#property indicator_maximum 100
input ENUM_APPLIED_PRICE  TS_MomPin_Applied_Price = PRICE_CLOSE;  // Preços para o cálculo do ROC
input uint    TS_MomPin_RSI_Period = 3;                           // Período do RSI
input double  TS_MomPin_RSI_Overbought = 70;                      // Nível de sobrevenda de acordo com o RSI
input double  TS_MomPin_RSI_Oversold = 30;                        // Nível de sobrecompra de acordo com o RSI
double
  buff_Overbought_High[], buff_Overbought_Low[],                  // fundo da zona de sobrecompra
  buff_Oversold_High[], buff_Oversold_Low[],                      // fundo da zona de sobrevenda
  buff_Price[],                                                   // matriz dos preços estimados das barras
  buff_ROC[],                                                     // matriz do ROC a partir dos preços calculados
  buff_RSI[],                                                     // RSI do ROC
  buff_Positive[], buff_Negative[]                                // matizes auxiliares para calcular o RSI
;
int OnInit() {
  // atribuição dos buffers de indicador:
  
  // zona de sobrecompra
  SetIndexBuffer(0, buff_Overbought_High, INDICATOR_DATA);
    PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
    PlotIndexSetInteger(0, PLOT_SHOW_DATA, false);
  SetIndexBuffer(1, buff_Overbought_Low, INDICATOR_DATA);
  
  // zona de sobrevenda
  SetIndexBuffer(2, buff_Oversold_High, INDICATOR_DATA);
    PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
    PlotIndexSetInteger(1, PLOT_SHOW_DATA, false);
  SetIndexBuffer(3, buff_Oversold_Low, INDICATOR_DATA);
  
  // curva do RSI
  SetIndexBuffer(4, buff_RSI, INDICATOR_DATA);
    PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
  
  // buffers auxiliares para calcular o RSI
  SetIndexBuffer(5, buff_Price, INDICATOR_CALCULATIONS);
  SetIndexBuffer(6, buff_ROC, INDICATOR_CALCULATIONS);
  SetIndexBuffer(7, buff_Negative, INDICATOR_CALCULATIONS);
  SetIndexBuffer(8, buff_Positive, INDICATOR_CALCULATIONS);
  
  IndicatorSetInteger(INDICATOR_DIGITS, 2);
  IndicatorSetString(INDICATOR_SHORTNAME, "LBR/RSI");
  
  return(INIT_SUCCEEDED);
    }

No manipulador de eventos OnCalculate, organizamos dois ciclos separados: o primeiro prepara a matriz de dados do ROC, o segundo calculo os valores de um oscilador baseado nos dados desta matriz.

Na versão do Rates Of Change, oferecida por Linda Raschke, é necessário comparar os preços com omissão de uma barra entre eles. Ou seja, na estratégia de negociação, são utilizadas as variação nos preços dos dias que distam um ou três dias úteis do dia de negociação, respectivamente. Fazer isso é simples. Neste mesmo ciclo, organizamos o preenchimento de fundo das zonas de sobrevenda e sobrecompra. E não esquecemos realizar a capacidade de seleção do tipo de preço:

int
  i_RSI_Period = int(TS_MomPin_RSI_Period),         // conversão do período do RSI no tipo int
  i_Bar, i_Period_Bar                               // dois índices de barras para uso simultâneo
;
double
  d_Sum_Negative, d_Sum_Positive,                   // variáveis auxiliares para calcular o RSI
  d_Change                                          // variável auxiliar para calcular o ROC
;
// Preencher o buffer do ROC e o fundo da zona:
i_Period_Bar = 1;
while(++i_Period_Bar < rates_total && !IsStopped()) {
// preço da barra estimado:
  switch(TS_MomPin_Applied_Price) {
    case PRICE_CLOSE:     buff_Price[i_Period_Bar] = Close[i_Period_Bar]; break;
    case PRICE_OPEN:      buff_Price[i_Period_Bar] = Open[i_Period_Bar]; break;
    case PRICE_HIGH:      buff_Price[i_Period_Bar] = High[i_Period_Bar]; break;
    case PRICE_LOW:       buff_Price[i_Period_Bar] = Low[i_Period_Bar]; break;
    case PRICE_MEDIAN:    buff_Price[i_Period_Bar] = 0.50000 * (High[i_Period_Bar] + Low[i_Period_Bar]); break;
    case PRICE_TYPICAL:   buff_Price[i_Period_Bar] = 0.33333 * (High[i_Period_Bar] + Low[i_Period_Bar] + Open[i_Period_Bar]); break;
    case PRICE_WEIGHTED:  buff_Price[i_Period_Bar] = 0.25000 * (High[i_Period_Bar] + Low[i_Period_Bar] + Open[i_Period_Bar] + Open[i_Period_Bar]); break;
  }
  // diferença dos preços estimados das barras (valor do ROC):
  if(i_Period_Bar > 1) buff_ROC[i_Period_Bar] = buff_Price[i_Period_Bar] - buff_Price[i_Period_Bar - 2];
  
  // preenchimento do fundo:
  buff_Overbought_High[i_Period_Bar] = 100;
  buff_Overbought_Low[i_Period_Bar] = TS_MomPin_RSI_Overbought;
  buff_Oversold_High[i_Period_Bar] = TS_MomPin_RSI_Oversold;
  buff_Oversold_Low[i_Period_Bar] = 0;
        }

O segundo ciclo (cálculo do RSI) não tem quaisquer peculiaridades. Praticamente, ele repete completamente o algoritmo do oscilador padrão deste ciclo:

i_Period_Bar = prev_calculated - 1;
if(i_Period_Bar <= i_RSI_Period) {
  buff_RSI[0] = buff_Positive[0] = buff_Negative[0] = d_Sum_Positive = d_Sum_Negative = 0;
  i_Bar = 0;
  while(i_Bar++ < i_RSI_Period) {
    buff_RSI[0] = buff_Positive[0] = buff_Negative[0] = 0;
    d_Change = buff_ROC[i_Bar] - buff_ROC[i_Bar - 1];
    d_Sum_Positive += (d_Change > 0 ? d_Change : 0);
    d_Sum_Negative += (d_Change < 0 ? -d_Change : 0);
  }
  buff_Positive[i_RSI_Period] = d_Sum_Positive / i_RSI_Period;
  buff_Negative[i_RSI_Period] = d_Sum_Negative / i_RSI_Period;
  
  if(buff_Negative[i_RSI_Period] != 0)
    buff_RSI[i_RSI_Period] = 100 - (100 / (1. + buff_Positive[i_RSI_Period] / buff_Negative[i_RSI_Period]));
  else
    buff_RSI[i_RSI_Period] = buff_Positive[i_RSI_Period] != 0 ? 100 : 50;
  
  i_Period_Bar = i_RSI_Period + 1;
}

i_Bar = i_Period_Bar - 1;
while(++i_Bar < rates_total && !IsStopped()) {
  d_Change = buff_ROC[i_Bar] - buff_ROC[i_Bar - 1];
  
  buff_Positive[i_Bar] = (buff_Positive[i_Bar - 1] * (i_RSI_Period - 1) + (d_Change> 0 ? d_Change : 0)) / i_RSI_Period;
  buff_Negative[i_Bar] = (buff_Negative[i_Bar - 1] * (i_RSI_Period - 1) + (d_Change <0 ? -d_Change : 0)) / i_RSI_Period;
  
  if(buff_Negative[i_Bar] != 0)
    buff_RSI[i_Bar] = 100 - 100. / (1. + buff_Positive[i_Bar] / buff_Negative[i_Bar]);
  else
    buff_RSI[i_Bar] = buff_Positive[i_Bar] != 0 ? 100 : 50;
}

Chamamos o indicador de LBR_RSI.mq5 e colocamo-lo na pasta padrão do indicador do catálogo de dados do terminal. Ele será escrito na função iCustom do módulo de sinal, por isso não deve ser alterado.

Módulo de sinal

No módulo de sinal conectado ao expert advisor e ao indicador, colocamos as configurações da estratégia de negociação "Momentum Pinball". Os autores citam os valores fixos, para cálculo do indicador LBR/RSI (período RSI = 3, nível de sobrecompra = 30, nível de sobrevenda = 70). Mas, para experiências, nós, sem dúvida, tornamo-los variáveis, assim como os métodos de fechamento de posição - no livro, são mencionados três variantes. Vamos programar todos eles. Assim, o usuário terá um recurso para selecionar a opção necessária:

  • fechar a posição segundo o trailing do nível de Stop Loss;
  • fechá-lo na amanhã do dia seguinte;
  • esperar no segundo dia de rompimento do extremo do dia de abertura da posição.

O conceito "manha" é bastante vago, para estabelecer as regras é necessário ter uma definição mais precisa. Embora Raschke e Connors não falem sobre isso, é razoável supor que regras de outros sistemas de negociação usem uma ligação à primeira barra de um novo dia que aponte para o rótulo "manhã" de uma linha do tempo de 24 horas.

Não nos esqueçamos de mais duas configurações da estratégia de negociação, isto é, os recuos a partir das bordas do primeiro dia. Elas devem definir os níveis de posicionamento da ordem pendente e o nível de StopLoss:

enum ENUM_EXIT_MODE {     // Lista de métodos de saída
  CLOSE_ON_SL_TRAIL,      // apenas de acordo com o trailing stop
  CLOSE_ON_NEW_1ST_CLOSE, // imediatamente após o fechamento da primeira barra do seguinte dia
  CLOSE_ON_DAY_BREAK      // imediatamente após o rompimento do extremo do dia da abertura da posição
};
// configurações personalizadas
input ENUM_APPLIED_PRICE  TS_MomPin_Applied_Price = PRICE_CLOSE;     // Momentum Pinball: Preços para cálculo do ROC
input uint    TS_MomPin_RSI_Period = 3;                              // Momentum Pinball: Período do RSI
input double  TS_MomPin_RSI_Overbought = 70;                         // Momentum Pinball: Nível de sobrevenda segundo o RSI
input double  TS_MomPin_RSI_Oversold = 30;                           // Momentum Pinball: Nível de sobrecompra segundo o RSI
input uint    TS_MomPin_Entry_Offset = 10;                           // Momentum Pinball: Recuo do nível de saída a partir da borda H1 (em pontos)
input uint    TS_MomPin_Exit_Offset = 10;                            // Momentum Pinball: Recuo do nível de saída a partir da borda H1 (em pontos)
  input ENUM_EXIT_MODE  TS_MomPin_Exit_Mode = CLOSE_ON_SL_TRAIL;       // Momentum Pinball: Método de fechamento da posição lucrativa

A função principal do módulo fe_Get_Entry_Signal será unificada com a função do módulo de sinal da estratégia de negociação anterior do livro de Raschke e Connors, bem como com módulos analógicos ulteriores de outros sistemas de negociação descritos nesta fonte. Isso significa que a função deve ter esse conjunto de parâmetros, referências a variáveis e esse mesmo tipo de retorno:

ENUM_ENTRY_SIGNAL fe_Get_Entry_Signal(      // Análise do padrão de duas velas (D1 + H1)
  datetime  t_Time,                         // hora atual
  double&    d_Entry_Level,                 // nível de saída (referência à variável)
  double&    d_SL,                          // nível de StopLoss (referência à variável)
  double&    d_TP,                          // nível de TakeProfit (referência à variável)
  double&    d_Range_High,                  // máximo da faixa da barra da primeira hora (referência à variável)
  double&    d_Range_Low                    // mínimo da faixa da barra da primeira hora (referência à variável)
) {
  // corpo da função
    }

Como na versão anterior, em cada tick, ao chamar a função a partir do robô, não vamos calcular de novo tudo, em vez disso, entre ticks, vamos armazenar os níveis calculados nas variáveis estatísticas. No entanto, trabalhar com esta função no indicador usando negociação manual terá diferenças substanciais, sendo assim, deve ser fornecida a anulação de variáveis ​​estáticas ao chamar a função a partir do indicador. Para distinguir entre a chamada a partir do indicador e a chamada a partir do robô, utilizamos a variável t_Time - o indicador vai interpretá-la, isto é, tornar negativo o valor:

static ENUM_ENTRY_SIGNAL se_Trade_Direction = ENTRY_UNKNOWN;   // direção da negociação para hoje
static double
  // variáveis para armazenamento dos níveis calculados entre ticks
  sd_Entry_Level = 0,
  sd_SL = 0, sd_TP = 0,
  sd_Range_High = 0, sd_Range_Low = 0
;
if(t_Time < 0) {                                               // apenas para chamada desde o indicador
  sd_Entry_Level = sd_SL = sd_TP = sd_Range_High = sd_Range_Low = 0;
  se_Trade_Direction = ENTRY_UNKNOWN;
}
// por padrão, utilizamos os níveis de entrada/saída armazenados anteriormente:
      d_Entry_Level = sd_Entry_Level; d_SL = sd_SL; d_TP = sd_TP; d_Range_High = sd_Range_High; d_Range_Low = sd_Range_Low;

Abaixo, encontra-se o código, para receber o identificador do indicador LBR/RSI, ao chamar a função pela primeira vez:

static int si_Indicator_Handle = INVALID_HANDLE;
if(si_Indicator_Handle == INVALID_HANDLE) {
  // buscar o identificador do indicador na primeira chamada da função:
  si_Indicator_Handle = iCustom(_Symbol, PERIOD_D1, "LBR_RSI",
    TS_MomPin_Applied_Price,
    TS_MomPin_RSI_Period,
    TS_MomPin_RSI_Overbought,
    TS_MomPin_RSI_Oversold
  );
  
  if(si_Indicator_Handle == INVALID_HANDLE) { // identificador do indicador não recebido
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: erro ao obter o identificador do indicador LBR_RSI #%u", __FUNCTION__, _LastError);
    return(ENTRY_INTERNAL_ERROR);
  }
    }

Uma vez por dia, o robô deve analisar o valor do indicador, na última barra diária fechada, e definir a direção de negociação permitida hoje. Ou deve desativar a negociação se o valor LBR/RSI estiver numa zona neutra. Código de recuperação deste valor do buffer de indicador e sua análise, com funções de registro, tendo em vista possíveis erros e peculiaridades da chamada do indicador de negociação manual:

static int si_Indicator_Handle = INVALID_HANDLE;
if(si_Indicator_Handle == INVALID_HANDLE) {
  // buscar o identificador do indicador na primeira chamada da função:
  si_Indicator_Handle = iCustom(_Symbol, PERIOD_D1, "LBR_RSI",
    TS_MomPin_Applied_Price,
    TS_MomPin_RSI_Period,
    TS_MomPin_RSI_Overbought,
    TS_MomPin_RSI_Oversold
  );
  
  if(si_Indicator_Handle == INVALID_HANDLE) {       // identificador não recebido
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: erro ao obter o identificador do indicador LBR_RSI #%u", __FUNCTION__, _LastError);
    return(ENTRY_INTERNAL_ERROR);
  }
}
// saber a hora da barra diária do dia anterior:
datetime ta_Bar_Time[];
if(CopyTime(_Symbol, PERIOD_D1, fabs(t_Time), 2, ta_Bar_Time) < 2) {
  if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyTime: erro #%u", __FUNCTION__, _LastError);
  return(ENTRY_INTERNAL_ERROR);
}
// análise de ontem, se for a 1º chamada de hoje:
static datetime st_Prev_Day = 0;
if(t_Time < 0) st_Prev_Day = 0;                     // apenas para chamada desde o indicador
if(st_Prev_Day < ta_Bar_Time[0]) {
  // redefinição de parâmetros do dia anterior:
  se_Trade_Direction = ENTRY_UNKNOWN;
  d_Entry_Level = sd_Entry_Level = d_SL = sd_SL = d_TP = sd_TP = d_Range_High = sd_Range_High = d_Range_Low = sd_Range_Low = 0;
  
  // extrair o valor do LBR/RSI do dia anterior:
  double da_Indicator_Value[];
  if(1 > CopyBuffer(si_Indicator_Handle, 4, ta_Bar_Time[0], 1, da_Indicator_Value)) {
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyBuffer: erro #%u", __FUNCTION__, _LastError);
    return(ENTRY_INTERNAL_ERROR);
  }
  
  // se há algo de errado com o valor LBR / RSI:
  if(da_Indicator_Value[0] > 100. || da_Indicator_Value[0] < 0.) {
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: Erro do valor (%f) do buffer de indicador", __FUNCTION__, da_Indicator_Value[0]);
    return(ENTRY_UNKNOWN);
  }
  
  st_Prev_Day = ta_Bar_Time[0];                     // tentativa contada
  
  // lembramos a direção da negociação de hoje:
  if(da_Indicator_Value[0] > TS_MomPin_RSI_Overbought) se_Trade_Direction = ENTRY_SELL;
  else se_Trade_Direction = da_Indicator_Value[0] > TS_MomPin_RSI_Oversold ? ENTRY_NONE : ENTRY_BUY;
  
  // no log:
  if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: Direção da negociação em %s: %s. LBR/RSI: (%.2f)",
    __FUNCTION__,
    TimeToString(ta_Bar_Time[1], TIME_DATE),
    StringSubstr(EnumToString(se_Trade_Direction), 6),
    da_Indicator_Value[0]
  );
    }

Deixamos esclarecida a direção de negociação permitida. A próxima tarefa é determinar os níveis de entrada e a limitação de perdas (Stop Loss). Basta fazê-lo uma vez por dia, logo após fechar a primeira barra do dia, no timeframe horário. No entanto, tendo em conta às peculiaridades do funcionamento do indicador de negociação manual, teremos de complicar um pouco o algoritmo. Isso é causado pelo fato de que o indicador não deve apenas detectar níveis de sinal, em tempo real, mas também fazer marcas no histórico:

// hoje não estamos procurando um sinal
if(se_Trade_Direction == ENTRY_NONE) return(ENTRY_NONE);
// análise da primeira barra de hoje H1, se isso ainda não tiver sido feito:
if(sd_Entry_Level == 0.) {
  // obter os dados das últimas 24 barras H1:
  MqlRates oa_H1_Rates[];
  int i_Price_Bars = CopyRates(_Symbol, PERIOD_H1, fabs(t_Time), 24, oa_H1_Rates);
  if(i_Price_Bars == WRONG_VALUE) {                      // processamento do erro da função CopyRates
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyRates: erro #%u", __FUNCTION__, _LastError);
    return(ENTRY_INTERNAL_ERROR);
  }
  
  // encontrar, entre 24 barras, a primeira barra de hoje e lembrar o High, Low:
  int i_Bar = i_Price_Bars;
  while(i_Bar-- > 0) {
    if(oa_H1_Rates[i_Bar].time < ta_Bar_Time[1]) break;      // última barra H1 de ontem
    
    // bordas da faixa da primeira barra H1:
    sd_Range_High = d_Range_High = oa_H1_Rates[i_Bar].high;
    sd_Range_Low = d_Range_Low = oa_H1_Rates[i_Bar].low;
  }
  // primeira barra H1 ainda não tem sido fechada:
  if(i_Price_Bars - i_Bar < 3) return(ENTRY_UNKNOWN);
  
  // calcular os níveis de negociação:
  
  // nível de entrada no mercado:  
  d_Entry_Level = _Point * TS_MomPin_Entry_Offset;           // cálculos auxiliares
  sd_Entry_Level = d_Entry_Level = se_Trade_Direction == ENTRY_SELL ? d_Range_Low - d_Entry_Level : d_Range_High + d_Entry_Level;
  // nível inicial SL:  
  d_SL = _Point * TS_MomPin_Exit_Offset;                     // cálculos auxiliares
  sd_SL = d_SL = se_Trade_Direction == ENTRY_BUY ? d_Range_Low - d_SL : d_Range_High + d_SL;
    }

Após isto, resta concluir o trabalho da função retornando a direção de negociação detectada:

return(se_Trade_Direction);

Agora, vamos programar a análise das condições para o sinal de fechamento da posição. Nós temos três variantes, uma das quais (trailing do nível de Stop Loss) já foi realizada no código do EA das versões anteriores. As outras duas variantes exigem o preço e hora de entrada, direção da posição, para os cálculos. Vamos transferi-los para a função fe_Get_Exit_Signal juntamente com o tempo atual e o método de fechamento selecionado:

ENUM_EXIT_SIGNAL fe_Get_Exit_Signal(    // Detecção do sinal de fechamento da posição
  double            d_Entry_Level,      // nível de entrada
  datetime          t_Entry_Time,       // momento de entrada
  ENUM_ENTRY_SIGNAL e_Trade_Direction,  // direção da negociação
  datetime          t_Current_Time,     // hora atual
  ENUM_EXIT_MODE    e_Exit_Mode         // método de saída
) {
  static MqlRates soa_Prev_D1_Rate[];   // dados da barra D1 do dia anterior
  static int si_Price_Bars = 0;         // contador auxiliar
  if(t_Current_Time < 0) {              // diferenciar entre a chamada do indicador e a chamada do expert advisor
    t_Current_Time = -t_Current_Time;
    si_Price_Bars = 0;
  }
  double
    d_Curr_Entry_Level,
    d_SL, d_TP,
    d_Range_High,  d_Range_Low
  ;
  
  if(e_Trade_Direction < 1) {          // nenhuma posição, zerar tudo
    si_Price_Bars = 0;
  }
  
  switch(e_Exit_Mode) {
    case CLOSE_ON_SL_TRAIL:            // apenas de acordo com o trailing stop
            return(EXIT_NONE);
                      
    case CLOSE_ON_NEW_1ST_CLOSE:       // imediatamente após o fechamento da primeira barra do seguinte dia
            if((t_Current_Time - t_Current_Time % 86400)
              ==
              (t_Entry_Time - t_Current_Time % 86400)
            ) return(EXIT_NONE);       // dia de abertura da posição ainda não tem sido concluído
            
            if(fe_Get_Entry_Signal(t_Current_Time, d_Curr_Entry_Level, d_SL, d_TP, d_Range_High, d_Range_Low)
              < ENTRY_UNKNOWN
            ) {
              if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: primeira barra do dia seguinte fechada", __FUNCTION__);
              return(EXIT_ALL);
            }
            return(EXIT_NONE);         // não fechado
            
    case CLOSE_ON_DAY_BREAK:           // imediatamente após o rompimento do extremo do dia da abertura da posição
            if((t_Current_Time - t_Current_Time % 86400)
              ==
              (t_Entry_Time - t_Current_Time % 86400)
            ) return(EXIT_NONE);       // dia de abertura da posição ainda não tem sido concluído
            
            if(t_Current_Time % 86400 > 36000) return(EXIT_ALL); // tempo saiu
            
            if(si_Price_Bars < 1) {
              si_Price_Bars = CopyRates(_Symbol, PERIOD_D1, t_Current_Time, 2, soa_Prev_D1_Rate);
              if(si_Price_Bars == WRONG_VALUE) { // processamento de erro da função CopyRates
                if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyRates: erro #%u", __FUNCTION__, _LastError);
                return(EXIT_UNKNOWN);
              }
              
              if(e_Trade_Direction == ENTRY_BUY) {
                if(soa_Prev_D1_Rate[1].high < soa_Prev_D1_Rate[0].high) return(EXIT_NONE);        // não rompeu
                
                if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: o preço quebrou o High de ontem: %s > %s", __FUNCTION__, DoubleToString(soa_Prev_D1_Rate[1].high, _Digits), DoubleToString(soa_Prev_D1_Rate[0].high, _Digits));
                return(EXIT_BUY);
              } else {
                if(soa_Prev_D1_Rate[1].low > soa_Prev_D1_Rate[0].low) return(EXIT_NONE);          // não rompeu
                
                if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: o preço quebrou o Low de ontem: %s < %s", __FUNCTION__, DoubleToString(soa_Prev_D1_Rate[1].low, _Digits), DoubleToString(soa_Prev_D1_Rate[0].low, _Digits));
                return(EXIT_SELL);
              }
            }
            
            return(EXIT_NONE); // pelo sim pelo não
  }
  
  return(EXIT_UNKNOWN);
    }

Aqui, temos uma "tampa" no caso de ter sido selecionada a opção "sair segundo trailing" - a função retorna a ausência do sinal sem qualquer análise. Para outras duas opções, é identificada a ocorrência de eventos "manhã chegou" e "extremo de ontem quebrado". Variantes das funções retornadas dos valores do tipo ENUM_EXIT_SIGNAL são muito similares à lista análoga de valores de sinal de entrada (ENUM_ENTRY_SIGNAL):

enum ENUM_EXIT_SIGNAL {  // Lista de sinais de saída
  EXIT_UNKNOWN,          // indefinido
  EXIT_BUY,              // fechar compras
  EXIT_SELL,             // fechar vendas
  EXIT_ALL,              // fechar tudo
  EXIT_NONE              // não fechar nada
    };

Indicador para negociação manual

Описанный выше сигнальный модуль предназначен для использования в роботе для автоматической торговли. Mais tarde, examinaremos em detalhes essas formas de implementação. Primeiro, criamos uma ferramenta, a fim de ter uma imagem mais clara das peculiaridades do sistema de negociação, nos gráficos do terminal. Ela será o indicador que usa um módulo de sinais, sem quaisquer alterações, e exibe níveis de negociação - nível de posicionamento da ordem pendente e o nível de Stop Loss. Neste indicador, o fechamento da transação lucrativa será fornecido só após atingido o nível definido (TakeProfit). Talvez você se lembre, no módulo, programamos os algoritmos mais complexos, a fim de identificar os sinais para sair da transação, mas vamos deixá-los para serem implementados no robô.

Além dos níveis de negociação, o indicador irá destacar, usando o plano de fundo, as barras da primeira hora do dia. Isso tornará clara a razão pela qual são usados estes níveis. Este layout ajudará a avaliar visualmente os prós e contras da maioria das regras da estratégia \"Momentum Pinball\" - identificar o que não pode ser revelado pelos relatórios do testador de estratégias. A análise visual das estatísticas do teste permite fazer com que as regras do sistema de negociação sejam mais eficientes.

Adicionamos um sistema de alerta, em tempo real, ao indicador, desse modo, será possível usá-lo, na negociação manual. Este alerta conterá a direção de entrada, recomendada pelo módulo de sinal, juntamente com os níveis de posicionamento da ordem pendente e saída de emergência (Stop Loss). O alerta é enviado de três maneiras: janela pop-up com texto e aviso sonoro, e-mail e notificação por push para o dispositivo móvel.

Todas as exigências para o indicador têm sido listadas. Sendo assim, avançamos para a programação. Para desenhar no gráfico todos os objetos planejados por nós, o indicador deve ter um buffer do tipo DRAW_FILLING (para preenchimento da faixa de barras da primeira hora do dia) e três buffers para exibição de níveis de negociação (nível de entrada, nível de fixação do lucro, nível de restrição da perda). Um deles (o nível de posicionamento da ordem pendente) deve ser capaz de mudar a alterar de cor (tipo DRAW_COLOR_LINE), dependendo da direção do trade, enquanto os outros dois podem ser de uma só cor - tipo DRAW_LINE:

#property indicator_chart_window
#property indicator_buffers  6
#property indicator_plots    4
#property indicator_label1  "primeira hora do dia"
#property indicator_type1   DRAW_FILLING
#property indicator_color1  C\"255,208,234\", C\"179,217,255\"
#property indicator_width1  1
#property indicator_label2  "Nível de entrada"
#property indicator_type2   DRAW_COLOR_LINE
#property indicator_style2  STYLE_DASHDOT
#property indicator_color2  clrDodgerBlue, clrDeepPink
#property indicator_width2  2
#property indicator_label3  "Stop Loss"
#property indicator_type3   DRAW_LINE
#property indicator_style3  STYLE_DASHDOTDOT
#property indicator_color3  clrCrimson
#property indicator_width3  1
#property indicator_label4  "Take Profit"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrGreen
  #property indicator_width4  1

Agora, é necessário declarar listas. Algumas delas não são necessárias no indicador, pois são usadas apenas pelo indicador, no entanto estão envolvidas nas funções do módulo de sinal. Trata-se das variáveis do tipo enum, necessárias para trabalhar com o registro em log e com vários métodos de fechamento de posições, métodos esses que omitimos no indicador - lembre que aqui apenas será imitada uma fixação simples do lucro, no nível definido (Take Profit). Após a declaração destas variáveis, pode-se conectar o módulo externo, listar configurações de usuário e declarar variáveis ​​globais:

enum ENUM_LOG_LEVEL {  // Lista de níveis de registro em log
  LOG_LEVEL_NONE,      // registro em log desativado
  LOG_LEVEL_ERR,       // apenas informações sobre erros
  LOG_LEVEL_INFO,      // erros + comentários do robô
  LOG_LEVEL_DEBUG      // tudo sem exceção
};
enum ENUM_ENTRY_SIGNAL {  // Lista de sinais para entrada
  ENTRY_BUY,              // sinal de compra
  ENTRY_SELL,             // sinal de venda
  ENTRY_NONE,             // sem sinal
  ENTRY_UNKNOWN,          // status indefinido
  ENTRY_INTERNAL_ERROR    // erro de função interno
};
enum ENUM_EXIT_SIGNAL {  // Lista de sinais de saída
  EXIT_UNKNOWN,          // indefinido
  EXIT_BUY,              // fechar compras
  EXIT_SELL,             // fechar vendas
  EXIT_ALL,              // fechar tudo
  EXIT_NONE              // não fechar nada
};
#include <Expert\Signal\Signal_Momentum_Pinball.mqh>     // módulo de sinal ТС \"Momentum Pinball\"
input uint    TS_MomPin_Take_Profit = 10;                // Momentum Pinball: Take Profit (em pontos)
input bool    Show_1st_H1_Bar = true;                    // Mostrar a faixa da primeira barra horário do dia?
input bool    Alert_Popup = true;                        // Alerta: Mostra uma janela pop-up?
input bool    Alert_Email = false;                       // Alerta: Enviar um e-mail?
input string  Alert_Email_Subj = "";                     // Alerta: Tema do e-mail—mensagem
input bool    Alert_Push = true;                         // Alerta: Enviar notificação por push?
input uint  Days_Limit = 7;                              // Profundidade do layout do histórico (dias do calendário)
ENUM_LOG_LEVEL  Log_Level = LOG_LEVEL_DEBUG;             // Modo de registro
double
  buff_1st_H1_Bar[], buff_1st_H1_Bar_Zero[],             // buffers para preenchimento da faixa da primeira barra horário do dia
  buff_Entry[], buff_Entry_Color[],                      // buffer da linha da ordem pendente
  buff_SL[],                                             // buffer da linha do StopLoss
  buff_TP[],                                             // buffer da linha do TakeProfit
  gd_Entry_Offset = 0,                                   // TS_MomPin_Entry_Offset nos preços do instrumento
  gd_Exit_Offset = 0                                     // TS_MomPin_Exit_Offset nos preços do instrumento
    ;

A função de inicialização não tem nada de extraordinário - nela, às matrizes declaradas - pelos nomes para os buffers do indicador - atribuímos os índices desses buffers. Além disso, aqui, nas configurações personalizadas, os pontos devem ser convertidos no preço do instrumento, a fim de, por um lado, reduzir o consumo de recursos e, por outro, não fazer essa conversão muitas vezes, durante o andamento do programa principal:

int OnInit() {
  // conversão de pontos no preço do instrumento:
  gd_Entry_Offset = TS_MomPin_Entry_Offset * _Point;
  gd_Exit_Offset = TS_MomPin_Exit_Offset * _Point;
  
  // atribuição dos buffers de indicador:
  
  // retângulo da faixa da primeira hora da barra do dia
  SetIndexBuffer(0, buff_1st_H1_Bar, INDICATOR_DATA);
    PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0);
  SetIndexBuffer(1, buff_1st_H1_Bar_Zero, INDICATOR_DATA);
    PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0);
  
  // linha de instalação da ordem pendente
  SetIndexBuffer(2, buff_Entry, INDICATOR_DATA);
    PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0);
  SetIndexBuffer(3, buff_Entry_Color, INDICATOR_COLOR_INDEX);
  
  // linha do SL
  SetIndexBuffer(4, buff_SL, INDICATOR_DATA);
    PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, 0);
  
  // linha do TP
  SetIndexBuffer(5, buff_TP, INDICATOR_DATA);
    PlotIndexSetDouble(3, PLOT_EMPTY_VALUE, 0);
  
  IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
  IndicatorSetString(INDICATOR_SHORTNAME, "Momentum Pinball");
  
  return(INIT_SUCCEEDED);
    }

No artigo anterior desta série, no código do indicador, foi criada certa entidade para armazenar informações de qualquer tipo entre ticks. Nesse texto, você pode ler em mais detalhes por que ela é necessária e como é construída, aqui, por sua vez, vamos empregá-la sem quaisquer alterações. Nesta versão do indicador, de toda a funcionalidade do "duende", apenas será usado o sinalizador do início de barra. No entanto, se o que se quer é tornar o indicador - para negociação automática - mais avançado, outras funções do "duende" serão de grande ajuda. O código completo da estrutura go_Brownie pode ser visto no final do arquivo do arquivo de origem do código do indicador (TS_Momentum_Pinball.mq5) no anexo a este artigo. Ali, pode-se ver o código da função de envio de alertas f_Do_Alert. Nela, não há nenhuma alteração em comparação com o indicador anterior desta série, por isso não é necessário considerá-lo em detalhes.

Dentro do manipulador de eventos (OnCalculate), antes do início do ciclo principal do programa, é necessário declarar as variáveis ​​necessárias. Se esta não for a primeira chamada do ciclo principal, será necessário limitar a faixa de recálculo nas barras atuais - para esta estratégia de negociação, são as barras de hoje e de ontem. Se esta for o primeira chamada do ciclo após a inicialização, será necessário limpar os buffers de dados residuais. Se isso não for feito, as áreas irrelevantes permanecerão sombreadas, após alternância do timeframe. Além disso, é necessário limitar a chamada da função principal a uma vez por barra. Tudo isso pode ser feito facilmente com a ajuda de estrutura go_Brownie (duende):

go_Brownie.f_Update(prev_calculated, prev_calculated);     // "alimentar" de informações o duende
datetime t_Time = TimeCurrent();                           // última hora conhecida do servidor
int
  i_Period_Bar = 0,                                        // contador auxiliar
  i_Current_TF_Bar = 0                                     // índice da barra do início do ciclo
;
if(go_Brownie.b_First_Run) {                               // se isto for a primeira inicialização
  i_Current_TF_Bar = rates_total — Bars(_Symbol, PERIOD_CURRENT, t_Time — t_Time % 8640086400 * Days_Limit, t_Time);
  // limpar o buffer após a reinicialização:
  ArrayInitialize(buff_1st_H1_Bar, 0); ArrayInitialize(buff_1st_H1_Bar_Zero, 0);
  ArrayInitialize(buff_Entry, 0); ArrayInitialize(buff_Entry_Color, 0);
  ArrayInitialize(buff_TP, 0);
  ArrayInitialize(buff_SL, 0);
} else if(!go_Brownie.b_Is_New_Bar) return(rates_total);   // esperamos o fechamento da barra
else {                                                     // nova barra
  // profundidade da conversão - desde o início do dia:
  i_Current_TF_Bar = rates_total — Bars(_Symbol, PERIOD_CURRENT, t_Time — t_Time % 86400, t_Time);
}
ENUM_ENTRY_SIGNAL e_Entry_Signal = ENTRY_UNKNOWN;          // sinal de entrada
double
  d_SL = WRONG_VALUE,                                      // nível do SL
  d_TP = WRONG_VALUE,                                      // nível do TP
  d_Entry_Level = WRONG_VALUE,                             // nível de entrada
  d_Range_High = WRONG_VALUE, d_Range_Low = WRONG_VALUE    // borda da faixa da primeira faixa do padrão
;
datetime
  t_Curr_D1_Bar = 0,                                       // hora da barra atual D1 (segunda barra do padrão)
  t_Last_D1_Bar = 0,                                       // hora da última barra D1, em que houve um sinal
  t_Entry_Bar = 0                                          // hora da barra de instalação da ordem pendente
;
// verificar que o índice da barra inicial de conversão inicial esteja dentro do limite aceitável:
    i_Current_TF_Bar = int(fmax(0, fmin(i_Current_TF_Bar, rates_total — 1)));

Agora, programamos ciclo de trabalho principal. No início de cada iteração, é necessário buscar dados no módulo de sinais, controlar o resultado de presença de erros e gerenciar a transição para a próxima iteração do ciclo, se não houver sinal:

while(++i_Current_TF_Bar < rates_total && !IsStopped()) {                // pesquisa detalhada do timeframe atual
  // buscar os dados do módulo de sinal:
  e_Entry_Signal = fe_Get_Entry_Signal(-Time[i_Current_TF_Bar], d_Entry_Level, d_SL, d_TP, d_Range_High, d_Range_Low);
  if(e_Entry_Signal == ENTRY_INTERNAL_ERROR) {                           // erro de cópia de dados a partir do buffer do indicador externo
    // tem de se repetirem os cálculos e a plotagem no próximo tick:
    go_Brownie.f_Reset();
    return(rates_total);
  }
    if(e_Entry_Signal > 1) continue;                                       // não existe sinal ativo nesta barra

Se o módulo identificar um sinal - na barra considerada - e retornar o nível de entrada, primeiro, calculamos o nível de fixação de lucro (Take Profit):

t_Curr_D1_Bar = Time[i_Current_TF_Bar] - Time[i_Current_TF_Bar] % 86400; // início do dia ao qual pertence esta barra

Em seguida, marcamos no histórico o trade em desenvolvimento, se ele for a primeira barra de um novo dia:

t_Curr_D1_Bar = Time[i_Current_TF_Bar] - Time[i_Current_TF_Bar] % 86400;            // início do dia ao qual pertence esta barra
if(t_Last_D1_Bar < t_Curr_D1_Bar) {                                                 // esta é a primeira barra do dia em que há um sinal
    t_Entry_Bar = Time[i_Current_TF_Bar];                                             // lembrar a hora de início da negociação

Começamos com o preenchimento de plano de fundo das barras da primeira hora do dia, utilizadas no níveis de cálculo:

// Preenchimento de fundo das barras de primeira hora:
if(Show_1st_H1_Bar) {
  i_Period_Bar = i_Current_TF_Bar;
  while(Time[--i_Period_Bar] >= t_Curr_D1_Bar && i_Period_Bar > 0)
    if(e_Entry_Signal == ENTRY_BUY) {                               // padrão de alta
      
      buff_1st_H1_Bar_Zero[i_Period_Bar] = d_Range_High;
      buff_1st_H1_Bar[i_Period_Bar] = d_Range_Low;
    } else {                                                        // padrão de baixa
      buff_1st_H1_Bar[i_Period_Bar] = d_Range_High;
      buff_1st_H1_Bar_Zero[i_Period_Bar] = d_Range_Low;
    }
    }

Em seguida, desenhamos a linha de posicionamento da ordem pendente, antes da ordem pendente se tornar uma posição aberta, isto é, antes do preço alcançar este nível:

// Linha de entrada antes de sua barra intersetada:
i_Period_Bar = i_Current_TF_Bar - 1;
if(e_Entry_Signal == ENTRY_BUY) {                               // padrão de alta
  while(++i_Period_Bar < rates_total) {
    if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) {            // final do dia
      e_Entry_Signal = ENTRY_NONE;                              // ordem pendente não foi ativada
      break;
    }
    
    // estender linha:
    buff_Entry[i_Period_Bar] = d_Entry_Level;
    buff_Entry_Color[i_Period_Bar] = 0;
    
    if(d_Entry_Level <= High[i_Period_Bar]) break;               // a entrada foi nesta barra
  }
} else {                                                         // padrão de baixa
  while(++i_Period_Bar < rates_total) {
    if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) {             // final do dia
      e_Entry_Signal = ENTRY_NONE;                               // ordem pendente não foi ativada
      break;
    }
    
    // estender linha:
    buff_Entry[i_Period_Bar] = d_Entry_Level;
    buff_Entry_Color[i_Period_Bar] = 1;
    
    if(d_Entry_Level >= Low[i_Period_Bar]) break;               // a entrada foi nesta barra
  }
    }

Se, antes do final do dia, o preço não atingir o nível calculado, passaremos para a próxima etapa do ciclo principal:

if(e_Entry_Signal == ENTRY_NONE) {                 // ordem pendente não foi ativada até o final do dia
  i_Current_TF_Bar = i_Period_Bar;                 // já não estamos interessados nas barras deste dia
  continue;
    }

Se esse dia ainda não tiver finalizado e o destino da ordem pendente não estiver definido, não há nenhuma razão para continuar o ciclo principal do programa:

if(i_Period_Bar >= rates_total - 1) break;        // dia (inconcluído) atual trabalhado

Após estes dois filtros, resta apenas um possível cenário - a ativação da ordem pendente. Encontramos a barra de execução da ordem pendente e, desta barra, plotamos os níveis de Take Profit e Stop Loss, antes de o preço cruzar um deles, ou seja, antes do fechamento da posição. Além disso, é necessário ter em mente que o fechamento da posição poderá ocorrer na mesma barra, neste caso, será necessário prolongar a linha uma barra no passado, para torná-la visível no gráfico:

// a ordem foi ativada, encontrar a barra de fechamento da posição:
i_Period_Bar = fmin(i_Period_Bar, rates_total - 1);
buff_SL[i_Period_Bar] = d_SL;
while(++i_Period_Bar < rates_total) {
  if(TS_MomPin_Exit_Mode == CLOSE_ON_SL_TRAIL) {
    if(Time[i_Period_Bar] >= t_Curr_D1_Bar + 86400) break;        // esta é a barra do dia seguinte 
    
    // Linha do TP e SL antes da barra que cruzou uma delas:
    buff_SL[i_Period_Bar] = d_SL;
    buff_TP[i_Period_Bar] = d_TP;
    
    if((
      e_Entry_Signal == ENTRY_BUY && d_SL >= Low[i_Period_Bar]
      ) || (
      e_Entry_Signal == ENTRY_SELL && d_SL <= High[i_Period_Bar]
    )) {                                                          // saía com o Stop-Loss
      if(buff_SL[int(fmax(0, i_Period_Bar - 1))] == 0.) {
   // estendemos - 1 barra no passado - o início e final na mesma barra
        buff_SL[int(fmax(0, i_Period_Bar - 1))] = d_SL;
        buff_TP[int(fmax(0, i_Period_Bar - 1))] = d_TP;
      }
      break;
    }
    
    if((
      e_Entry_Signal == ENTRY_BUY && d_TP <= High[i_Period_Bar]
      ) || (
      e_Entry_Signal == ENTRY_SELL && d_SL >= Low[i_Period_Bar]
    )) {                                                         // saída com o Take-Profit
      if(buff_TP[int(fmax(0, i_Period_Bar - 1))] == 0.) {
        // estendemos - 1 barra no passado - o início e final na mesma barra
        buff_SL[int(fmax(0, i_Period_Bar - 1))] = d_SL;
        buff_TP[int(fmax(0, i_Period_Bar - 1))] = d_TP;
      }
      break;
    }
  }
    }

Após o fechamento da posição, as barras restantes do dia podem ser ignoradas, no ciclo principal do programa:

i_Period_Bar = i_Current_TF_Bar;
t_Curr_D1_Bar = Time[i_Period_Bar] - Time[i_Period_Bar] % 86400;
while(
  ++i_Period_Bar < rates_total
  &&
  t_Curr_D1_Bar == Time[i_Period_Bar] - Time[i_Period_Bar] % 86400
    ) i_Current_TF_Bar = i_Period_Bar;

Assim conclui o código do ciclo principal. Resta apenas organizar o alerta, se o sinal for detectado na barra atual:

i_Period_Bar = rates_total - 1;                                            // barra atual
if(Alert_Popup + Alert_Email + Alert_Push == 0) return(rates_total);       // tudo desabilitado
if(t_Entry_Bar != Time[i_Period_Bar]) return(rates_total);                 // nesta barra não há sinal
// mensagem de texto:
string s_Message = StringFormat("Estratégia de negociação Momentum Pinball: precisa de %s @ %s, Stop-Loss: %s",
  e_Entry_Signal == ENTRY_BUY ? "BuyStop" : "SellStop",
  DoubleToString(d_Entry_Level, _Digits),
  DoubleToString(d_SL, _Digits)
);
// alerta:
    f_Do_Alert(s_Message, Alert_Popup, false, Alert_Email, Alert_Push, Alert_Email_Subj);

Todo o código do indicador pode ser visto no arquivo TS_Momentum_Pinball.mq5, no anexo a este artigo.

Advisor para testar a estratégia de negociação "Momentum Pinball"

A funcionalidade do expert básico deve ser estendido ligeiramente, na preparação do teste da seguinte estratégia de negociação, a partir do livro de Raschke e Connors. O código-fonte desta versão, juntamente com uma descrição detalhada, pode ser encontrado no artigo anterior. Para evitar a repetição, aqui consideraremos apenas as alterações e complementos mais relevantes - são apenas dois.

O primeiro complemento é a lista de sinais para saída. Ele esteve ausente não na versão anterior do robô de negociação. Além disso, foi adicionado o estado ENTRY_INTERNAL_ERROR à lista de sinais para entrada. Estas listas numeradas não diferem das listas enum, no indicador discutido acima. No código do robô, colocamo-las antes da linha de conexão da classe das operações de negociação da biblioteca padrão. No arquivo Street_Smarts_Bot_MomPin.mq5, as linhas 24..32 são os anexos ao artigo.

A segunda alteração é devida ao facto de o módulo de sinal emitir sinais para o fechamento da posição. Adicionamos o bloco do código correspondente para trabalhar com este sinal. Na versão anterior do robô, está o operador condicional if para verificar se a posição existente é nova (linha 139) - a verificação é usada para calcular e definir o nível inicial de StopLoss. Nesta versão, ao operador if adicionamos, por meio do else, o bloco correspondente do código, para acessar o módulo de sinal. Se o resultado exigir essa consulta, o expert advisor deve fechar a posição:

} else {                       // não há posição nova
                               // surgiram as condições para fechamento da posição?
  ENUM_EXIT_SIGNAL e_Exit_Signal = fe_Get_Exit_Signal(d_Entry_Level, datetime(PositionGetInteger(POSITION_TIME)), e_Entry_Signal, TimeCurrent(), TS_MomPin_Exit_Mode);
  if((
      e_Exit_Signal == EXIT_BUY && e_Entry_Signal == ENTRY_BUY
    ) || (
      e_Exit_Signal == EXIT_SELL && e_Entry_Signal == ENTRY_SELL
    ) || e_Exit_Signal == EXIT_ALL
  ) {
                              // é necessário fechar
    CTrade o_Trade;
    o_Trade.LogLevel(LOG_LEVEL_ERRORS);
    o_Trade.PositionClose(_Symbol);
    return;
  }
    }

No código-fonte do robô, são as linhas 171..186.

Há algumas alterações no código da função que controla a distância aos níveis de negociação fb_Is_Acceptable_Distance (linhas 424..434).


Teste de estratégia com base no histórico de dados

Criamos um par de instrumentos (indicador e expert advisor) para estudo de um sistema de negociação famoso graças ao livro de Linda Raschke e Laurence Connors. O principal objetivo da execução do EA, nos dados históricos, consiste na verificação da eficiência do robô de negociação, num destes instrumentos. Por isso, não fiz a otimização de parâmetros, e realizei o teste com as configurações padrão.

Os resultados completos de todas as execuções podem ser encontrados no arquivo anexado; aqui apresento só os gráficos de variação no saldo. Apenas como ilustração, o segundo objetivo mais importante do teste consiste na avaliação bruta (sem otimização de parâmetros) da integridade da estratégia de negociação, nas condições do mercado atual. Lembro-lhe que os autores ilustraram a estratégia usando gráficos do fim do século passado.

Gráfico de variação no saldo, durante o teste do EA, desde o início de 2014, nas cotações do servidor demo da MetaQuotes. Instrumento - EURJPY, timeframe - H1:


Um gráfico semelhante para o instrumento EURUSD, com os mesmos timeframe e período de teste:


Ao testar, sem alterar as configurações, nas cotações de um dos metais (XAUUSD) para o mesmo período e no mesmo timeframe, o gráfico é o seguinte:


Fim do artigo

As regras listadas no livro Street Smarts: High Probability Short-Term Trading Strategies para a estratégia de negociação "Momentum Pinball" foram transferidas para o código do indicador e do expert advisor. Infelizmente, a descrição não é tão detalhada como deveria ser, e deixa mais de uma variação nas regras de acompanhamento e de fechamento de posições. Portanto, os interessados em estudar em detalhes as características do sistema de negociação têm um campo muito amplo para a seleção de parâmetros e algoritmos. O código gerado permite esta possibilidade; espero, o código-fonte será útil no aproveitamento da programação orientada a objetos.

No MQL5.zip, os códigos-fonte, arquivos compilados e biblioteca se encontram nos seus diretórios correspondentes. O objetivo de cada um deles:

# Nome do arquivo Tipo  Descrição 
 1  LBR_RSI.mq5  indicador Indicador contendo o ROC e RSI. É usado para determinar a direção da negociação (ou sua restrição) do novo dia
 2  TS_Momentum_Pinball.mq5  indicador Indicador para negociação manual de acordo com esta estratégia de negociação. Mostra os níveis calculados de entradas e saídas, realça a faixa da primeira hora, com base na qual são feitos os cálculos
 3   Signal_Momentum_Pinball.mqh  biblioteca  Biblioteca de funções, estruturas e configurações personalizadas. Utiliza-se pelo indicador e expert advisor
 4  Street_Smarts_Bot_MomPin.mq5   expert advisor Expert advisor para negociação automatizada de acordo com esta estratégia de negociação