Sistema de negociação 'Turtle Soup' e sua modificação 'Turtle Soup Plus One'
- Introdução
- Sistema de negociação 'Turtle Soup' e sua modificação 'Turtle Soup Plus One'
- Definição de parâmetros do canal
- Função de geração de sinais
- Expert Advisor básico para verificação da Estratégia de Negociação
- Teste de estratégia com base no histórico de dados
- 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:
- Certifique-se de que, desde o anterior mínimo de 20 dias, passaram não menos de 3 dias
- Aguarde até que o preço do instrumento caia abaixo do mínimo de 20 dias
- Coloque uma ordem pendente para compra 5-10 pontos acima do preço mínimo que acabou de ser atravessado de cima para baixo
- Imediatamente após a ativação da ordem pendente, coloque seu StopLoss 1 ponto abaixo do mínimo do dia
- Use Trailing Stop quando a posição se torne rentável
- 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:
- 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.
- 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áveisirã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 } };
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áveisque 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áveisglobais 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:
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:
Gráfico de resultados de teste nas cotações do ouro nos últimos cinco anos. Estratégia Turtle Soup:
Turtle Soup Plus One:
Gráfico de resultados de teste nas cotações do petróleo (crude oil) nos últimos quatro anos. Estratégia Turtle Soup:
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
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso