English Русский 中文 Español Deutsch 日本語
Sistema de negociação 'Turtle Soup' e sua modificação 'Turtle Soup Plus One'

Sistema de negociação 'Turtle Soup' e sua modificação 'Turtle Soup Plus One'

MetaTrader 5Exemplos | 7 novembro 2016, 10:51
2 213 0
Alexander Puzanov
Alexander Puzanov

  1. Introdução
  2. Sistema de negociação 'Turtle Soup' e sua modificação 'Turtle Soup Plus One'
  3. Definição de parâmetros do canal
  4. Função de geração de sinais
  5. Expert Advisor básico para verificação da Estratégia de Negociação
  6. Teste de estratégia com base no histórico de dados
  7. Conclusão

Introdução

Os autores do livro "Street Smarts: High Probability Short-Term Trading Strategies" Laurence Connors e Linda Raschke são um par de traders bem-sucedidos com um total de 34 anos de experiência no mudo do trading. Sua rica experiência inclui negociação em bolsas de valores, trabalho em bancos e fundos de hedge, corretoras e empresas de consultoria. Na sua opinião, para uma negociação consistentemente rentável, é suficiente ter apenas uma estratégia de negociação no ativo. No obstante, o livro contém quase duas dúzias de opções de Estratégias de Negociação divididas em quatro grupos. Cada grupo se refere a uma determinada fase dos ciclos de mercado e explora um dos padrões estáveis de comportamento dos preços.

As estratégias descritas no livro receberam uma ampla acolhida, no entanto é importante entender que os autores conceberam suas ideias com base no comportamento do mercado de há 15-20 anos. Portanto, este artigo tem dois objetivos, isto é, vamos começar com a implementação da Estratégia de Negociação em linguagem MQL5 descrita por Linda Raschke e Laurence Connors e, em seguida, vamos tentar avaliar sua eficácia usando o testador de estratégias MT5. Além disso, vamos empregar os dados históricos dos últimos anos disponíveis através do servidor demo MetaQuotes.

Ao escrever código, vou me concentrar no usuário MQL5 com um conhecimento básico da linguagem, ou seja, um iniciante ligeiramente avançado. Por tanto, não haverá explicações sobre o trabalho com funções padrão, a fundamentação da seleção de tipos de variáveis e todo o que exija apreender de zero sobre programação de Experts. Por outro lado, não vou me focar em desenvolvedores experientes de robôs, uma vez que eles possuem bibliotecas que contêm suas próprias soluções particulares e que, por sua vez, não vão abandoná-las durante a implementação da nova Estratégia de Negociação.

Para a maioria dos programadores interessados neste artigo, dominar a programação orientada a objetos é um desafio constante. É por isso que vou tentar fazer com que o trabalho sobre este Expert Advisor seja útil para assumir esse desafio da melhor maneira possível. Para fazer mais simples a transição de uma abordagem processual para uma programação orientada a objetos, não vamos usar o tópico mais difícil em POO, isto é, as classes. Em vez disso, vamos utilizar seu equivalente mais simples, isto é, as estruturas. Além combinar dados logicamente relacionados de diferentes tipos e funções para trabalhar com eles, as estruturas possuem quase todas as caraterísticas inerentes das classes, incluindo a herança. No entanto, para seu uso, não é necessário conhecer as regras de registro do código de classes, basta complementar a programação procedural com a qual você já está familiarizado.


Sistema de negociação 'Turtle Soup' e sua modificação 'Turtle Soup Plus One'


A Estratégia de Negociação "Sopa de tartaruga" (Turtle Soup) abre um conjunto de estratégias da série "Tests". Para tornar isto mais claro, de acordo com a caraterística que foi escolhida esta série, valia a pena intitulá-la "Teste com o preço dos limites de intervalos ou níveis de suporte/resistência". A Turtle Soup é baseada no pressuposto de que o preço não conseguirá atravessar o intervalo de 20 dias sem quicar a partir do limite deste intervalo. Partindo do recuo temporário a partir do limite - talvez devida a uma fuga falsa - temos que tentar extrair lucro. A posição de negociação sempre será dirigida para dentro do intervalo, isto dá razão para incluir a Estratégia de Negociação na categoria de "resguardo".

A propósito, a semelhança do nome "Turtle Soup" com a famosa estratégia "Turtles" não é acidental, uma vez que ela radica em que ambas acompanham o comportamento do preço nos limites do intervalo de 20 dias. Segundo os autores, eles tentaram durante um tempo utilizar um par de estratégias de fuga, incluindo "Turtles", porém a abundância de falsas fugas e profundos recuos tornou essa negociação ineficaz. No entanto, graças a isso, os padrões revelados têm ajudado a criar um conjunto de regras para lucrar a partir do movimento do preço na direção oposta respeito à fuga.

O conjunto completo de regras da Estratégia de Negociação "Turtle Soup" para entrada no mercado pode ser formulado da seguinte maneira:

  1. Certifique-se de que, desde o anterior mínimo de 20 dias, passaram não menos de 3 dias
  2. Aguarde até que o preço do instrumento caia abaixo do mínimo de 20 dias
  3. Coloque uma ordem pendente para compra 5-10 pontos acima do preço mínimo que acabou de ser atravessado de cima para baixo
  4. Imediatamente após a ativação da ordem pendente, coloque seu StopLoss 1 ponto abaixo do mínimo do dia
  5. Use Trailing Stop quando a posição se torne rentável
  6. Se a posição foi fechada por Stop no primeiro ou segundo dia, é permitida a re-entrada no nível inicial 

As regras para entrar numa transação para vender são semelhantes e, como você sabe, você deve aplicá-las no limite superior do intervalo, ou seja, no máximo de preços de 20 dias.

Na biblioteca de códigos fonte existe um indicador que, sob determinadas configurações, exibe os limites do canal em cada barra do histórico. Ele pode ser utilizado para exibição durante a negociação manual.

 

Na descrição da Estratégia de Negociação, você não encontrará uma resposta direta que satisfaça a pergunta de como se deve manter uma ordem pendente, é por isso que vamos nos basear ainda na lógica simples, isto é, durante o teste de limite de intervalo, o preço cria novo extremo que no dia seguinte vai tornar impossível a primeira das condições descritas acima. Como, neste dia, não há sinal, nós devemos cancelar a ordem pendente do dia anterior.

A modificação desta Estratégia de Negociação, chamada de 'Turtle Soup Plus One', tem apenas 2 diferenças:

  1. Em vez de colocar uma ordem pendente, mediatamente após a ruptura do intervalo de 20 dias,é necessário esperar a confirmação do sinal, isto é, o fechamento da barra deste dia fora dos limites do intervalo. Vamos ficar bastante satisfeitos, se o dia fecha exatamente no limite do canal horizontal examinado.
  2. Para determinar o nível do StopLoss inicial, usa-se o respetivo extremo de dois dias (máximo ou mínimo).

  

Definição de parâmetros do canal


Para verificar a conformidade com as condições, precisamos saber os máximos e mínimos do preço do intervalo, para o seu cálculo, por sua vez, é necessário determinar os limites de tempo. Como essas quatro variáveis determinam o canal em qualquer momento dado, é lógico que combiná-las numa estrutura comum. Adicionamos-lhe mais duas variáveis envolvidas na Estratégia de Negociação — número de dias (barras) após o preço atingir o máximo e mínimo do intervalo:

struct CHANNEL {
  double    d_High;           // preço do limite superior do intervalo
  double    d_Low;            // preço do limite inferior do intervalo
  datetime  t_From;           // data/tempo da primeira (a mais velha) barra do canal
  datetime  t_To;             // data/tempo da última barra do canal
  int       i_Highest_Offset; // número de barras à direita do máximo do preço
  int       i_Lowest_Offset;  // número de barras à direita do mínimo do preço
};

Todas essas variáveis​irão atualizar imediatamente a função f_Set. Para isso é necessário especificar qual é a primeira barra a partir da qual é preciso construir o canal virtual (i_Newest_Bar_Shift) e com qual profundidade vamos ver o histórico (i_Bars_Limit):

void f_Set(int i_Bars_Limit, int i_Newest_Bar_Shift = 1) {
  double da_Price_Array[]; // matriz adicional para preços High/Low de todas as barras do canal
  
  // definição do limite superior do intervalo::
  
  int i_Price_Bars = CopyHigh(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array);
  int i_Bar = ArrayMaximum(da_Price_Array);
  d_High = da_Price_Array[i_Bar]; // o limite superior do intervalo está definido
  i_Highest_Offset = i_Price_Bars - i_Bar; // crescimento (em barras) do máximo
  
  // definição do limite inferior do intervalo::
  
  i_Price_Bars = CopyLow(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array);
  i_Bar = ArrayMinimum(da_Price_Array);
  d_Low = da_Price_Array[i_Bar]; // o limite inferior do intervalo está definido
  i_Lowest_Offset = i_Price_Bars - i_Bar; // crescimento (em barras) do mínimo
  
  datetime ta_Time_Array[];
  i_Price_Bars = CopyTime(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, ta_Time_Array);
  t_From = ta_Time_Array[0];
  t_To = ta_Time_Array[i_Price_Bars - 1];
}

Nessa função, existem apenas 13 cadeias de caracteres, mas, se você leu atentamente o guia sobre funções MQL para extração de dados a partir das TimeSeries (CopyHigh, CopyLow, CopyTime...), então você sabe que com eles não é tão fácil lidar. Em alguns casos, as funções não retornam o mesmo número de valores em relação ao que você definiu, porque os dados solicitados podem não estar prontos no primeiro acesso à TimeSeries desejada. No entanto, ao processar corretamente os resultados, a cópia de dados a partir da TimeSeries funciona como pretendido.

É por isso que vamos cumprir pelo menos os critérios mínimos de programação de qualidade e adicionar manipuladores de erro simples ao código. Para que seja mais fácil lidar com eles, organizamos a impressão para registrar informações de erro. O registro em log é um muito útil durante a depuração, pois permite que você tenha informações detalhadas sobre a base na qual o robô tomou uma decisão particular. Por isso, introduzimos uma nova variável do tipo enumerado, ela vai determinar o quão detalhado o registro deve ser:

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

O nível desejado irá ser selecionado pelo usuário; nós colocamos, em muitas funções, os correspondentes operadores de saída de informações no log. Por conseguinte, a lista, e a variável personalizada Log_Level não devem ser colocadas no bloco de sinal, mas sim no início do programa principal.

No entanto, voltemos à função f_Set — com todas as verificações, ela adquirirá a seguinte aparência (as cadeia de caracteres adicionadas estão destacadas):

void f_Set(int i_Bars_Limit, int i_Newest_Bar_Shift = 1) {
  double da_Price_Array[]; // matriz adicional para preços High/Low de todas as barras do canal
  
  // definição do limite superior do intervalo::
  
  int i_Price_Bars = CopyHigh(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array);
  
  if(i_Price_Bars == WRONG_VALUE) {
    // tratamento de erro da função CopyHigh
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyHigh: erro #%u", __FUNCSIG__, _LastError);
    return;
  }
  
  if(i_Price_Bars < i_Bars_Limit) {
    // a função CopyRates extraiu dados parcialmente
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyHigh: copiado %u barras de %u", __FUNCSIG__, i_Price_Bars, i_Bars_Limit);
    return;
  }
  
  int i_Bar = ArrayMaximum(da_Price_Array);
  if(i_Bar == WRONG_VALUE) {
    // tratamento de erro da função ArrayMaximum
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: ArrayMaximum: erro #%u", __FUNCSIG__, _LastError);
    return;
  }
  
  d_High = da_Price_Array[i_Bar]; // o limite superior do intervalo está definido
  i_Highest_Offset = i_Price_Bars - i_Bar; // crescimento (em barras) do máximo
  
  // definição do limite inferior do intervalo::
  
  i_Price_Bars = CopyLow(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array);
  
  if(i_Price_Bars == WRONG_VALUE) {
    // tratamento de erro da função CopyLow
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyLow: erro #%u", __FUNCSIG__, _LastError);
    return;
  }
  
  if(i_Price_Bars < i_Bars_Limit) {
    // a função CopyLow extraiu dados parcialmente
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyLow: copiado %u barras de %u", __FUNCSIG__, i_Price_Bars, i_Bars_Limit);
    return;
  }
  
  i_Bar = ArrayMinimum(da_Price_Array);
  if(i_Bar == WRONG_VALUE) {
    // tratamento de erro da função ArrayMinimum
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: ArrayMinimum: erro #%u", __FUNCSIG__, _LastError);
    return;
  }
  d_Low = da_Price_Array[i_Bar]; // o limite inferior do intervalo está definido
  i_Lowest_Offset = i_Price_Bars - i_Bar; // crescimento (em barras) do mínimo
  
  datetime ta_Time_Array[];
  i_Price_Bars = CopyTime(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, ta_Time_Array);
  if(i_Price_Bars < 1) t_From = t_To = 0;
  else {
    t_From = ta_Time_Array[0];
    t_To = ta_Time_Array[i_Price_Bars - 1];
  }
}

Se for detectado um erro, simplesmente interrompemos a execução com a expectativa de que, no seguinte tick, o terminal carregue suficientes dados históricos para poder trabalhar corretamente com as funções de cópia. Para que outras funções personalizadas não utilizem o canal antes de concluir o procedimento, adicionamos à estrutura o correspondente sinalizador b_Ready (true = dados de preparados, false = processo não concluído). Ao mesmo tempo, adicionamos o sinalizador de alteração do parâmetros do canal (b_Updated). Para um desempenho ideal, é útil saber se os 4 parâmetros envolvidos na Estratégia de Negociação não foram alterados. Para fazer isso, vamos ter de introduzir outra variável, isto é, a assinatura do canal (s_Signature), um tipo de molde de parâmetros. Também colocamos na estrutura a função f_Set, assim a estrutura CHANNEL tomará a aparência final: 

// as informações sobre o canal e funções para sua compilação e atualização se encontra na estrutura
struct CHANNEL {
  // variáveis
  double    d_High;           // preço do limite superior do intervalo
  double    d_Low;            // preço do limite inferior do intervalo
  datetime  t_From;           // data/tempo da primeira (a mais velha) barra do canal
  datetime  t_To;             // data/tempo da última barra do canal
  int       i_Highest_Offset; // número de barras à direita do máximo do preço
  int       i_Lowest_Offset;  // número de barras à direita do mínimo do preço
  bool      b_Ready;          // já concluiu o procedimento de atualização de parâmetros?
  bool      b_Updated;        // os parâmetros do canal foram alterados?
  string    s_Signature;      // assinatura do último conjunto de dados conhecido
  
  // функции:
  
