English Русский 中文 Español Deutsch 日本語
Mini-emulador do mercado ou Testador de estratégias manual

Mini-emulador do mercado ou Testador de estratégias manual

MetaTrader 5Testador | 15 dezembro 2017, 16:09
5 159 2
Dmitriy Zabudskiy
Dmitriy Zabudskiy

Introdução

O trabalho no mercado de moedas começa com o estudo dos fundamentos teóricos: estratégias de lucro, métodos de análise de dados, modelos de negociação bem-sucedidos. Todos os traders principiantes são guiados pela mesma ideia, isto é, ganhar dinheiro. No entanto, todos definem suas próprias prioridades, prazos, possibilidades, objetivos, etc.

Existem vários cenários para o comportamento de um trader novato.

  • "Tudo de uma vez": a maioria dos iniciantes quer ganhar muito e rapidamente. Eles sucumbem a anúncios que prometem estratégias mágicas, sem falhas e por muito pouco dinheiro ou mesmo de graça. Isso parece algo rápido e fácil, porém, o depósito também se perde rápido e fácil.
  • "Estudar, estudar e estudar novamente": há novatos que mergulham a fundo no aprendizado, sem acreditar em contos de fadas. Eles estudam minuciosamente as leis do mercado e os sistemas de negociação. Logo, começam a negociar usando uma conta real, mas os ganhos são menores do que o esperado a partir dos livros. Como isso acontece e o que fazer a seguir?

Estando na primeira situação, a maioria dos principiantes fica sempre desapontada com os mercados financeiros. Novatos do segundo cenário, por sua vez, continuam estudando a teoria e suas estratégias práticas.

Este artigo se destina principalmente a iniciantes que não podem esperar negociar numa conta de demonstração e testar suas estratégias. E, neste caso, também há dois possíveis cenários:

  • Está o grupo que quer submeter a uma prova uma estratégia de curto prazo previamente estudada. Mas, se trabalham em tempo integral, para fazer isso, só podem usar horas noturnas e fins de semana, porém, o domingo o mercado está fechado.
  • Está uma segunda categoria de traders, os que trabalham com estratégias de médio ou longo prazos e que não querem passar um ano inteiro refinando sua estratégia numa conta de demostração.

Claro, você pode me perguntar: "mas, para que complicar tudo, quando existem gráficos baseados no histórico, onde se pode rápida e eficientemente testar qualquer estratégia?" Na prática, no entanto, isso nem sempre funciona, pois, muitas vezes acontece que uma estratégia com esplêndidos resultados de backtest funciona muito mal no "mercado ao vivo", por algum motivo. De qualquer forma, o melhor é aprender a operar em sistemas mais ou menos próximos da realidade. Por exemplo, os emuladores de mercado são bastante apropriados para esses fins, e podem ser comprados na Internet.

Neste artigo, quero falar sobre a minha própria realização de um sistema desse tipo no MetaTrader 5. Eu programei o indicador Mini-emulador do mercado com funcionalidade reduzida em comparação com a versão completa do terminal. Ele é projetado para verificação teórica de estratégias.

Recursos do aplicativo

O aplicativo possui seu próprio painel de controle, bem como alguns botões do "sistema mãe", ou seja, do próprio terminal MetaTrader 5.

Estas estão as principais ações que podem ser realizadas pelo emulador.

  1. Só podem ser colocadas duas ordens em diferentes direções: buy e sell. Além disso, pode-se definir a configuração do stop-loss e take-profit antes de colocar a ordem e seu volume. Uma vez que a ordem é colocada, ela pode ser modificada, e seus níveis de stop podem ser arrastados.
  2. Existem apenas sete velocidades de plotagem, elas podem ser divididas em três grupos. A primeira, a "de joalharia", envolve uma plotagem com base na geração de ticks a partir do timeframe de minuto, quase como no Testador de estratégias. A segunda considera os dados de minuto construídos sem geração (este modo é mais rápido, mas menos preciso). O terceiro grupo é o mais rápido: é construída uma vela por segundo, independentemente do timeframe.
  3. São fornecidas as informações de negociação atuais, isto é, lucro, número de pontos e volume. Os dados são fornecidos para as ordens atuais e passadas, bem como a negociação desde o início da emulação.
  4. Estão disponíveis todos os objetos gráficos padrão presentes no terminal.
  5. Todos os timeframes padrão são suportados (são alternados pelos botões do painel do terminal).


Fig.1. Controles e aparência do aplicativo


Sistema de geração de ticks

O princípio da geração de ticks foi tomado do artigo "O algoritmo de geração de ticks dentro do examinador de estratégia do MetaTrader 5". Ele foi revisado criativamente e apresentado como uma versão alternativa.

Duas funções são responsáveis ​​pela geração de ticks - uma principal e outra auxiliar.

A função principal éTick Generation. Nela são carregados dois parâmetros: a vela em si e a matriz para dados de resposta (ticks). E se os quatro níveis de preço da vela de entrada forem iguais, o volume de ticks será igual a um tick. Isto foi feito para eliminar a possibilidade de divisão por zero erros em caso de transferência de dados incorretos.

Isto é seguido pela formação de uma nova vela. Se houver 1-3 ticks dentro da vela, o processo de geração de ticks continua conforme descrito no artigo acima mencionado.

Se houver mais de 3 ticks, o trabalho se torna mais complicado. A vela passada é dividida em três partes desiguais (o princípio da divisão é fornecido no código abaixo, separadamente para velas de baixa e de alta). Em seguida, caso não haja mais ticks na parte superior e inferior, é realizada a correção. Depois, o controle é transferido para a função auxiliar dependendo da natureza da vela.

