Aplicando a teoria da probabilidade na negociação de gaps

Aleksey Nikolayev | 8 fevereiro, 2019

Conteúdo

Introdução

O artigo continua o tópico da aplicação da teoria da probabilidade e estatística matemática na negociação, iniciado nos artigos anteriores do autor. Nós vamos considerar o possível uso dos métodos apropriados para criar e testar estratégias de negociação.

Primeiro, nós veremos como encontrar as oportunidades de negociação como detectar desvios da hipótese do passeio aleatório. Está provado que, se os preços se comportarem como um passeio aleatório de deslocamento de zero (sem tendência direcional), então a negociação com lucro é impossível. Isso fornece uma base para encontrar maneiras de refutar essa hipótese. Se uma maneira de refutar a hipótese for encontrada, nós podemos tentar usá-la para desenvolver uma estratégia de negociação.

Nós também vamos continuar estudando o tópico de risco que começamos nos artigos publicados anteriormente. Mais adiante, nós nos vamos nos referir a eles como primeiro e segundo artigos.

Uma vez que nossa abordagem é baseada na teoria da probabilidade, a compreensão de seus fundamentos é aconselhável, mas não obrigatória. É importante entender a essência dos métodos probabilísticos - quanto mais sistematicamente e frequentemente eles são usados, mais notável e significativo é o resultado obtido (devido à lei dos grandes números). Evidentemente, sua aplicação deve ser suficientemente fundamentada e adequada.

Considerações gerais sobre os Expert Advisors

O desenvolvimento de EAs pode ser dividido em três etapas:

  1. Gerar uma ideia.
  2. Verificar a ideia usando todos os tipos de simplificações.
  3. Adaptar a ideia às realidades do mercado.

O artigo lidará principalmente com a segunda etapa, permitindo que nos concentremos detalhadamente no tópico declarado. Além disso, esta etapa é discutida no fórum com muito menos frequência do que as outras.

Vamos descrever as simplificações implementadas. Nós nos limitaremos aos EAs negociando um único ativo. Nós vamos assumir que o preço do ativo é expresso na moeda da conta. Nós vamos excluir as operações sem negociação (como swap e retirada de fundos da conta) e não consideraremos diferentes tipos de ordens (deixando apenas a compra e venda à mercado). Nós vamos negligenciar o desvio ao executar ordens, enquanto o spread (s) deve ser considerado como um valor fixo. Nós também iremos assumir que o EA gerencia todos os fundos em nossa conta, e não há outros EAs nesta conta.

Com todas essas simplificações, o resultado da operação do EA é definido sem ambigüidade como a função v(t) - volume da posição dependendo do tempo. v(t) positivo corresponde a compra, enquanto que negativo significa venda. Além disso, existe a função p(t) (preço do ativo) e c0 (saldo inicial). A imagem abaixo mostra um possível gráfico da posição v=v(t).

Posição de exemplo

A média aritmética entre os preços de compra e venda.

A v(t) e p(t) são constantes por partes (passo a passo) porque seus valores são múltiplos de algumas etapas de incremento mínimo. Se uma definição matemática mais rigorosa é necessária, então elas podem ser consideradas contínuas da direita e ter um limite nos pontos de gap à esquerda. Nós assumimos que os pontos de gap de v(t) nunca coincidem com a p(t). Em outras palavras, não mais que um valor dos dois pode mudar a qualquer momento - um preço, um volume da posição ou ambos permanecem inalterados. Vale a pena notar que os pontos no tempo, nos quais as alterações de preço ou volume podem ocorrer, também são múltiplos de uma certa etapa mínima.

Com base nesses dados, nós podemos encontrar a função c(t) - o valor dos fundos, dependendo do tempo. Ele é definido como o valor do saldo para a parte da conta gerenciada pelo EA no caso de encerrarmos uma posição no momento t. Como temos um único EA na conta, esse valor coincide com o patrimônio da conta definido na MetaTrader 5.

Definido c(t) que se altera conforme t. Se o volume e o preço não mudarem neste momento, então ele é naturalmente zero. Se o preço muda, o aumento do saldo é igual ao produto do volume pelo incremento de preço. Se o volume muda, duas opções são possíveis - quando o volume da posição absoluta é diminuído, os fundos permanecem os mesmos, quando é aumentado, os fundos são diminuídos em um montante igual ao produto do spread pelo valor absoluto da variação do volume. Em outras palavras, se uma posição for encerrada parcialmente, o patrimônio líquido não se altera, ao passo que o acréscimo à posição leva à uma pequena desvalorização do patrimônio líquido. Portanto, o valor do saldo c(t) no momento t é igual à soma de c0=c(0) e todas as suas alterações que ocorreram a partir do momento zero até t.

Ao desenvolver nossa teoria de risco (nos dois artigos anteriores), nós usamos o conceito de 'negócio'. Este conceito não coincide exatamente com o que é chamado de 'negócio' na MetaTrader 5 e corresponde mais ao que é chamado de 'negociação' lá. Para ser exato, ele corresponde ao que chamamos de posição simples. Uma posição simples, por nossa definição, é definida pelos momentos de abertura e fechamento. Seu volume e direção permanecem constantes entre esses momentos. Abaixo está um exemplo do gráfico v=v(t) para uma posição simples.

Posição simples (negócio)

Qualquer posição (uma vez que ele é sempre constante por partes) pode ser imaginada como uma soma de posições simples. Tal representação pode ser feita de um infinito número de maneiras. O gráfico abaixo mostra uma única posição representada de duas maneiras diferentes como uma soma de outras simples. A posição inicial é exibida em azul, enquanto os negócios nos quais ela é dividida são exibidos em verde e vermelho. Ao usar um conceito de negociações na MetaTrader 5, temos ainda outra opção. Cada um desses métodos pode ser bastante razoável.

Duas maneiras de dividir uma posição em negócios

Existem alguns EAs, para os quais tais representações não fazem muito sentido. Por exemplo, pode haver EAs em que uma posição é gradualmente aumentada e gradualmente diminuída posteriormente. Ao mesmo tempo, existem EAs, para os quais tal apresentação é bastante natural. Por exemplo, uma posição pode consistir em uma sequência de posições simples que não se cruzam no tempo. O quadro abaixo contém exemplos de tais posições.

Posições inadequadas e apropriadas para serem apresentadas como uma soma de negócios

A variação relativa do saldo c1/c0 após cada negócio (posição simples) é expresso em dois valores - rentabilidade a e risco r: c1/c0=1+ra. A rentabilidade é igual à razão entre o aumento do preço durante o negócio e a diferença entre os preços de entrada e o stop loss, enquanto o risco é proporcional ao volume negociado e significa uma parte do saldo que seria perdido em caso da ativação exata do stop loss.

Assim, em vez de considerar as funções pelo tempo v(t), p(t) e c(t), recorremos à análise de seqüências numéricas que caracterizam a sequência de negócios. Isso simplifica muito o estudo adicional. Em particular, evitamos a necessidade de aplicar a teoria de processos aleatórios, limitando-nos a conjuntos finitos de variáveis aleatórias quando procedemos ao modelo probabilístico de incerteza.

A teoria da probabilidade é um método geralmente aceito de modelagem matemática da incerteza no comportamento dos preços dos ativos e nos resultados da negociação. De acordo com esta abordagem, nós devemos considerar as funções v(t), p(t) e c(t) como implementações específicas (trajetórias) de alguns processos aleatórios. Em termos gerais, essa tarefa é praticamente insolúvel. A principal razão é a falta de modelos probabilísticos adequados que descrevam com precisão o comportamento dos preços. Portanto, faz sentido considerar casos especiais em que uma solução é possível. Como mencionado acima, neste artigo, nós vamos considerar os EAs formando posições que podem ser adequadamente representadas como uma sequência de posições simples (negócios).

Vale ressaltar outra questão relacionada aos EAs - parâmetros. Seria útil considerá-los em detalhes para obter alguma formalização (padronização) do processo de desenvolvimento do EA. Vamos dividir os parâmetros em três tipos:

  1. Parâmetros históricos. Parâmetros que podem mudar durante a operação do EA de um negócio para outro. Estes são valores de indicadores, hora do dia, dados de notícias, fases da lua, etc. Em geral, são funções pelo tempo, assim como preços ou volumes de posição. No caso de simplificação aplicada, nós podemos considerá-las sequências de números conhecidas no momento de realizar um negócio. Os parâmetros de cada negócio específico (direção, volume, stop loss e take profit) são definidos com base nos valores dos parâmetros históricos.
  2. Parâmetros atuais. Vamos simplesmente chamá-los de parâmetros para brevidade. Eles são definidos quando um EA inicia a negociação e podem ser aplicados somente ao testar e otimizar o EA.
  3. Meta-parâmetros definem o algoritmo de otimização do EA, por exemplo, parâmetro de critério de otimização personalizado. Suponha que nós queremos otimizar o EA por dois critérios, embora isso possa ser feito apenas por um. Nós formamos um novo critério dos dois originais, tendo sua soma com alguns pesos. Esses pesos são servidos como meta-parâmetros.

Por exemplo, no EA baseado em gap descrito abaixo, o intervalo mínimo é o parâmetro do EA, enquanto o tamanho de cada lacuna específica é um parâmetro histórico. Neste caso, os meta-parâmetros podem incluir o número do critério de otimização (assumimos que os critérios são numerados em alguma ordem, por exemplo, otimização por lucro é #1, enquanto a otimização por rebaixamento é #2, etc.).

Neste artigo, nós usaremos uma simplificação significativa associada aos parâmetros históricos. Quando nós falamos sobre a distribuição de retornos em um negócio, isso geralmente depende desses parâmetros. Nós assumimos que essa dependência é insignificante. A principal razão é que uma tentativa de levar em conta essa dependência, geralmente, complica excessivamente o modelo, o que pode, eventualmente, levar a um ajuste excessivo.

Estratégia de negociação como tentativa de rejeitar a hipótese do passeio aleatório

Nós já mencionamos a ausência de modelos precisos descrevendo o comportamento do preço. No entanto, existem modelos aproximados que podem ser úteis. Por exemplo, há um modelo de comportamento de preço bem conhecido, considerando os preços como um passeio aleatório com desvio zero (ausência de uma tendência direcionada). Este modelo é chamado de hipótese de passeio aleatório. De acordo com essa hipótese, qualquer EA terá um lucro zero, em média, ou uma perda pequena, se considerarmos um spread.

Provar a impossibilidade de ganhar dinheiro em uma caminhada aleatória é bastante difícil porque requer o envolvimento de um aparato matemático complexo da teoria de processos aleatórios (Cálculo de Itô, Tempo de Parada, etc). Em geral, resume-se à afirmação de que, ao negociar em um passeio aleatório sem uma tendência, o capital é um martingale (teoria da probabilidade, não deve ser confundido como o sistema de apostas martingale). Um martingale é um processo aleatório com seu valor médio (expectativa matemática) sem variação com o tempo. No nosso caso, isso significa que a expectativa matemática do valor do capital a qualquer momento é igual ao seu valor inicial.

Assim, nós devemos começar a considerar uma ideia de negociação buscando desvios de preço estatisticamente significativos do passeio aleatório. Para fazer isso, nós usaremos idéias da teoria da probabilidade e estatística matemática, mas primeiro, vamos fazer algumas observações:

Vamos construir um método para a busca por desvios aleatórios. Para fazer isso, nós vamos considerar alguma variável aleatória, para a qual construiremos uma distribuição de probabilidade empírica baseada em uma amostra formada usando preços reais. Além disso, nós construiremos uma distribuição de probabilidade teórica para o mesmo valor, assumindo que o comportamento do preço é um passeio aleatório. Comparando estas distribuições, nós tomaremos uma decisão sobre a rejeição (ou impossibilidade de rejeitar) a hipótese do passeio aleatório.

Vamos construir um exemplo de um valor adequado. Suponha que no momento inicial t0 no tempo, o preço é igual a p0. Vamos dar outro valor de preço p1 diferente de p0. Aguardar até o momento t1 quando o preço atingir o valor p(t1)=p1. Vamos encontrar o preço p2 na qual está mais distante de p1 fora dos preços no intervalo entre t0 e t1. Vamos introduzir o valor K=(p2-p0)/(p0-p1). A condição p1<p0p2 ou p2p0<p1 está sempre em vigor, portanto K0 em todo os momentos. O gráfico que explica essa ideia é fornecido abaixo. A linha azul significa o nível de preço p0, no momento em que ele cruza o gráfico de preços é t0. A linha vermelha significa o nível de preço p1, no momento em que ele toca o gráfico de preços após t0 é t1. A linha verde significa o nível de preço p2 localizado o mais longe de p1 possível.

preços p0, p1 e p2

A ideia por trás do valor é simples. Suponha que nós entramos em um negócio em t0. Vamos supor que seja uma venda em p0, enquanto p1, p1>p0 − stop loss. p2 é o preço menos viável para o take profit, enquanto K é o maior lucro viável para um negócio. Na verdade, nós não sabemos o exato valor de K ao realizar um negócio. Dentro da estrutura do modelo probabilístico dessa incerteza, nós podemos apenas falar sobre o conhecimento de seu padrão de distribuição de probabilidade. Suponha que nós conheçamos a função Fk(x) de distribuição de probabilidade, que é definida como a probabilidade em que K<x. Suponha que nós usamos um certo preço pk como um take profit: pk-p0=k(p0-p1). Nesse caso, Fk(k) é igual à probabilidade de que um stop loss seja atingido antes do take profit. Conforme, 1-Fk(k) é igual à probabilidade de que o take profit seja ativado anteriormente. Vamos deixar o spread igual a zero por enquanto. Então, no caso de ativação do stop loss, a rentabilidade é igual a -1, enquanto no caso da ativação do lucro, é igual a k. A expectativa matemática em tal negócio: M=(-1)*Fk(k)+k*(1-Fk(k))=k-(k+1)*Fk(k), que é igual a zero se Fk(k)=k/(k+1).

Se nós sabemos a forma da equação Fk(x), até podemos realizar uma otimização preliminar de um EA. Por exemplo, nós podemos procurar a taxa ótima de take profit/stop loss que maximize a expectativa matemática da lucratividade do negócio. Então, nós podemos encontrar o valor do risco ideal no negócio. Assim, o EA pode ser otimizado antes mesmo de estar pronto. Isso economiza tempo e permite descartar idéias obviamente inadequadas em um estágio inicial.

Se assumirmos que os preços se comportam como um passeio aleatório sem tendência, então a distribuição do valor de K é definido pela função de distribuição Fk(x)=Fk0(x), onde Fk0(x)=0 se x0 e Fk0(x)=x/(x+1) se x>0. Para maior certeza, nós podemos supor que o passeio aleatório usado aqui é um processo de Wiener com deslocamento zero (sem tendência). Como nós podemos ver, se a hipótese de passeio aleatório é preenchida e o spread é igual a zero, a expectativa matemática da rentabilidade é igual a zero em qualquer razão de take profit/stop loss. No caso de um spread diferente de zero, ele é negativo.

Em vez de K, nós podemos considerar o valor Q=K/(K+1)=(p2-p0)/(p2-p1), K=Q/(1-Q). Esse valor pode ser representado como a razão de take profit para a soma de stop loss e take profit. É mais conveniente porque leva valores dentro do intervalo [0;1) e tem uma distribuição mais simples do que K (uniforme neste intervalo), no caso de um passeio aleatório.

Mais adiante, nós falaremos principalmente sobre o valor Q. Vamos considerar como sua função de distribuição empírica Fq(x) é construída e aplicada. Suponha que tenhamos uma ideia de negociação, verificamos o histórico de preços. Nós temos um conjunto de n pontos de entrada. O preço de entrada p0,i e o stop loss p1,i, onde i=1,...,n, é definido para cada um deles. Agora nós devemos definir se essa ideia tem algum potencial de lucro. Para cada acordo, devemos encontrar o preço p2,i localizado o mais distante possível do stop loss até o momento de sua ativação. Com base nos preços, nós obtemos a amostra n Qi=(p2,i-p0,i)/(p2,i-p1,i)i=1,...,n. A função de distribuição empírica construída por esta amostra é definida pela equação Fq(x)=m(x)/n onde m(x) é igual ao número de elementos da amostra Qi menores que x. Se os preços se comportam como um passeio aleatório sem uma tendência (processo Wiener com deslocamento zero), a função de distribuição Fq0(x) de valor parece simples: Fq0(x)=0 if x0Fq0(x)=x se 0<x1, e Fq0(x)=1 se x>1.

Se Fq(x) é significativamente diferente da função de distribuição teórica com um passeio aleatório Fq0(x), nós precisamos verificar o significado dessa diferença em termos de lucratividade. Se a rentabilidade é suficientemente positiva, mesmo que o spread seja levado em consideração, então é hora de escolher a razão apropriada de lucro/stop loss. Isso pode ser feito maximizando a expectativa de lucratividade. Depois disso, nós podemos selecionar um valor ideal para o valor de risco por negócio e passar a testar preliminarmente a ideia. Se o resultado for positivo, faz sentido avançar para a criação de um EA de negociação real. Mais adiante, nós tentaremos mostrar esse algoritmo na prática real.

Surge a questão - como fazer comparações semelhantes com um passeio aleatório para algoritmos de encerramento à mercado mais complexos. A resposta geral é da mesma forma que para o caso considerado acima. A questão principal é que a distribuição de lucratividade em uma passeio aleatório pode ser obtido de forma analítica em raras ocasiões. Mas é sempre possível obter sua aproximação empírica usando o método de simulação de Monte Carlo.

Estratégia de negociação de Gap

Antes de analisar a ideia, devo observar que nossa principal tarefa é demonstrar os métodos de análise, e não estratégias de negociação lucrativas. Muita concentração no lucro nos teria enterrado em detalhes insignificantes que nos distraem do quadro geral.

Os preços dos ativos são discretos e, portanto, sempre mudam rapidamente. Esses saltos podem diferir em tamanho. Quando eles são grandes, eles são chamados de gaps. Não há limites específicos que separem os gaps das alterações de preços usuais. Nós somos livres para estabelecer essa fronteira onde acharmos melhor.

Os gaps são adequadas para demonstrar a teoria descrita na seção anterior. Cada um deles é definido por dois pontos de tempo e preços de ativos dentro deles. Usando a notação de preço anteriormente introduzida, nós assumimos que p0 é o preço posterior, enquanto p1 é o anterior. Entramos em uma negociação assim que o gap ocorrer. O preço p1 pode ser considerado não apenas como um stop loss, mas também como um take profit. Isso significa que nós podemos escolher um sistema de um dos dois tipos - esperando por um rápido fechamento do gap, ou um grande movimento do preço na direção do gap. O fechamento do gap significa que o preço retorna ao nível p1 ou rompe-o.

Uma vez que os gaps em sua forma padrão são relativamente raros para ativos Forex, o autor foi aconselhado a generalizar esse conceito ao discutir o tópico deste artigo com a administração do fórum. Ao definir um gap, você pode abandonar o requisito afirmando que apenas um gap entre dois preços subsequentes é levado em consideração. Obviamente, o número de possíveis gaps neste caso se tornará inimaginavelmente grande e, portanto, vale a pena nos limitarmos às opções razoáveis do ponto de vista de negociação. Por exemplo, o autor foi aconselhado a considerar as diferenças entre o preço de fechamento de uma sessão americana e o preço de abertura da próxima.

Deixe-me explicar como o conceito de uma sessão é formalizado. É definido pelos três intervalos de tempo: período, duração e deslocamento. As sessões são periódicas e têm um comprimento que não excede o período. Qualquer tick pertence a qualquer sessão ou não pertence a nenhuma delas (o último é possível se seu comprimento for estritamente menor que o período). O deslocamento é um intervalo de tempo entre o ponto zero no tempo e o início da primeira sessão após ele. Ele deve ser menor que o período. Este conceito da sessão é um pouco mais amplo do que o habitual e nos permite considerar, por exemplo, os gaps entre as barras de minutos. Vamos ilustrar no diagrama abaixo. A seta verde representa o período de tempo que define o deslocamento, a vermelha mostra o período, enquanto a azul demonstra a duração da sessão.

Sessões

Nós usaremos dois EAs ligeiramente diferentes para coletar estatísticas relacionadas aos gaps. O primeiro (gaps_reg_stat.mq5) considera os gaps entre dois pontos subsequentes, enquanto o segundo (gaps_ses_stat.mq5) considera os gaps entre as sessões. Naturalmente, esses EAs não são negociados e são executados apenas no modo de teste. Recomenda-se executar o primeiro deles apenas em ticks reais, enquanto o segundo - em OHLC de barras de minuto. Os códigos do EA são fornecidos abaixo.

// gaps_reg_stat.mq5
#define ND 100

input double gmin=0.1;                 // tamanho mínimo do gap: USDJPY - 0.1, EURUSD - 0.001
input string fname="gaps\\stat.txt";   // nome do arquivo para estatísticas             

struct SGap
  { double p0;
    double p1;
    double p2;
    double p3;
    double s;
    void set(double p_1,double p,double sprd);
    bool brkn();
    void change(double p);
    double gap();
    double Q();
  };

class CGaps
  { SGap gs[];
    int ngs;
    int go[];
    int ngo;
    public:
    void init();
    void add(double p_1,double p,double sprd);
    void change(double p);
    void gs2f(string fn);
  };

CGaps gaps;
MqlTick tick0;
bool is0=false;

void OnTick()
  { MqlTick tick;
    if (!SymbolInfoTick(_Symbol, tick)) return;
    if(is0)
      { double p=(tick.bid+tick.ask)*0.5, p0=(tick0.bid+tick0.ask)*0.5;
        gaps.change(p);
        if(MathAbs(p-p0)>=gmin) gaps.add(p0,p,tick.ask-tick.bid);
      }
    else is0=true;
    tick0=tick;
  }

int OnInit()
  { gaps.init();
     return(INIT_SUCCEEDED);
  }
  
void OnDeinit(const int reason)
  { gaps.gs2f(fname);
  }

void SGap :: set(double p_1,double p,double sprd)
  { p1=p_1; p0=p2=p3=p; s=sprd;
  }

bool SGap :: brkn()
  { return ((p0>p1)&&(p3<=p1))||((p0<p1)&&(p3>=p1));
  }

void SGap :: change(double p)
  { if(brkn()) return;
    if((p0>p1&&p>p2) || (p0<p1&&p<p2)) p2=p;
    p3=p;
  }

double SGap :: gap()
  { return MathAbs(p0-p1);
  }

double SGap :: Q()
  { double q=p2-p1;
    if(q==0.0) return 0.0;
    return (p2-p0)/q;
  }

void CGaps :: init()
  { ngs=ngo=0;
  }

void CGaps :: add(double p_1,double p,double sprd)
  { ++ngs;
    if(ArraySize(gs)<ngs) ArrayResize(gs,ngs,ND);
    gs[ngs-1].set(p_1,p,sprd);
    int i=0;
    for(; i<ngo; ++i) if(go[i]<0) break;
    if(i==ngo)
      {
        ++ngo;
        if(ArraySize(go)<ngo) ArrayResize(go,ngo,ND);
      }
    go[i]=ngs-1;
  }

void CGaps :: change(double p)
  { for(int i=0; i<ngo; ++i)
      { if(go[i]<0) continue;
        gs[go[i]].change(p);
        if(gs[go[i]].brkn()) go[i]=-1;
      }
  }

void CGaps :: gs2f(string fn)
  { int f=FileOpen(fn, FILE_WRITE|FILE_COMMON|FILE_ANSI|FILE_TXT), c;
    for(int i=0;i<ngs;++i)
      { if (gs[i].brkn()) c=1; else c=0;
        FileWriteString(f,(string)gs[i].gap()+" "+(string)gs[i].Q()+" "+(string)c+" "+(string)gs[i].s);
        if(i==ngs-1) break;
        FileWriteString(f,"\n");
      }
    FileClose(f);
  }
// gaps_ses_stat.mq5
#define ND 100

input double gmin=0.001;               // tamanho mínimo do gap: USDJPY - 0.1, EURUSD - 0.001
input uint   mperiod=1;                // período da sessão em minutos
input uint   mlength=1;                // duração da sessão em minutos
input uint   mbias=0;                  // viés da primeira sessão em minutos
input string fname="gaps\\stat.txt";   // nome do arquivo para estatísticas             

struct SGap
  { double p0;
    double p1;
    double p2;
    double p3;
    double s;
    void set(double p_1,double p,double sprd);
    bool brkn();
    void change(double p);
    double gap();
    double Q();
  };

class CGaps
  { SGap gs[];
    int ngs;
    int go[];
    int ngo;
    public:
    void init();
    void add(double p_1,double p,double sprd);
    bool change(double p);
    void gs2f(string fn);
  };

CGaps gaps;
MqlTick tick0;
int ns0=-1;
ulong sbias=mbias*60, speriod=mperiod*60, slength=mlength*60;

void OnTick()
  { MqlTick tick;
    if (!SymbolInfoTick(_Symbol, tick)) return;
    double p=(tick.bid+tick.ask)*0.5;
    gaps.change(p);
    int ns=nsession(tick.time);
    if(ns>=0)
      { double p0=(tick0.bid+tick0.ask)*0.5;
        if(ns0>=0&&ns>ns0&&MathAbs(p-p0)>=gmin) gaps.add(p0,p,tick.ask-tick.bid);
        ns0=ns;
        tick0=tick;
      }
  }

int OnInit()
  { if(speriod==0||slength==0||speriod<slength||speriod<=sbias)
      { Print("wrong session format");
        return(INIT_FAILED);
      }
    gaps.init();
    return(INIT_SUCCEEDED);
  }
  
void OnDeinit(const int reason)
  { gaps.gs2f(fname);
  }

int nsession(datetime t)
  { ulong t0=(ulong)t;
    if(t0<sbias) return -1;
    t0-=sbias;
    if(t0%speriod>slength) return -1;
    return (int)(t0/speriod);
  }

void SGap :: set(double p_1,double p,double sprd)
  { p1=p_1; p0=p2=p3=p; s=sprd;
  }

bool SGap :: brkn()
  { return ((p0>p1)&&(p3<=p1))||((p0<p1)&&(p3>=p1));
  }

void SGap :: change(double p)
  { if(brkn()) return;
    if((p0>p1&&p>p2) || (p0<p1&&p<p2)) p2=p;
    p3=p;
  }

double SGap :: gap()
  { return MathAbs(p0-p1);
  }

double SGap :: Q()
  { double q=p2-p1;
    if(q==0.0) return 0.0;
    return (p2-p0)/q;
  }

void CGaps :: init()
  { ngs=ngo=0;
  }

void CGaps :: add(double p_1,double p,double sprd)
  { ++ngs;
    if(ArraySize(gs)<ngs) ArrayResize(gs,ngs,ND);
    gs[ngs-1].set(p_1,p,sprd);
    int i=0;
    for(; i<ngo; ++i) if(go[i]<0) break;
    if(i==ngo)
      {
        ++ngo;
        if(ArraySize(go)<ngo) ArrayResize(go,ngo,ND);
      }
    go[i]=ngs-1;
  }

bool CGaps :: change(double p)
  { bool chngd=false;
    for(int i=0; i<ngo; ++i)
      { if(go[i]<0) continue;
        gs[go[i]].change(p);
        if(gs[go[i]].brkn()) {go[i]=-1; chngd=true;}
      }
    return chngd;
  }

void CGaps :: gs2f(string fn)
  { int f=FileOpen(fn, FILE_WRITE|FILE_COMMON|FILE_ANSI|FILE_TXT), c;
    for(int i=0;i<ngs;++i)
      { if (gs[i].brkn()) c=1; else c=0;
        FileWriteString(f,(string)gs[i].gap()+" "+(string)gs[i].Q()+" "+(string)c+" "+(string)gs[i].s);
        if(i==ngs-1) break;
        FileWriteString(f,"\n");
      }
    FileClose(f);
  }

Os EAs são bem simples, embora faça sentido mencionar o array go[] na classe CGaps onde os índices de gaps, que não foram fechados são armazenados permitindo acelerar o trabalho dos EAs.

Em qualquer caso, os seguintes dados são registrados para cada gap: valor do gap absoluto, valor de Q, dados sobre seu fechamento e valor do spread no momento do gap. Então, a diferença entre o valor da distribuição empírica Q e o uniforme são verificados e a decisão sobre a análise posterior é realizada. Os métodos gráficos e computacionais (cálculo estatístico de Kolmogorov) são usados para verificar a diferença. Por simplicidade, nos restringiremos ao valor-p do teste de Kolmogorov-Smirnov como resultado dos cálculos. Ele leva valores entre zero e um, e quanto menor ele for, menos provável é que a distribuição da amostra coincida com a teórica.

Nós selecionamos o teste de Kolmogorov-Smirnov (uma amostra) para considerações matemáticas. A principal razão é que estamos interessados em distinguir as funções de distribuição na métrica de convergência uniforme, não em nenhuma métrica integral. Este teste não foi encontrado nas bibliotecas MQL5, então eu tive que usar a linguagem R. Vale a pena notar que se houver números correspondentes na amostra, a precisão deste critério é um pouco reduzida (a R dá um aviso apropriado), mas permanece bastante aceitável.

Se uma discrepância significativa entre as distribuições teóricas e empíricas for descoberta, nós deveríamos estudar a possibilidade de extrair lucro disso. Se não houver discrepância significativa, nós descartamos essa ideia ou tentamos melhorá-la.

Como mencionei acima, existem duas maneiras de inserir um negócio no preço p0 quando o gap é formado − na direção do gap ou na direção oposta. Vamos calcular a expectativa de retornos para esses dois casos. Ao fazer isso, vamos considerar um spread considerando-o constante e denotando-o como s. O valor do intervalo absoluto é denotado como g, enquanto que g0 significa seu valor mínimo.

As estatísticas foram coletadas para dois símbolos:

  1. EURUSD
  2. USDJPY

Os seguintes tipos de gaps foram considerados para cada um deles:

  1. Entre ticks consecutivos.
  2. Entre barras de minuto.
  3. Entre as sessões de negociação. Para o EURUSD, esta é uma sessão americana (as de Chicago e Nova York combinadas), enquanto o USDJPY é da de Tóquio.

Para cada uma dessas seis opções, as estatísticas foram estudadas para os mais recentes:

  1. 200 gaps
  2. 50 gaps

Como resultado, nós temos 12 opções. Os resultados para cada um deles são os seguintes:

  1. valor-p para a estatística de Kolmogorov
  2. O valor do spread médio no momento em que o gap é formado
  3. Gráfico das funções empíricas e teóricas (linha vermelha) do valor da distribuição Q
  4. O gráfico da expectativa matemática de rentabilidade M_cont para negociação em uma direção do gap, dependendo de q em comparação com a linha teórica (vermelho) M_cont=0. Aqui, q significa a soma da razão de take profit/take profit e stop loss.
  5. O gráfico da expectativa matemática de rentabilidade M_rvrs para negociação contrária a direção do gap, dependendo de q em comparação com a linha teórica (vermelho) M_rvrs=0. Aqui, q significa a soma da razão stop loss/take profit e stop loss.

Todas as opções de resultados são fornecidas abaixo.

  1. EURUSD, 200 últimos gaps entre ticks consecutivos. valor-p: 3.471654e-07, spread médio: 0.000695

    EURUSD, 200 últimos gaps entre ticks consecutivos


  2. EURUSD, 50 últimos gaps entre ticks consecutivos. valor-p: 0.2428457, spread médio: 0.0005724

    EURUSD, 50 últimos gaps entre ticks consecutivos

  3. EURUSD, 200 últimos gaps entre as barras de minutos. valor-p: 8.675995e-06, spread médio: 0.0004352

    EURUSD, 200 últimos gaps entre barras de minutos

  4. EURUSD, 50 últimos gaps entre as barras de minutos. valor-p: 0.0125578, spread médio: 0.000404

    EURUSD, 50 últimos gaps entre barras de minutos

  5. EURUSD, 200 últimos gaps entre as sessões de negociação. valor-p: 0.6659917, spread médio: 0.0001323

    EURUSD, 200 últimos gaps entre as sessões de negociação

  6. EURUSD, 50 últimos gaps entre as sessões de negociação. valor-p: 0.08915716, spread médio: 0.0001282

    EURUSD, 50 últimos gaps entre as sessões de negociação

  7. USDJPY, 200 últimos gaps entre os ticks consecutivos. valor-p: 2.267454e-06, spread médio: 0.09563

    USDJPY, 200 últimos gaps entre ticks consecutivos

  8. USDJPY, 50 últimos gaps entre os ticks consecutivos. valor-p: 0.03259067, spread médio: 0.0597

    USDJPY, 50 últimos gaps entre ticks consecutivos

  9. USDJPY, 200 últimos gaps entre as barras de minutos. valor-p: 0.0003737335, spread médio: 0.05148

    USDJPY, 200 últimos gaps entre barras de minutos

  10. USDJPY, 50 últimos gaps entre as barras de minutos. valor-p: 0.005747542, spread médio: 0.0474

    USDJPY, 50 últimos gaps entre barras de minutos

  11. USDJPY, 200 últimos gaps entre as sessões de negociação. valor-p: 0.07743524, spread médio: 0.02023

    USDJPY, 200 últimos gaps entre as sessões de negociação

  12. USDJPY, 50 últimos gaps entre as sessões de negociação. valor-p: 0.009191665, spread médio: 0.0185

    USDJPY, 50 últimos gaps entre as sessões de negociação

Nós podemos tirar as seguintes conclusões destes resultados:

Testando a estratégia e calculando o risco ideal por negócio

A versão do sistema baseada em intervalos entre as barras de minutos do USDJPY parece mais promissora. O aumento significativo do spread que detectamos no momento da formação do gap significa que devemos prestar mais atenção à sua definição. Vamos especificá-lo da seguinte maneira. Nós não vamos considerar o gap para o preço médio, mas para bid e ask. Além disso, nós vamos escolher um deles, para o qual precisamos entrar na operação. Isso significa que nós vamos definir um gap ascendente por bid e um gap descendente — por ask. O mesmo vale para o seu encerramento.

Vamos desenvolver o EA alterando ligeiramente o que nós usamos para coletar estatísticas sobre os gaps entre as sessões. A principal mudança diz respeito à estrutura do gap. Como nós temos uma correspondência definitiva entre gaps e negócios, toda a informação necessária para a negociação (volume de negócios e condição de fechamento) deve ser armazenada nessa estrutura. Duas funções são adicionadas para negociação. Uma delas (pp2v()) calcula o volume para cada negócio individual, enquanto outro (trade()) salva a correspondência entre a soma dos volumes negociados e o volume da posição de negociação. O código do EA (gaps_ses_test.mq5) é fornecido abaixo.

// gaps_ses_test.mq5
#define ND 100

input uint   mperiod=1;                 // período da sessão em minutos
input uint   mlength=1;                 // duração da sessão em minutos
input uint   mbias=0;                   // viés da primeira sessão em minutos
input double g0=0.1;                    // tamanho mínimo do intervalo: USDJPY - 0.1, EURUSD - 0.001
input double q0=0.4;                    // q0=sl/(sl+tp)
input double r=0.01;                    // risco no negócio
input double s=0.02;                    // spread aproximado
input string fname="gaps\\stat.txt";    // nome do arquivo para estatísticas             

struct SGap
  { double p0;
    double p1;
    double p2;
    double v;
    int brkn();
    bool up();
    void change(double p);
    double gap();
    double Q();
    double a();
  };

class CGaps
  { SGap gs[];
    int ngs;
    int go[];
    int ngo;
    public:
    void init();
    void add(double p_1,double p);
    bool change(double pbid,double pask);
    double v();
    void gs2f(string fn);
  };

CGaps gaps;
MqlTick tick0;
int ns0=-1;
ulong sbias=mbias*60, speriod=mperiod*60, slength=mlength*60;
double dv=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP);