  CHANNEL() {
    d_High = d_Low = 0;
    t_From = t_To = 0;
    b_Ready = b_Updated = false;
    s_Signature = "-";
    i_Highest_Offset = i_Lowest_Offset = WRONG_VALUE;
  }
  
  void f_Set(int i_Bars_Limit, int i_Newest_Bar_Shift = 1) {
    b_Ready = false; // PitStop: colocamos o sinalizador de manutenção
    
    double da_Price_Array[]; // matriz adicional para preços High/Low de todas as barras do canal
    
    // definição do limite superior do intervalo::
    
    int i_Price_Bars = CopyHigh(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array);
    if(i_Price_Bars == WRONG_VALUE) {
      // tratamento de erro da função CopyHigh
      if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyHigh: erro #%u", __FUNCSIG__, _LastError);
      return;
    }
    
    if(i_Price_Bars < i_Bars_Limit) {
      // a função CopyRates extraiu dados parcialmente
      if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyHigh: copiado %u barras de %u", __FUNCSIG__, i_Price_Bars, i_Bars_Limit);
      return;
    }
    
    int i_Bar = ArrayMaximum(da_Price_Array);
    if(i_Bar == WRONG_VALUE) {
      // tratamento de erro da função ArrayMaximum
      if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: ArrayMaximum: erro #%u", __FUNCSIG__, _LastError);
      return;
    }
    
    d_High = da_Price_Array[i_Bar]; // o limite superior do intervalo está definido
    i_Highest_Offset = i_Price_Bars - i_Bar; // crescimento (em barras) do máximo
    
    // definição do limite inferior do intervalo::
    
    i_Price_Bars = CopyLow(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, da_Price_Array);
    
    if(i_Price_Bars == WRONG_VALUE) {
      // tratamento de erro da função CopyLow
      if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyLow: erro #%u", __FUNCSIG__, _LastError);
      return;
    }
    
    if(i_Price_Bars < i_Bars_Limit) {
      // a função CopyLow extraiu dados parcialmente
      if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyLow: copiado %u barras de %u", __FUNCSIG__, i_Price_Bars, i_Bars_Limit);
      return;
    }
    
    i_Bar = ArrayMinimum(da_Price_Array);
    if(i_Bar == WRONG_VALUE) {
      // tratamento de erro da função ArrayMinimum
      if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: ArrayMinimum: erro #%u", __FUNCSIG__, _LastError);
      return;
    }
    d_Low = da_Price_Array[i_Bar]; // o limite inferior do intervalo está definido
    i_Lowest_Offset = i_Price_Bars - i_Bar; // crescimento (em barras) do mínimo
    
    datetime ta_Time_Array[];
    i_Price_Bars = CopyTime(_Symbol, PERIOD_CURRENT, i_Newest_Bar_Shift, i_Bars_Limit, ta_Time_Array);
    if(i_Price_Bars < 1) t_From = t_To = 0;
    else {
      t_From = ta_Time_Array[0];
      t_To = ta_Time_Array[i_Price_Bars - 1];
    }
    
    string s_New_Signature = StringFormat("%.5f%.5f%u%u", d_Low, d_High, t_From, t_To);
    if(s_Signature != s_New_Signature) {
      // os dados do canal foram alterados
      b_Updated = true;
      if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: canal atualizado: %s .. %s / %s .. %s, мин: %u макс: %u ", __FUNCTION__, DoubleToString(d_Low, _Digits), DoubleToString(d_High, _Digits), TimeToString(t_From, TIME_DATE|TIME_MINUTES), TimeToString(t_To, TIME_DATE|TIME_MINUTES), i_Lowest_Offset, i_Highest_Offset);
      s_Signature = s_New_Signature;
    }
    
    b_Ready = true; // atualização de dados concluída com sucesso 
  }
};
Imediatamente declaramos globalmente um objeto-canal deste tipo (de modo que esteja disponível a partir de múltiplas funções definidas pelo usuário):

CHANNEL go_Channel;

Função de geração de sinais


O sinal para compra deste sistema está definido de acordo com dois condições:

1. Após o anterior mínimo de 20 dias anterior, passou pelo menos três dias de negociação

2a. O preço do instrumento caiu abaixo do mínimo de 20 dias (Turtle Soup)

2б. A barra diária fechou abaixo do mínimo de 20 dias (Turtle Soup Plus One)



 Todas as outras regras da Estratégia de Negociação enumeradas acima estão relacionadas com os parâmetros da ordem de negociação e acompanhamento da posição, não vamos inclui-las no bloco de sinal.

No módulo, nós programamos a exibição de sinais de acordo com as regras de ambas as modificações das Estratégias de Negociação (Turtle Soup e Turtle Soup Plus One), enquanto, nas configurações, do Expert Advisor adicionamos a possibilidade de selecionar a versão de regras desejada. A correspondente variável personalizada vai se chamar Strategy_Type. Na lista de estratégias, por agora vamos ter apenas duas opções, de modo que é mais fácil de gerir usando true/false (variável de tipo bool). Porém deixamos à nossa escolha a possibilidade, após finalizar este ciclo de artigos, minimizar - num Expert Advisor - todas as estratégias convertidas para o código, a partir do livro, por isso vamos usar uma lista numerada:

enum ENUM_STRATEGY {     // Lista de estratégias
  TS_TURTLE_SOUP,        // Turtle Soup
  TS_TURTLE_SOUP_PLUS_1  // Turtle Soup Plus One
};
input ENUM_STRATEGY  Strategy_Type = TS_TURTLE_SOUP;  // Estratégia de Negociação:

A partir do programa principal, é necessário transferir a função de detecção do sinal, ou seja, deixar saber se é preciso esperar o fechamento da barra (do dia), isto é, variável b_Wait_For_Bar_Close do tipo bool. A segunda variável necessária é a duração da pausa após o extremo anterior i_Extremum_Bars. A função deve retornar o status do sinal, quer dizer, deve dizer se as condições existentes são adequadas para compra/venda ou se vale a pena esperar por agora. A correspondente lista numerada também será colocada no arquivo principal do Expert Advisor:

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           // estado não definido
};

A estrutura padrão do tipo MqlTick será usada pelo módulo de sinal e as funções do programa principal, isto é, o objeto global go-Tick que contém informações sobre o último tick. Essa estrutura será, por um lado, declarada no arquivo principal, por outro, programada - quanto à sua atualização - no corpo do programa principal (na função OnTick).

MqlTick go_Tick; // informações sobre o último tick conhecido

Agora, finalmente, podemos tratar das funções principais do módulo

ENUM_ENTRY_SIGNAL fe_Get_Entry_Signal(
  bool b_Wait_For_Bar_Close = false,
  int i_Extremum_Bars = 3
) {}

Vamos começar com a verificação das condições para o sinal de venda, isto é, se há um número suficiente de dias (barras) desde o máximo anterior (primeira condição) e se o preço do limite superior do intervalo atravessou (segunda):

if(go_Channel.i_Highest_Offset > i_Extremum_Bars) // primeira condição
  if(go_Channel.d_High < d_Actual_Price) // segunda condição não está satisfeita
    return(ENTRY_SELL); // ambas as condições de entrada para venda estão satisfeitas

A verificação das condições para o sinal compra é realizada de modo semelhante:

if(go_Channel.i_Lowest_Offset > i_Extremum_Bars) // primeira condição
  if(go_Channel.d_Low > d_Actual_Price) { // segunda condição não está satisfeita
    return(ENTRY_BUY); // ambas as condições de entrada para compra são satisfeitas

Aqui, usa-se a variável d_Actual_Price, que vai conter o preço atual para essa versão da Estratégia de Negociação. Para a Turtle Soup, ela é o último prelo conhecido bid, enquanto para a Turtle Soup Plus One é o preço de fechamento do dia anterior (barra):

double d_Actual_Price = go_Tick.bid; // preço por padrão para a versão Turtle Soup
if(b_Wait_For_Bar_Close) { // para a versão Turtle Soup Plus One
  double da_Price_Array[1]; // matriz auxiliar
  CopyClose(_Symbol, PERIOD_CURRENT, 1, 1, da_Price_Array));
  d_Actual_Price = da_Price_Array[0];
}

A função, incluindo o mínimo exigido, fica assim:

ENUM_ENTRY_SIGNAL fe_Get_Entry_Signal(bool b_Wait_For_Bar_Close = false, int i_Extremum_Bars = 3) {
  double d_Actual_Price = go_Tick.bid; // preço por padrão para a versão Turtle Soup
  if(b_Wait_For_Bar_Close) { // para a versão Turtle Soup Plus One
    double da_Price_Array[1];
    CopyClose(_Symbol, PERIOD_CURRENT, 1, 1, da_Price_Array));
    d_Actual_Price = da_Price_Array[0];
  }
  
  // limite superior:
  if(go_Channel.i_Highest_Offset > i_Extremum_Bars) // primeira condição
    if(go_Channel.d_High < d_Actual_Price) { // segunda condição não está satisfeita
      // o preço atravessou o limite superior
      return(ENTRY_SELL);
    }
  
  // limite inferior
  if(go_Channel.i_Lowest_Offset > i_Extremum_Bars) // primeira condição
    if(go_Channel.d_Low > d_Actual_Price) { // segunda condição não está satisfeita
      // o preço atravessou o limite inferior
      return(ENTRY_BUY);
    }
  
  return(ENTRY_NONE);
}

Agora lembre-se que o objeto-canal pode não estar preparado para ler dados a partir dele (sinalizador go_Channel.b_Ready = false). Isso significa que temos que adicionar a verificação deste sinalizador. Nessa função, também utilizamos uma das funções padrão de cópia de dados a partir da TimeSeries (CopyClose), daí que cuidamos da manipulação do possível erro. E não se esqueça da depuração de registro em log de dados importantes:

ENUM_ENTRY_SIGNAL fe_Get_Entry_Signal(bool b_Wait_For_Bar_Close = false, int i_Extremum_Bars = 3) {
  if(!go_Channel.b_Ready) {
    // os dados do canal não estão preparados para serem usados
    if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: os parâmtros do canal não estão preparados", __FUNCTION__);
    return(ENTRY_UNKNOWN);
  }
  
  double d_Actual_Price = go_Tick.bid; // preço por padrão para a versão Turtle Soup
  if(b_Wait_For_Bar_Close) { // para a versão Turtle Soup Plus One
    double da_Price_Array[1];
    if(WRONG_VALUE == CopyClose(_Symbol, PERIOD_CURRENT, 1, 1, da_Price_Array)) {
      // tratamento de erro da função CopyClose
      if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyClose: erro #%u", __FUNCSIG__, _LastError);
      return(ENTRY_NONE);
    }
    d_Actual_Price = da_Price_Array[0];
  }
  
  // limite superior:
  if(go_Channel.i_Highest_Offset > i_Extremum_Bars) // primeira condição
    if(go_Channel.d_High < d_Actual_Price) { // segunda condição não está satisfeita
      // o preço atravessou o limite superior
      if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: preço (%s) atravessou o limite superior (%s)", __FUNCTION__, DoubleToString(d_Actual_Price, _Digits), DoubleToString(go_Channel.d_High, _Digits));
      return(ENTRY_SELL);
    }
  
  // limite inferior
  if(go_Channel.i_Lowest_Offset > i_Extremum_Bars) // primeira condição
    if(go_Channel.d_Low > d_Actual_Price) { // segunda condição não está satisfeita
      // o preço atravessou o limite inferior
      if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: preço (%s) atravessou o limite inferior (%s)", __FUNCTION__, DoubleToString(d_Actual_Price, _Digits), DoubleToString(go_Channel.d_Low, _Digits));
      return(ENTRY_BUY);
    }
  
  // se o programa chegou até esta linha, significa que o preço está dentro do intervalo e a segunda condição não está satisfeita
  
  return(ENTRY_NONE);
}

Esta função será chamada em cada tick, centenas de milhares de vezes por dia. No entanto se a primeira condição (não menos de três dias desde o último extremo) não for satisfeita, todo o trabalho após a primeira verificação se torna sem sentido. As regras do bom estilo de programação exigem minimizar o consumo de recursos, por isso ensinamos a função a hibernar até a próxima barra (do dia), ou seja, antes da atualização dos parâmetros do canal:

ENUM_ENTRY_SIGNAL fe_Get_Entry_Signal(bool b_Wait_For_Bar_Close = false, int i_Extremum_Bars = 3) {
  static datetime st_Pause_End = 0; // tempo da seguinte verificação
  if(st_Pause_End > go_Tick.time) return(ENTRY_NONE);
  st_Pause_End = 0;
  
  if(go_Channel.b_In_Process) {
    // os dados do canal não estão preparados para serem usados
    if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: os parâmtros do canal não estão preparados", __FUNCTION__);
    return(ENTRY_UNKNOWN);
  }
  if(go_Channel.i_Lowest_Offset < i_Extremum_Bars && go_Channel.i_Highest_Offset < i_Extremum_Bars) {
    // primeira condição não satisfeita
    if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: primeira condição não satisfeita", __FUNCTION__);
    
    // colocamos em pausa até a atualização do canal
    st_Pause_End = go_Tick.time + PeriodSeconds() - go_Tick.time % PeriodSeconds();
    
    return(ENTRY_NONE);
  }
  
  double d_Actual_Price = go_Tick.bid; // preço por padrão para a versão Turtle Soup
  if(b_Wait_For_Bar_Close) { // para a versão Turtle Soup Plus One
    double da_Price_Array[1];
    if(WRONG_VALUE == CopyClose(_Symbol, PERIOD_CURRENT, 1, 1, da_Price_Array)) {
      // tratamento de erro da função CopyClose
      if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyClose: erro #%u", __FUNCSIG__, _LastError);
      return(ENTRY_NONE);
    }
    d_Actual_Price = da_Price_Array[0];
  }
  
  // limite superior:
  if(go_Channel.i_Highest_Offset > i_Extremum_Bars) // primeira condição
    if(go_Channel.d_High < d_Actual_Price) { // segunda condição
      // o preço atravessou o limite superior
      if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: preço (%s) atravessou o limite superior (%s)", __FUNCTION__, DoubleToString(d_Actual_Price, _Digits), DoubleToString(go_Channel.d_High, _Digits));
      return(ENTRY_SELL);
    }
  
  // limite inferior
  if(go_Channel.i_Lowest_Offset > i_Extremum_Bars) // primeira condição
    if(go_Channel.d_Low > d_Actual_Price) { // segunda condição não está satisfeita
      // o preço atravessou o limite inferior
      if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: preço (%s) atravessou o limite inferior (%s)", __FUNCTION__, DoubleToString(d_Actual_Price, _Digits), DoubleToString(go_Channel.d_Low, _Digits));
      return(ENTRY_BUY);
    }
  
  // se o programa chegou até esta linha, significa que o preço está dentro do intervalo e segunda condição não está satisfeita não é satisfeita
  
  if(b_Wait_For_Bar_Close) // para a versão Turtle Soup Plus One
    // colocamos em pausa até o fim da barra atual
    st_Pause_End = go_Tick.time + PeriodSeconds() - go_Tick.time % PeriodSeconds();
  
  return(ENTRY_NONE);
}

Este é o código final da função. Chamamos o arquivo do módulo de sinal Signal_Turtle_Soup.mqh, colocamos nele o código específico do canal e sinais, no início do arquivo adicionamos o caixa de edição de configurações do usuário:

enum ENUM_STRATEGY {     // Opções de estratégia
  TS_TURTLE_SOUP,        // Turtle Soup
  TS_TURTLE_SOUP_PLIS_1  // Turtle Soup Plus One
};

// configurações personalizadas
input ENUM_STRATEGY  Turtle_Soup_Type = TS_TURTLE_SOUP;  // Turtle Soup: Opções de estratégia
input uint           Turtle_Soup_Period_Length = 20;     // Turtle Soup: Profundidade de busca de estremos (em barras)
input uint           Turtle_Soup_Extremum_Offset = 3;    // Turtle Soup: Pausa após o último extremo (em barras)
input double         Turtle_Soup_Entry_Offset = 10;      // Turtle Soup: Entrada: Recuo a partir do nível do extremo (em pontos)
input double         Turtle_Soup_Exit_Offset = 1;        // Turtle Soup: Saída: Recuo a partir do extremo oposto (em pontos)

Este arquivo deve ser salvo no diretório de dados do terminal, isto é, MQL5\Include\Expert\Signal.

 

Expert Advisor básico para verificação da Estratégia de Negociação


Perto do início do Expert Advisor, colocamos os campos de configurações personalizações, e antes deles as listas de tipo enumerado enum utilizadas nas configurações. Dividimos em dois grupos os campos de configurações — "Opções de estratégias" e "Abertura e acompanhamento da posição." As configurações do primeiro grupo serão incluídas a partir do arquivo da biblioteca de sinal durante a compilação. Até agora, criamos um arquivo desse tipo, mas em artigos subsequentes serão formalizadas e programadas outras estratégias a partir do livro e será possível substituir (ou adicionar) módulos de sinal, juntamente com as personalizações necessárias para eles.

Aqui, no início do código, habilitamos o arquivo de biblioteca padrão MQL5 para negociação:

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           // estado não definido
};

#include <Trade\Trade.mqh> // classe para execução de operações de negociação



input string  _ = "** Opções de estratégia:";  // .

#include <Expert\Signal\Signal_Turtle_Soup.mqh> // módulo de sinal

                                                        
input string  __ = "** Abertura e acompanhamento de posições:"; // .
input double  Trade_Volume = 0.1;                  // Volume da transação
input uint    Trail_Trigger = 100;                 // Trail: Distância de ativação do trail (em pontos)
input uint    Trail_Step = 5;                      // Trail: Incremento de deslocação do SL (em pontos)
input uint    Trail_Distance = 50;                 // Trail: distância máxima a partir do preços até ao SL (em pontos)
input ENUM_LOG_LEVEL  Log_Level = LOG_LEVEL_INFO;  // Modo de registro:

Os autores não mencionam nenhum regime especial de gestão de riscos e gestão de capital para esta estratégia, por isso vamos usar um tamanho fixo de lote para todas as transações.

Os opções de trai são inseridas em pontos. Com o advento de cotações de cinco dígitos apareceu alguma confusão com estas unidades, por isso é bom esclarecer que um ponto corresponde à mudança mínima no preço do instrumento. Isto significa que as cotações com cinco caracteres após o ponto decimal é igual a 0,00001, enquanto as cotações com quatro caracteres, 0,0001. Não se deve confundir pontos com pips, uma vez o que os pips ignoram a precisão real das cotações sempre mudando-las para quatro dígitos. Ou seja, se a mudança mínima do preço (ponto) é igual a 0,00001, então, um pip é igual a 10 pontos, mas com um preço de 0,0001 pontos, os preços do pip e ponto coincidem.

Durante o trabalho com tick, estas configurações são usadas a cada instante, o re-cálculo de pontos introduzidos pelo usuário nos preços reais do instrumento, embora não consumam recursos do processador de maneira significativa, ainda é realizado centenas de milhares de vezes por dia. O correto seria re-calcular uma vez os valores inseridos pelo usuário, ao inicializar o Expert Advisor, e guardá-los em variáveis globais para uso futuro. Você pode proceder da mesma maneira com as variáveis​que serão envolvidas na normalização do tamanho do lote, isto é, as limitações do servidor quanto ao tamanho máximo e mínimo, bem como as mudanças graduais permanecem constantes em todos os momentos durante a operação do robô, por isso não há necessidade de lê-los cada vez novamente. A declaração de variáveis​globais e a função de inicialização ficarão assim:

int
  gi_Try_To_Trade = 4, // número de tentativas de enviar a ordem de negociação
  gi_Connect_Wait = 2000 // pausa entre as tentativas (em milissegundos)
;
double
  gd_Stop_Level, // StopLevel a partir das configurações do servidos, transferido para o preço do instrumento
  gd_Lot_Step, gd_Lot_Min, gd_Lot_Max, // limitações de tamanho do lote a partir das configurações do servidor
  gd_Entry_Offset, // entrada: recuo a partir do extremo nos preços do instrumento
  gd_Exit_Offset, // saída: recuo a partir do extremo nos preços do instrumento
  gd_Trail_Trigger, gd_Trail_Step, gd_Trail_Distance // parâmetros do trail convertidos para preço do instrumento
;
MqlTick go_Tick; // informações sobre o último tick conhecido



int OnInit() {
  // Conversão de configurações de puntos para preços do instrumento:
  double d_One_Point_Rate = pow(10, _Digits);
  gd_Entry_Offset = Turtle_Soup_Entry_Offset / d_One_Point_Rate;
  gd_Exit_Offset = Turtle_Soup_Exit_Offset / d_One_Point_Rate;
  gd_Trail_Trigger = Trail_Trigger / d_One_Point_Rate;
  gd_Trail_Step = Trail_Step / d_One_Point_Rate;
  gd_Trail_Distance = Trail_Distance / d_One_Point_Rate;
  gd_Stop_Level = SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) / d_One_Point_Rate;
  // Inicialização de limites de lote:
  gd_Lot_Min = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
  gd_Lot_Max = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
  gd_Lot_Step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
  
  return(INIT_SUCCEEDED);
}

Deve-se notar que na biblioteca padrão MQL5 existe um módulo de trail do tipo requerido por nós (TrailingFixedPips.mqh) e ele pode ser incluído no código da mesma forma como fizemos com a classe para concluir operações de negociação. Porém ele não cumpre integralmente as peculiaridades desse Expert Advisor em particular, assim, nós mesmos inserimos o trail no corpo do robô num formato de função auto-personalizada:

bool fb_Trailing_Stop(    // Função de deslocação da posição SL do instrumento atual
  double d_Trail_Trigger,  // distância de ativação do trail (em preços do instrumento)
  double d_Trail_Step,    // mudança gradual da deslocação do SL (em preços do instrumento)
  double d_Trail_Distance  //  distância mínima a partir do preço até ao SL (em preços do instrumento)
) {
  if(!PositionSelect(_Symbol)) return(false); // a posição não existe, não há nada para fazer trailing
  
  // valor de base para o cálculo do novo nível SL - valor atual do preço:
  double d_New_SL = PositionGetDouble(POSITION_PRICE_CURRENT);
  
  if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { // para uma posição longa
    if(d_New_SL - PositionGetDouble(POSITION_PRICE_OPEN) < d_Trail_Trigger)
      return(false); // o preço não passou a distância suficiente para ativar o trail
      
    if(d_New_SL - PositionGetDouble(POSITION_SL) < d_Trail_Distance + d_Trail_Step)
      return(false); // as alterações do preço são menores do que a mudança gradual do SL
    
    d_New_SL -= d_Trail_Distance; // novo nível SL
  } else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { // para uma posição curta
    if(PositionGetDouble(POSITION_PRICE_OPEN) - d_New_SL < d_Trail_Trigger)
      return(false); // o preço não passou a distância suficiente para ativar o trail
    
    if(PositionGetDouble(POSITION_SL) > 0.0) if(PositionGetDouble(POSITION_SL) - d_New_SL < d_Trail_Distance + d_Trail_Step)
      return(false); // o preço não passou a distância suficiente para ativar o trail
    
    d_New_SL += d_Trail_Distance; // novo nível SL
  } else return(false);
  
  // sera que as configurações do servidor permitem colocar o SL de cálculo nessa distância a partir do preço atual?
  if(!fb_Is_Acceptable_Distance(d_New_SL, PositionGetDouble(POSITION_PRICE_CURRENT))) return(false);
  
  CTrade Trade;
  Trade.LogLevel(LOG_LEVEL_ERRORS);
  // colocamos o SL
  Trade.PositionModify(_Symbol, d_New_SL, PositionGetDouble(POSITION_TP));
  
  return(true);
}



bool fb_Is_Acceptable_Distance(double d_Level_To_Check, double d_Current_Price) {
  return(
    fabs(d_Current_Price - d_Level_To_Check)
    >
    fmax(gd_Stop_Level, go_Tick.ask - go_Tick.bid)
  );
}

Aqui a verificação da validação da colocação do SL no nível calculado é movida para uma função separada fb_Is_Acceptable_Distance, para utilizá-la ao validar a colocação da ordem pendente e ao definir StopLoss da posição aberta.

Agora, passamos para a principal área de trabalho no código do Expert Advisor, ela é chamada pela função-manipulador de evento do novo tick OnTick. De acordo com as regras da estratégia, se houver uma posição aberta, não vale a pena buscar novos sinais, por conseguinte, começamos com a verificação correspondente. Se existir a posição, no robot haverá duas opções para agir: quer calcular e configurar o StopLoss inicial para uma nova posição, ou ativar a função de trail que vai determinar se há necessidade de mover o StopLoss e fará com que a operação apropriada seja executada. Com o chamamento da função de trail tudo é simples, enquanto, para calcular o nível StopLoss, vamos usar o recuo a partir do extremo gd_Exit_Offset, lembremos que esse recuo foi inserido pelo usuário e re-calculado a partir dos pontos para o preço do instrumento. Definimos o estremo de preço usando as funções MQL5 padrão CopyHigh ou CopyLow. Os níveis calculados dessem modo deverão ser verificados quanto a validade com ajuda da função fb_Is_Acceptable_Distance e o valor do preço atual contido na estrutura go_Tick. Estes cálculos e verificações para ordens BuyStop e SellStop no código serão divididos:

if(PositionSelect(_Symbol)) { // há posição aberta
  if(PositionGetDouble(POSITION_SL) == 0.) { // nova posição
    double
      d_SL = WRONG_VALUE, // nível SL
      da_Price_Array[] // matriz auxiliar
    ;
    
    // calcular o nível StopLoss:
    if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { // para uma posição longa
      if(WRONG_VALUE == CopyLow(_Symbol, PERIOD_CURRENT, 0, 1 + (Turtle_Soup_Type == TS_TURTLE_SOUP_PLIS_1), da_Price_Array)) {
        // tratamento de erro da função CopyLow
        if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyLow: erro #%u", __FUNCTION__, _LastError);
        return;
      }
      d_SL = da_Price_Array[ArrayMinimum(da_Price_Array)] - gd_Exit_Offset;
      
      // será que é suficiente a distância a partir do preço atual?
      if(!fb_Is_Acceptable_Distance(d_SL, go_Tick.bid)) {
        if(Log_Level > LOG_LEVEL_NONE) PrintFormat("Nível de cálculo SL %s substituído pelo mínimo aceitável %s", DoubleToString(d_SL, _Digits), DoubleToString(go_Tick.bid + fmax(gd_Stop_Level, go_Tick.ask - go_Tick.bid), _Digits));
        d_SL = go_Tick.bid - fmax(gd_Stop_Level, go_Tick.ask - go_Tick.bid);
      }
      
    } else { // para posição curta
      if(WRONG_VALUE == CopyHigh(_Symbol, PERIOD_CURRENT, 0, 1 + (Turtle_Soup_Type == TS_TURTLE_SOUP_PLIS_1), da_Price_Array)) {
        // tratamento de erro da função CopyHigh
        if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyHigh: erro #%u", __FUNCTION__, _LastError);
        return;
      }
      d_SL = da_Price_Array[ArrayMaximum(da_Price_Array)] + gd_Exit_Offset;
      
      // será que é suficiente a distância a partir do preço atual?
      if(!fb_Is_Acceptable_Distance(d_SL, go_Tick.ask)) {
        if(Log_Level > LOG_LEVEL_NONE) PrintFormat("Nível de cálculo SL %s substituído pelo mínimo aceitável %s", DoubleToString(d_SL, _Digits), DoubleToString(go_Tick.ask - fmax(gd_Stop_Level, go_Tick.ask - go_Tick.bid), _Digits));
        d_SL = go_Tick.ask + fmax(gd_Stop_Level, go_Tick.ask - go_Tick.bid);
      }
    }
    
    CTrade Trade;
    Trade.LogLevel(LOG_LEVEL_ERRORS);
    // colocar o SL
    Trade.PositionModify(_Symbol, d_SL, PositionGetDouble(POSITION_TP));
    return;
  }
  
  // trail
  fb_Trailing_Stop(gd_Trail_Trigger, gd_Trail_Step, gd_Trail_Distance);
  return;
}

Além dos novos parâmetros lidos do tick, na atualização serão necessários os parâmetros de canal, uma vez que eles são utilizados durante a detecção do sinal. Só faz sentido, após o fechamento da próxima barra, chamar a função de atualização f_Set da estrutura go_Channel, o resto do tempo esses parâmetros estão inalterados. O robô também remove a ordem pendente de ontem. Programamos estes dois passos:

int
  i_Order_Ticket = WRONG_VALUE, // bilhete da ordem pendente
  i_Try = gi_Try_To_Trade, // número de tentativas de executar a operação
  i_Pending_Type = -10 // tipo de ordem pendente atual
;
static int si_Last_Tick_Bar_Num = 0; // número da barra do tick anterior (0 = início do sistema de cronologia em MQL)