//+------------------------------------------------------------------+
//| Func Tick Generation                                             |
//+------------------------------------------------------------------+
void func_tick_generation(
MqlRates &rates,      // dados sobre a vela
double &tick[]        // matriz dinâmica de ticks
)
{
 if(rates.open==rates.close && rates.high==rates.low && rates.open==rates.high){rates.tick_volume=1;}
 if(rates.tick_volume<4)// menos de quatro ticks
 {
ArrayResize(tick,int(rates.tick_volume));         // medimos quantos ticks tem a matriz
if(rates.tick_volume==1)tick[0]=rates.close;      // um tick
if(rates.tick_volume==2)                          // dois ticks
{
 tick[0]=rates.open;
 tick[1]=rates.close;
}
if(rates.tick_volume==3)                          // três ticks
{
 tick[0]=rates.open;
 tick[2]=rates.close;
 if(rates.open==rates.close)                      // tomou uma direção e voltou para o nível Open
 {
if(rates.high==rates.open)tick[1]=rates.low;
if(rates.low==rates.open)tick[1]=rates.high;
 }
 if(rates.close==rates.low && rates.open!=rates.high)tick[1]=rates.high;           // tomou uma direção e rompeu o nível de Open
 if(rates.close==rates.high && rates.open!=rates.low)tick[1]=rates.low;
 if(rates.open==rates.high && rates.close!=rates.low)tick[1]=rates.low;            // tomou uma direção, recuou, mas não atingiu o nível de Open
 if(rates.open==rates.low && rates.close!=rates.high)tick[1]=rates.high;
 if((rates.open==rates.low && rates.close==rates.high) || (rates.open==rates.high && rates.close==rates.low))
 {
tick[1]=NormalizeDouble((((rates.high-rates.low)/2)+rates.low),_Digits);       // vários puntos numa só direção
 }
}
 }
 if(rates.tick_volume>3)      // mais de três ticks
 {

 // calculamos o tamanho da vela por pontos
int candle_up=0;
int candle_down=0;
int candle_centre=0;
if(rates.open>rates.close)
{
 candle_up=int(MathRound((rates.high-rates.open)/_Point));
 candle_down=int(MathRound((rates.close-rates.low)/_Point));
}
if(rates.open<=rates.close)
{
 candle_up=int(MathRound((rates.high-rates.close)/_Point));
 candle_down=int(MathRound((rates.open-rates.low)/_Point));
}
candle_centre=int(MathRound((rates.high-rates.low)/_Point));
int candle_all=candle_up+candle_down+candle_centre;      // comprimento total do movimento
int point_max=int(MathRound(double(candle_all)/2));      // número máximo de ticks
double share_up=double(candle_up)/double(candle_all);
double share_down=double(candle_down)/double(candle_all);
double share_centre=double(candle_centre)/double(candle_all);

// calculamos o número de pontos de referência em cada área
char point=0;
if(rates.tick_volume<10)point=char(rates.tick_volume);
else point=10;
if(point>point_max)point=char(point_max);
char point_up=char(MathRound(point*share_up));
char point_down=char(MathRound(point*share_down));
char point_centre=char(MathRound(point*share_centre));

// verificamos a presença de pontos de referência nas faixas selecionadas
if(candle_up>0 && point_up==0)
{point_up=1;point_centre=point_centre-1;}
if(candle_down>0 && point_down==0)
{point_down=1;point_centre=point_centre-1;}

// alteramos o tamanho da matriz de saída
ArrayResize(tick,11);
char p=0;                     // índice da matriz de ticks (tick[])
tick[p]=rates.open;           // primeiro tick igual ao preço de abertura
if(rates.open>rates.close)    // descendente
{
 func_tick_small(rates.high,1,candle_up,point_up,tick,p);
 func_tick_small(rates.low,-1,candle_centre,point_centre,tick,p);
 func_tick_small(rates.close,1,candle_down,point_down,tick,p);
 ArrayResize(tick,p+1);
}
if(rates.open<=rates.close)   // ascendente ou doji
{
 func_tick_small(rates.low,-1,candle_down,point_down,tick,p);
 func_tick_small(rates.high,1,candle_centre,point_centre,tick,p);
 func_tick_small(rates.close,-1,candle_up,point_up,tick,p);
 ArrayResize(tick,p+1);
}
 }
}

Como o nome sugere, a função Tick Small realiza uma pequena geração de ticks. Ela recebe informações sobre o último tick processado, a direção a seguir (para cima ou para baixo), o número de etapas necessárias, o último preço e passa as etapas calculadas para a matriz de ticks acima mencionada. A matriz resultante contém não mais de 11 ticks.

//+------------------------------------------------------------------+
//| Func Tick Small                                                  |
//+------------------------------------------------------------------+
void func_tick_small(
 double end,        // fim do movimento
 char route,        // direção do movimento
 int candle,        // distância do movimento
 char point,        // número de pontos
 double &tick[],    // matriz de ticks
 char&i           // índice atual da matriz
 )
{
 if(point==1)
 {
i++;
if(i>10)i=10;       // correção
tick[i]=end;
 }
 if(point>1)
 {
double wave_v=(point+1)/2;
double step_v=(candle-1)/MathFloor(wave_v)+1;
step_v=MathFloor(step_v);
for(char p_v=i+1,i_v=i; p_v<i_v+point;)
{
 i++;
 if(route==1)tick[i]=tick[i-1]+(step_v*_Point);
 if(route==-1)tick[i]=tick[i-1]-(step_v*_Point);
 p_v++;
 if(p_v<i_v+point)
 {
i++;
if(route==1)tick[i]=tick[i-1]-_Point;
if(route==-1) tick[i]=tick[i-1]+_Point;
 }
 p_v++;
}
if(NormalizeDouble(tick[i],_Digits)!=NormalizeDouble(end,_Digits))
{
 i++;
 if(i>10)i=10;    // correção
 tick[i]=end;
}
 }
}

Isto é, por assim dizer, o coração de toda a plotagem "de joalheria" (na conclusão, explica-se por que se chama "de joalheria"). Agora avançamos para a questão da interação do sistema.

Interoperabilidade e intercâmbio de dados

O código do sistema parece confuso à primeira vista. As funções não são inteiramente sequenciais e podem ser chamadas de diferentes partes do programa. Isso é assim, porque o sistema deve interagir não apenas com o usuário, mas também com o terminal. Aqui está um esquema aproximado dessas interações (Fig. 2):


Fig. 2. Esquema de interações no aplicativo