void OnTick()
  { MqlTick tick;
    if (!SymbolInfoTick(_Symbol, tick)) return;
    bool chngd=gaps.change(tick.bid,tick.ask);
    int ns=nsession(tick.time);
    if(ns>=0)
      { if(ns0>=0&&ns>ns0)
          { if(tick0.ask-tick.ask>=g0) {gaps.add(tick0.ask,tick.ask); chngd=true;}
              else if(tick.bid-tick0.bid>=g0) {gaps.add(tick0.bid,tick.bid); chngd=true;}
          }
        ns0=ns;
        tick0=tick;
      }
    
    if(chngd) trade(gaps.v());
  }

int OnInit()
  {
     gaps.init();
     return(INIT_SUCCEEDED);
  }
  
void OnDeinit(const int reason)
  { gaps.gs2f(fname);
  }

int nsession(datetime t)
  { ulong t0=(ulong)t;
    if(t0<sbias) return -1;
    t0-=sbias;
    if(t0%speriod>slength) return -1;
    return (int)(t0/speriod);
  }

double pp2v(double psl, double pen)
  { if(psl==pen) return 0.0;
    double dc, dir=1.0;
    double c0=AccountInfoDouble(ACCOUNT_EQUITY);
    bool ner=true;
    if (psl<pen) ner=OrderCalcProfit(ORDER_TYPE_BUY,_Symbol,dv,pen+s,psl,dc);
      else {ner=OrderCalcProfit(ORDER_TYPE_SELL,_Symbol,dv,pen,psl+s,dc); dir=-1.0;}
    if(!ner) return 0.0;
    return -dir*r*dv*c0/dc;
  }

void trade(double vt)
  { double v0=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN);
    if(-v0<vt<v0) vt=v0*MathRound(vt/v0);
    double vr=0.0;
    if(PositionSelect(_Symbol))
      { vr=PositionGetDouble(POSITION_VOLUME);
        if(PositionGetInteger(POSITION_TYPE)==POSITION_TYPE_SELL) vr=-vr;
      }
    int vi=(int)((vt-vr)/dv);
    if(vi==0) return;
    MqlTradeRequest request={0};
    MqlTradeResult  result={0};
    request.action=TRADE_ACTION_DEAL;
    request.symbol=_Symbol; 
    if(vi>0)
      { request.volume=vi*dv;
        request.type=ORDER_TYPE_BUY;
      }
      else
        { request.volume=-vi*dv;
          request.type=ORDER_TYPE_SELL;
        }
    if(!OrderSend(request,result)) PrintFormat("OrderSend error %d",GetLastError());
  }

int SGap :: brkn()
  { if(((p0>p1)&&(p2<=p1))||((p0<p1)&&(p2>=p1))) return 1;
    if(Q()>=q0) return -1;
    return 0;
  }

bool SGap :: up()
  { return p0>p1;
  }

void SGap :: change(double p)
  { if(brkn()==0) p2=p;
  }