// processamento de eventos ligado ao início do novo dia (barra):
if(si_Last_Tick_Bar_Num < int(floor(go_Tick.time / PeriodSeconds()))) {
  // Olá, novo dia :)
  si_Last_Tick_Bar_Num = int(floor(go_Tick.time / PeriodSeconds()));
  
  // há alguma ordem pendente muito velha?
  i_Pending_Type = fi_Get_Pending_Type(i_Order_Ticket);
  if(i_Pending_Type == ORDER_TYPE_SELL_STOP || i_Pending_Type == ORDER_TYPE_BUY_STOP) {
    // removemos a ordem velha:
    if(Log_Level > LOG_LEVEL_ERR) Print("Remoção da ordem pendente de ontem");
    
    CTrade o_Trade;
    o_Trade.LogLevel(LOG_LEVEL_ERRORS);
    while(i_Try-- > 0) { // tentativa de remover
      if(o_Trade.OrderDelete(i_Order_Ticket)) { // tentativa bem-sucedida
        i_Try = -10; // sinalizador após a operação bem-sucedida
        break;
      }
      // a tentativa falhou
      Sleep(gi_Connect_Wait); // mantemos a pausa antes da seguinte tentativa
    }
    
    if(i_Try == WRONG_VALUE) { // remoção da ordem pendente falhou
      if(Log_Level > LOG_LEVEL_NONE) Print("Erro de remoção da ordem pendente");
      return; // esperamos até o próximo tick
    }
  }
  
  // atualização dos parâmetros do canal:
  go_Channel.f_Set(Turtle_Soup_Period_Length, 1 + (Turtle_Soup_Type == TS_TURTLE_SOUP_PLIS_1));
}

A função fi_Get_Pending_Type utilizada aqui retona o tipo de ordem pendente, e, segundo a referência recebida para a variável i_Order_Ticket, coloca nela o número do bilhete. Este tipo de ordem será necessária mais tarde para conciliar, neste tick, com a direção atual do sinal, e o bilhete será usado no caso de ter que remover uma ordem. Se não houver ordem pendente, ambos os valores serão igual a WRONG_VALUE. Listagem da função:

int fi_Get_Pending_Type( // detector de presença de ordem pendente no simbolo atual
  int& i_Order_Ticket // referência para o bilhete da ordem pendente selecionada
) {
  int
    i_Order = OrdersTotal(), // número total de ordens
    i_Order_Type = WRONG_VALUE // variável para o tipo de ordem
  ;
  i_Order_Ticket = WRONG_VALUE; // valor por padrão retornado do bilhete
  
  if(i_Order < 1) return(i_Order_Ticket); // sem ordens
  
  while(i_Order-- > 0) { // iteração das ordens existentes
    i_Order_Ticket = int(OrderGetTicket(i_Order)); // leitura do bilhete
    if(i_Order_Ticket > 0)
      if(StringCompare(OrderGetString(ORDER_SYMBOL), _Symbol, false) == 0) {
        i_Order_Type = int(OrderGetInteger(ORDER_TYPE));
        // apenas são necessárias ordens pendentes:
        if(i_Order_Type == ORDER_TYPE_BUY_LIMIT || i_Order_Type == ORDER_TYPE_BUY_STOP || i_Order_Type == ORDER_TYPE_SELL_LIMIT || i_Order_Type == ORDER_TYPE_SELL_STOP)
          break; // ordem pendente encontrada
      }
    i_Order_Ticket = WRONG_VALUE; // ainda não encontrada
  }
  
  return(i_Order_Type);
}

Agora estamos prontos para determinar o estado do sinal. Se as condições da Estratégia de Negociação não forem satisfeitas (o sinal adoptará o estado ENTRY_NONE ou ENTRY_UNKNOWN), é possível finalizar o trabalho do programa principal neste tick:

// obter o estado do sinal:
ENUM_ENTRY_SIGNAL e_Signal = fe_Get_Entry_Signal(Turtle_Soup_Type == TS_TURTLE_SOUP_PLIS_1, Turtle_Soup_Extremum_Offset);
if(e_Signal > 1) return; // sem sinal

Se houver sinal, comparamos com a direção da ordem pendente atual, se já tiver sido colocada:

// aclaramos o tipo de ordem pendente e seu bilhete, se isso não tiver sido feito:
if(i_Pending_Type == -10)
  i_Pending_Type = fi_Get_Pending_Type(i_Order_Ticket);

// é necessária uma nova ordem pendente?
if(
  (e_Signal == ENTRY_SELL && i_Pending_Type == ORDER_TYPE_SELL_STOP)
  ||
  (e_Signal == ENTRY_BUY && i_Pending_Type == ORDER_TYPE_BUY_STOP)
) return; // já existe uma ordem pendente na direção do sinal

// é necessário excluir a ordem pendente?
if(
  (e_Signal == ENTRY_SELL && i_Pending_Type == ORDER_TYPE_BUY_STOP)
  ||
  (e_Signal == ENTRY_BUY && i_Pending_Type == ORDER_TYPE_SELL_STOP)
) { // a direção da ordem pendente não coincide com a direção do sinal
  if(Log_Level > LOG_LEVEL_ERR) Print("A direção da ordem pendente não coincide com a direção do sinal");
    
  i_Try = gi_Try_To_Trade;
  while(i_Try-- > 0) { // tentativa de remover
    if(o_Trade.OrderDelete(i_Order_Ticket)) { // tentativa bem-sucedida
      i_Try = -10; // sinalizador após a operação bem-sucedida
      break;
    }
    // a tentativa falhou
    Sleep(gi_Connect_Wait); // mantemos a pausa antes da seguinte tentativa
  }
  
  if(i_Try == WRONG_VALUE) { // remoção da ordem pendente falhou
    if(Log_Level > LOG_LEVEL_NONE) Print("Erro de remoção da ordem pendente");
    return; // esperamos até o próximo tick
  }
}

Agora que não temos dúvidas sobre a necessidade de uma nova ordem pendente, calculamos seus parâmetros. De acordo com as regras da estratégia, é necessário colocar a ordem com recuado para dentro dos limites do canal. O StopLoss deve ser colocado no lado oposto do limite, perto do extremo do preço de hoje ou de dois dias (dependendo da estratégia escolhida). Mas, devemos calcular a posição StopLoss apenas após a ativação da ordem pendente, o código para esta operação foi citado em cima.



Lemos o limite atual do canal real a partir da estrutura go_Channel, enquanto o recuo para entrada está contido na variável gd_Entry_Offset, esse recuo foi inserido pelo utilizador e re-calculado para o preço. O nível calculado será necessário para verificar a validade usando a função fb_Is_Acceptable_Distance e o valor de preço atual contido na estrutura go_Tick. Estes cálculos e verificações para ordens BuyStop e SellStop no código serão divididos:

double d_Entry_Level = WRONG_VALUE; // nível de colocação da ordem pendente
if(e_Signal == ENTRY_BUY) { // para ordem pendente de compra
  // verificamos a possibilidade de colocar a ordem:
  d_Entry_Level = go_Channel.d_Low + gd_Entry_Offset; // nível de colocação da ordem
  if(!fb_Is_Acceptable_Distance(d_Entry_Level, go_Tick.ask)) {
    // a distância a partir do preço atual não é suficiente 
    if(Log_Level > LOG_LEVEL_ERR)
      PrintFormat("É impossível colocar BuyStop no nível %s. Bid: %s Ask: %s StopLevel: %s",
        DoubleToString(d_Entry_Level, _Digits),
        DoubleToString(go_Tick.bid, _Digits),
        DoubleToString(go_Tick.ask, _Digits),
        DoubleToString(gd_Stop_Level, _Digits)
      );
    
    return; // esperamos mudanças no preço atual
  }
} else {
  // verificamos a possibilidade de colocar a ordem:
  d_Entry_Level = go_Channel.d_High - gd_Entry_Offset; // nível de colocação da ordem
  if(!fb_Is_Acceptable_Distance(d_Entry_Level, go_Tick.bid)) {
    // a distância a partir do preço atual não é suficiente 
    if(Log_Level > LOG_LEVEL_ERR)
      PrintFormat("É impossível colocar SellStop no nível %s. Bid: %s Ask: %s StopLevel: %s",
        DoubleToString(d_Entry_Level, _Digits),
        DoubleToString(go_Tick.bid, _Digits),
        DoubleToString(go_Tick.ask, _Digits),
        DoubleToString(gd_Stop_Level, _Digits)
      );
    
    return; // esperamos mudanças no preço atual
  }
}

Se o nível calculado de colocação de ordem pendente passar a verificação, você pode organizar o envio da ordem necessária para o servidor usando a biblioteca de classes padrão:

// fazemos com que o lote cumpra as exigências do servidor:
double d_Volume = fd_Normalize_Lot(Trade_Volume);

// colocamos a ordem pendente:
i_Try = gi_Try_To_Trade;

if(e_Signal == ENTRY_BUY) {
  while(i_Try-- > 0) { // tentativa de colocar BuyStop
    if(o_Trade.BuyStop(
      d_Volume,
      d_Entry_Level,
      _Symbol
    )) { // tentativa bem-sucedida
      Alert("Colocada ordem pendente para compra!");
      i_Try = -10; // sinalizador após a operação bem-sucedida
      break;
    }
    // falhou
    Sleep(gi_Connect_Wait); // mantemos a pausa antes da seguinte tentativa
  }
} else {
  while(i_Try-- > 0) { // tentativa de colocar SellStop
    if(o_Trade.SellStop(
      d_Volume,
      d_Entry_Level,
      _Symbol
    )) { // tentativa bem-sucedida
      Alert("Colocada ordem pendente para venda!");
      i_Try = -10; // sinalizador após a operação bem-sucedida
      break;
    }
    // falhou
    Sleep(gi_Connect_Wait); // mantemos a pausa antes da seguinte tentativa
  }
}

if(i_Try == WRONG_VALUE) // a colocação de ordem pendente falhou
  if(Log_Level > LOG_LEVEL_NONE) Print("Erro de colocação da ordem pendente");

Neste ponto fica concluída a programação do Expert Advisor, e, após a compilação, voltamos para a análise de seu trabalho no testador de estratégias.

 

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


Em seu livro, Connors e Raschke ilustram as estratégias usando gráficos de há mais de duas décadas, assim, a principal finalidade dos testes era a verificação de sua eficiência em dados mais atuais. Usaram-se as configurações originais e o timeframe diário especificados pelos autores. Há 20 anos, as cotações de cinco dígitos não eram muito famosas, no entanto o teste foi realizado nas cotações de cinco dígitos do servidor demo MetaQuotes, é por isso que os recuos originais de 1 e 10 pontos foram transformados em 10 e 100. Os parâmetros de trail não são mencionados na descrição da estratégia, é por isso que use aquilos que pareciam mais adequados para o timeframe diário.

Gráfico de resultados de teste da estratégia Turtle Soup no par USDJPY nos últimos cinco anos:

Turtle Soup, USDJPY, D1, 5 anos


Gráfico de resultados de teste da estratégia Turtle Soup Plus One com os mesmos parâmetros, região do histórico e instrumento:

Turtle Soup Plus One, USDJPY, D1, 5 anos


Gráfico de resultados de teste nas cotações do ouro nos últimos cinco anos. Estratégia Turtle Soup:

Turtle Soup, XAUUSD, D1, 5 anos


Turtle Soup Plus One:

Turtle Soup Plus One, XAUUSD, D1, 5 anos

 


Gráfico de resultados de teste nas cotações do petróleo (crude oil) nos últimos quatro anos. Estratégia Turtle Soup:

Turtle Soup, OIL, D1, 4 anos


Turtle Soup Plus One:

Turtle Soup Plus One, OIL, D1, 4 anos


Os relatórios completos de todos os testes se encontram nos arquivos anexados.

Eu deixo vocês tirar conclusões, no entanto eu sou obrigado a dar a explicação necessária. Connors e Raschke alertam contra uma repetição puramente mecânica das regras de quaisquer estratégias dadas no livro. Eles consideram obrigatória uma análise de como o preço se aproxima dos limites do canal e como se comportam após testá-las. Infelizmente, nesse ponto eles não revelam detalhes. No que diz respeito à otimização, é claro que é possível tentar adaptar os parâmetros a outros timeframes, selecionar os instrumentos e parâmetros mais adequados para esta Estratégia de Negociação.

Conclusão

Nós formalizamos e programamos as regras do primeiro par a partir das descritas no livro Street Smarts: High Probability Short-Term Trading Strategies das estratégias de negociação Turtle Soup e Turtle Soup Plus One. O Expert Advisor e a biblioteca de sinal contêm todas as regras de Raschke e Connors, mas neles não existem alguns detalhes importantes sobre as negociações dos autores, além disso, elas são mencionados apenas de passagem. É fácil supor que devemos, pelo menos, ter em conta as lacunas e os limites de pregões. Também parece lógico tentar limitar a negociação quer usando uma entrada por dia ou uma entrada rentável, quer mantendo a ordem pendente mais do que antes do início do dia seguinte.

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

Arquivos anexados |
Reports.zip (607.7 KB)
MQL5.zip (83.57 KB)
Interfaces Gráficas IX: Os Controles Barra de Progresso e Gráfico de Linha (Capítulo 2) Interfaces Gráficas IX: Os Controles Barra de Progresso e Gráfico de Linha (Capítulo 2)
O segundo capítulo da parte nove é dedicado aos controles barra de progresso e gráfico de linha. Como sempre, teremos exemplos detalhados para revelar como esses controles podem ser utilizados nas aplicações MQL personalizadas.
Distribuição Estatística no MQL5 - tirando o melhor de R e o fazendo mais rápido Distribuição Estatística no MQL5 - tirando o melhor de R e o fazendo mais rápido
As funções para trabalhar com as distribuições estatísticas básicas implementadas na linguagem R são consideradas. as distribuições de Cauchy, Weibull, normal, log-normal, logistic, exponential, uniform, gamma, beta central e não-central, qui-quadrado, F de Fisher-Snedecor, t de Student, assim como as distribuições binomiais discretas e binomiais negativas, distribuições geométricas, hipergeométricas e de Poisson. Existem funções para o cálculo de momentos teóricos de distribuições, que permitem avaliar o grau de conformidade da distribuição real com o modelado.
Interfaces gráficas X: Atualizações para a Biblioteca Easy And Fast (Build 2) Interfaces gráficas X: Atualizações para a Biblioteca Easy And Fast (Build 2)
Desde a publicação do artigo anterior da série, a biblioteca Easy And Fast tem recebido algumas funcionalidades novas. A estrutura e o código da biblioteca foram parcialmente otimizados, reduzindo ligeiramente a carga da CPU. Alguns métodos recorrentes em muitas classes de controle foram transferidos para a classe base CElement.
Interfaces Gráficas IX: O Controle Seletor de Cores (Capítulo 1) Interfaces Gráficas IX: O Controle Seletor de Cores (Capítulo 1)
Com este artigo, nós começamos o capítulo nove da série de artigos dedicados à criação das interfaces gráficas nos terminais de negociação MetaTrader. Ele consiste de dois capítulos onde são apresentados os novos elementos do controle da interface, tais como o seletor de cores, o botão do seletor de cores, a barra de progresso e o gráfico de linha.