O controle de alterações para períodos foi emprestado do shell do terminal, a fim de reduzir o número de objetos de controle na janela do indicador. Como, ao alternar entre períodos, o aplicativo é reiniciado e todas as variáveis globais e locais são substituídas, a matriz de dados é copiada cada vez que ocorre uma alternância. São copiados os dados de dois períodos, nomeadamente, М1 e o selecionado. Os parâmetros para o processamento subsequente desses dados são selecionados no painel: a velocidade e a qualidade da plotagem ("de joelharia" ou simples rápida). Uma vez que tudo esteja pronto, da-se início à plotagem do gráfico.

Pode-se tanto colocar como apagar ordens por meio do painel de controle. Para fazer isso, o programa acessa a função "COrder". Esta classe também é usada para gerenciar ordens à medida que o gráfico é plotado.

Como mencionado acima, se o período do gráfico for alterado, o indicador será reiniciado. Por conseguinte, a fim de fornecer comunicação em toda a estrutura do aplicativo, usam-se as variáveis globais do terminal do cliente. Ao contrário das variáveis ​​globais convencionais, elas são armazenadas por mais tempo (4 semanas) e são tolerantes aos reinícios. A única desvantagem é que os dados só podem ser do tipo double. Porém, geralmente, isso é muito mais conveniente do que estar sempre criando um arquivo separado, escrever e ler a partir dele.

Passemos a tratar do código da interoperabilidade, diretamente.

Realização do código

Início do código

Primeiro, são realizados procedimento padrão para declarar variáveis. A seguir, na função OnInit(), são inicializados os buffers, é plotada a interface do painel de controle e é calculado o recuo em relação ao início da emulação. Esse deslocamento é necessário para garantir que a plotagem não comece com um gráfico vazio, mas sim com certo histórico, a fim de iniciar imediatamente a verificação da estratégia.

Aqui copiamos as matrizes de dados e lemos a variável básica de comunicação (chamada time_end), que indica o momento em que a plotagem parou:

//--- definimos até que momento (tempo) foi plotado o indicador
 if(GlobalVariableCheck(time_end))end_time_indicator=datetime(GlobalVariableGet(time_end));

Desta forma, o indicador sempre sabe onde parou. A função OnInit() termina com a chamada do temporizador, que, de fato, dá o comando para exibir um novo tick ou para formar uma vela inteira (dependendo da velocidade).

Função temporizador

O estado do botão "play" no painel de controle é verificado no início da função. Se for pressionado, será executado o código adicional.

Primeiro, é determinada a barra do indicador em que a plotagem foi interrompida (em relação à hora atual). O tempo final de plotagem end_time_indicator e o tempo atual são tomados como pontos finais. Os dados são recalculados a cada segundo, pois o gráfico se move constantemente (exceto aos sábados e domingos) e não está sincronizado no tempo. Assim, o gráfico é rastreado dinamicamente e movido pela função ChartNavigate().

Depois disso, são calculadas as variáveis number_now_rates, bars_now_rates, all_bars_indicator. Logo, é verificado o tempo. Se, de acordo com os parâmetros de entrada, o tempo não estiver chegando ao fim, a plotagem será realizada usando a função func_merger(). Em seguida, são verificadas as posições atuais e sua rentabilidade, com os valores registrados nas variáveis ​​globais e exibição no bloco de informações do indicador.

Além disso, aqui é chamada a classe "COrder", nomeadamente, são invocadas suas partes responsáveis pela exclusão automática da ordem como resultado das ações do usuário (position.Delete) ou desencadeamento dos níveis de stop (position.Check).

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
{
//---
 if(button_play)
 {
end_bar_indicator=Bars(_Symbol,_Period,end_time_indicator,TimeCurrent());      // número de barras - da mais antiga até a mais recente
ChartNavigate(0,CHART_END,-end_bar_indicator);                                 // transferência do gráfico (indicador) para a plotagem atual
number_now_rates=(Bars(_Symbol,_Period,real_start,end_time_indicator)-1);      // barra atualmente utilizada para plotagem
bars_now_rates=(Bars(_Symbol,_Period,real_start,stop)-1);                      // número de barras usadas a partir do histórico de acordo com o período atual
all_bars_indicator=(Bars(_Symbol,_Period,real_start,TimeCurrent()))-1;         // número de barras desde o início da plotagem até agora

if(end_time_indicator<stop)                                                    // verificação do tempo de plotagem
{
 func_merger();
 ObjectSetDouble(0,line_bid,OBJPROP_PRICE,price_bid_now);
 if(ObjectFind(0,line_ask)>=0)
 {ObjectSetDouble(0,line_ask,OBJPROP_PRICE,price_ask_now);}

 //--- cálculos atuais de ordens
 int point_now=0;
 double vol_now=0;
 double money_now=0;
 if(ObjectFind(0,order_buy)>=0 && GlobalVariableGet(order_buy)>0)             // existe ordem de compra
 {
int p_now=int((price_bid_now-GlobalVariableGet(order_buy))*dig_pow);
double v_now=GlobalVariableGet(vol_buy);
double m_now=p_now*v_now*10;
point_now+=p_now;
vol_now+=v_now;
money_now+=m_now;
 }
 if(ObjectFind(0,order_sell)>=0 && GlobalVariableGet(order_sell)>0)           // existe ordem de venda
 {
int p_now=int((GlobalVariableGet(order_sell)-price_ask_now)*dig_pow);
double v_now=GlobalVariableGet(vol_sell);
double m_now=p_now*v_now*10;
point_now+=p_now;
vol_now+=v_now;
money_now+=m_now;
 }
 GlobalVariableSet(info_point_now,point_now);
 GlobalVariableSet(info_vol_now,vol_now);
 GlobalVariableSet(info_money_now,money_now);
}

COrder position;    //objeto de classe "COrder"
position.Delete(price_bid_now,price_ask_now,(-1));
position.Check(end_time_indicator,GlobalVariableGet(order_buy),GlobalVariableGet(tp_buy),GlobalVariableGet(sl_buy),
 GlobalVariableGet(order_sell),GlobalVariableGet(tp_sell),GlobalVariableGet(sl_sell));

func_info_print("Money All: ",info_money_all,2);
func_info_print("Money Last: ",info_money_last,2);
func_info_print("Money Now: ",info_money_now,2);
func_info_print("Volume All: ",info_vol_all,2);
func_info_print("Volume Last: ",info_vol_last,2);
func_info_print("Volume Now: ",info_vol_now,2);
func_info_print("Point All: ",info_point_all,0);
func_info_print("Point Last: ",info_point_last,0);
func_info_print("Point Now: ",info_point_now,0);

position.Modify();
 }
//--- controle do botão Hide
 char x=char(GlobalVariableGet("hide"));
 if(x==1)
 {
ObjectSetInteger(0,"20",OBJPROP_STATE,false);
ObjectSetInteger(0,"14",OBJPROP_YDISTANCE,24);
ObjectSetInteger(0,"15",OBJPROP_YDISTANCE,24);
ObjectSetInteger(0,"16",OBJPROP_YDISTANCE,24);
ObjectSetInteger(0,"17",OBJPROP_YDISTANCE,24);
ObjectSetInteger(0,"18",OBJPROP_YDISTANCE,24);
ObjectSetInteger(0,"19",OBJPROP_YDISTANCE,24);
 }
 if(x==2)
 {
ObjectSetInteger(0,"20",OBJPROP_STATE,true);
ObjectSetInteger(0,"14",OBJPROP_YDISTANCE,-24);
ObjectSetInteger(0,"15",OBJPROP_YDISTANCE,-24);
ObjectSetInteger(0,"16",OBJPROP_YDISTANCE,-24);
ObjectSetInteger(0,"17",OBJPROP_YDISTANCE,-24);
ObjectSetInteger(0,"18",OBJPROP_YDISTANCE,-24);
ObjectSetInteger(0,"19",OBJPROP_YDISTANCE,-24);
 }
}