double SGap :: gap()
  { return MathAbs(p0-p1);
  }

double SGap :: Q()
  { if(p2==p1) return 0.0;
    return (p2-p0)/(p2-p1);
  }

double SGap :: a()
  { double g=gap(), k0=q0/(1-q0);
    return (g-s)/(k0*g+s);
  }

void CGaps :: init()
  { ngs=ngo=0;
  }

void CGaps :: add(double p_1,double p)
  { ++ngs;
    if(ArraySize(gs)<ngs) ArrayResize(gs,ngs,ND);
    gs[ngs-1].p0=gs[ngs-1].p2=p;
    gs[ngs-1].p1=p_1;
    double ps=p+(p-p_1)*q0/(1-q0);
    gs[ngs-1].v=pp2v(ps,p);
    int i=0;
    for(; i<ngo; ++i) if(go[i]<0) break;
    if(i==ngo)
      {
        ++ngo;
        if(ArraySize(go)<ngo) ArrayResize(go,ngo,ND);
      }
    go[i]=ngs-1;
  }

bool CGaps :: change(double pbid,double pask)
  { bool ch=false;
    for(int i=0; i<ngo; ++i)
      { if(go[i]<0) continue;
        if(gs[go[i]].up()) gs[go[i]].change(pbid); else gs[go[i]].change(pask);
        if(gs[go[i]].brkn()!=0) {go[i]=-1; ch=true;}
      }
    return ch;
  }

