Sistema de negociação '80-20'
Introdução
'80-20' é o nome de uma das estratégias de negociação (EN) descritas no livro Street Smarts: High Probability Short-Term Trading Strategies de Linda Raschke e Laurence Connors. Como o par de estratégias discutidas no meu artigo anterior, as autoras atribuíram-lo para a fase de teste de limites de preço do intervalo. Ele também foca na obtenção de lucro a partir de fuga falsa ou reversão de limites. Mas, desta vez, para detectar o sinal, é analisado o movimento dos preços, no período significativamente mais curto do histórico, quer dizer, apenas o dia anterior. O tempo de vida do sinal recebido também é relativamente pequeno, uma vez que o sistema é projetado para negociação intradia.
O primeiro dos objetivos do presente artigo é descrever como criar, usando a linguagem MQL5, um módulo de sinal que implemente as regras da estratégia de negociação '80-20'. Para, em seguida, conectar esse módulo ao Expert Advisor, criado no artigo anterior desta série, após editá-lo um pouco. Nós usamos este módulo inalterado ao criar um indicador para negociação manual.
Lembre-se que, nesta série de artigos, o código criado está focado principalmente na categoria dos programadores que pode ser definida como "iniciantes ligeiramente avançados". Portanto, além da tarefa principal, o ccódigo é projetado para ajudar a mudança de programação procedural para programação de alto nível orientada a objetos. Nele, não serão geradas classes, porém serão usados na totalidade análogos mais simples para desenvolver, nomeadamente, estruturas.
Outro objetivo do artigo consiste em criar instrumentos que permitem verificar quão atual é esta estratégia hoje em dia, já que quando foi criada, Raschke e Connors levavam em conta o comportamento do mercado no final do século passado. No final do artigo, serão apresentados vários testes - com base no histórico de dados atual - do Expert Advisor criado.
Sistema de negociação '80-20'
Como justificação teórica, os autores referem-se ao livro The Taylor Trading Technique do George Taylor, bem como ao trabalho na análise de computador dos mercados de futuros do Steve Moore e experiência prática do trader Derek Gipson. Formada a base da hipótese da Estratégia de Negociação, pode ser resumidamente afirmado como se segue: se os preços de abertura e fechamento de ontem estiverem distribuídos na região oposta do intervalo diário, hoje haverá uma grande probabilidade de que você possa esperar uma inversão na direção de abertura de ontem. Assim, é importante que, por um lado, os preços de abertura e fechamento de ontem estejam bastante perto dos limites do intervalo, por outro, a inversão comece hoje, mas não antes do fechamento da barra de ontem. O conjunto de regras da Estratégia de Negociação '80-20' adicional alterado pelos autores para entrada no mercado pode ser formulado da seguinte forma:
1. Certifique-se de que ontem o mercado abriu acima de 20% do intervalo diário e fechou abaixo de 20% do intervalo
2. Espere até que o atual preço mínimo atinja o mínimo de ontem pelo menos em 5 ticks
3. Coloque uma ordem pendente de comprar no limite inferior do intervalo de ontem
4. Imediatamente após a ativação da ordem pendente, defina seu StopLoss inicial no mínimo do dia
5. Use Trailing Stop para proteger os lucros obtidos
As regras são semelhantes para vender, no entanto a barra de ontem debe ser de alta, é necessário colocar, por um lado, a ordem de venda no limite superior desta barra e, por outro, o StopLoss no nível do máximo atual.
Outro detalhe importante aparece no livro, ao discutir as ilustrações para a Estratégia de Negociação nos gráficos a partir do histórico, aqui os autores chamar a atenção para o tamanho da barra diária que foi fechada. De acordo com Linda Raschke, ela deve ser superior ao tamanho médio das barras diárias. Na verdade, ele não especifica o número de dias de histórico deve ser tomado em consideração ao calcular o intervalo médio diário.
Não se esqueça que a Estratégia de Negociação é projetada exclusivamente para negociação intradia, os exemplos mostrados no livro usam gráficos do timeframe de 15 minutos.
Abaixo vou descrever o bloco de sinal e o indicador que faz a layout para essa Estratégia de Negociação. Abaixo você pode ver algumas capturas de tela com o resultado do indicador. Nelas são claramente visíveis os padrões, que correspondem às regras do sistema, e os níveis de negociação vinculados a esses padrões.
Timeframe de 5 minutos:
Como consequência da análise deste padrão, é preciso colocar uma ordem pendente para compra. Os respetivos níveis de negociação podem ser apreciados melhor no timeframe de minuto:
Um padrão semelhante, com negociação no sentido oposto no timeframe de cinco minutos:
Seus níveis de negociação (timeframe de minuto):
Módulo de sinal
Para exibir um exemplo de como adicionar opções adicionais à Estratégia de Negociação do autor, adicionamos o cálculo do nível Take Profit. Na versão original este nível não existe, para fechar uma posição, usa-se apenas o trailing de nível Stop Loss. Vamos fazer com que o Take Profit dependa do tamanho mínimo de fuga definido pelo usuário (TS_8020_Extremum_Break) — vamos multiplica-lo pelo coeficiente personalizado TS_8020_Take_Profit_Ratio.
A partir da função base do sinal de módulo fe_Get_Entry_Signal vamos precisar: o estado atual do sinal, os níveis atuais de entrada e saída (Stop Loss e Take Profit), bem como os limites do intervalo de ontem. Obtemos todos os níveis das referências - da função - transferidas para as variáveis, enquanto o estado retornado irá usar a lista de opções a partir do artigo anterior:
ENTRY_BUY, // sinal para compra
ENTRY_SELL, // sinal para venda
ENTRY_NONE, // sem sinal
ENTRY_UNKNOWN // estado não definido
};
ENUM_ENTRY_SIGNAL fe_Get_Entry_Signal( // Análise de padrão de dois velas D1
datetime t_Time, // tempo atual
double& d_Entry_Level, // nível de entrada (referência para a variável)
double& d_SL, // nível StopLoss (referência para a variável)
double& d_TP, // nível TakeProfit (referência para a variável)
double& d_Range_High, // máximo do intervalo da primeira barra do padrão(referência para a variável)
double& d_Range_Low // mínimo do intervalo da primeira barra do padrão (referência para a variável)
) {}
Para identificar o sinal, é necessário examinar as duas últimas barras do timeframe diário. Vamos começar com a primeira delas, se ela não satisfazer os critérios da Estratégia de Negociação, não fará sentido verificar a segunda barra. Dois critérios:
1. A magnitude da barra (diferença entre os preços High e Low) deve ser superior à média para os últimos XX dias (é definida pela configuração de usuário TS_8020_D1_Average_Period)
2. Os níveis de apertura e fechamento da barra devem estar relacionados com o oposto 20% do intervalo da barra
Se essas condições forem atendidas, para uso futuro, será necessário lembrar os preços High e Low. Como os parâmetros da primeira barra do padrão não são alterados durante todo o dia, não vamos verificá-los a cada chamada da função, em vez disso levamos em conta as variáveis estatísticas:
input uint TS_8020_D1_Average_Period = 20; // 80-20: Número de dias para cálculo da intervalo médio diário
input uint TS_8020_Extremum_Break = 50; // 80-20: fuga mínimo do extremo de ontem (em pontos)
static ENUM_ENTRY_SIGNAL se_Possible_Signal = ENTRY_UNKNOWN; // direção do sinal na primeira barra do padrão
static double
// variáveis para armazenamento dos níveis calculados entre os ticks
sd_Entry_Level = 0,
sd_SL = 0, sd_TP = 0,
sd_Range_High = 0, sd_Range_Low = 0
;
// verificação da primeira barra do padrão em D1:
if(se_Possible_Signal == ENTRY_UNKNOWN) { // hoje ainda não foi levada a cabo
st_Last_D1_Bar = t_Curr_D1_Bar; // neste dia a primeira barra já não irá mudar
// intervalo médio diário
double d_Average_Bar_Range = fd_Average_Bar_Range(TS_8020_D1_Average_Period, PERIOD_D1, t_Time);
if (ma_Rates[0].high — ma_Rates[0].low <= d_Average_Bar_Range) {
// a primeira barra não grande o suficiente
se_Possible_Signal = ENTRY_NONE; // significa que hoje não haverá sinal
return(se_Possible_Signal);
}
double d_20_Percents = 0.2 * (ma_Rates[0].high — ma_Rates[0].low); // 20% do intervalo de ontem
if((
// barra de baixa:
ma_Rates[0].open > ma_Rates[0].high — d_20_Percents // a barra abriu em máximos do 20% do intervalo
&&
ma_Rates[0].close < ma_Rates[0].low + d_20_Percents // a barra fechou em mínimos do 20% do intervalo
) || (
// de alta:
ma_Rates[0].close > ma_Rates[0].high — d_20_Percents // a barra fechou em máximos do 20% do intervalo
&&
ma_Rates[0].open < ma_Rates[0].low + d_20_Percents // a barra abriu em mínimos do 20% do intervalo
)) {
// a primeira barra atende as regras
// definição da direção da negociação para hoje de acordo com a primeira barra do padrão:
se_Possible_Signal = ma_Rates[0].open > ma_Rates[0].close ? ENTRY_BUY : ENTRY_SELL;
// nível de entrada no mercado:
sd_Entry_Level = d_Entry_Level = se_Possible_Signal == ENTRY_BUY ? ma_Rates[0].low : ma_Rates[0].high;
// limite do intervalo da primeira barra do padrão:
sd_Range_High = d_Range_High = ma_Rates[0].high;
sd_Range_Low = d_Range_Low = ma_Rates[0].low;
} else {
// ps níveis de apertura/fechamento da primeira barra não cumprem as condições
se_Possible_Signal = ENTRY_NONE; // significa que hoje não haverá sinal
return(se_Possible_Signal);
}
}
Listagem de função de definição do intervalo médio da barra para o número de barras estabelecido do timeframe especificado, começando com as funções de tempo indicadas:
int i_Bars_Limit, // quantas barras levar em conta
ENUM_TIMEFRAMES e_TF = PERIOD_CURRENT, // timeframe de barras
datetime t_Time = WRONG_VALUE // a partir de que momento começar o cálculo
) {
double d_Average_Range = 0; // variável para soma de valores
if(i_Bars_Limit < 1) return(d_Average_Range);
MqlRates ma_Rates[]; // matriz para informação sobre as barras
// obtenção de informação sobre as barras a partir da parte especificada do histórico:
if(t_Time == WRONG_VALUE) t_Time = TimeCurrent();
int i_Price_Bars = CopyRates(_Symbol, e_TF, t_Time, i_Bars_Limit, ma_Rates);
if(i_Price_Bars == WRONG_VALUE) { // tratamento de erros da função CopyRates
if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyRates: erro #%u", __FUNCTION__, _LastError);
return(d_Average_Range);
}
if(i_Price_Bars < i_Bars_Limit) { // a função CopyRates removeu dados parcialmente
if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyRates: copiado %u de barras de %u" , __FUNCTION__, i_Price_Bars, i_Bars_Limit);
}
// soma de intervalos:
int i_Bar = i_Price_Bars;
while(i_Bar-- > 0)
d_Average_Range += ma_Rates[i_Bar].high — ma_Rates[i_Bar].low;
// valor médio:
return(d_Average_Range / double(i_Price_Bars));
}
Para a segunda barra (atual) do padrão, o critério é apenas um, isto é, a fuga através do limite do intervalo de ontem deve ser superior à fuga definida nas configurações (TS_8020_Extremum_Break). Uma vez que este nível é atingido, aparece o sinal para colocar uma ordem pendente:
if(se_Possible_Signal == ENTRY_BUY) {
sd_SL = d_SL = ma_Rates[1].low; // StopLoss — para o máximo do preço de hoje
if(TS_8020_Take_Profit_Ratio > 0) sd_TP = d_TP = d_Entry_Level + _Point * TS_8020_Extremum_Break * TS_8020_Take_Profit_Ratio; // TakeProfit
return(
// será que a fuga é o suficientemente pronunciada para abaixo?
ma_Rates[1].close < ma_Rates[0].low — _Point * TS_8020_Extremum_Break ?
ENTRY_BUY : ENTRY_NONE
);
}
if(se_Possible_Signal == ENTRY_SELL) {
sd_SL = d_SL = ma_Rates[1].high; // StopLoss — para o mínimo do preço de hoje
if(TS_8020_Take_Profit_Ratio > 0) sd_TP = d_TP = d_Entry_Level — _Point * TS_8020_Extremum_Break * TS_8020_Take_Profit_Ratio; // TakeProfit
return(
// será que a fuga é o suficientemente pronunciada para cima?
ma_Rates[1].close > ma_Rates[0].high + _Point * TS_8020_Extremum_Break ?
ENTRY_SELL : ENTRY_NONE
);
}
No arquivo da biblioteca mqh, armazenamos as funções descritas acima (fe_Get_Entry_Signal e fd_Average_Bar_Range) e as configurações personalizadas relacionadas com a obtenção do sinal. Uma lista completa está no anexo a este artigo. Vamos chamar o arquivo Signal_80-20.mqh e colocá-lo na pasta apropriada (MQL5\Include\Expert\Signal) do diretório de dados do terminal.
Indicador para negociação manual
Tanto o indicador quanto o Expert Advisor vão usar o módulo de sinal descrito acima. Ele (o indicador) deve informar o trader sobre a chegada do sinal para colocação da ordem pendente e notificar sobre níveis calculados, ou seja, os níveis de colocação da ordem, os níveis Take Profit e Stop Loss. O usuário pode selecionar por si mesmo os métodos de notificação: uma janela pop-up, uma mensagem no e-mail ou notificação no dispositivo móvel. É possível escolher tudo de uma só vez ou qualquer combinação destas convenientes opções.
Outra finalidade do indicador consiste no layout no histórico de negociação com base na Estratégia de Negociação '80-20'. Ele vai destacar as barras diárias, conforme os critérios do sistema, e desenhar os níveis de negociação e cálculo. Com base nas linhas dos níveis, será possível avaliar a forma como a situação evoluiu ao longo do tempo. Para maior clareza, fazemos o seguinte: após o preço tocar a linha de sinal, ela se acaba, começa a linha da ordem pendente; após a ativação da ordem pendente, acaba-se sua linha e começam as linhas Take Profit e Stop Loss. Estas linhas são interrompidas, após o preço tocar uma delas (a ordem é fechada). Usando esse layout (ou esboço), será mais fácil avaliar a eficácia das regras do sistema de negociação e entender o que pode ser melhorado.
Vamos começar com a declaração de buffers e seus parâmetros de exibição. Em primeiro lugar, precisamos de declarar dois buffers com preenchimento da região vertical (DRAW_FILLING). Um deles vai destacar todo o intervalo da barra do dia anterior, o outro, apenas a região interna, isto com o fim de separá-la dos máximos e mínimos - do 20% do intervalo - envolvidos na Estratégia de Negociação. Em seguida, declaramos dois buffers para as linhas de sinal multi-coloridas e linhas de ordem pendente(DRAW_COLOR_LINE). Sua cor vai depender da direção da negociação. Haverá também dois linhas (Take Proft e Stop Loss) que não mudarão de cor (DRAW_LINE), uma vez que elas vão utilizar as mesmas cores padrão que lhes foram atribuídas no terminal. À exceção da linha simples, todos os tipos de exibição selecionados requerem dois buffers, por isso o código vai ter a seguinte aparência:
#property indicator_buffers 10
#property indicator_plots 6
#property indicator_label1 "primeira barra do padrão"
#property indicator_type1 DRAW_FILLING
#property indicator_color1 clrDeepPink, clrDodgerBlue
#property indicator_width1 1
#property indicator_label2 "primeira barra do padrão"
#property indicator_type2 DRAW_FILLING
#property indicator_color2 clrDeepPink, clrDodgerBlue
#property indicator_width2 1
#property indicator_label3 "Nível de sinal"
#property indicator_type3 DRAW_COLOR_LINE
#property indicator_style3 STYLE_SOLID
#property indicator_color3 clrDeepPink , clrDodgerBlue
#property indicator_width3 2
#property indicator_label4 "Nível de entrada"
#property indicator_type4 DRAW_COLOR_LINE
#property indicator_style4 STYLE_DASHDOT
#property indicator_color4 clrDeepPink, clrDodgerBlue
#property indicator_width4 2
#property indicator_label5 "Stop Loss"
#property indicator_type5 DRAW_LINE
#property indicator_style5 STYLE_DASHDOTDOT
#property indicator_color5 clrCrimson
#property indicator_width5 1
#property indicator_label6 "Take Profit"
#property indicator_type6 DRAW_LINE
#property indicator_style6 STYLE_DASHDOTDOT
#property indicator_color6 clrLime
#property indicator_width6 1
Permitimos que o usuário: desative o preenchimento da primeira barra do padrão diário, selecione as opções de notificação sobre o sinal e limite a profundidade de layout do histórico. Aqui, a partir do módulo de sinal, habilitamos todas as configurações do sistema de negociação. Para fazer isso, será preciso calcular antecipadamente as variáveis envolvidas no módulo, mesmo se apenas algumas delas são utilizadas no Expert Advisor e não são necessárias no indicador:
input bool Show_Outer = true; // primeira barra do padrão: Mostrar todo o intervalo?
input bool Show_Inner = true ; // primeira barra do padrão: Exibir a região interna?
input bool Alert_Popup = true; // Alerta: Exibir a janela pop-up?
input bool Alert_Email = false; // Alerta: Enviar e-mail?
input string Alert_Email_Subj = ""; // Alerta: Tema mensagem e-mail
input bool Alert_Push = true; // Alerta: Enviar notificação push?
input uint Bars_Limit = 2000; // Profundidade do layout do histórico (nas barras do timeframe atual)
ENUM_LOG_LEVEL Log_Level = LOG_LEVEL_NONE; // Modo detalhado
double
buff_1st_Bar_Inner[], buff_1st_Bar_Inner_Zero[], // buffers para traçado dos internos 60% da primeira barra do padrão
buff_1st_Bar_Inner[], buff_1st_Bar_Inner_Zero[], // buffers para traçado dos internos 60% da primeira barra do padrão
buff_Signal[], buff_Signal_Color[], // buffers da linha de sinal
buff_Entry[], buff_Entry_Color[], // buffers da linha da ordem pendente
buff_SL[], buff_TP[], // buffers das linhas de StopLoss e TakeProfit
gd_Extremum_Break = 0 // TS_8020_Extremum_Break nos preços do instrumento
;
int
gi_D1_Average_Period = 1, // valor correto para TS_8020_D1_Average_Period
gi_Min_Bars = WRONG_VALUE // número mínimo obrigatório de barras para cálculo
;
int OnInit() {
// verificação do parâmetro introduzido TS_8020_D1_Average_Period:
gi_D1_Average_Period = int(fmin(1, TS_8020_D1_Average_Period));
// transferência de pontos para os preços do instrumento:
gd_Extremum_Break = TS_8020_Extremum_Break * _Point;
// número mínimo obrigatório de barras para cálculo = número de barras do timeframe atual em dias
gi_Min_Bars = int(86400 / PeriodSeconds());
// finalidade dos buffers do indicador:
// re(c)tângulo de todo o intervalo da primeira barra
SetIndexBuffer (0, buff_1st_Bar_Outer, INDICATOR_DATA);
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0);
SetIndexBuffer(1, buff_1st_Bar_Outer_Zero, INDICATOR_DATA);
// re(c)tângulo da região interna da primeira barra
SetIndexBuffer(2, buff_1st_Bar_Inner, INDICATOR_DATA);
PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0);
SetIndexBuffer(3, buff_1st_Bar_Inner_Zero, INDICATOR_DATA);
// linha de sinal
SetIndexBuffer(4, buff_Signal, INDICATOR_DATA);
PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, 0);
SetIndexBuffer(5, buff_Signal_Color, INDICATOR_COLOR_INDEX);
// Linha de colocação da ordem pendente
SetIndexBuffer(6, buff_Entry, INDICATOR_DATA);
PlotIndexSetDouble(3, PLOT_EMPTY_VALUE, 0);
SetIndexBuffer(7, buff_Entry_Color, INDICATOR_COLOR_INDEX);
// Linha SL
SetIndexBuffer(8, buff_SL, INDICATOR_DATA);
PlotIndexSetDouble(4, PLOT_EMPTY_VALUE, 0);
// Linha TP
SetIndexBuffer(9, buff_TP, INDICATOR_DATA);
PlotIndexSetDouble(5, PLOT_EMPTY_VALUE , 0);
IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
IndicatorSetString(INDICATOR_SHORTNAME, "EN 80-20");
return(INIT_SUCCEEDED);
}
Na função padrão OnCalculate, colocamos o código principal do programa, em outras palavras, organizamos o ciclo que irá iterar - do passado para o futuro - as barras do timeframe atual e verificar a presença de sinal nelas usando uma função a partir do módulo de sinal. Declaramos e inicializamos previamente as variáveis necessárias usando os valores iniciais. Tendo em conta o limite - definido pelo usuário - de profundidade de histórico (Bars_Limit), estabelecemos a barra mais antiga do ciclo para o primeiro cálculo. Para as chamadas subsequentes, nós vamos recalcular todas as barras do dia atual, uma vez que o padrão de dois barras, de fato, pertence ao gráfico D1, independentemente do timeframe atual.
Além disso, vamos ter que tomar medidas: se, após a inicialização, não termos limpado previamente os buffers de indicador, então, ao alternar gráficos ou mudar de símbolo, no novo gráfico irão permanecer áreas pintadas não atualizadas. É por isso que, após a inicialização do indicador, será necessário vincular a limpeza de buffers à primeira chamada da função OnCalculate. Como a variável padrão prev_calculated contém zero tanto ao chamar pela primeira vez a função quanto "ao alterar a soma de verificação", será impossível determinar com ajuda dela que se trata da primeira chamada. Para resolver este problema criamos uma estrutura - independente do zeramento da variável prev_calculated - que vai armazenar e processar os dados úteis usados frequentemente nos indicadores:
- sinalizador da primeira execução da função OnCalculate;
- contador de barras contadas não-zerável, ao alterar a soma de verificação;
- sinalizador de alteração da soma de verificação;
- sinalizador do início da nova barra;
- tempo de início da barra atual.
A estrutura que junta todos estes dados será declarada e poderá coletar ou fornecer informações em/de quaisquer funções padrão ou personalizadas. Um nome bastante adequado para a essência desse programa seria "Duende amigo" (Brownie). Podemos colocá-lo no final do código do indicador. Assim, declaramos um objeto-estrutura global chamado go_Brownie:
datetime t_Last_Bar_Time; // tempo da última barra processada
int i_Prew_Calculated; // número de barras contadas
bool b_First_Run; // sinalizador da primeira execução
bool b_History_Updated; // sinalizador da atualização do histórico
bool b_Is_New_Bar; // sinalizador da abertura da nova barra
BROWNIE() { // construtor
// valores por padrão:
t_Last_Bar_Time = 0;
i_Prew_Calculated = WRONG_VALUE ;
b_First_Run = b_Is_New_Bar = true;
b_History_Updated = false;
}
void f_Reset(bool b_Reset_First_Run = true) { // zeramento das variáveis
// valores por padrão:
t_Last_Bar_Time = 0;
i_Prew_Calculated = WRONG_VALUE;
if(b_Reset_First_Run) b_First_Run = true; // zeramento, se houver permissão
b_Is_New_Bar = true;
b_History_Updated = false;
}
void f_Update(int i_New_Prew_Calculated = WRONG_VALUE) { // zeramento das variáveis
// sinalizador da primeira chamada da função padrão OnCalculate
if(b_First_Run && i_Prew_Calculated > 0) b_First_Run = false;
// nova barra?
datetime t_This_Bar_Time = TimeCurrent() - TimeCurrent() % PeriodSeconds();
b_Is_New_Bar = t_Last_Bar_Time == t_This_Bar_Time;
// atualizar o tempo da barra atual?
if(b_Is_New_Bar) t_Last_Bar_Time = t_This_Bar_Time;
if(i_New_Prew_Calculated > -1) {
// existem algumas alterações no histórico?
b_History_Updated = i_New_Prew_Calculated == 0 && i_Prew_Calculated > WRONG_VALUE;
// usar prew_calculated, se for a primeira chamada de OnCalculate
if(i_Prew_Calculated == WRONG_VALUE ) i_Prew_Calculated = i_New_Prew_Calculated;
// ou se não houver atualização no histórico
else if(i_New_Prew_Calculated > 0) i_Prew_Calculated = i_New_Prew_Calculated;
}
}
};
BROWNIE go_Brownie;
Prevemos o informamento do 'Duende amigo' sobre o evento de anulação da inicialização do indicador:
go_Brownie.f_Reset(); // notificar o Duente amigo
}
É possível estender a coleta de informações salvadas pelo 'Duende amigo', caso funções personalizadas ou classes forem precisadas, por exemplo, nos preços, volumes ou tamanho de spread da barra atual (Open, High, Low, Close, tick_volume, volume, spread). É mais fácil pegar os dados prontos a partir da função OnCalculate e transferi-los através do 'Duende amigo' do que utilizar as funções de cópia do Timeseries (CopyOpen, CopyHigh ou CopyRates) — isso vai poupar recursos da CPU e eliminar a necessidade de organizar o tratamento de erros destas funções da linguagem.
Voltemos às principais funções de visualização. A declaração de variáveis e preparação de matrizes usando a estrutura go_Brownie ficará assim:
int
i_Period_Bar = 0, // contador auxiliar
i_Current_TF_Bar = rates_total - int(Bars_Limit) // índice da barra de início de ciclo do timeframe atual
;
static datetime st_Last_D1_Bar = 0; // tempo da última barra D1 a partir do par processado (segunda barra do padrão)
static int si_1st_Bar_of_Day = 0; // índice da primeira barra do dia atual
if(go_Brownie.b_First_Run) { // se este for a primeira execução
// limpar o buffer ao reinicializar:
ArrayInitialize(buff_1st_Bar_Inner, 0); ArrayInitialize(buff_1st_Bar_Inner_Zero, 0);
ArrayInitialize (buff_1st_Bar_Outer, 0); ArrayInitialize(buff_1st_Bar_Outer_Zero, 0);
ArrayInitialize(buff_Entry, 0 ); ArrayInitialize(buff_Entry_Color, 0);
ArrayInitialize(buff_Signal, 0); ArrayInitialize (buff_Signal_Color, 0);
ArrayInitialize(buff_TP, 0);
ArrayInitialize(buff_SL, 0);
st_Last_D1_Bar = 0;
si_1st_Bar_of_Day = 0;
} else { // este não é a primeira execução
datetime t_Time = TimeCurrent();
// profundidade mínimo de recálculo - a partir do dia anterior:
i_Current_TF_Bar = rates_total - Bars(_Symbol, PERIOD_CURRENT, t_Time - t_Time % 86400, t_Time) - 1;
}
ENUM_ENTRY_SIGNAL e_Signal = ENTRY_UNKNOWN; // sinal
double
d_SL = WRONG_VALUE, // nível SL
d_TP = WRONG_VALUE, // nível TP
d_Entry_Level = WRONG_VALUE, // nível de entrada
d_Range_High = WRONG_VALUE, d_Range_Low = WRONG_VALUE // limites do intervalo da primeira barra do padrão
;
datetime
t_Curr_D1_Bar = 0, // tempo da barra atual D1 (segunda barra do padrão)
t_D1_Bar_To_Fill = 0 // tempo da barra D1 que é preciso pintar (primeira barra do padrão)
;
// verificamos que o índice da barra inicial de recálculo esteja dentro do limite aceitável:
i_Current_TF_Bar = int(fmax(0 , fmin(i_Current_TF_Bar, rates_total - gi_Min_Bars)));
while(++i_Current_TF_Bar < rates_total && !IsStopped()) { // iteração do timeframe atual
// aqui vai estar o ciclo principal do programa
}
Ao iterar as barras do timeframe atual, vamos verificar a presença do sinal:
if(e_Signal > 1) continue; // no dia a que pertence a esta barra, não exites sinal
Se, por um lado, existir o sinal e, por outro, ele ser a primeira barra do novo dia, será necessário organizar o preenchimento do intervalo da barra diária anterior. O valor da variável t_D1_Bar_To_Fill do tipo datetime será o sinalizador; se ela não tiver definido o valor WRONG_VALUE, nesta barra o preenchimento não será´necessário. Pressupõe-se que a partir desta primeira barra deve começar a linha de sinal, mas para melhor compreensão do layout, vamos estendê-lo até a última barra do dia anterior. Como os cálculos do nível de sinal, cores das linhas e as áreas sombreadas para barras de alta e baixa são diferentes, vamos fazer mais dois blocos semelhantes entre si:
if(st_Last_D1_Bar < t_Curr_D1_Bar) { // esta é a barra do novo dia
t_D1_Bar_To_Fill = Time[i_Current_TF_Bar — 1] — Time[i_Current_TF_Bar — 1] % 86400;
si_1st_Bar_of_Day = i_Current_TF_Bar;
}
else t_D1_Bar_To_Fill = WRONG_VALUE; // barra do dia anterior, não é necessário um novo preenchimento
st_Last_D1_Bar = t_Curr_D1_Bar; // lembramos
if(t_D1_Bar_To_Fill != WRONG_VALUE) { // nova barra D1
// Preenchimento da barra D1 do dia anterior:
i_Period_Bar = i_Current_TF_Bar;
if(d_Entry_Level < d_Range_High) { // barra de baixa D1
if(Show_Outer) while(--i_Period_Bar > 0) { // todo o intervalo
if(Time[i_Period_Bar] < t_D1_Bar_To_Fill) break;
buff_1st_Bar_Outer_Zero[i_Period_Bar] = d_Range_Low;
buff_1st_Bar_Outer[i_Period_Bar] = d_Range_High;
}
if(Show_Inner) { // área interna
i_Period_Bar = i_Current_TF_Bar;
while(--i_Period_Bar > 0) {
if(Time[i_Period_Bar] < t_D1_Bar_To_Fill) break;
buff_1st_Bar_Inner_Zero[i_Period_Bar] = d_Range_Low + 0.2 * (d_Range_High — d_Range_Low);
buff_1st_Bar_Inner[i_Period_Bar] = d_Range_High — 0.2 * (d_Range_High — d_Range_Low);
}
}
// início da linha de sinal — a partir da última barra do dia anterior
buff_Signal[i_Current_TF_Bar] = buff_Signal[i_Current_TF_Bar — 1] = d_Range_Low — gd_Extremum_Break;
buff_Signal_Color[i_Current_TF_Bar] = buff_Signal_Color[i_Current_TF_Bar — 1] = 0;
} else { // barra de alta D1
if(Show_Outer) while(--i_Period_Bar > 0) { // todo o intervalo
if(Time[i_Period_Bar] < t_D1_Bar_To_Fill) break;
buff_1st_Bar_Outer_Zero[i_Period_Bar] = d_Range_High;
buff_1st_Bar_Outer[i_Period_Bar] = d_Range_Low;
}
if(Show_Inner) { // área interna
i_Period_Bar = i_Current_TF_Bar;
while(--i_Period_Bar > 0) {
if(Time[i_Period_Bar] < t_D1_Bar_To_Fill) break;
buff_1st_Bar_Inner_Zero[i_Period_Bar] = d_Range_High — 0.2 * (d_Range_High — d_Range_Low);
buff_1st_Bar_Inner[i_Period_Bar] = d_Range_Low + 0.2 * (d_Range_High — d_Range_Low);
}
}
// início da linha de sinal — a partir da última barra do dia anterior
buff_Signal[i_Current_TF_Bar] = buff_Signal[i_Current_TF_Bar — 1] = d_Range_High + gd_Extremum_Break;
buff_Signal_Color[i_Current_TF_Bar] = buff_Signal_Color[i_Current_TF_Bar — 1] = 1;
}
} else continue;
Aqui (dentro do ciclo de iteração de barras do timeframe atual) vamos organizar o desenho do resto de linhas de layout. Deixe-me lembrá-lo, a linha de sinal deve terminar na barra, onde ela foi tocada pelo preço. Nesta barra deve começar a linha da ordem pendente. Ela deve terminar na barra de contato com o preço, e nesta mesma barra devem começar as linhas Take Profit e Stop Loss. Na barra de contato de uma delas com o preço, o layout deste padrão será finalizado:
i_Period_Bar = i_Current_TF_Bar;
if(d_Entry_Level < d_Range_High) { // barra de baixa D1
while(++i_Period_Bar < rates_total) {
if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) break;
buff_Signal[i_Period_Bar] = d_Range_Low — gd_Extremum_Break;
buff_Signal_Color[i_Period_Bar] = 0;
if(d_Range_Low — gd_Extremum_Break >= Low[i_Period_Bar]) break;
}
} else { // barra de alta D1
while(++i_Period_Bar < rates_total) {
if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) break;
buff_Signal[i_Period_Bar] = d_Range_High + gd_Extremum_Break;
buff_Signal_Color[i_Period_Bar] = 1;
if(d_Range_High + gd_Extremum_Break <= High[i_Period_Bar]) break;
}
}
// Linha de entrada até à barra pela qual foi atravessada:
if(d_Entry_Level < d_Range_High) { // barra de baixa D1
while(++i_Period_Bar < rates_total) {
if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) break;
buff_Entry[i_Period_Bar] = d_Range_Low;
buff_Entry_Color[i_Period_Bar] = 0;
if(d_Range_Low <= High[i_Period_Bar]) {
if(buff_Entry[i_Period_Bar — 1] == 0.) {
// início e final na mesma barra, estendemos 1 barra no passado
buff_Entry[i_Period_Bar — 1] = d_Range_Low;
buff_Entry_Color[i_Period_Bar — 1] = 0;
}
break;
}
}
} else { // barra de baixa D1
while(++i_Period_Bar < rates_total) {
if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) break;
buff_Entry[i_Period_Bar] = d_Range_High;
buff_Entry_Color[i_Period_Bar] = 1;
if(d_Range_High >= Low[i_Period_Bar]) {
if(buff_Entry[i_Period_Bar — 1] == 0.) {
// início e final na mesma barra, estendemos 1 barra no passado
buff_Entry[i_Period_Bar — 1] = d_Range_High;
buff_Entry_Color[i_Period_Bar — 1] = 1;
}
break;
}
}
}
// Linhas TP e SL até à barra pela qual foi atravessada uma delas:
if(d_Entry_Level < d_Range_High) { // barra de baixa D1
// SL igual ao mínimo desde o início do dia:
d_SL = Low[ArrayMinimum(Low, si_1st_Bar_of_Day, i_Period_Bar — si_1st_Bar_of_Day)];
while(++i_Period_Bar < rates_total) {
if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) break;
buff_SL[i_Period_Bar] = d_SL;
buff_TP[i_Period_Bar] = d_TP;
if(d_TP <= High[i_Period_Bar] || d_SL >= Low[i_Period_Bar]) {
if(buff_SL[i_Period_Bar — 1] == 0.) {
// início e final na mesma barra, estendemos 1 barra no passado
buff_SL[i_Period_Bar — 1] = d_SL;
buff_TP[i_Period_Bar — 1] = d_TP;
}
break;
}
}
} else { // barra de alta D1
// SL igual ao máximo desde o início do dia:
d_SL = High[ArrayMaximum(High, si_1st_Bar_of_Day, i_Period_Bar — si_1st_Bar_of_Day)];
while(++i_Period_Bar < rates_total) {
if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) break;
buff_SL[i_Period_Bar] = d_SL;
buff_TP[i_Period_Bar] = d_TP;
if(d_SL <= High[i_Period_Bar] || d_TP >= Low[i_Period_Bar]) {
if(buff_SL[i_Period_Bar — 1] == 0.) {
// início e final na mesma barra, estendemos 1 barra no passado
buff_SL[i_Period_Bar — 1] = d_SL;
buff_TP[i_Period_Bar — 1] = d_TP;
}
break;
}
}
}
Fora do ciclo, colocamos o código de chamada da função de alerta sobre o sinal f_Do_Alert. A função pode trabalhar com arquivos de áudio, quer dizer, para sinais de compra e venda, é possível adicionar, nas configurações personalizadas, a ativação desta opção e a escolha de arquivos separados. Listagem da função:
string s_Message, // texto para o alerta
bool b_Alert = true, // exibir janela pop-up?
bool b_Sound = false, // reproduzir o arquivo de som?
bool b_Email = false, // enviar e-mail?
bool b_Notification = false, // enviar notificação push?
string s_Email_Subject = "", // tema para a mensagem do e-mail
string s_Sound = "alert.wav" // arquivo de som
) {
static string ss_Prev_Message = "houve silêncio"; // texto do alerta anterior
static datetime st_Prev_Time; // tempo da barra do alerta anterior
datetime t_This_Bar_Time = TimeCurrent() — PeriodSeconds() % PeriodSeconds(); // tempo da barra anterior
if(ss_Prev_Message != s_Message || st_Prev_Time != t_This_Bar_Time) {
// alerta de outra e/ou da primeira nesta barra
// lembre-se:
ss_Prev_Message = s_Message;
st_Prev_Time = t_This_Bar_Time;
// gerar cadeia de caracteres da mensagem:
s_Message = StringFormat("%s | %s | %s | %s",
TimeToString(TimeLocal(), TIME_SECONDS), // tempo local
_Symbol, // símbolo
StringSubstr(EnumToString(ENUM_TIMEFRAMES(_Period)), 7), // timeframe
s_Message // mensagem
);
// enviar sinal de alerta:
if(b_Alert) Alert(s_Message);
if(b_Email) SendMail(s_Email_Subject + " " + _Symbol, s_Message);
if(b_Notification) SendNotification(s_Message);
if(b_Sound) PlaySound(s_Sound);
}
}
Este são o código de autenticação da chamada desta função e a formação do texto de mensagem para ela, localizados no corpo do programa, antes de o manipulador de eventos OnCalculate finalizar:
i_Period_Bar = rates_total — 1; // barra atual
if(Alert_Popup + Alert_Email + Alert_Push == 0) return(rates_total); // tudo desativado
if(buff_Signal[i_Period_Bar] == 0) return(rates_total); // pronto ou não há nada que apanhar
if(
buff_Signal[i_Period_Bar] > High[i_Period_Bar]
||
buff_Signal[i_Period_Bar] < Low[i_Period_Bar]
) return(rates_total); // não existe contato da linha de sinal
// texto da mensagem:
string s_Message = StringFormat("TS 80-20: precisa %s @ %s, TP: %s, SL: %s",
buff_Signal_Color[i_Period_Bar] > 0 ? "BuyStop" : "SellStop",
DoubleToString(d_Entry_Level, _Digits),
DoubleToString(d_TP, _Digits),
DoubleToString(d_SL, _Digits)
);
// alerta:
f_Do_Alert(s_Message, Alert_Popup, false, Alert_Email, Alert_Push, Alert_Email_Subj);
return(rates_total); // finalização do trabalho de OnCalculate
Todo o código-fonte do indicador está no arquivo anexado com o nome TS_80-20.mq5. Quanto ao seu uso, podemos dizer que seu layout tem uma melhor apresentação nos gráficos de minutos.
É importante levar em conta, ao usar este layout, que o indicador utiliza os dados das barras, em vez de uma sequência de ticks dentro das barras. Ou seja, se, na mesma barra, o preço atravessar várias linhas do layout (por exemplo, as linas Take Profit e Stop Loss), não sempre será possível determinar qual delas foi atravessada primeiro. Outro ponto desfavorável está relacionado com o fato de as barras de início e final de linha não coincidirem, caso contrário as linhas a partir buffer de tipo DRAW_LINE e DRAW_COLOR_LINE simplesmente não seriam visíveis para o usuário. Embora estas características tornam o layout não totalmente preciso, ele continua sendo ainda muito convincente.
Expert Advisor para teste da Estratégia de Negociação '80-20'
Expert Advisor para teste de estratégias do livro Street Smarts: High Probability Short-Term Trading Strategies descrito em detalhe no primeiro artigo. Vamos fazer nele duas alterações significativas. O primeiro tem a ver com o fato de que o módulo de sinal será usado também no indicador e, portanto, ele fará nele um cálculo racional de níveis de negociação. Nós já temos feito isto acima: a função fe_Get_Entry_Signal,, além do estado do sinal, retorna os níveis de colocação das ordens Stop Loss e Take Profit. Por isso, removemos, a partir da versão anterior do Expert Advisor, a parte relevante do código, adicionamos as variáveis para recepção do código, a partir da função, e editamos a chamada desta função. Eu não vou fazer a listagem dos blocos novos e velhos do código, você pode vê-los no arquivo anexado (linhas de 128 a 141).
Como a Estratégia de Negociação tem de lidar com a tendência de curto prazo, ao contrário dos dois anteriores, justifica-se uma segunda adição significativa ao código base do Expert Advisor. Ela assume que o recuo vai acontecer uma vez durante o dia e é improvável que seja repetido. Isso significa que o robô deve fazer apenas uma entrada e ignorar o sinal existente todo o tempo restante até o dia seguinte. A maneira mais simples de implementar isso consiste em usar um sinalizador especial, por exemplo, uma variável estática o globar do tipo bool na memória do programa. Mas se o trabalho do Expert Advisor for interrompido por qualquer motivo, em seguida, perde-se o valor do sinalizador. Portanto, após reiniciado o Expert Advisor, dever haver alguma chance de verificar se o sinal de hoje foi modificado anteriormente. Para este efeito, é possível analisar o histórico de negociação de hoje, armazenar a data da última entrada nas variáveis globais do terminal. Nós vamos usar a segunda opção, pois é muito mais fácil de implementar.
Nós damos ao usuário a capacidade de controlar a opção 'uma entrada por dia', bem como definir o identificador de cada versão do robô em execução, uma vez que ele é necessário para a utilização de variáveis globais de nível de terminal:
input uint Magic_Number = 2016; // Identificado do Expert Advisor(Magic Number)
Para implementação da opção 'uma entrada por dia', adicionamos a declaração das variáveis necessárias ao bloco de determinação de variáveis globais do programa. Na função OnInit, vamos inicializá-las:
gs_Prefix // identificador de nomes (super)de variáveis globais
;
bool
gb_Position_Today = false,
gb_Pending_Today = false
;
int OnInit() {
...
// Criação do prefixo de nomes (super)de variáveis globais:
gs_Prefix = StringFormat("SSB %s %u %s", _Symbol, Magic_Number, MQLInfoInteger(MQL_TESTER) ? "t " : "");
// Hoje o robô trabalhou com ordens pendentes ou de mercado?
gb_Position_Today = int(GlobalVariableGet(gs_Prefix + "Last_Position_Date")) == TimeCurrent() — TimeCurrent() % 86400;
gb_Pending_Today = int(GlobalVariableGet(gs_Prefix + "Last_Pending_Date")) == TimeCurrent() — TimeCurrent() % 86400;
...
}
Aqui o robô considera os valores das variáveis globais e compara o tempo registrado nelas com o tempo de início do dia, desse modo determina se o sinal de hoje já foi trabalhado. Organizamos o registro de tempo nessas variáveis em dois lugares; no código de colocação de ordem pendente adicionamos o bloco apropriado:
if(Log_Level > LOG_LEVEL_NONE) Print("Erro de colocação da ordem pendente");
// a distância a partir do preço atual é insuficiente:(
if(Log_Level > LOG_LEVEL_ERR)
PrintFormat("É impossível colocar a ordem pendente para o 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)
);
} else { // conseguiu
// atualizar o sinalizador:
GlobalVariableSet( // nas variáveis globais do terminal
gs_Prefix + "Last_Pending_Date",
TimeCurrent() — TimeCurrent() % 86400
);
gb_Pending_Today = true; // nas variáveis globais do programa
}
Colocamos o segundo bloque depois do código que define a posição recentemente aberta:
if(PositionGetDouble(POSITION_SL) == 0.) { // nova posição
if(!gb_Position_Today) { // esta é a primeira posição de hoje
// atualizar o sinalizador:
GlobalVariableSet( // nas variáveis globais do terminal
gs_Prefix + "Last_Position_Date",
TimeCurrent() — TimeCurrent() % 86400
);
gb_Position_Today = true; // nas variáveis globais do programa
}
...
Não exitem outras alterações significativas no código da versão anterior do Expert Advisor. No anexo ao artigo, você pode encontrar o código fonte da nova versão em sua forma final.
Teste de estratégia com base no histórico de dados
Os autores do sistema de negociação como prova de sua viabilidade citam padrões em gráficos de final do século passado, no entanto nós precisamos verificar sua relevância no mercado de hoje. Para os testes, eu tomei o par de moedas mais popular no mercado Forex EURUSD, bem como o mais volátil USDJPY e o par de metais XAUUSD. Eu aumentei 10 vezes os recuos especificados por Raschke e Connors, porque naquela época eram usadas cotações de quatro dígitos, e ou testei o Expert Advisor nas de cinco dígitos. Como não havia nenhuma orientação feita pelos autores quanto aos parâmetros de trail, eu escolhi os que pareciam mais adequados para o timeframe diário e volatilidade do instrumento. O mesmo se aplica ao algoritmo de cálculo de Take Profit adicionado às suas regras originais, quer dizer, o coeficiente para seu cálculo foi selecionado arbitrariamente, sem uma otimização profunda.
Gráfico de mudanças do balanço durante o teste no histórico de cinco anos EURUSD com regras originais (sem Take Profit):
Com as mesmas configurações adicionamos o Take Profit:
Gráfico de mudanças do balanço durante o teste no histórico de cinco anos USDJPY:
O mesmo instrumento e timeframe com as mesmas configurações, mas com adição de Take Profit:
As regras originais em cotações diárias de ouro nos últimos 4 anos mostra o seguinte gráfico de mudança de balanço:
Informações completas sobre cada teste das configurações do robô podem ser encontradas no arquivo anexado ao artigo, nele encontrará relatórios completos de cada teste.
Conclusão
As regras programadas no módulo de sinal correspondem ao sistema de negociação 80-20' descrito a partir do livro de Linda Raschke e Laurence Connors Street Smarts: High Probability Short-Term Trading Strategies. Há uma pequena extensão das regras de direitos autorais. Estas ferramentas (robô e indicador) devem ajudar a fazer suas próprias conclusões sobre a relevância da Estratégia de Negociação no mercado de hoje. Na minha humilde opinião, ela precisa de uma atualização séria. Neste artigo eu tentei falar em detalhe sobre a criação do código do módulo de sinal e seu robô e indicador, e pois espero que isto ajude aqueles que decidam empreender tal modernização. Além da atualização das regras, você pode tentar escolher os instrumentos que melhor encaixem no sistema de negociação, as melhores configurações do sinal e o melhor acompanhamento de posições.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/2785
- 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