Classe COrder

Nesta classe, encontram-se as funções de abertura e fechamento de posições, bem como a alteração e verificação do estado atual das ordens (gerenciamento de seus níveis de take-profit e stop-loss).

Comecemos pela colocação da ordem Placed. Selecionamos o tipo de ordem (buy ou sell) com ajuda do operador de comutação switch, inserimos os dados numa variável global (order_buy ou order_sell). Se m_take_profit e m_stop_loss forem previamente definidos, deveremos inseri-los nas variáveis ​​globais correspondentes e plotá-los no gráfico. As linhas são definidas pela função Line desta classe.

//+------------------------------------------------------------------+
//| Class COrder                                                     |
//+------------------------------------------------------------------+
class COrder
{
public:
 void Placed(
 char m_type,// tipo de ordem (1-buy, 2-sell)
 double m_price_bid, //preço bid
 double m_price_ask, //preço ask
 int m_take_profit,//pontos até o take profit
 int m_stop_loss //pontos até o stop loss
 )
 {
switch(m_type)
{
 case 1:
 {
GlobalVariableSet(order_buy,m_price_ask);
Line(GlobalVariableGet(order_buy),order_buy,col_buy,STYLE_SOLID,1,true);
if(m_take_profit>0)
{
 GlobalVariableSet(tp_buy,(m_price_ask+(_Point*m_take_profit)));
 Line(GlobalVariableGet(tp_buy),tp_buy,col_tp,STYLE_DASH,1,true);
}
if(m_stop_loss>0)
{
 GlobalVariableSet(sl_buy,(m_price_ask-(_Point*m_stop_loss)));
 Line(GlobalVariableGet(sl_buy),sl_buy,col_sl,STYLE_DASH,1,true);
}
 }
 break;
 case 2:
 {
GlobalVariableSet(order_sell,m_price_bid);
Line(GlobalVariableGet(order_sell),order_sell,col_sell,STYLE_SOLID,1,true);
if(m_take_profit>0)
{
 GlobalVariableSet(tp_sell,(m_price_bid-(_Point*m_take_profit)));
 Line(GlobalVariableGet(tp_sell),tp_sell,col_tp,STYLE_DASH,1,true);
}
if(m_stop_loss>0)
{
 GlobalVariableSet(sl_sell,(m_price_bid+(_Point*m_stop_loss)));
 Line(GlobalVariableGet(sl_sell),sl_sell,col_sl,STYLE_DASH,1,true);
}
 }
 break;
}
 }

Em seguida, vem a função de exclusão Delete. Novamente, o operador de comutação seleciona uma de três opções, isto é, exclusão automática, buy ou sell. Nesse caso, a exclusão automática ocorre quando a ordem é removida excluindo sua linha do gráfico.

Isso é feito pelas funções auxiliares de classe Small_del_buy e Small_del_sell.

