
Criando um Consultor Especialista, que negocia em um número de instrumentos
Introdução
O lado técnico de implementar o código do programa de forma que um único Consultor Especialista, lançado em um único gráfico, seja capaz de negociar com diferentes ativos financeiros ao mesmo tempo. Em geral, isso não era um problema mesmo no MQL4. Mas apenas com o advento do terminal do cliente do MetaTrader 5, negociantes finalmente conseguiram a oportunidade de realizar uma análise completa do trabalho de tais automatizados, utilizando verificadores de estratégia.
Então agora automatizados com múltiplas moedas correntes se tornarão mais populares do que nunca, e nós podemos prever uma onda de interesse na construção de tais sistemas de negociação. Mas o problema principal de implementação de tais robôs é o fato de que suas dimensões no código do programa expandem, no melhor dos casos, em progressão aritmética, e isso não é fácil de aceitar para um programador típico.
Nesse artigo escreveremos um Consultor Especialista simples com múltiplas moedas correntes, no qual as falhas de estrutura são, se não ausentes, ao menos minimizadas.
1. Implementando um sistema seguidor de tendências simples
De fato, poderíamos começar com um sistema de negociação maximamente simples, seguindo a tendência na base de um terminal integrado de um indicador técnico média móvel exponencial tripla. Este é um algorítimo muito simples, que não requer comentários especiais, e que incorporaremos no código do programa.
Mas, primeiro e antes de tudo, gostaria de fazer as conclusões mais gerais sobre o Expert Advisor. Faz sentido começar com o bloco de parâmetros do Consultor Especialista, declarados em um nível global.
Então, primeiro de tudo precisamos escolher os ativos financeiros em que trabalharemos. Isso pode ser feito utilizando variáveis de entrada, nas quais os símbolos do ativo podem ser armazenados. Agora seria bom possuir um interruptor de proibição para cada ativo financeiro, que permitiria desabilitar operações de negociação pelo ativo.
Naturalmente, cada recurso deveria estar associado com seus parâmetros individuais de stop loss (parar perdas), take profit (obter lucros), o volume da posição aberta, e slippage (derrapagem). E por razões óbvias, os parâmetros de entrada do indicador média móvel exponencial tripla para cada chip de negociação deveriam ser individual.
Aqui está um bloco final de variáveis de entrada para apenas um chip, realizado de acordo com esses argumentos. Os blocos restantes diferem apenas pelos números nos nomes dos parâmetros de entrada do Consultor Especialista. Para esse exemplo me limitei a apenas doze ativos financeiros, apesar de idealmente não haver limitações no software para o número de tais blocos.
Nós apenas precisamos de algo para negociar! E mais importante - nosso PC deve possuir recursos o suficiente para resolver esse problema.
input string Symb0 = "EURUSD"; input bool Trade0 = true; input int Per0 = 15; input ENUM_APPLIED_PRICE ApPrice0 = PRICE_CLOSE; input int StLoss0 = 1000; input int TkProfit0 = 2000; input double Lots0 = 0.1; input int Slippage0 = 30;
Agora que nós resolvemos as variáveis no nível global, podemos proceder para a construção do código dentro da função OnTick(). A opção mais racional seria a divisão do algoritmo para receber sinais de negociação e a parte da negociação de fato do Consultor Especialista em duas funções personalizadas.
E já que o Consultor Especialista funciona com doze ativos financeiros ao mesmo tempo, deve sempre haver doze chamadas dessas funções dentro do bloco OnTick().
Naturalmente, o primeiro parâmetro de entrada dessas funções deveria ser um número único, sob o qual esses ativos de negociação serão listados. O segundo parâmetro de entrada, por razões óbvias, será o nome da linha do ativo financeiro de negociação.
Para o papel de terceiro parâmetro, vamos configurar uma variável lógica para resolver a negociação. Depois, para o algorítimo de determinação de sinais de negociação, siga os sinais do indicador de entrada e para uma função de negócio - a distância para as ordens pendentes, o volume da posição e deslize (deslize permitido do preço da posição aberta).
Para transferir os sinais de negociação de uma função para a outra, arranjos estáticos deveriam ser configurados como os parâmetros da função, que derivam seus valores através de uma referência. Essa é a versão final do código proposto para a função OnTick().
void OnTick() { //--- declare variables arrays for trade signals static bool UpSignal[12], DnSignal[12], UpStop[12], DnStop[12]; //--- get trade signals TradeSignalCounter( 0, Symb0, Trade0, Per0, ApPrice0, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 1, Symb1, Trade1, Per1, ApPrice1, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 2, Symb2, Trade2, Per2, ApPrice2, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 3, Symb3, Trade3, Per3, ApPrice3, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 4, Symb4, Trade4, Per4, ApPrice4, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 5, Symb5, Trade5, Per5, ApPrice5, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 6, Symb6, Trade6, Per6, ApPrice6, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 7, Symb7, Trade7, Per7, ApPrice7, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 8, Symb8, Trade8, Per8, ApPrice8, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter( 9, Symb9, Trade9, Per9, ApPrice9, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter(10, Symb10, Trade10, Per10, ApPrice10, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter(11, Symb11, Trade11, Per11, ApPrice11, UpSignal, DnSignal, UpStop, DnStop); //--- perform trade operations TradePerformer( 0, Symb0, Trade0, StLoss0, TkProfit0, Lots0, Slippage0, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 1, Symb1, Trade1, StLoss1, TkProfit1, Lots1, Slippage1, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 2, Symb2, Trade2, StLoss2, TkProfit2, Lots2, Slippage2, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 3, Symb3, Trade3, StLoss3, TkProfit3, Lots3, Slippage3, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 4, Symb4, Trade4, StLoss4, TkProfit4, Lots4, Slippage4, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 5, Symb5, Trade5, StLoss5, TkProfit5, Lots5, Slippage5, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 6, Symb6, Trade6, StLoss6, TkProfit6, Lots6, Slippage6, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 7, Symb7, Trade7, StLoss7, TkProfit7, Lots7, Slippage7, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 8, Symb8, Trade8, StLoss8, TkProfit8, Lots8, Slippage8, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 9, Symb9, Trade9, StLoss9, TkProfit9, Lots9, Slippage9, UpSignal, DnSignal, UpStop, DnStop); TradePerformer(10, Symb10, Trade10, StLoss10, TkProfit10, Lots10, Slippage10, UpSignal, DnSignal, UpStop, DnStop); TradePerformer(11, Symb11, Trade11, StLoss11, TkProfit11, Lots11, Slippage11, UpSignal, DnSignal, UpStop, DnStop); //--- }
Dentro da função TradeSignalCounter(), é apenas necessário obter o nome do indicador técnico média móvel exponencial tripla uma vez na inicialização de cada chip, e depois a cada mudança da barra para calcular os sinais de negociação.
Esse esquema relativamente simples com a implementação do código está começando a transbordar com detalhes pequenos.
bool TradeSignalCounter(int Number, string Symbol_, bool Trade, int period, ENUM_APPLIED_PRICE ApPrice, bool &UpSignal[], bool &DnSignal[], bool &UpStop[], bool &DnStop[]) { //--- check if trade is prohibited if(!Trade)return(true); //--- declare variable to store final size of variables arrays static int Size_=0; //--- declare array to store handles of indicators as static variable static int Handle[]; static int Recount[],MinBars[]; double TEMA[4],dtema1,dtema2; //--- initialization if(Number+1>Size_) // Entering the initialization block only on first start { Size_=Number+1; // For this number entering the block is prohibited //--- change size of variables arrays ArrayResize(Handle,Size_); ArrayResize(Recount,Size_); ArrayResize(MinBars,Size_); //--- determine minimum number of bars, sufficient for calculation MinBars[Number]=3*period; //--- setting array elements to 0 DnSignal[Number] = false; UpSignal[Number] = false; DnStop [Number] = false; UpStop [Number] = false; //--- use array as timeseries ArraySetAsSeries(TEMA,true); //--- get indicator's handle Handle[Number]=iTEMA(Symbol_,0,period,0,ApPrice); } //--- check if number of bars is sufficient for calculation if(Bars(Symbol_,0)<MinBars[Number])return(true); //--- get trade signals if(IsNewBar(Number,Symbol_,0) || Recount[Number]) // Entering the block on bar change or on failed copying of data { DnSignal[Number] = false; UpSignal[Number] = false; DnStop [Number] = false; UpStop [Number] = false; //--- using indicator's handles, copy values of indicator's //--- buffers into static array, specially prepared for this purpose if(CopyBuffer(Handle[Number],0,0,4,TEMA)<0) { Recount[Number]=true; // As data were not received, we should return // into this block (where trade signals are received) on next tick! return(false); // Exiting the TradeSignalCounter() function without receiving trade signals } //--- all copy operations from indicator buffer are successfully completed Recount[Number]=false; // We may not return to this block until next change of bar int Digits_ = int(SymbolInfoInteger(Symbol_,SYMBOL_DIGITS)+4); dtema2 = NormalizeDouble(TEMA[2] - TEMA[3], Digits_); dtema1 = NormalizeDouble(TEMA[1] - TEMA[2], Digits_); //---- determining the input signals if(dtema2 > 0 && dtema1 < 0) DnSignal[Number] = true; if(dtema2 < 0 && dtema1 > 0) UpSignal[Number] = true; //---- determining the output signals if(dtema1 > 0) DnStop[Number] = true; if(dtema1 < 0) UpStop[Number] = true; } //----+ return(true); }
Nesse aspecto, o código da função TradePerformer() acaba por ser bem simples:
bool TradePerformer(int Number, string Symbol_, bool Trade, int StLoss, int TkProfit, double Lots, int Slippage, bool &UpSignal[], bool &DnSignal[], bool &UpStop[], bool &DnStop[]) { //--- check if trade is prohibited if(!Trade)return(true); //--- close opened positions if(UpStop[Number])BuyPositionClose(Symbol_,Slippage); if(DnStop[Number])SellPositionClose(Symbol_,Slippage); //--- open new positions if(UpSignal[Number]) if(BuyPositionOpen(Symbol_,Slippage,Lots,StLoss,TkProfit)) UpSignal[Number]=false; //This trade signal will be no more on this bar! //--- if(DnSignal[Number]) if(SellPositionOpen(Symbol_,Slippage,Lots,StLoss,TkProfit)) DnSignal[Number]=false; //This trade signal will be no more on this bar! //--- return(true); }Mas isso é apenas porque os comandos reais para o desempenho das operações de negociação estão empacotados em quatro funções adicionais:
BuyPositionClose(); SellPositionClose(); BuyPositionOpen(); SellPositionOpen();
Todas as quatro funções funcionam de forma completamente análoga, então podemos nos limitar a examinação de apenas uma delas:
bool BuyPositionClose(const string symbol,ulong deviation) { //--- declare structures of trade request and result of trade request MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); //--- check if there is BUY position if(PositionSelect(symbol)) { if(PositionGetInteger(POSITION_TYPE)!=POSITION_TYPE_BUY) return(false); } else return(false); //--- initializing structure of the MqlTradeRequest to close BUY position request.type = ORDER_TYPE_SELL; request.price = SymbolInfoDouble(symbol, SYMBOL_BID); request.action = TRADE_ACTION_DEAL; request.symbol = symbol; request.volume = PositionGetDouble(POSITION_VOLUME); request.sl = 0.0; request.tp = 0.0; request.deviation=(deviation==ULONG_MAX) ? deviation : deviation; request.type_filling=ORDER_FILLING_FOK; //--- string word=""; StringConcatenate(word, "<<< ============ BuyPositionClose(): Close Buy position at ", symbol," ============ >>>"); Print(word); //--- send order to close position to trade server if(!OrderSend(request,result)) { Print(ResultRetcodeDescription(result.retcode)); return(false); } //----+ return(true); }
Basicamente, isso é tudo do Consultor Especialista de múltiplas moedas correntes (Exp_TEMA.mq5)!
Além das funções consideradas, ele contém duas funções adicionais do usuário:
bool IsNewBar(int Number, string symbol, ENUM_TIMEFRAMES timeframe); string ResultRetcodeDescription(int retcode);
A primeira dessas funções retorna ao valor verdadeiro no momento da mudança da barra, baseado no símbolo selecionado e cronograma, e o segundo, retorna a linha pelo código resultante da transação de negociação, derivado do campo retcode da estrutura do pedido de negociação MqlTradeResult.
O Consultor Especialista está pronto para começar com os testes! Não há diferenças sérias visíveis no teste do Consultor Especialista de múltiplas moedas correntes e de seu colega Consultor Especialista de moeda corrente única.
Determine as configurações a aba "Parâmetros" do verificador de estratégia:
Figura 1. Aba "Configurações" do verificador de estratégia
Se necessário, ajuste os valores dos parâmetros de entrada na aba "parâmetros de entrada:
Figura 2. Aba "Parâmetros" do verificador de estratégia
e então clique no botão "Iniciar" no verificador de estratégia na aba "Configurações":
Figura 3. Executando o teste do Consultor Especialista
O tempo que passa do primeiro teste do Consultor Especialista pode acabar por ser muito significante, devido ao carregamento do histórico para todos os doze símbolos. Após completar o teste no verificador de estratégia, abra a aba "Resultados":
Figura 4. Resultados do teste
e faça uma análise dos dados utilizando conteúdos da aba "Gráfico":
Figura 5. Gráfico de dinâmica de balanço e equidade
e "Diário"
Figura 6. Diário do verificador de estratégia
Muito naturalmente, a essência das entradas e saídas do algoritmo do mercado desse Consultor Especialista é muito simples, e seria ingênuo esperar resultados muito significantes, quando usando os primeiros parâmetros aleatórios. Mas o nosso objetivo aqui é demonstrar a ideia fundamental de construir um Consultor Especialista de múltiplas moedas correntes da forma mais simples possível.
Com a otimização desse Consultor Especialista algumas inconveniências podem surgir devido a muitos parâmetros de entrada. O algoritmo de otimização genérico exige uma quantidade muito menor desses parâmetros, então o Consultor Especialista deve ser otimizado em cada chip individualmente, desabilitando os chips restantes de parâmetros de entrada TradeN.
Agora quando a essência da abordagem tiver sido esboçada, você pode começar a trabalhar com o algoritmo mais interessante de tomada de decisões para o robô de múltiplas moedas correntes.
2. Ressonâncias dos mercados financeiros e suas aplicações em sistemas de negociação
A ideia de levar em conta as correlações entre os ativos financeiros diferentes, em geral, não é nova, e poderia ser interessante implementar o algorítimo, que poderia ser baseado precisamente na análise de tais tendências. Neste artigo, implementarei uma moeda múltipla automatizada, baseado no artigo de Vasily Yakimkin "Resonances - a New Class of Technical Indicators" publicado na revista "Currency Speculator" (em Russo) 04, 05, 2001.
A essência dessa abordagem em poucas palavras se parece com o seguinte. Por exemplo, para pesquisar sobre EUR / USD, nós utilizamos não somente os resultados de alguns indicadores do ativo financeiro, mas também os resultados do mesmo indicador relacionado a ativos EUR/USD - EUR/JPY e USD/JPY. é melhor utilizar o indicador, os valores do qual são normalizados na mesma faixa de mudanças pela simplicidade e facilidade de medições e cálculos.
Considerando essas exigências, um bem adequado para esse clássico é o indicador estocástico. Apesar de, na realidade, não haver diferença no uso de outros indicadores. Como a tendência de direção, consideraremos o sinal de diferença entre o valor de estocástico Stoh e sua linha de sinal Sign .
Figura 7. Determinando a direção da tendência
Para o símbolo variável dStoh há uma tabela inteira de possíveis combinações e suas interpretações para a direção da tendência atual:
Figura 8. Combinações do símbolo variável dStoh e a direção da tendência
Em um caso onde dois sinais dos ativos EUR / JPY e USD / JPY possuem valores opostos, devemos determinar a soma deles e se essa soma é maior do que zero, considerar ambos os sinais como positivos, caso contrário - como negativos.
Assim, para abrir Longs, usamos a situação quando a tendência está crescendo e para sair, usamos a tendência para baixo, ou uma tendência quando os sinais do indicador do ativo principal EUR / USD são negativos. Também, saia da compra se o ativo principal não possui sinais, e se a soma da variável dStoh para os ativos restantes for menor que zero. Para as vendas, tudo é absolutamente análogo, apenas a situação é oposta.
A solução mais racional seria colocar toda uma parte analítica do Consultor Especialista no indicador de múltiplas moedas correntes, e para o Consultor Especialista dos amortecedores indicadores, tomar apenas os sinais prontos para controle de negociação. A versão desse tipo de indicador é apresentada pelo indicador MultiStochastic.mq5, fornecendo análises visuais das condições do mercado.
Figura 9. Indicador multi-estocástico
A barra verde sinaliza a abertura e retenção de compras, e as barras vermelhas - de vendas, respectivamente. Pontos em rosa e verde claro na extremidade superior do gráfico representam os sinais para sair das posições de compra e venda.
Esse indicador pode ser diretamente utilizado para receber sinais no Consultor Especialista, mas ainda seria melhor facilitar seu trabalho e remover todos os amortecedores e elementos desnecessários de visualização, deixando apenas o que está diretamente envolvido no fornecimento de sinais de negociação. Isso é precisamente o que foi feito no indicador MultiStochastic_Exp.mq5.
Nesse Consultor Especialista, eu negociei com apenas três chips, então o código da função OnTick() se tornou excessivamente simples:
void OnTick() { //--- declare variables arrays for trade signals static bool UpSignal[], DnSignal[], UpStop[], DnStop[]; //--- get trade signals TradeSignalCounter(0, Trade0, Kperiod0, Dperiod0, slowing0, ma_method0, price_0, SymbolA0, SymbolB0, SymbolC0, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter(1, Trade1, Kperiod1, Dperiod1, slowing1, ma_method1, price_1, SymbolA1, SymbolB1, SymbolC1, UpSignal, DnSignal, UpStop, DnStop); TradeSignalCounter(2, Trade2, Kperiod2, Dperiod2, slowing2, ma_method2, price_2, SymbolA2, SymbolB2, SymbolC2, UpSignal, DnSignal, UpStop, DnStop); //--- perform trade operations TradePerformer( 0, SymbolA0, Trade0, StopLoss0, 0, Lots0, Slippage0, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 1, SymbolA1, Trade1, StopLoss1, 0, Lots1, Slippage1, UpSignal, DnSignal, UpStop, DnStop); TradePerformer( 2, SymbolA2, Trade2, StopLoss2, 0, Lots2, Slippage2, UpSignal, DnSignal, UpStop, DnStop); //--- }
No entanto, o código da função TradeSignalCounter() é um pouco mais complexo: O fato é que o indicador de moeda múltipla funciona diretamente com três séries de tempo de diferentes ativos financeiros, e assim, implementamos uma verificação mais adequada das barras para a adequação do número mínimo delas em uma das três séries de tempo, usando a função Rates_Total().
Adicionalmente, uma verificação adicional da sincronização da série de tempo feita é realizada, utilizando a função SynchroCheck(), para garantir a precisão da determinação do momento quando uma mudança de barra ocorre e toda a série de tempo simultaneamente.
bool TradeSignalCounter(int Number, bool Trade, int Kperiod, int Dperiod, int slowing, ENUM_MA_METHOD ma_method, ENUM_STO_PRICE price_, string SymbolA, string SymbolB, string SymbolC, bool &UpSignal[], bool &DnSignal[], bool &UpStop[], bool &DnStop[]) { //--- check if trade is prohibited if(!Trade)return(true); //--- declare variable to store sizes of variables arrays static int Size_=0; //--- declare arrays to store handles of indicators as static variables static int Handle[]; static int Recount[],MinBars[]; //--- double dUpSignal_[1],dDnSignal_[1],dUpStop_[1],dDnStop_[1]; //--- change size of variables arrays if(Number+1>Size_) { uint size=Number+1; //---- if(ArrayResize(Handle,size)==-1 || ArrayResize(Recount,size)==-1 || ArrayResize(UpSignal, size) == -1 || ArrayResize(DnSignal, size) == -1 || ArrayResize(UpStop, size) == -1 || ArrayResize(DnStop, size) == -1 || ArrayResize(MinBars,size) == -1) { string word=""; StringConcatenate(word,"TradeSignalCounter( ",Number, " ): Erro!!! Unable to change sizes of variables arrays!!!"); int error=GetLastError(); ResetLastError(); //--- if(error>4000) { StringConcatenate(word,"TradeSignalCounter( ",Number," ): Error code ",error); Print(word); } Size_=-2; return(false); } Size_=int(size); Recount[Number] = false; MinBars[Number] = Kperiod + Dperiod + slowing; //--- get indicator's handle Handle[Number]=iCustom(SymbolA,0,"MultiStochastic_Exp", Kperiod,Dperiod,slowing,ma_method,price_, SymbolA,SymbolB,SymbolC); } //--- check if number of bars is sufficient for calculation if(Rates_Total(SymbolA,SymbolB,SymbolC)<MinBars[Number])return(true); //--- check timeseries synchronization if(!SynchroCheck(SymbolA,SymbolB,SymbolC))return(true); //--- get trade signals if(IsNewBar(Number,SymbolA,0) || Recount[Number]) { DnSignal[Number] = false; UpSignal[Number] = false; DnStop [Number] = false; UpStop [Number] = false; //--- using indicators' handles, copy values of indicator's //--- buffers into static arrays, specially prepared for this purpose if(CopyBuffer(Handle[Number], 1, 1, 1, dDnSignal_) < 0){Recount[Number] = true; return(false);} if(CopyBuffer(Handle[Number], 2, 1, 1, dUpSignal_) < 0){Recount[Number] = true; return(false);} if(CopyBuffer(Handle[Number], 3, 1, 1, dDnStop_ ) < 0){Recount[Number] = true; return(false);} if(CopyBuffer(Handle[Number], 4, 1, 1, dUpStop_ ) < 0){Recount[Number] = true; return(false);} //--- convert obtained values into values of logic variables of trade commands if(dDnSignal_[0] == 300)DnSignal[Number] = true; if(dUpSignal_[0] == 300)UpSignal[Number] = true; if(dDnStop_ [0] == 300)DnStop [Number] = true; if(dUpStop_ [0] == 300)UpStop [Number] = true; //--- all copy operations from indicator's buffers completed successfully //--- unnecessary to return into this block until next bar change Recount[Number]=false; } //----+ return(true); }
Não há outras diferenças radicais ideológicas do código desse Consultor Especialista (Exp_ResonanceHunter.mq5) devido ao fato de que ele é compilado na base dos mesmos componentes funcionais. Então, não acho que seja necessário gastar mais tempo em sua estrutura interna.
Conclusão
Em minha opinião, o código do Consultor Especialista de múltiplas moedas correntes no MQL5 é absolutamente análogo ao código de um Consultor Especialista normal.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/105





- 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