double CGaps :: v(void)
  { double v=0;
    for(int i=0; i<ngo; ++i) if(go[i]>=0) v+=gs[go[i]].v;
    return v;
  }

void CGaps :: gs2f(string fn)
  { int f=FileOpen(fn, FILE_WRITE|FILE_COMMON|FILE_ANSI|FILE_TXT);
    int na=0, np=0, bk;
    double kt=0.0, pk=0.0;
    for(int i=0;i<ngs;++i)
      { bk=gs[i].brkn();
        if(bk==0) continue;
        ++na; if(bk>0) ++np;
        kt+=gs[i].a();
      }
     if(na>0)
       { kt/=na;
         pk=((double)np)/na;
       }
     FileWriteString(f,"na = "+(string)na+"\n");
     FileWriteString(f,"kt = "+(string)kt+"\n");
     FileWriteString(f,"pk = "+(string)pk);
     FileClose(f);
  }

Vamos testar o EA no ano de 2017 e definir o valor do risco para negociação em 2018 com base em seus resultados. O gráfico de saldo/capital com base nos resultados do teste de 2017 é fornecido abaixo.

2017

Eu tenho que fazer alguns esclarecimentos antes de proceder ao cálculo de risco. Primeiro, nós precisamos justificar a necessidade de determinar o nível correto de risco. Em segundo lugar, é necessário explicar a vantagem de aplicar nossa teoria para esse propósito.