 void Delete(
 double m_price_bid,      //preço bid
 double m_price_ask,      //preço ask
 char m_del_manual        //tipo de remoção (-1 auto, 1 buy, 2 sell)
 )
 {
switch(m_del_manual)
{
 case(-1):
if(ObjectFind(0,order_buy)<0 && GlobalVariableGet(order_buy)>0)
{Small_del_buy(m_price_bid);}
if(ObjectFind(0,order_sell)<0 && GlobalVariableGet(order_sell)>0)
{Small_del_sell(m_price_ask);}
break;
 case 1:
if(ObjectFind(0,order_buy)>=0)
{
 ObjectDelete(0,order_buy);
 Small_del_buy(m_price_bid);
}
break;
 case 2:
if(ObjectFind(0,order_sell)>=0)
{
 ObjectDelete(0,order_sell);
 Small_del_sell(m_price_ask);
}
break;
}
 }

Examinemos uma delas, nomeadamente, Small_del_sell.

Verificamos a presença de linhas de take-profit e stop-loss. Se estiverem presentes, removê-las-emos. Em seguida, zeramos a variável global order_sell, o que será necessário mais tarde se quisermos verificar a presença de ordens em variáveis globais.

Além disso, nas variáveis globais inserimos informações sobre o lucro obtido com a ordem (info_point_last, info_vol_last, info_money_last). Isso é feito pela função small_concatenation (ela é um algo parecido com o operador +=, mas com variáveis globais). Totalizamos o lucro (volume) e também armazenamo-lo nas variáveis globais (info_point_all, info_vol_all, info_money_all).

void Small_del_sell(double m_price_ask)
 {
if(ObjectFind(0,tp_sell)>=0)ObjectDelete(0,tp_sell);       // excluímos a linha de take profit 
 if(ObjectFind(0,sl_sell)>=0)ObjectDelete(0,sl_sell);      // excluímos a linha de stop loss 
 int point_plus=int(MathRound((GlobalVariableGet(order_sell)-m_price_ask)/_Point));      // calculamos o lucro da transação
GlobalVariableSet(order_sell,0);                           //zeramos a variável do preço da ordem colocada
GlobalVariableSet(info_vol_last,GlobalVariableGet(vol_sell));
GlobalVariableSet(vol_sell,0);
GlobalVariableSet(info_point_last,point_plus);
GlobalVariableSet(info_money_last,(GlobalVariableGet(info_point_last)*GlobalVariableGet(info_vol_last)*10));
Small_concatenation(info_point_all,info_point_last);
Small_concatenation(info_vol_all,info_vol_last);
Small_concatenation(info_money_all,info_money_last);
 }

A modificação de uma ordem é feita alterando sua localização com o mouse. Existem duas maneiras de fazer isso. A primeira consiste em tentar arrastar a linha de abertura da ordem. Neste caso, são traçadas novas linhas de take-profit e stop-loss, dependendo da direção do movimento e do tipo de ordem. A função Small_mod também é implementada na classe COrder. Os parâmetros de entrada para isso são o nome do objeto, a permissão para mover o objeto e o tipo de ordem.

No início da função Small_mod, é verificada a presença do objeto. Logo, se for permitido o deslocamento de linhas de take-profit/stop-loss, a alteração do preço será armazenada na variável global. Se o deslocamento for restrito (linhas buy e sell), dependendo do tipo de ordem, surgirá uma linha de take-profit ou stop-loss, no novo local da linha, enquanto a linha da ordem retornará ao seu lugar.

 void Small_mod(string m_name,      // nome do objeto e variável global
bool m_mode,                        // permissão para alterar a posição
char m_type                         // 1 — buy, 2 — sell
)
 {
if(ObjectFind(0,m_name)>=0)
{
 double price_obj_double=ObjectGetDouble(0,m_name,OBJPROP_PRICE);
 int price_obj=int(price_obj_double*dig_pow);
 double price_glo_double=GlobalVariableGet(m_name);
 int price_glo=int(price_glo_double*dig_pow);
 if(price_obj!=price_glo && m_mode==true)
 {
GlobalVariableSet(m_name,(double(price_obj)/double(dig_pow)));
 }
 if(price_obj!=price_glo && m_mode==false)
 {
switch(m_type)
{
 case 1:                         // order buy
if(price_obj>price_glo)          //TP
{
 GlobalVariableSet(tp_buy,(double(price_obj)/double(dig_pow)));
 Line(GlobalVariableGet(tp_buy),tp_buy,col_tp,STYLE_DASH,1,true);
}
if(price_obj<price_glo)          //SL
{
 GlobalVariableSet(sl_buy,(double(price_obj)/double(dig_pow)));
 Line(GlobalVariableGet(sl_buy),sl_buy,col_sl,STYLE_DASH,1,true);
}
break;
 case 2:                        // order sell
if(price_obj>price_glo)         //SL
{
 GlobalVariableSet(sl_sell,(double(price_obj)/double(dig_pow)));
 Line(GlobalVariableGet(sl_sell),sl_sell,col_sl,STYLE_DASH,1,true);
}
if(price_obj<price_glo)         //TP
{
 GlobalVariableSet(tp_sell,(double(price_obj)/double(dig_pow)));
 Line(GlobalVariableGet(tp_sell),tp_sell,col_tp,STYLE_DASH,1,true);
}
break;
}
ObjectSetDouble(0,m_name,OBJPROP_PRICE,(double(price_glo)/double(dig_pow)));
 }
}
 }

Durante a plotagem do gráfico, as ordens são constantemente verificadas pela função Check da classe COrder. À função são passadas todas as variáveis ​​globais que armazenam informações sobre as ordens. Há também uma variável global separada que contém as informações sobre a hora da última chamada. Isso permite, a cada chamada, verificar toda a faixa de preço (timeframe de minuto) no intervalo entre a última chamada da função e o tempo atual de plotagem do gráfico.

Caso, durante este tempo, o preço chegue ou rompa a uma das linhas de stop, o controle é passado para a função de exclusão de ordens (classe COrder, função Delete).

 void Check(
datetime m_time,
double m_price_buy,
double m_price_tp_buy,
double m_price_sl_buy,
double m_price_sell,
double m_price_tp_sell,
double m_price_sl_sell
)
 {
int start_of_z=0;
int end_of_z=0;
datetime time_end_check=datetime(GlobalVariableGet(time_end_order_check));
if(time_end_check<=0){time_end_check=m_time;}
GlobalVariableSet(time_end_order_check,m_time);
start_of_z=Bars(_Symbol,PERIOD_M1,real_start,time_end_check);
end_of_z=Bars(_Symbol,PERIOD_M1,real_start,m_time);
for(int z=start_of_z; z<end_of_z; z++)
{
 COrder del;
 double p_bid_high=period_m1[z].high;
 double p_bid_low=period_m1[z].low;
 double p_ask_high=p_bid_high+(spread*_Point);
 double p_ask_low=p_bid_low+(spread*_Point);
 if(m_price_buy>0)                                              // existe ordem BUY
 {
if(ObjectFind(0,tp_buy)>=0)
{
 if(m_price_tp_buy<=p_bid_high && m_price_tp_buy>=p_bid_low)    // Take-Profit desencadeado
 {del.Delete(m_price_tp_buy,0,1);}                              // fechamos com o preço de Take-Profit
} 
if(ObjectFind(0,sl_buy)>=0)
{
 if(m_price_sl_buy>=p_bid_low && m_price_sl_buy<=p_bid_high)    // Stop-Loss desencadeado
 {del.Delete(m_price_sl_buy,0,1);}                              // fechamos com o preço de Stop-Loss
}
 }
 if(m_price_sell>0)                                                   // existe ordem SELL
 {
if(ObjectFind(0,tp_sell)>=0)
{
 if(m_price_sl_sell<=p_ask_high && m_price_sl_sell>=p_ask_low)  // Stop-Loss desencadeado
 {del.Delete(0,m_price_sl_sell,2);}                             // fechamos com o preço de Stop-Loss
}
if(ObjectFind(0,sl_sell)>=0)
{
 if(m_price_tp_sell>=p_ask_low && m_price_tp_sell<=p_ask_high)  // Take-Profit desencadeado
 {del.Delete(0,m_price_tp_sell,2);}                             // fechamos com o preço de Take-Profit
}
 }
}
 }

Bem, aqui terminam as principais funções da classe. Examinemos as funções responsáveis ​​pelo desenho das velas no gráfico.

Função de preenchimento func_filling()

Como, ao alternar o período, o indicador é reiniciado, é preciso preencher, sempre que isso acontece, o gráfico e colocar nele as velas passadas até o momento atual (a chamada cauda). Esta função é usada antes da formação de uma nova vela, o que permite normalizar a cauda do gráfico e aumentar a precisão na exibição.

À função é passada uma matriz de dados do período atual, o tempo de exibição atual, o número de todas as velas e a vela atualmente desenhada. Uma vez executada, a função retorna o tempo de abertura da última vela exibida e o tempo de abertura da vela que a segue. Também é preenchida a matriz de indicadores e é retornado o sinalizador de trabalho concluído work_status.

Na função, com ajuda do ciclo for, é preenchido todo o buffer do indicador anteriormente exibido até a vela atualmente plotada, além disso, são definidos os valores da vela atualmente desenhada (geralmente igual aos preços de abertura).

//+------------------------------------------------------------------+
//| Func Filling                                                     |
//+------------------------------------------------------------------+
void func_filling(MqlRates &input_rates[],                // dados de entrada (período atual) para preenchimento
datetime input_end_time_indicator,      // valor atual do indicador
int input_all_bars_indicator,           // número total de barras do indicador
datetime &output_time_end_filling,      // tempo de abertura da última barra
datetime &output_time_next_filling,     // tempo de abertura da última barra
int input_end_bar_indicator,            // barra atual (plotada) do indicador
double &output_o[],
double &output_h[],
double &output_l[],
double &output_c[],
double &output_col[],
char &work_status)                      // status de andamento
{
 if(work_status==1)
 {
int stopped_rates_bar;
for(int x=input_all_bars_indicator,y=0;x>0;x--,y++)
{
 if(input_rates[y].time<input_end_time_indicator)
 {
output_o[x]=input_rates[y].open;
output_h[x]=input_rates[y].high;
output_l[x]=input_rates[y].low;
output_c[x]=input_rates[y].close;
if(output_o[x]>output_c[x])output_col[x]=0;
else output_col[x]=1;
output_time_end_filling=input_rates[y].time;
output_time_next_filling=input_rates[y+1].time;
input_end_bar_indicator=x;
stopped_rates_bar=y;
 }
 else break;
}
output_o[input_end_bar_indicator]=input_rates[stopped_rates_bar].open;
output_h[input_end_bar_indicator]=output_o[input_end_bar_indicator];
output_l[input_end_bar_indicator]=output_o[input_end_bar_indicator];
output_c[input_end_bar_indicator]=output_o[input_end_bar_indicator];
work_status=-1;
 }
}

Uma vez executado, o controle é transferido para uma das três funções de plotagem da vela atual. Vamos examiná-las começando com a mais rápida.

Função func_candle_per_seconds() para desenhar velas a cada segundo

Ao contrário das outras duas funções, aqui o controle não é transferido para outras funções antes da reinicialização do indicador ou a alteração na velocidade de desenho do gráfico. Cada nova chamada ocorre a cada segundo de acordo com o temporizador, e é durante este tempo que a vela atual é desenhada (preenchimento com dados). Primeiro, os dados são copiados da matriz - à qual foram passados - para a vela atual, logo, os dados iniciais são passados ​​para a próxima vela. No final, a função passa o tempo de formação da última vela.

A função descrita acima é responsável pela "sétima velocidade" de geração de velas (veja o painel de controle).

//+------------------------------------------------------------------+
//| Func Candle Per Seconds                                          |
//+------------------------------------------------------------------+
void func_candle_per_seconds(MqlRates &input_rates[],
 datetime &input_end_time_indicator,
 int input_bars_now_rates,
 int input_number_now_rates,
 int &input_end_bar_indicator,
 double &output_o[],
 double &output_h[],
 double &output_l[],
 double &output_c[],
 double &output_col[],
 char &work_status)
{
 if(work_status==-1)
 {
if(input_number_now_rates<input_bars_now_rates)
{
 if(input_number_now_rates!=0)
 {
output_o[input_end_bar_indicator]=input_rates[input_number_now_rates-1].open;
output_h[input_end_bar_indicator]=input_rates[input_number_now_rates-1].high;
output_l[input_end_bar_indicator]=input_rates[input_number_now_rates-1].low;
output_c[input_end_bar_indicator]=input_rates[input_number_now_rates-1].close;
if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0;
else output_col[input_end_bar_indicator]=1;
 }
 input_end_bar_indicator--;
 output_o[input_end_bar_indicator]=input_rates[input_number_now_rates].open;
 output_h[input_end_bar_indicator]=input_rates[input_number_now_rates].high;
 output_l[input_end_bar_indicator]=input_rates[input_number_now_rates].low;
 output_c[input_end_bar_indicator]=input_rates[input_number_now_rates].close;
 if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0;
 else output_col[input_end_bar_indicator]=1;
 input_end_time_indicator=input_rates[input_number_now_rates+1].time;
}
 }
}

As duas funções seguintes são muito semelhantes entre si. Uma delas constrói velas de acordo com o tempo, sem reparar em ticks, enquanto outra("de joalheria") usa o gerador de ticks descrito no início do artigo para uma emulação mais completa do mercado.

Função func_of_form_candle() para geração de velas

Os parâmetros de entrada são os mesmos que antes (OHLC). Quanto à funcionalidade, tudo é muito simples. Os preços são ciclicamente copiados a partir dos dados do timeframe M1 para a vela atual, a partir da hora que chega desde a função de preenchimento func_filling(). Acontece que, alterando o tempo, é formada gradualmente uma vela. Desta maneira, são construídas da segunda à sexta velocidades (veja o painel de controle). Após o tempo alcançar a formação total da vela no timeframe atual, é alterado o sinalizador work_status, a fim de, na seguinte inicialização do timeframe, primeiro, executar novamente a função de preenchimento func_filling().

//+------------------------------------------------------------------+
//| Func Of Form Candle                                              |
//+------------------------------------------------------------------+
void func_of_form_candle(MqlRates &input_rates[],
 int input_bars,
 datetime &input_time_end_filling,
 datetime &input_end_time_indicator,
 datetime &input_time_next_filling,
 int input_end_bar_indicator,
 double &output_o[],
 double &output_h[],
 double &output_l[],
 double &output_c[],
 double &output_col[],
 char &work_status)
{
 if(work_status==-1)
 {
int start_of_z=0;
int end_of_z=0;
start_of_z=Bars(_Symbol,PERIOD_M1,real_start,input_time_end_filling);
end_of_z=Bars(_Symbol,PERIOD_M1,real_start,input_end_time_indicator);
for(int z=start_of_z; z<end_of_z; z++)
{
 output_c[input_end_bar_indicator]=input_rates[z].close;
 if(output_h[input_end_bar_indicator]<input_rates[z].high)output_h[input_end_bar_indicator]=input_rates[z].high;
 if(output_l[input_end_bar_indicator]>input_rates[z].low)output_l[input_end_bar_indicator]=input_rates[z].low;
 if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0;
 else output_col[input_end_bar_indicator]=1;
}
if(input_end_time_indicator>=input_time_next_filling)work_status=1;
 }
}

Finalmente, passemos para a função capaz de projetar velas o mais próximo possível do mercado.

Função func_of_form_jeweler_candle() para plotagem "de joalheria" de velas

No início da função, tudo acontece como na versão anterior. Os dados do timeframe de minuto enchem completamente a vela atual, com exceção do último minuto. Seus dados são transmitidos para a função de geração de ticks descrita no início do artigo func_tick_generation(). Com cada chamada da função, a matriz de ticks resultante é gradualmente transferida pelo preço de fechamento, tendo em conta a correção de "sombras". Quando os "ticks" de matriz se acabam, o processo é repetido.

//+------------------------------------------------------------------+
//| Func Of Form Jeweler Candle                                      |
//+------------------------------------------------------------------+
void func_of_form_jeweler_candle(MqlRates &input_rates[],                    //informações para geração de ticks
 int input_bars,                             //tamanho da matriz de informações
 datetime &input_time_end_filling,           //tempo final de preenchimento rápido
 datetime &input_end_time_indicator,         //tempo da última plotagem do indicador
 datetime &input_time_next_filling,          //tempo anterior à formação da barra completa do timeframe atual
 int input_end_bar_indicator,                //barra atual (sendo desenhada) do indicador
 double &output_o[],
 double &output_h[],
 double &output_l[],
 double &output_c[],
 double &output_col[],
 char &work_status                           // vista do trabalho final (comando para a função de preenchimento rápido)
 )
{
 if(work_status==-1)
 {
int start_of_z=0;
int current_of_z=0;
start_of_z=Bars(_Symbol,PERIOD_M1,real_start,input_time_end_filling)-1;
current_of_z=Bars(_Symbol,PERIOD_M1,real_start,input_end_time_indicator)-1;
if(start_of_z<current_of_z-1)
{
 for(int z=start_of_z; z<current_of_z-1; z++)
 {
output_c[input_end_bar_indicator]=input_rates[z].close;
if(output_h[input_end_bar_indicator]<input_rates[z].high)output_h[input_end_bar_indicator]=input_rates[z].high;
if(output_l[input_end_bar_indicator]>input_rates[z].low)output_l[input_end_bar_indicator]=input_rates[z].low;
if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0;
else output_col[input_end_bar_indicator]=1;
 }
 input_end_time_indicator=input_rates[current_of_z].time;
}
//obtemos os ticks na matriz
static int x=0;                   // cálculo da matriz e sinalizador de início
static double tick_current[];
static int tick_current_size=0;
if(x==0)
{
 func_tick_generation(input_rates[current_of_z-1],tick_current);
 tick_current_size=ArraySize(tick_current);
 if(output_h[input_end_bar_indicator]==0)
 {output_h[input_end_bar_indicator]=tick_current[x];}
 if(output_l[input_end_bar_indicator]==0)
 {output_l[input_end_bar_indicator]=tick_current[x];}
 output_c[input_end_bar_indicator]=tick_current[x];
}
if(x<tick_current_size)
{
 output_c[input_end_bar_indicator]=tick_current[x];
 if(tick_current[x]>output_h[input_end_bar_indicator])
 {output_h[input_end_bar_indicator]=tick_current[x];}
 if(tick_current[x]<output_l[input_end_bar_indicator])
 {output_l[input_end_bar_indicator]=tick_current[x];}
 if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0;
 else output_col[input_end_bar_indicator]=1;
 x++;
}
else
{
 input_end_time_indicator=input_rates[current_of_z+1].time;
 x=0;
 tick_current_size=0;
 ArrayFree(tick_current);
}
if(input_end_time_indicator>input_time_next_filling)
{work_status=1;}
 }
}

Todas as três variantes da plotagem das velas estão concentradas na função Merger.

Função func_merger() para plotagem de concatenação

Dependendo da velocidade selecionada pelo operador comutador switch, é determinada a função que estará em operação. A função tem três tipos. Cada rótulo começa com a função de preenchimento func_filling(), logo, o controle é passado por uma das três funções de geração de vela: func_of_form_jeweler_candle(), func_of_form_candle() ou func_candle_per_seconds(). O tempo é recalculado a cada passagem, da segunda velocidade até a sexta. A função func_calc_time() calcula a porção necessária do período atual e a adiciona ao tempo atual. O preço Bid é tomado a partir do preço de fechamento da vela atual, e Ask é calculado com base em dados de spread recebidos do servidor.

//+------------------------------------------------------------------+
//| Func Merger                                                      |
//+------------------------------------------------------------------+
void func_merger()
{
 switch(button_speed)
 {
case 1:
{
 func_filling(period_array,end_time_indicator,all_bars_indicator,time_open_end_rates,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 func_of_form_jeweler_candle(period_m1,bars_m1,time_open_end_rates,end_time_indicator,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 price_bid_now=TST_C_C[end_bar_indicator];
 price_ask_now=price_bid_now+(spread*_Point);
}
break;
case 2:
{
 func_filling(period_array,end_time_indicator,all_bars_indicator,time_open_end_rates,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 func_of_form_candle(period_m1,bars_m1,time_open_end_rates,end_time_indicator,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 price_bid_now=TST_C_C[end_bar_indicator];
 price_ask_now=price_bid_now+(spread*_Point);
 end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,13);
}
break;
case 3:
{
 ...
 end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,11);
}
break;
case 4:
{
 ...
 end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,9);
}
break;
case 5:
{
 ...
 end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,7);
}
break;
case 6:
{
 ...
 end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,5);
}
break;
case 7:
{
 func_filling(period_array,end_time_indicator,all_bars_indicator,time_open_end_rates,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 func_candle_per_seconds(period_array,end_time_indicator,bars_now_rates,number_now_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 price_bid_now=TST_C_C[end_bar_indicator];
 price_ask_now=price_bid_now+(spread*_Point);
}
break;
 }
}

Possíveis usos

Eu sugiro utilizar este indicador para testar novos desenvolvimentos e estratégias de negociação, modelar o comportamento do trader principiante numa situação em particular, melhorar as entradas no mercado e saídas dele. Basicamente, isso se aplica a indicadores técnicos, por exemplo, neste indicador é possível construir ondas de Elliott, canais ou verificar o trabalho das linhas de suporte/resistência.

Exemplo de trabalho do indicador:

Fim do artigo

Agora vou revelar um segredo, isto é, o porquê decidi chamar um dos tipos de plotagem de "de joalharia."

É simples. Ao desenvolver este aplicativo, conclui que, para verificar a maioria das estratégias, não é necessária uma plotagem tão harmoniosa e precisa. E, portanto, é uma espécie de luxo, uma joia. Estes ticks simulam flutuações, quase comparáveis ao tamanho do spread, e não afetam o andamento da estratégia, e muito menos se considerarmos a velocidade da verificação. É improvável que alguém passe vários dias a pegar o ponto de entrada, tendo a possibilidade de retroceder para o próximo ponto propício.

No que diz respeito ao código, não descarto a possibilidade de ocorrerem falhas. Mas, isso não deve afetar a análise da estratégia em geral, uma vez que as ações básicas são gravadas nas variáveis globais, e basta simplesmente reiniciar o timeframe ou terminal (sem fechar a janela do indicador), após o qual continua a emulação.

Na descrição do código são omitidas muitas funções auxiliares que são auto-explicativas ou são explicadas na documentação. Seja como for, pergunte se de repente você ver algo estranho. Como sempre, quaisquer comentários são bem-vindos.


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

Arquivos anexados |
STSv1.1.ex5 (120.96 KB)
STSv1.1.mq5 (127.38 KB)
for_STS.zip (13.39 KB)
Últimos Comentários | Ir para discussão (2)
Diou
Diou | 2 abr 2020 em 00:30

Boa noite, como fazer a instalaçao desse mini emulador ? tentei aqui porem nao tive sucesso

Dmitriy Zabudskiy
Dmitriy Zabudskiy | 5 abr 2020 em 13:38
Diou:

Boa noite, como fazer a instalaçao desse mini emulador ? tentei aqui porem nao tive sucesso

Abra o MetaEditor. 

Escolha como na figura (abaixo), coloque o arquivo "STSv1.1.mq5" na pasta que é aberta. E abra o arquivo colocado (STSv1.1.mq5).

Em seguida, selecione como na imagem (abaixo), na pasta aberta, coloque a pasta descompactada "for_STS.zip" com imagens.

Então compile.

Use como um indicador no MT5.

R quadrado como uma estimativa da qualidade da curva de saldo da estratégia R quadrado como uma estimativa da qualidade da curva de saldo da estratégia
Este artigo descreve a construção do R² - critério de otimização personalizado. Esse critério pode ser usado para estimar a qualidade da curva de saldo de uma estratégia e para selecionar as estratégias mais consistentes e lucrativas. O trabalho discute os princípios de sua construção e os métodos estatísticos utilizados na estimativa de propriedades e qualidade desta métrica.
Expert Advisor Multiplataforma: As classes CExpertAdvisor e CExpertAdvisors Expert Advisor Multiplataforma: As classes CExpertAdvisor e CExpertAdvisors
Este artigo aborda principalmente as classes CExpertAdvisor e CExpertAdvisors, que servem como contêiner para todos os outros componentes descritos nesta série de artigos sobre expert advisors multiplataforma.
Teste de padrões que surgem ao negociar cestas de pares de moedas Parte II Teste de padrões que surgem ao negociar cestas de pares de moedas Parte II
Continuamos a testar padrões e métodos descritos nos artigos sobre negociação de cestas de pares de moedas. Consideraremos, na prática, se é possível usar os padrões de cruzamento entre o gráfico do WPR combinado e o da média móvel.
Uso do filtro de Kalman na previsão da tendência Uso do filtro de Kalman na previsão da tendência
Para o sucesso na negociação, quase sempre são necessários indicadores, cujo objetivo é a separação entre o movimento principal do preço e as flutuações ruidosas. Neste artigo, é examinado um dos filtros digitais mais promissores, o filtro de Kalman. Além disso, são descritos tanto sua construção como uso na prática.