A negociação especulativa está sempre associada à incerteza. Qualquer sistema de negociação, por sua vez, realiza algumas operações perdedoras. Por esse motivo, o risco não deve ser muito grande. Caso contrário, o rebaixamento será excessivo. Por outro lado, o mercado pode alterar a qualquer momento transformando um sistema lucrativo em um sistema perdedor. Portanto, o "tempo de vida" do sistema é finito e não é conhecido com precisão. Por esse motivo, o risco não deve ser muito pequeno. Caso contrário, você não será capaz de obter todos os lucros possíveis do seu sistema de negociação.

Vamos agora considerar as principais abordagens (diferentes das nossas) para definir o risco acompanhado de características breves:

Nosso método, ao contrário dos descritos acima, nos permite obter valores de risco adequados e razoáveis. Ele possui parâmetros ajustáveis que podem ser adaptados ao estilo de negociação específico. Vamos descrever a essência de nossa abordagem para o cálculo de risco. Nós assumimos que usamos o sistema de negociação exatamente até que sua lucratividade média dentro do número especificado de negócios caia abaixo do nível mínimo especificado, ou até que o rebaixamento exceda o nível máximo especificado na mesma sequência de negócios. Depois disso, a negociação baseada neste sistema é interrompida (por exemplo, seus parâmetros são re-otimizados). O valor do risco é selecionado de modo que a probabilidade de que o sistema ainda seja lucrativo (e a redução de rebaixamento ou lucratividade seja uma flutuação aleatória natural) não seja maior do que o valor especificado.

O método é descrito em detalhes nos artigos anteriores. No segundo artigo, você pode encontrar um script para calcular o valor de risco ideal. Esse script é aplicável para encerrar os negócios pelos níveis de stop loss e take profit especificados durante a entrada com taxa fixa para todas as operações. Ele é chamado de bn.mq5 no artigo mencionado.

Como resultado de um passe de teste, nosso EA grava os dados que são necessários como parte dos parâmetros para o script de cálculo de risco em um arquivo de texto. Os parâmetros restantes são conhecidos antecipadamente ou selecionados por pesquisa exaustiva. Se o valor de risco proposto pelo script for zero, então nós devemos descartar essa ideia de negociação ou enfraquecer nossos requisitos de rebaixamento/rentabilidade (alterando os parâmetros) ou usar os dados de negócios de um histórico maior. Abaixo está uma parte do script com os valores dos parâmetros a serem definidos.

input uint na=26;                     // número de negócios na série
input double kt=1.162698185452029     // razão take profit/stop loss
input double pk=0.5769230769230769    // probabilidade de lucro

double G0=0.0;                        // menor rentabilidade média
double D0=0.9;                        // menor incremento mínimo
double dlt=0.17;                      // nível de significância

Como os resultados dos testes para 2017 não são inspiradores em termos de qualidade de negociação, nossos requisitos são bastante moderados. Nós estabelecemos a condição de que dentro de 26 negócios (na=26), o EA não é deficitário (G0=0.0) e o rebaixamento não excede 10% (D0=0.9). Para obter um valor de risco diferente de zero, nós temos que estabelecer um nível de significância bastante alto (dlt=0.17). Na verdade, é melhor que não seja mais do que um décimo. O fato de termos que torná-lo tão grande indica resultados de negociação ruins. O EA com esses parâmetros não deve ser usado na negociação real neste símbolo. Com os parâmetros especificados, o script fornece o seguinte resultado para o risco: r = 0.014. Abaixo você pode encontrar o resultado do teste do EA para 2018 com este valor de risco.

2018

Apesar dos lucros demonstrados pela EA durante o teste, é improvável que eles permaneçam em negociação real. A futilidade de negociar gaps comuns nos símbolos examinados parece óbvia. Esses gaps são muito raros (e se tornam mais raros com o tempo) e pequenos em tamanho. A consideração mais aprofundada da generalização dos gaps - variações de preço entre as sessões - parece mais promissora. Além disso, faz sentido prestar atenção aos ativos onde os gaps comuns são mais comuns.

Conclusão

Métodos probabilísticos são bastante adequados para o desenvolvimento e configuração de EAs. Ao mesmo tempo, eles não são de forma alguma contrários à outros métodos possíveis. Em vez disso, eles muitas vezes tornam possível suplementar ou repensar.

Neste artigo, nós não abordamos o tópico da otimização geral do EA por seus parâmetros, mencionando-o apenas de passagem. Há uma conexão significativa entre essa área e a abordagem probabilística (a teoria das soluções estatísticas). Talvez, eu estudarei essa questão em detalhes mais tarde.

Arquivos anexados

Abaixo, você pode encontrar dois EAs usados para coletar estatísticas e um EA usado para negociação de teste.

 #  Nome Tipo 
 Descrição
1 gaps_reg_stat.mq5 EA Coleta estatísticas dos gaps entre ticks consecutivos
2
gaps_ses_stat.mq5 EA Coleta estatísticas de gaps entre as sessões
3 gaps_ses_test.mq5 EA Teste de negociação usando gaps entre as sessões