English Русский Deutsch 日本語
preview
Criando um Expert Advisor simples multimoeda usando MQL5 (Parte 5): Bandas de Bollinger no canal de Keltner — Sinais dos indicadores

Criando um Expert Advisor simples multimoeda usando MQL5 (Parte 5): Bandas de Bollinger no canal de Keltner — Sinais dos indicadores

MetaTrader 5Negociação | 17 maio 2024, 14:32
206 0
Roberto Jacobs
Roberto Jacobs

Introdução

Neste artigo, por EA multimoeda, entendemos um robô investidor, que pode negociar (abrir/fechar ordens, gerenciar ordens, por exemplo, do tipo trailing stop-loss e trailing profit) mais de um par de moedas em um gráfico. Neste artigo, o robô investidor negociará 30 pares.

Neste artigo, utilizaremos sinais de dois indicadores, nomeadamente Bandas de Bollinger (Bollinger Bands®) e canal de Keltner.

No MetaTrader 4, esse tipo de sinais era usado através da função iBandsOnArray.

No manual do MQL4, na explicação da função iBandsOnArray, é mencionado que, ao contrário da iBands(...), a função iBandsOnArray() não obtém dados pelo nome do símbolo, timeframe e preço aplicado. Os dados de preços devem ser preparados previamente...

Em discussões no fórum do MQL5, encontrei opiniões de que iBandOnArray() não existe no MQL5.
Na verdade, iBandOnArray() não está na lista de funções do MQL5, mas com o handle do indicador iBands() podemos facilmente criar a iBandOnArray(). Na minha opinião, usar o handle do indicador no MQL5 é mais simples e prático do que usar a função iBandsOnArray() no MetaTrader 4.

Como já foi demonstrado em artigos anteriores, a negociação multimoeda tanto no terminal de trading quanto no testador de estratégias é possível graças ao MQL5.

Bem, afinal o objetivo é atender às necessidades básicas dos traders que precisam de robôs de negociação eficazes e práticos. Com as forças e capacidades do MQL5, podemos criar um Expert Advisor simples multimoeda, que nesta série de artigos usa sinais de dois indicadores para abrir ordens: Bandas de Bollinger e canal de Keltner. As Bandas de Bollinger usarão os dados de preços do canal de Keltner. Para trailing stops, continuaremos a usar o indicador Parabolic SAR (iSAR).

Características

1. Pares de negociação.

O robô investidor negociará com os seguintes pares:

EURUSD,GBPUSD,AUDUSD,NZDUSD,USDCAD,USDCHF,USDJPY,EURGBP,EURAUD, EURNZD, EURCAD, EURCHF, EURJPY, GBPAUD, GBPNZD, GBPCAD,GBPCHF,GBPJPY,AUDNZD,AUDCAD,AUDCHF,AUDJPY,NZDCAD,NZDCHF,NZDJPY, CADCHF, CADJPY, CHFJPY = 28 pares

Mais 2 pares de metais: XAUUSD (ouro) e XAGUSD (prata).

Total de 30 pares.

Como fizemos no artigo anterior, simplificamos a tarefa adicionando propriedades de entrada especiais para prefixo e sufixo do nome do par. Em seguida, com uma função simples, processamos o prefixo e/ou sufixo do nome do par em combinação com os 30 nomes de pares registrados para que o robô funcione corretamente caso a corretora use nomes de símbolos especiais.

A desvantagem da função de detecção de nomes de símbolos que têm prefixos e sufixos é que ela funciona apenas com pares ou nomes de símbolos para forex e metais no MetaTrader 5, mas não funciona com símbolos especiais e índices. Além disso, é necessário evitar erros de digitação (deve-se respeitar maiúsculas e minúsculas) no nome do prefixo e/ou sufixo do par. Por isso, é necessário ter precisão e cuidado ao inserir o prefixo e/ou sufixo do nome do par.

Como no artigo anterior, também adicionamos 10 opções, para os pares negociados, a este robô investidor. Uma das 10 opções de pares que serão negociados é Trader's Desired Pairs (pares desejados pelo trader), onde os pares negociáveis devem ser inseridos manualmente pelo trader nas propriedades do robô. Lembre-se de que o nome do par inserido já deve estar na lista de 30 pares.

Nesta versão do robô investidor, também adicionamos a opção de sessão de negociação (fuso horário), para que os pares negociáveis correspondam ao horário da sessão de negociação.

2. Indicadores de sinal.

Nesta versão do ea, utilizaremos sinais de dois indicadores, nomeadamente das Bandas de Bollinger e do canal de Keltner. Usaremos o canal de Keltner como dados de preço para as Bandas de Bollinger.

2.1. Canal de Keltner.

O canal de Keltner foi apresentado pela primeira vez por Chester Keltner na década de 1960. Na fórmula original para calcular as bandas, foram utilizadas médias móveis simples (SMA) e a faixa de preços high/low. Na década de 1980, foi introduzida uma nova fórmula que usava o intervalo médio real (ATR). O método ATR é amplamente utilizado hoje.

Ao construir o canal de Keltner, aplicável aos robôs neste artigo, usei o método popular atualmente, ou seja, a média móvel exponencial (EMA) com período de 20, com bandas superior e inferior usando o indicador ATR com período de 20.

Os parâmetros de entrada do canal de Keltner são os seguintes:

BBOnKC_Keltner Channel Indicator-cr

2.2. Bandas de Bollinger.

As Bandas de Bollinger foram criadas por John Bollinger na década de 1980 e rapidamente se tornaram um dos indicadores mais populares na análise técnica. As Bandas de Bollinger consistem em três bandas — superior, média e inferior — que são usadas para indicar extremos de preço de curto prazo no mercado. A banda superior indica sobrecompra, enquanto a inferior indica sobrevenda. A maioria dos analistas financeiros usa as Bandas de Bollinger e as combina com outros indicadores para obter uma melhor visão analítica das condições do mercado.

No robô para este artigo, usaremos as Bandas de Bollinger com período de 38, que utiliza os dados de preço do canal de Keltner.

Os parâmetros de entrada das Bandas de Bollinger são os seguintes:

BBOnKC_Bollinger Bands Indicator-cr

As figuras 1 e 2 mostram o canal de Keltner como dados de preço para as Bandas de Bollinger para sinais de compra e venda.

BBOnKC_BUY signal

Fig. 1. Sinal de compra


BBOnKC_SELL signal

Fig. 2. Sinal de venda

Na ilustração acima, o sinal é gerado apenas quando a linha média do canal de Keltner cruza a banda superior ou inferior das Bandas de Bollinger. Mas, para o robô neste artigo, o sinal do indicador na verdade é o cruzamento da linha média do canal de Keltner com as linhas superior, média e inferior das Bandas de Bollinger.

  • Para sinais de compra:

  1. Primeiro sinal: A linha média do canal de Keltner cruza a banda inferior das Bandas de Bollinger de baixo para cima; ou
  2. Segundo sinal: A linha média do canal de Keltner cruza a banda média das Bandas de Bollinger de baixo para cima; ou
  3. Terceiro sinal: A linha média do canal de Keltner cruza a banda superior das Bandas de Bollinger de baixo para cima.

  • Para sinais de venda:

  1. Primeiro sinal: A linha média do canal de Keltner cruza a banda superior das Bandas de Bollinger de cima para baixo; ou
  2. Segundo sinal: A linha média do canal de Keltner cruza a banda média das Bandas de Bollinger de cima para baixo; ou
  3. Terceiro sinal: A linha média do canal de Keltner cruza a banda inferior das Bandas de Bollinger de cima para baixo.

3. Gestão de operações e ordens

O Expert Advisor multimoeda oferece várias opções para gerenciar suas transações:

3.1. Ordens stop-loss

Opções: Use Order Stop Loss (Yes) ou (No) - usar ordem de stop-loss: sim ou não

  • Ao escolher Use Order Stop Loss (No), todas as ordens serão abertas sem stop-loss.

  • Ao escolher Use Order Stop Loss (Yes), surge novamente a escolha: Use Automatic Calculation Stop Loss (Yes) ou (No) - usar cálculo automático de stop-loss: sim ou não
  • Ao escolher Automatic Calculation Stop Loss (Yes), o stop-loss é calculado pelo EA.
  • Ao escolher Automatic Calculation Stop Loss (No), o trader precisa inserir o valor do stop-loss em pips.

  • Ao escolher Use Order Stop Loss (No), o robô verificará a execução das condições do sinal. Se elas forem atendidas, a ordem será mantida. Se o sinal enfraquecer, a ordem deve ser fechada para preservar o lucro ou o estado do sinal mudar de direção, e a ordem deve ser fechada para registrar a perda.

Nota: Antes de fechar uma operação devido a um sinal fraco, é solicitada a confirmação do usuário.

Quando No, mesmo que o sinal enfraqueça, a ordem ainda será mantida ou não será fechada para preservar o lucro.

Quando Yes, as condições para o canal de Keltner e Bandas de Bollinger são as seguintes:

  • Para fechar ordens de compra: quando a linha inferior do indicador do canal de Keltner cruza a linha superior das Bandas de Bollinger, a ordem de compra será fechada.
  • Para fechar ordens de venda: Quando a linha superior do indicador do canal de Keltner cruza a linha superior das Bandas de Bollinger, a ordem de venda será fechada.

O código para configurar o stop-loss é o seguinte:

double MCEA::OrderSLSet(const string xsymb,ENUM_ORDER_TYPE type,double atprice)
  {
//---
    slv=0.0;
    int x=PairsIdxArray(xsymb);
    Pips(xsymb);
    RefreshTick(xsymb);
    //--
    switch(type) 
      { 
       case (ORDER_TYPE_BUY):
         {
           if(use_sl==Yes && autosl==Yes) slv=mc_symbol.NormalizePrice(atprice-38*pip);
           else
           if(use_sl==Yes && autosl==No)  slv=mc_symbol.NormalizePrice(atprice-SLval*pip);
           else slv=0.0;
           //--
           break;
         }
       case (ORDER_TYPE_SELL):
         {
           if(use_sl==Yes && autosl==Yes) slv=mc_symbol.NormalizePrice(atprice+38*pip);
           else
           if(use_sl==Yes && autosl==No)  slv=mc_symbol.NormalizePrice(atprice+SLval*pip);
           else slv=0.0;
         }
      }
    //---
    return(slv);
//---
  } //-end OrderSLSet()
//---------//

O código para fechar a transação e preservar o lucro devido a um sinal fraco:

int MCEA::GetCloseInWeakSignal(const string symbol,int exis) // Signal Indicator Position Close in profit
  {
//---
    int ret=0;
    int rise=1,
        down=-1;
    //--
    int br=3;
    Pips(symbol);
    double difud=mc_symbol.NormalizePrice(1.5*pip);
    //--
    double KCub[],
           KClb[];
    double BBub[],
           BBlb[];
    //--
    ArrayResize(KCub,br,br);
    ArrayResize(KClb,br,br);
    ArrayResize(BBub,br,br);
    ArrayResize(BBlb,br,br);
    ArraySetAsSeries(KCub,true);
    ArraySetAsSeries(KClb,true);
    ArraySetAsSeries(BBub,true);
    ArraySetAsSeries(BBlb,true);
    //--
    int xx=PairsIdxArray(symbol);
    //--
    CopyBuffer(hKC[xx],1,0,br,KCub);
    CopyBuffer(hKC[xx],2,0,br,KClb);
    CopyBuffer(hBB[xx],1,0,br,BBub);
    CopyBuffer(hBB[xx],2,0,br,BBlb);
    //--
    int dirmove=DirectionMove(symbol,TFt);
    bool closebuy=(KClb[1]>=BBub[1] && KClb[0]<BBub[0]-difud);
    bool closesel=(KCub[1]<=BBlb[1] && KCub[0]>BBlb[0]+difud);
    //--
    if(exis==down && closesel && dirmove==rise) ret=rise;
    if(exis==rise && closebuy && dirmove==down) ret=down;
    //--
    return(ret);
//---
  } //-end GetCloseInWeakSignal()
//---------//

3.2. Ordens take-profit

Opções: Use Order Take Profit (Yes) ou (No) - usar take-profit da ordem: sim ou não

  • Ao escolher Use Order Take Profit (Não), todas as ordens serão abertas sem take-profit.

  • Ao escolher Use Order Take Profit (Yes), surge novamente a opção: Use Automatic Calculation Order Take Profit (Yes) ou (No) - usar take-profit calculado automaticamente: sim ou não

  • Ao escolher Automatic Calculation Order Take Profit (Yes), o take-profit é calculado pelo robô.

  • Ao escolher Automatic Calculation Order Take Profit (No), o trader precisa inserir o valor do take-profit em pips.

O código para configurar o take-profit é o seguinte:

double MCEA::OrderTPSet(const string xsymb,ENUM_ORDER_TYPE type,double atprice)
  {
//---
    tpv=0.0;
    int x=PairsIdxArray(xsymb);
    Pips(xsymb);
    RefreshTick(xsymb);
    //--
    switch(type) 
      { 
       case (ORDER_TYPE_BUY):
         {
           if(use_tp==Yes && autotp==Yes) tpv=mc_symbol.NormalizePrice(atprice+50*pip);
           else
           if(use_tp==Yes && autotp==No)  tpv=mc_symbol.NormalizePrice(atprice+TPval*pip);
           else tpv=0.0;
           //--
           break;
         }
       case (ORDER_TYPE_SELL):
         {
           if(use_tp==Yes && autotp==Yes) tpv=mc_symbol.NormalizePrice(atprice-50*pip);
           else
           if(use_tp==Yes && autotp==No)  tpv=mc_symbol.NormalizePrice(atprice-TPval*pip);
           else tpv=0.0;
         }
      }
    //---
    return(tpv);
//---
  } //-end OrderTPSet()
//---------//

3.3. Trailing stop e trailing take-profit

Opções: Use Trailing SL/TP (Yes) ou (No) - usar stop-loss/take-profit de trailing: sim ou não

  • Na opção Use Trailing SL/TP (No), o robô não usará trailing stop e trailing take-profit.

  • Ao escolher Use Trailing SL/TP (Yes), surge novamente a opção: Use Automatic Trailing (Yes) ou (No) - usar trailing automático: sim ou não 

  • Ao selecionar Use Automatic Trailing (Yes), o trailing-stop é executado pelo EA usando o valor do Parabolic SAR (iSAR) simultaneamente com o cálculo dos sinais do timeframe e, ao mesmo tempo, obtendo lucro flutuante com base no valor da variável TPmin (valor do lucro flutuante).
  • Com Use Automatic Trailing (No), o trailing-stop é executado pelo EA usando o valor do parâmetro de entrada.

Nota: O EA executa o trailing do take-profit simultaneamente com o trailing-stop.

Função Trailing Stop Price:

double MCEA::TSPrice(const string xsymb,ENUM_POSITION_TYPE ptype,int TS_type)
  {
//---
    int br=2;
    double pval=0.0;
    int x=PairsIdxArray(xsymb);
    Pips(xsymb);
    //--
    switch(TS_type)
      {
        case 0:
          {
            RefreshTick(xsymb);
            if(ptype==POSITION_TYPE_BUY)  pval=mc_symbol.NormalizePrice(mc_symbol.Bid()-TSval*pip);
            if(ptype==POSITION_TYPE_SELL) pval=mc_symbol.NormalizePrice(mc_symbol.Ask()+TSval*pip);
            break;
          }
        case 1:
          {
            double PSAR[];
            ArrayResize(PSAR,br,br);
            ArraySetAsSeries(PSAR,true);
            CopyBuffer(hParIU[x],0,0,br,PSAR);
            RefreshPrice(xsymb,TFt,br);
            //--
            if(ptype==POSITION_TYPE_BUY  && (PSAR[0]<iLow(xsymb,TFt,0)))
               pval=PSAR[0];
            if(ptype==POSITION_TYPE_SELL && (PSAR[0]>iHigh(xsymb,TFt,0)))
               pval=PSAR[0];
            break;
          }
      }
    //--
    return(pval);
//---
  } //-end TSPrice()
//---------//

Alteração da função SL/TP:

bool MCEA::ModifySLTP(const string symbx,int TS_type)
  {
//---
   ResetLastError();
   MqlTradeRequest req={};
   MqlTradeResult  res={};
   MqlTradeCheckResult check={};
   //--
   int TRSP=TS_type;
   bool modist=false;
   int x=PairsIdxArray(symbx);
   Pips(symbx);
   //--
   int total=PositionsTotal();
   //--        
   for(int i=total-1; i>=0; i--) 
     {
       string symbol=PositionGetSymbol(i);
       if(symbol==symbx && mc_position.Magic()==magicEA)
         {
           ENUM_POSITION_TYPE opstype = mc_position.PositionType();
           if(opstype==POSITION_TYPE_BUY) 
             {
               RefreshTick(symbol);
               double price = mc_position.PriceCurrent();
               double vtrsb = mc_symbol.NormalizePrice(TSPrice(symbx,opstype,TRSP));
               double pos_open   = mc_position.PriceOpen();
               double pos_stop   = mc_position.StopLoss();
               double pos_profit = mc_position.Profit();
               double pos_swap   = mc_position.Swap();
               double pos_comm   = mc_position.Commission();
               double netp=pos_profit+pos_swap+pos_comm;
               double modstart=mc_symbol.NormalizePrice(pos_open+TSmin*pip);
               double modminsl=mc_symbol.NormalizePrice(vtrsb+TSmin*pip);
               double modbuysl=vtrsb;
               double modbuytp=mc_symbol.NormalizePrice(price+TPmin*pip);
               bool modbuy = (price>modminsl && modbuysl>modstart && (pos_stop==0.0||modbuysl>pos_stop));
               //--
               if(modbuy && netp>0.05)
                 {
                   modist=mc_trade.PositionModify(symbol,modbuysl,modbuytp);
                 }  
             }
           if(opstype==POSITION_TYPE_SELL) 
             {
               RefreshTick(symbol);
               double price = mc_position.PriceCurrent();
               double vtrss = mc_symbol.NormalizePrice(TSPrice(symbx,opstype,TRSP));
               double pos_open   = mc_position.PriceOpen();
               double pos_stop   = mc_position.StopLoss();
               double pos_profit = mc_position.Profit();
               double pos_swap   = mc_position.Swap();
               double pos_comm   = mc_position.Commission();
               double netp=pos_profit+pos_swap+pos_comm;
               double modstart=mc_symbol.NormalizePrice(pos_open-TSmin*pip);
               double modminsl=mc_symbol.NormalizePrice(vtrss-TSmin*pip);
               double modselsl=vtrss;
               double modseltp=mc_symbol.NormalizePrice(price-TPmin*pip);
               bool modsel = (price<modminsl && modselsl<modstart && (pos_stop==0.0||modselsl<pos_stop)); 
               //--
               if(modsel && netp>0.05)
                 {
                   modist=mc_trade.PositionModify(symbol,modselsl,modseltp);
                 }  
             }
         }
     }
    //--
    return(modist);
//---
  } //-end ModifySLTP()
//---------//

4. Gestão manual de ordens.

Para aumentar a eficiência, serão adicionados vários botões.

4.1. Set SL / TP All Orders (definir stop-loss/take-profit para todas as ordens)

Se o trader configurar Use Order Stop Loss (No) e/ou Use Order Take Profit (No),
mas depois quiser usar stop-loss ou take-profit para todas as ordens, ele só precisa pressionar o botão

Set SL / TP All Orders para alterar todas as ordens e aplicar o stop-loss e/ou take-profit.

4.2. Close All Orders - fechar todas as ordens.

4.3. Close All Orders Profit - fechar todas as ordens lucrativas.

5. Management Orders and Chart Symbols.

Uma função muito útil para EAs multimoeda, que negociam 30 pares a partir de um único gráfico, será a presença de um painel único de botões, para que os traders possam alternar gráficos ou símbolos com um clique, para ver a precisão do sinal do indicador ao abrir ou fechar uma ordem pelo robô.

Implementação em um programa MQL5

1. Cabeçalho do programa e parâmetros de entrada

Inclusão do arquivo de cabeçalho em MQL5

//+------------------------------------------------------------------+
//|                             Include                              |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\AccountInfo.mqh>
//--
CTrade              mc_trade;
CSymbolInfo         mc_symbol;
CPositionInfo       mc_position; 
CAccountInfo        mc_account;
//---

Enumeração para uso do fuso horário

//--
enum tm_zone
 {
   Cus_Session,        // Trading on Custom Session
   New_Zealand,        // Trading on New Zealand Session
   Australia,          // Trading on Australia Sydney Session
   Asia_Tokyo,         // Trading on Asia Tokyo Session
   Europe_London,      // Trading on Europe London Session
   US_New_York         // Trading on US New York Session
 };
//--

Enumeração para seleção de horas

//--
enum swhour
  {
    hr_00=0,   // 00:00
    hr_01=1,   // 01:00
    hr_02=2,   // 02:00
    hr_03=3,   // 03:00
    hr_04=4,   // 04:00
    hr_05=5,   // 05:00
    hr_06=6,   // 06:00
    hr_07=7,   // 07:00
    hr_08=8,   // 08:00
    hr_09=9,   // 09:00
    hr_10=10,  // 10:00
    hr_11=11,  // 11:00
    hr_12=12,  // 12:00
    hr_13=13,  // 13:00
    hr_14=14,  // 14:00
    hr_15=15,  // 15:00
    hr_16=16,  // 16:00
    hr_17=17,  // 17:00
    hr_18=18,  // 18:00
    hr_19=19,  // 19:00
    hr_20=20,  // 20:00
    hr_21=21,  // 21:00
    hr_22=22,  // 22:00
    hr_23=23   // 23:00
  };
//--

Enumeração para seleção de atas

//--
enum inmnt
  {
    mn_00=0,   // Minute 0
    mn_05=5,   // Minute 5
    mn_10=10,  // Minute 10
    mn_15=15,  // Minute 15
    mn_20=20,  // Minute 20
    mn_25=25,  // Minute 25
    mn_30=30,  // Minute 30
    mn_35=35,  // Minute 35
    mn_40=40,  // Minute 40
    mn_45=45,  // Minute 45
    mn_50=50,  // Minute 50
    mn_55=55   // Minute 55
  };
//--

Enumeração para escolha de pares de opções para negociação

//--
enum PairsTrade
 {
   All30,  // All Forex 30 Pairs
   TrdWi,  // Trader Wishes Pairs 
   Usds,   // Forex USD Pairs
   Eurs,   // Forex EUR Pairs
   Gbps,   // Forex GBP Pairs
   Auds,   // Forex AUD Pairs
   Nzds,   // Forex NZD Pairs
   Cads,   // Forex CDD Pairs
   Chfs,   // Forex CHF Pairs
   Jpys    // Forex JPY Pairs
 };   
//--

Enumeração YN usada para opções (Yes) ou (No) no parâmetro do robô investidor

A enumeração para usar o tamanho do lote na gestão de capital

//--
enum YN
  {
   No,
   Yes
  };
//--

Enumeração para seleção do timeframe que será usado no cálculo dos indicadores de sinal

//--
enum TFUSE
  {
   TFM5,     // PERIOD_M5
   TFM15,    // PERIOD_M15
   TFM30,    // PERIOD_M30
   TFH1,     // PERIOD_H1
   TFH2,     // PERIOD_H2
   TFH3,     // PERIOD_H3
   TFH4,     // PERIOD_H4
   TFH6,     // PERIOD_H6
   TFH8,     // PERIOD_H8
   TFH12,    // PERIOD_H12
   TFD1      // PERIOD_D1
  };
//--

Nota: Na enumeração TFUSE, limitamos o uso de cálculos de intervalos de tempo para robôs apenas a partir de TF-M5 até TF-D1

Parâmetros de entrada do EA

//---
input group               "=== Global Strategy EA Parameter ==="; // Global Strategy EA Parameter
input TFUSE               tfinuse = TFH1;             // Select Expert TimeFrame, default PERIOD_H1
input int                KCPeriod = 20;               // Input Keltner Channel Period, default 20
input ENUM_MA_METHOD     KCMethod = MODE_EMA;         // Select Keltner Channel MA Method, default EMA
input ENUM_APPLIED_PRICE   KCMAAP = PRICE_TYPICAL;    // Select Keltner Channel MA Applied Price, default Price Typical
input int                KCATRPer = 20;               // Input Keltner Channel ATR Period, default 20
input double      KCATRBandsMulti = 1.0;              // Input Keltner Channel ATR bands multiplier
input int                BBPeriod = 38;               // Input Bollinger Bands® Indicator period, default 38
input double               BBDevi = 1.0;              // Input Bollinger Bands® Indicator Deviations, default 1.00
//---
input group               "=== Select Pairs to Trade ===";  // Selected Pairs to trading
input PairsTrade         usepairs = All30;           // Select Pairs to Use
input string         traderwishes = "eg. eurusd,usdchf"; // If Use Trader Wishes Pairs, input pair name here, separate by comma
input string           sym_prefix = "";              // Input the symbol prefix in case sensitive (if any)
input string           sym_suffix = "";              // Input the symbol suffix in case sensitive (if any)
//--
input group               "=== Money Management Lot Size Parameter ==="; // Money Management Lot Size Parameter
input mmt                  mmlot = DynamLot;         // Money Management Type
input double                Risk = 10.0;             // Percent Equity Risk per Trade (Min=1.0% / Max=10.0%)
input double                Lots = 0.01;             // Input Manual Lot Size FixedLot
//--Trade on Specific Time
input group               "=== Trade on Specific Time ==="; // Trade on Specific Time
input YN           trd_time_zone = Yes;              // Select If You Like to Trade on Specific Time Zone
input tm_zone            session = Cus_Session;      // Select Trading Time Zone
input swhour            stsescuh = hr_00;            // Time Hour to Start Trading Custom Session (0-23)
input inmnt             stsescum = mn_15;            // Time Minute to Start Trading Custom Session (0-55)
input swhour            clsescuh = hr_23;            // Time Hour to Stop Trading Custom Session (0-23)
input inmnt             clsescum = mn_55;            // Time Minute to Stop Trading Custom Session (0-55)
//--Day Trading On/Off
input group               "=== Day Trading On/Off ==="; // Day Trading On/Off
input YN                    ttd0 = No;               // Select Trading on Sunday (Yes) or (No)
input YN                    ttd1 = Yes;              // Select Trading on Monday (Yes) or (No)
input YN                    ttd2 = Yes;              // Select Trading on Tuesday (Yes) or (No)
input YN                    ttd3 = Yes;              // Select Trading on Wednesday (Yes) or (No)
input YN                    ttd4 = Yes;              // Select Trading on Thursday (Yes) or (No)
input YN                    ttd5 = Yes;              // Select Trading on Friday (Yes) or (No)
input YN                    ttd6 = No;               // Select Trading on Saturday (Yes) or (No)
//--Trade & Order management Parameter
input group               "=== Trade & Order management Parameter ==="; // Trade & Order management Parameter
input YN                  use_sl = No;               // Use Order Stop Loss (Yes) or (No)
input YN                  autosl = Yes;              // Use Automatic Calculation Stop Loss (Yes) or (No)
input double               SLval = 30;               // If Not Use Automatic SL - Input SL value in Pips
input YN                  use_tp = Yes;              // Use Order Take Profit (Yes) or (No)
input YN                  autotp = Yes;              // Use Automatic Calculation Take Profit (Yes) or (No)
input double               TPval = 100;              // If Not Use Automatic TP - Input TP value in Pips
input YN            TrailingSLTP = Yes;              // Use Trailing SL/TP (Yes) or (No)
input YN                 autotrl = Yes;              // Use Automatic Trailing (Yes) or (No)
input double               TSval = 5;                // If Not Use Automatic Trailing Input Trailing value in Pips
input double               TSmin = 5;                // Minimum Pips to start Trailing Stop
input double               TPmin = 25;               // Input Trailing Profit Value in Pips
input YN           Close_by_Opps = Yes;              // Close Trade By Opposite Signal (Yes) or (No)
input YN               SaveOnRev = Yes;              // Close Trade and Save profit due to weak signal (Yes) or (No)
//--Others Expert Advisor Parameter
input group               "=== Others Expert Advisor Parameter ==="; // Others EA Parameter
input YN                  alerts = Yes;              // Display Alerts / Messages (Yes) or (No)
input YN           UseEmailAlert = No;               // Email Alert (Yes) or (No)
input YN           UseSendnotify = No;               // Send Notification (Yes) or (No)
input YN      trade_info_display = Yes;              // Select Display Trading Info on Chart (Yes) or (No)
input ulong              magicEA = 20231204;         // Expert ID (Magic Number)
//---

Nota: Se o parâmetro de entrada Expert ID (Magic Number) for deixado em branco, o robô poderá gerenciar ordens abertas manualmente.

No grupo de propriedades de entrada Global Strategy EA Parameters do robô, os traders podem selecionar o timeframe para o cálculo dos sinais do indicador e inserir os parâmetros para o canal de Keltner e as Bandas de Bollinger.

Ao criar um Expert Advisor nesta seção, o canal de Keltner no robô terá exatamente os mesmos parâmetros de entrada que no indicador.

//--
input int                period_kc = 20;             // Input Keltner Channel Period 
input ENUM_MA_METHOD     ma_method = MODE_EMA;       // Select MA Type of smoothing
input ENUM_APPLIED_PRICE  ma_price = PRICE_TYPICAL;  // Select MA Applied Price
input int               atr_period = 20;             // Input ATR Period (typically over 10 or 20)
input double            band_multi = 1.00;           // Input the Band Multiplier ATR Desired
//--

No grupo de propriedades de entrada Select Pairs to Trade, é necessário escolher um par para negociar entre as 10 opções fornecidas. O valor padrão é All Forex 30 Pairs (todos os 30 pares Forex).

Para configurar o par negociado, vamos chamar a função HandlingSymbolArrays(). Usando a função HandlingSymbolArrays(), trataremos todos os pares negociados.

void MCEA::HandlingSymbolArrays(void)
  {
//---
    string All30[]={"EURUSD","GBPUSD","AUDUSD","NZDUSD","USDCAD","USDCHF","USDJPY","EURGBP",
                    "EURAUD","EURNZD","EURCAD","EURCHF","EURJPY","GBPAUD","GBPNZD","GBPCAD",
                    "GBPCHF","GBPJPY","AUDNZD","AUDCAD","AUDCHF","AUDJPY","NZDCAD","NZDCHF",
                    "NZDJPY","CADCHF","CADJPY","CHFJPY","XAUUSD","XAGUSD"}; // 30 pairs
    string USDs[]={"USDCAD","USDCHF","USDJPY","AUDUSD","EURUSD","GBPUSD","NZDUSD","XAUUSD","XAGUSD"}; // USD pairs
    string EURs[]={"EURAUD","EURCAD","EURCHF","EURGBP","EURJPY","EURNZD","EURUSD"}; // EUR pairs
    string GBPs[]={"GBPAUD","GBPCAD","GBPCHF","EURGBP","GBPJPY","GBPNZD","GBPUSD"}; // GBP pairs
    string AUDs[]={"AUDCAD","AUDCHF","EURAUD","GBPAUD","AUDJPY","AUDNZD","AUDUSD"}; // AUD pairs
    string NZDs[]={"AUDNZD","NZDCAD","NZDCHF","EURNZD","GBPNZD","NZDJPY","NZDUSD"}; // NZD pairs
    string CADs[]={"AUDCAD","CADCHF","EURCAD","GBPCAD","CADJPY","NZDCAD","USDCAD"}; // CAD pairs
    string CHFs[]={"AUDCHF","CADCHF","EURCHF","GBPCHF","NZDCHF","CHFJPY","USDCHF"}; // CHF pairs
    string JPYs[]={"AUDJPY","CADJPY","CHFJPY","EURJPY","GBPJPY","NZDJPY","USDJPY"}; // JPY pairs
    //--
    sall=ArraySize(All30);
    arusd=ArraySize(USDs);
    aretc=ArraySize(EURs);
    ArrayResize(VSym,sall,sall);
    ArrayCopy(VSym,All30,0,0,WHOLE_ARRAY);
    //--
    if(usepairs==TrdWi && StringFind(traderwishes,"eg.",0)<0)
      {
        string to_split=traderwishes; // A string to split into substrings pairs name
        string sep=",";               // A separator as a character 
        ushort u_sep;                 // The code of the separator character 
        //--- Get the separator code 
        u_sep=StringGetCharacter(sep,0);
        //--- Split the string to substrings 
        int p=StringSplit(to_split,u_sep,SPC); 
        if(p>0)
          {
            for(int i=0; i<p; i++) StringToUpper(SPC[i]);
            //--
            for(int i=0; i<p; i++)
              {
                if(ValidatePairs(SPC[i])<0) ArrayRemove(SPC,i,1);
              }
          }
        arspc=ArraySize(SPC);
      }
    //--
    SetSymbolNamePS();      // With this function we will detect whether the Symbol Name has a prefix and/or suffix
    //--
    if(inpre>0 || insuf>0)
      {
        if(usepairs==TrdWi && arspc>0)
          {
            for(int t=0; t<arspc; t++)
              {
                SPC[t]=pre+SPC[t]+suf;
              }
          }
        //--
        for(int t=0; t<sall; t++)
          {
            All30[t]=pre+All30[t]+suf;
          }
        for(int t=0; t<arusd; t++)
          {
            USDs[t]=pre+USDs[t]+suf;
          }
        for(int t=0; t<aretc; t++)
          {
            EURs[t]=pre+EURs[t]+suf;
          }
        for(int t=0; t<aretc; t++)
          {
            GBPs[t]=pre+GBPs[t]+suf;
          }
        for(int t=0; t<aretc; t++)
          {
            AUDs[t]=pre+AUDs[t]+suf;
          }
        for(int t=0; t<aretc; t++)
          {
            NZDs[t]=pre+NZDs[t]+suf;
          }
        for(int t=0; t<aretc; t++)
          {
            CADs[t]=pre+CADs[t]+suf;
          }
        for(int t=0; t<aretc; t++)
          {
            CHFs[t]=pre+CHFs[t]+suf;
          }
        for(int t=0; t<aretc; t++)
          {
            JPYs[t]=pre+JPYs[t]+suf;
          }
      }
    //--
    ArrayCopy(VSym,All30,0,0,WHOLE_ARRAY);
    ArrayResize(AS30,sall,sall);
    ArrayCopy(AS30,All30,0,0,WHOLE_ARRAY);
    for(int x=0; x<sall; x++) {SymbolSelect(AS30[x],true);}
    if(ValidatePairs(Symbol())>=0) symbfix=true;
    if(!symbfix) 
      {
        Alert("Expert Advisors will not trade on pairs "+Symbol());
        Alert("-- "+expname+" -- ",Symbol()," -- Expert Advisor will be Remove from the chart.");
        ExpertRemove();
      }
    //--
    switch(usepairs)
      {
        case 0: // All Forex 30 Pairs
          {
            ArrayResize(DIRI,sall,sall);
            arrsymbx=sall;
            ArraySymbolResize();
            ArrayCopy(DIRI,All30,0,0,WHOLE_ARRAY);
            pairs="Multi Currency 30 Pairs";
            //--
            break;
          }
        case 1: // Trader wishes pairs
          {
            ArrayResize(DIRI,arspc,arspc);
            arrsymbx=arspc;
            ArraySymbolResize();
            ArrayCopy(DIRI,SPC,0,0,WHOLE_ARRAY);
            pairs="("+string(arspc)+") Trader Wishes Pairs";
            //--
            break;
          }
        case 2: // USD pairs
          {
            ArrayResize(DIRI,arusd,arusd);
            arrsymbx=arusd;
            ArraySymbolResize();
            ArrayCopy(DIRI,USDs,0,0,WHOLE_ARRAY);
            pairs="("+string(arusd)+") Multi Currency USD Pairs";
            //--
            break;
          }
        case 3: // EUR pairs
          {
            ArrayResize(DIRI,aretc,aretc);
            arrsymbx=aretc;
            ArraySymbolResize();
            ArrayCopy(DIRI,EURs,0,0,WHOLE_ARRAY);
            pairs="("+string(aretc)+") Forex EUR Pairs";
            //--
            break;
          }
        case 4: // GBP pairs
          {
            ArrayResize(DIRI,aretc,aretc);
            arrsymbx=aretc;
            ArraySymbolResize();
            ArrayCopy(DIRI,GBPs,0,0,WHOLE_ARRAY);
            pairs="("+string(aretc)+") Forex GBP Pairs";
            //--
            break;
          }
        case 5: // AUD pairs
          {
            ArrayResize(DIRI,aretc,aretc);
            arrsymbx=aretc;
            ArraySymbolResize();
            ArrayCopy(DIRI,AUDs,0,0,WHOLE_ARRAY);
            pairs="("+string(aretc)+") Forex AUD Pairs";
            //--
            break;
          }
        case 6: // NZD pairs
          {
            ArrayResize(DIRI,aretc,aretc);
            arrsymbx=aretc;
            ArraySymbolResize();
            ArrayCopy(DIRI,NZDs,0,0,WHOLE_ARRAY);
            pairs="("+string(aretc)+") Forex NZD Pairs";
            //--
            break;
          }
        case 7: // CAD pairs
          {
            ArrayResize(DIRI,aretc,aretc);
            arrsymbx=aretc;
            ArraySymbolResize();
            ArrayCopy(DIRI,CADs,0,0,WHOLE_ARRAY);
            pairs="("+string(aretc)+") Forex CAD Pairs";
            //--
            break;
          }
        case 8: // CHF pairs
          {
            ArrayResize(DIRI,aretc,aretc);
            arrsymbx=aretc;
            ArraySymbolResize();
            ArrayCopy(DIRI,CHFs,0,0,WHOLE_ARRAY);
            pairs="("+string(aretc)+") Forex CHF Pairs";
            //--
            break;
          }
        case 9: // JPY pairs
          {
            ArrayResize(DIRI,aretc,aretc);
            arrsymbx=aretc;
            ArraySymbolResize();
            ArrayCopy(DIRI,JPYs,0,0,WHOLE_ARRAY);
            pairs="("+string(aretc)+") Forex JPY Pairs";
            //--
            break;
          }
      }
    //--
    return;
//---
  } //-end HandlingSymbolArrays()
//---------//

Dentro da função HandlingSymbolArrays(), chamamos a função SetSymbolNamePS(). Usando a SetSymbolNamePS(), poderemos processar os nomes dos símbolos com prefixo e/ou sufixo.

void MCEA::SetSymbolNamePS(void)
  {
//---
   symbfix=false;
   int ptriml;
   int ptrimr;
   string insymbol=Symbol();
   int sym_Lenpre=StringLen(prefix);
   int sym_Lensuf=StringLen(suffix);
   if(sym_Lenpre>0)
     {
       ptriml=StringTrimLeft(suffix);
       ptriml=StringTrimRight(suffix);
     }
   if(sym_Lensuf>0)
     {
       ptrimr=StringTrimLeft(suffix);
       ptrimr=StringTrimRight(suffix);
     }
   string sym_pre=prefix;
   string sym_suf=suffix;
   //--
   pre=sym_pre;
   suf=sym_suf;
   inpre=StringLen(pre);
   insuf=StringLen(suf);
   posCur1=inpre;
   posCur2=posCur1+3;
   //--
   return;
//---
  } //-end SetSymbolNamePS()
//---------//

Nota: O robô investidor verificará os pares. Se o trader cometer um erro ao inserir o nome do par ou o nome do prefixo/sufixo, ou se a verificação do par falhar, o robô emitirá um aviso e será removido do gráfico.

No grupo de propriedades de entrada de dados "Trade on Specific Time", o trader precisa escolher "Trade on Specific Time Zone (Yes)" ou "(No)" - negociar em um fuso horário específico: sim ou não. Se Yes, você deve selecionar os parâmetros de enumeração:

  • Negociação em sessão personalizada
  • Negociação na sessão da Nova Zelândia
  • Negociação na sessão de Sydney da Austrália
  • Negociação na sessão asiática de Tóquio
  • Negociação na sessão da Europa em Londres (sessão de Londres)
  • Negociação na sessão de Nova York da América (sessão de Nova York)

Negociação em sessão personalizada: Os traders devem definir um horário ou horas e minutos para iniciar a negociação e horas e minutos para fechar a negociação. Assim, o Expert Advisor só executará ações durante o tempo especificado.

Em outros casos, os horários de início e término da negociação são escolhidos pelo Expert Advisor.

Classe para Operação do Expert Advisor

Para definir a estrutura e a configuração do processo de trabalho do Expert Advisor, criaremos uma classe que declara todas as variáveis, objetos e funções necessários.

//+------------------------------------------------------------------+
//| Class for working Expert Advisor                                 |
//+------------------------------------------------------------------+
class MCEA
  {
//---
    private:
    //---- 
    int              x_year;       // Year 
    int              x_mon;        // Month 
    int              x_day;        // Day of the month 
    int              x_hour;       // Hour in a day 
    int              x_min;        // Minutes 
    int              x_sec;        // Seconds
    //--
    int              oBm,
                     oSm,
                     ldig;
    //--- Variables used in prefix and suffix symbols
    int              posCur1,
                     posCur2;
    int              inpre,
                     insuf;
    bool             symbfix;
    string           pre,suf;
    string           prefix,suffix;       
    //--- Variables are used in Trading Time Zone
    int              ishour,
                     onhour;
    int              tftrlst,
                     tfcinws;
    datetime         rem,
                     znop,
                     zncl,
                     zntm;
    datetime         SesCuOp,
                     SesCuCl,
                     Ses01Op,
                     Ses01Cl,
                     Ses02Op,
                     Ses02Cl,
                     Ses03Op,
                     Ses03Cl,
                     Ses04Op,
                     Ses04Cl,
                     Ses05Op,
                     Ses05Cl,
                     SesNoOp,
                     SesNoCl;
    //--
    string           tz_ses,
                     tz_opn,
                     tz_cls;
    //--
    string           tmopcu,
                     tmclcu,
                     tmop01,
                     tmcl01,
                     tmop02,
                     tmcl02,
                     tmop03,
                     tmcl03,
                     tmop04,
                     tmcl04,
                     tmop05,
                     tmcl05,
                     tmopno,
                     tmclno;      
    //----------------------    
    //--
    double           LotPS;
    double           slv,
                     tpv,
                     pip,
                     xpip;
   double            SARstep,
                     SARmaxi;
    double           floatprofit,
                     fixclprofit;
    //--
    string           pairs,
                     hariini,
                     daytrade,
                     trade_mode;
    //--
    double           OPEN[],
                     HIGH[],
                     LOW[],
                     CLOSE[];
    datetime         TIME[];
    datetime         closetime;
    //--
    //------------
     
    //------------
    void             SetSymbolNamePS(void);
    void             HandlingSymbolArrays(void);
    void             Set_Time_Zone(void);
    void             Time_Zone(void);
    bool             Trade_session(void);
    string           PosTimeZone(void);
    int              ThisTime(const int reqmode);
    int              ReqTime(datetime reqtime,const int reqmode);
    //--
    int              DirectionMove(const string symbol,const ENUM_TIMEFRAMES stf);
    int              BBOnKeltnerChannel(const string symbol);
    int              PARSAR05(const string symbol);
    int              LotDig(const string symbol);
    //--
    double           MLots(const string symbx);
    double           NonZeroDiv(double val1,double val2);
    double           OrderSLSet(const string xsymb,ENUM_ORDER_TYPE type,double atprice);
    double           OrderTPSet(const string xsymb,ENUM_ORDER_TYPE type,double atprice);
    double           SetOrderSL(const string xsymb,ENUM_POSITION_TYPE type,double atprice);
    double           SetOrderTP(const string xsymb,ENUM_POSITION_TYPE type,double atprice);
    double           TSPrice(const string xsymb,ENUM_POSITION_TYPE ptype,int TS_type);
    //--
    string           ReqDate(int d,int h,int m);
    string           TF2Str(ENUM_TIMEFRAMES period);
    string           timehr(int hr,int mn);
    string           TradingDay(void);
    string           AccountMode();
    string           GetCommentForOrder(void)             { return(expname); }
    //------------

    public:
    //---
    
    //-- BBOnKeltnerChannel_MCEA Config --
    string           DIRI[],
                     AS30[],
                     VSym[];
    string           SPC[];
    string           USD[];
    string           EUR[];
    string           GBP[];
    string           AUD[];
    string           NZD[];
    string           CAD[];
    string           CHF[];
    string           JPY[];             
    //--                 
    string           expname;
    string           indiname;
    //--
    int              hKC[];
    int              hBB[];
    int              hParIU[],
                     hPar05[];
    int              ALO,
                     dgts,
                     arrsar,
                     arrsymbx;
    int              sall,
                     arusd,
                     aretc,
                     arspc,
                     arper;
    ulong            slip;        
    //--
    double           profitb[],
                     profits[];
    //--
    int              Buy,
                     Sell;
    int              ccur,
                     psec,
                     xtto,
                     checktml;
    int              OpOr[],xob[],xos[];         
    //--
    int              year,  // Year 
                     mon,   // Month 
                     day,   // Day 
                     hour,  // Hour 
                     min,   // Minutes 
                     sec,   // Seconds 
                     dow,   // Day of week (0-Sunday, 1-Monday, ... ,6-Saturday) 
                     doy;   // Day number of the year (January 1st is assigned the number value of zero)
    //--
    ENUM_TIMEFRAMES  TFt,
                     TFT05;
    //--
    bool             PanelExtra;
    //------------
                     MCEA(void);
                     ~MCEA(void);            
    //------------
    //--
    virtual void     BBOnKeltnerChannel_MCEA_Config(void);
    virtual void     ExpertActionTrade(void);
    //--
    void             ArraySymbolResize(void);
    void             CurrentSymbolSet(const string symbol);
    void             Pips(const string symbol);
    void             TradeInfo(void);
    void             Do_Alerts(const string symbx,string msgText);
    void             CheckOpenPMx(const string symbx);
    void             SetSLTPOrders(void);
    void             CloseBuyPositions(const string symbol);
    void             CloseSellPositions(const string symbol);
    void             CloseAllOrders(void);
    void             CheckClose(const string symbx);
    void             TodayOrders(void);
    void             UpdatePrice(const string symbol,ENUM_TIMEFRAMES xtf);
    void             RefreshPrice(const string symbx,ENUM_TIMEFRAMES xtf,int bars);
    //--
    bool             RefreshTick(const string symbx);  
    bool             TradingToday(void);
    bool             OpenBuy(const string symbol);
    bool             OpenSell(const string symbol);
    bool             ModifyOrderSLTP(double mStop,double ordtp);
    bool             ModifySLTP(const string symbx,int TS_type);          
    bool             CloseAllProfit(void);
    bool             ManualCloseAllProfit(void);
    //--
    int              PairsIdxArray(const string symbol);
    int              ValidatePairs(const string symbol);
    int              GetOpenPosition(const string symbol);
    int              GetCloseInWeakSignal(const string symbol,int exis);
    //--
    string           getUninitReasonText(int reasonCode);
    //--
    //------------
//---
  }; //-end class MCEA
//---------//

A primeira e mais importante função no trabalho do Expert Advisor multimoeda, chamada pela função OnInit(), é a BBOnKeltnerChannel_MCea_Config().

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(void)
  {
//---
   mc.BBOnKeltnerChannel_MCEA_Config();
   //--
   return(INIT_SUCCEEDED);
//---
  } //-end OnInit()
//---------//

Na função BBOnKeltnerChannel_MCea_Config(), todos os símbolos utilizados, todos os identificadores de indicadores e algumas funções importantes do arquivo de cabeçalho incluído são configurados.

Nas linhas de 468 a 484, é explicado como processar o timeframe e criar os identificadores dos indicadores para todos os indicadores utilizados.

//+------------------------------------------------------------------+
//| Expert Configuration                                             |
//+------------------------------------------------------------------+
void MCEA::BBOnKeltnerChannel_MCEA_Config(void) 
  {
//---
    //--
    HandlingSymbolArrays(); // With this function we will handle all pairs that will be traded
    //--
    TFT05=PERIOD_M5;
    ENUM_TIMEFRAMES TFs[]={PERIOD_M5,PERIOD_M15,PERIOD_M30,PERIOD_H1,PERIOD_H2,PERIOD_H3,PERIOD_H4,PERIOD_H6,PERIOD_H8,PERIOD_H12,PERIOD_D1};
    int arTFs=ArraySize(TFs);
    //--
    for(int x=0; x<arTFs; x++) if(tfinuse==x) TFt=TFs[x]; // TF for calculation signal
    //--
    //-- Keltner Channel and Bollinger Bands® Indicators handle for all symbol
    for(int x=0; x<arrsymbx; x++) 
      {
        hKC[x]=iCustom(DIRI[x],TFt,indiname,KCPeriod,KCMethod,KCMAAP,KCATRPer,KCATRBandsMulti); //-- Handle for the Keltner Channel indicator
        hParIU[x]=iSAR(DIRI[x],TFt,SARstep,SARmaxi);         //-- Handle for the iSAR indicator according to the selected Timeframe
        hPar05[x]=iSAR(DIRI[x],TFT05,SARstep,SARmaxi);       //-- Handle for the iSAR indicator for M5 Timeframe
        //--
      }
    //--
    for(int x=0; x<arrsymbx; x++) 
      hBB[x]=iBands(DIRI[x],TFt,BBPeriod,0,BBDevi,hKC[x]); //-- Handle for the iBands On Keltner Channel Indicator handle
    //--
    ALO=(int)mc_account.LimitOrders()>sall ? sall : (int)mc_account.LimitOrders();
    //--
    LotPS=(double)ALO;
    //--
    mc_trade.SetExpertMagicNumber(magicEA);
    mc_trade.SetDeviationInPoints(slip);
    mc_trade.SetMarginMode();
    Set_Time_Zone();
    //--
    return;
//---
  } //-end BBOnKeltnerChannel_MCEA_Config()
//---------//

2. Função Expert tick

Na função Expert tick (OnTick()), chamaremos uma das funções principais do robô multimoeda, a saber, a função ExpertActionTrade().

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(void)
  {
//---
    mc.ExpertActionTrade();
    //--
    return;
//---
  } //-end OnTick()
//---------//

A sequência do robô dentro dessa função.

A função ExpertActionTrade() executará todas as ações e gerenciará a negociação automática, incluindo a abertura/fechamento de ordens, trailing-stop, trailing take-profit, e outras ações adicionais. ✅

void MCEA::ExpertActionTrade(void)
  {
//---
    //--Check Trading Terminal
    ResetLastError();
    //--
    if(!MQLInfoInteger(MQL_TRADE_ALLOWED) && mc.checktml==0) //-- Check whether MT5 Algorithmic trading is Allow or Prohibit
      {
        mc.Do_Alerts(Symbol(),"Trading Expert at "+Symbol()+" are NOT Allowed by Setting.");
        mc.checktml=1;  //-- Variable checktml is given a value of 1, so that the alert is only done once.
        return;
      }
    //--
    if(!DisplayManualButton("M","C","R")) DisplayManualButton(); //-- Show the expert manual button panel
    //--
    if(trade_info_display==Yes) mc.TradeInfo(); //-- Displayed Trading Info on Chart
    //---
    //--
    int mcsec=mc.ThisTime(mc.sec); 
    //--
    if(fmod((double)mcsec,5.0)==0) mc.ccur=mcsec;
    //--
    if(mc.ccur!=mc.psec)
      {
        string symbol;
        //-- Here we start with the rotation of the name of all symbol or pairs to be traded
        for(int x=0; x<mc.arrsymbx && !IsStopped(); x++) 
          {
            //-- 
            if(mc.DIRI[x]==Symbol()) symbol=Symbol();
            else symbol=mc.DIRI[x];
            //--
            mc.CurrentSymbolSet(symbol);
            //--
            if(mc.TradingToday() && mc.Trade_session())
              {
                //--
                mc.OpOr[x]=mc.GetOpenPosition(symbol); //-- Get trading signals to open positions
                //--                                   //-- and store in the variable OpOr[x]
                if(mc.OpOr[x]==mc.Buy) //-- If variable OpOr[x] get result of GetOpenPosition(symbol) as "Buy" (value=1)
                  {
                    //--
                    mc.CheckOpenPMx(symbol);
                    //--
                    if(Close_by_Opps==Yes && mc.xos[x]>0) mc.CloseSellPositions(symbol);
                    //--
                    if(mc.xob[x]==0 && mc.xtto<mc.ALO) mc.OpenBuy(symbol);
                    else
                    if(mc.xtto>=mc.ALO)
                      {
                        //--
                        mc.Do_Alerts(symbol,"Maximum amount of open positions and active pending orders has reached"+
                                            "\n the limit = "+string(mc.ALO)+" Orders ");
                        //--
                        mc.CheckOpenPMx(symbol);
                        //--
                        if(mc.xos[x]>0 && mc.profits[x]<-1.02 && mc.xob[x]==0) {mc.CloseSellPositions(symbol); mc.OpenBuy(symbol);}
                        else
                        if(SaveOnRev==Yes) mc.CloseAllProfit();
                      }
                  }
                if(mc.OpOr[x]==mc.Sell) //-- If variable OpOr[x] get result of GetOpenPosition(symbol) as "Sell" (value=-1)
                  {
                    //--
                    mc.CheckOpenPMx(symbol);
                    //--
                    if(Close_by_Opps==Yes && mc.xob[x]>0) mc.CloseBuyPositions(symbol);
                    //--
                    if(mc.xos[x]==0 && mc.xtto<mc.ALO) mc.OpenSell(symbol);
                    else
                    if(mc.xtto>=mc.ALO)
                      {
                        //--
                        mc.Do_Alerts(symbol,"Maximum amount of open positions and active pending orders has reached"+
                                            "\n the limit = "+string(mc.ALO)+" Orders ");
                        //--
                        mc.CheckOpenPMx(symbol);
                        //--
                        if(mc.xob[x]>0 && mc.profitb[x]<-1.02 && mc.xos[x]==0) {mc.CloseBuyPositions(symbol); mc.OpenSell(symbol);}
                        else
                        if(SaveOnRev==Yes) mc.CloseAllProfit();
                      }
                  }
              }
            //--
            mc.CheckOpenPMx(symbol);
            //--
            if(mc.xtto>0)
              {
                //--
                if(SaveOnRev==Yes) //-- Close Trade and Save profit due to weak signal (Yes)
                  {
                    mc.CheckOpenPMx(symbol);
                    if(mc.profitb[x]>0.02 && mc.xob[x]>0 && mc.GetCloseInWeakSignal(symbol,mc.Buy)==mc.Sell) 
                      {
                        mc.CloseBuyPositions(symbol); 
                        mc.Do_Alerts(symbol,"Close BUY order "+symbol+" to save profit due to weak signal.");
                      }
                    if(mc.profits[x]>0.02 && mc.xos[x]>0 && mc.GetCloseInWeakSignal(symbol,mc.Sell)==mc.Buy)
                      {
                        mc.CloseSellPositions(symbol); 
                        mc.Do_Alerts(symbol,"Close SELL order "+symbol+" to save profit due to weak signal.");
                      }
                  }
                //--
                if(TrailingSLTP==Yes) //-- Use Trailing SL/TP (Yes)
                  {
                    if(autotrl==Yes) mc.ModifySLTP(symbol,1); //-- If Use Automatic Trailing (Yes)
                    if(autotrl==No)  mc.ModifySLTP(symbol,0); //-- Use Automatic Trailing (No)
                  }
              }
            //--
            mc.CheckClose(symbol);
          }
        //--
        mc.psec=mc.ccur;
      }
    //--
    return;
//---
  } //-end ExpertActionTrade()
//---------//

O grupo de propriedades "Day Trading On/Off" dá aos traders a possibilidade de negociar nos dias especificados, de domingo a sábado. Os traders podem ativar ou desativar o robô para negociação no dia especificado, usando a opção "Yes" ou "No".

//--Day Trading On/Off
input group               "=== Day Trading On/Off ==="; // Day Trading On/Off
input YN                    ttd0 = No;               // Select Trading on Sunday (Yes) or (No)
input YN                    ttd1 = Yes;              // Select Trading on Monday (Yes) or (No)
input YN                    ttd2 = Yes;              // Select Trading on Tuesday (Yes) or (No)
input YN                    ttd3 = Yes;              // Select Trading on Wednesday (Yes) or (No)
input YN                    ttd4 = Yes;              // Select Trading on Thursday (Yes) or (No)
input YN                    ttd5 = Yes;              // Select Trading on Friday (Yes) or (No)
input YN                    ttd6 = No;               // Select Trading on Saturday (Yes) or (No)

Propriedades Day Trading On/Off

bool MCEA::TradingToday(void)
  {
//---
    bool tradetoday=false;
    int trdday=ThisTime(dow);
    hariini="No";
    //--
    int ttd[];
    ArrayResize(ttd,7);
    ttd[0]=ttd0;
    ttd[1]=ttd1;
    ttd[2]=ttd2;
    ttd[3]=ttd3;
    ttd[4]=ttd4;
    ttd[5]=ttd5;
    ttd[6]=ttd6;
    //--
    if(ttd[trdday]==Yes) {tradetoday=true; hariini="Yes";}
   //--
   return(tradetoday);
//---
  } //-end TradingToday()
//---------//

Nota: As condições "Day Trading On/Off" serão exibidas nas informações de negociação no gráfico.

No grupo de propriedades do Expert Advisor "Trade on Specific Time", os traders podem escolher o fuso horário.

input group               "=== Trade on Specific Time ==="; // Trade on Specific Time
input YN           trd_time_zone = Yes;              // Select If You Like to Trade on Specific Time Zone
input tm_zone            session = Cus_Session;      // Select Trading Time Zone
input swhour            stsescuh = hr_00;            // Time Hour to Start Trading Custom Session (0-23)
input inmnt             stsescum = mn_15;            // Time Minute to Start Trading Custom Session (0-55)
input swhour            clsescuh = hr_23;            // Time Hour to Stop Trading Custom Session (0-23)
input inmnt             clsescum = mn_55;            // Time Minute to Stop Trading Custom Session (0-55)

Nota: Como mencionado anteriormente, nos demais casos, o horário de início e término da negociação é escolhido pelo EA. Assim, nas propriedades "Expert Entry", os traders precisam apenas definir a hora e o minuto de início da negociação, bem como a hora e o minuto de término para a sessão personalizada.

Especificamente para os fusos horários de negociação, a função ExpertActionTrade() adicionou a chamada da função lógica Trade_session().

Quando Trade_session() for true, o robô funcionará até o encerramento e, quando for false, o robô só executará as tarefas Close Trade e Save Profit devido a sinal fraco (Yes) e Trailing stop (Yes).

bool MCEA::Trade_session(void)
  {
//---
   bool trd_ses=false;
   ishour=ThisTime(hour);
   if(ishour!=onhour) Set_Time_Zone();
   datetime tcurr=TimeCurrent(); // Server Time
   //--
   switch(session)
     {
       case Cus_Session:
         {
           if(tcurr>=SesCuOp && tcurr<=SesCuCl) trd_ses=true;
           break;
         }
       case New_Zealand:
         {
           if(tcurr>=Ses01Op && tcurr<=Ses01Cl) trd_ses=true;
           break;
         }
       case Australia:
         {
           if(tcurr>=Ses02Op && tcurr<=Ses02Cl) trd_ses=true;
           break;
         }
       case Asia_Tokyo:
         {
           if(tcurr>=Ses03Op && tcurr<=Ses03Cl) trd_ses=true;
           break;
         }
       case Europe_London:
         {
           if(tcurr>=Ses04Op && tcurr<=Ses04Cl) trd_ses=true;
           break;
         }
       case US_New_York:
         {
           if(tcurr>=Ses05Op && tcurr<=Ses05Cl) trd_ses=true;
           break;
         }
     }
   //--
   if(trd_time_zone==No) 
     {
      if(tcurr>=SesNoOp && tcurr<=SesNoCl) trd_ses=true;
     }
   //--
   onhour=ishour;
   //--
   return(trd_ses);
//---  
  } //-end Trade_session()
//---------//

3. Obtenção de sinais de negociação para abrir posições

Para obter o sinal, a função ExpertActionTrade() chama a função GetOpenPosition().

int MCEA::GetOpenPosition(const string symbol) // Signal Open Position 
  {
//---
    int ret=0;
    int rise=1,
        down=-1;
    //--
    int BBOnKC=BBOnKeltnerChannel(symbol);
    //--
    if(BBOnKC==rise) ret=rise;
    if(BBOnKC==down) ret=down;
    //--
    return(ret);
//---
  } //-end GetOpenPosition()
//---------//

A função GetOpenPosition() chama as funções BBOnKeltnerChannel(), que calculam os sinais.

int MCEA::BBOnKeltnerChannel(const string symbol) // Fungction of Bollinger Bands® On Keltner Channel Indicator
  {
//---
    int ret=0;
    int rise=1,
        down=-1;
    int br=3;
    Pips(symbol);
    double difud=mc_symbol.NormalizePrice(1.5*pip);
    //--
    double KCmb[], // Destination array for Keltner Channel Middle Line buffer
           KCub[], // Destination array for Keltner Channel Upper Band buffer
           KClb[]; // Destination array for Keltner Channel Lower Band buffer
    double BBmb[], // Destination array for Bollinger Bands® BASE_LINE buffer
           BBub[], // Destination array for Bollinger Bands® UPPER_BAND buffer
           BBlb[]; // Destination array for Bollinger Bands® LOWER_BAND buffer
    //--
    ArrayResize(KCmb,br,br);
    ArrayResize(KCub,br,br);
    ArrayResize(KClb,br,br);
    ArrayResize(BBmb,br,br);
    ArrayResize(BBub,br,br);
    ArrayResize(BBlb,br,br);
    ArraySetAsSeries(KCmb,true);
    ArraySetAsSeries(KCub,true);
    ArraySetAsSeries(KClb,true);
    ArraySetAsSeries(BBmb,true);
    ArraySetAsSeries(BBub,true);
    ArraySetAsSeries(BBlb,true);
    //--
    int xx=PairsIdxArray(symbol);
    //--
    CopyBuffer(hKC[xx],0,0,br,KCmb);
    CopyBuffer(hKC[xx],1,0,br,KCub);
    CopyBuffer(hKC[xx],2,0,br,KClb);
    CopyBuffer(hBB[xx],0,0,br,BBmb);
    CopyBuffer(hBB[xx],1,0,br,BBub);
    CopyBuffer(hBB[xx],2,0,br,BBlb);
    //--
    bool BBKCrise1=(KCmb[1]<=BBlb[1] && KCmb[0]>BBlb[0]+difud);
    bool BBKCrise2=(KCmb[1]<=BBmb[1] && KCmb[0]>BBmb[0]+difud);
    bool BBKCrise3=(KCmb[1]<=BBub[1] && KCmb[0]>BBub[0]+difud);
    //--
    bool BBKCdown1=(KCmb[1]>=BBub[1] && KCmb[0]<BBub[0]-difud);
    bool BBKCdown2=(KCmb[1]>=BBmb[1] && KCmb[0]<BBmb[0]-difud);
    bool BBKCdown3=(KCmb[1]>=BBlb[1] && KCmb[0]<BBlb[0]-difud);
    //--
    if(BBKCrise1 || BBKCrise2 || BBKCrise3) ret=rise;
    if(BBKCdown1 || BBKCdown2 || BBKCdown3) ret=down;
    //--
    return(ret);
//---
  } //-end BBOnKeltnerChannel()
//---------//

Como podemos ver, dentro da função BBOnKeltnerChannel(), usamos e chamamos uma função — PairsIdxArray().

int xx=PairsIdxArray(symbol);
int MCEA::PairsIdxArray(const string symbol)
  {
//---
    int pidx=-1;
    //--
    for(int x=0; x<arrsymbx; x++)
      {
        if(DIRI[x]==symbol)
          {
            pidx=x;
            break;
          }
      } 
    //--
    return(pidx);
//---
  } //-end PairsIdxArray()
//---------//

A função PairsIdxArray() é usada para obter o nome do símbolo solicitado e os identificadores dos indicadores. Em seguida, o identificador apropriado do indicador é chamado para obter o valor do buffer de sinal do canal de Keltner e das Bandas de Bollinger desse timeframe.

Precisamos então descobrir o número do buffer do canal de Keltner. Os números dos buffers na função OnInit() são os seguintes: 0 - linha média, 1 - linha superior, 2 - linha inferior

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   //-- assignment of array to indicator buffer 
   SetIndexBuffer(0,KC_Middle,INDICATOR_DATA);
   SetIndexBuffer(1,KC_UpperB,INDICATOR_DATA);
   SetIndexBuffer(2,KC_LowerB,INDICATOR_DATA);
   SetIndexBuffer(3,KC_ATRtemp,INDICATOR_CALCULATIONS);
   //--
   handleMA=iMA(Symbol(),Period(),period_kc,0,MODE_EMA,ma_price);
   //--
   if(handleMA==INVALID_HANDLE) 
     { 
      //--- tell about the failure and output the error code 
      PrintFormat("Failed to create handle of the Moving Average indicator for the symbol %s/%s, error code %d", 
                  Symbol(), 
                  EnumToString(Period()), 
                  GetLastError()); 
      //--- the indicator is stopped early 
      return(INIT_FAILED); 
     } 
   //--
   handleATR=iATR(Symbol(),Period(),atr_period);
   //--
   if(handleATR==INVALID_HANDLE) 
     { 
      //--- tell about the failure and output the error code 
      PrintFormat("Failed to create handle of the ATR indicator for the symbol %s/%s, error code %d", 
                  Symbol(), 
                  EnumToString(Period()), 
                  GetLastError()); 
      //--- the indicator is stopped early 
      return(INIT_FAILED); 
     } 
   //--
   short_name=StringFormat("Keltner Channel(%d, %s, %.2f)",period_kc,EnumToString(ma_method),band_multi);
   IndicatorSetString(INDICATOR_SHORTNAME,short_name);
   IndicatorSetInteger(INDICATOR_DIGITS,Digits());
   //--
   return(INIT_SUCCEEDED);
//---
  }
//---------//

Nesse caso, também precisamos saber a quantidade de buffers das Bandas de Bollinger.

Como explicado na função iBands®:

"Os números dos buffers são: 0 - BASE_LINE, 1 - UPPER_BAND, 2 - LOWER_BAND"

applied_price

[in] Preço aplicado. Pode ser qualquer constante de preço ENUM_APPLIED_PRICE ou o identificador de outro indicador.

Nesta seção, os robôs usarão o preço aplicado das Bandas de Bollinger do identificador do canal de Keltner.

Assim, para obter o valor do buffer de cada linha do canal de Keltner e da Banda de Bollinger, copiaremos cada buffer do identificador dos indicadores.

Para copiar o buffer da linha média (buffer 0) do identificador do canal de Keltner para o array de destino:

CopyBuffer(hKC[xx],0,0,br,KCmb);

Para copiar o buffer da linha superior (buffer 1) do identificador do canal de Keltner para o array de destino:

CopyBuffer(hKC[xx],1,0,br,KCub);

Para copiar o buffer da linha inferior (buffer 2) do identificador do canal de Keltner para o array de destino:

CopyBuffer(hKC[xx],2,0,br,KClb);

Para copiar o buffer BASE_LINE (buffer 0) do identificador das Bandas de Bollinger para o array de destino:

CopyBuffer(hBB[xx],0,0,br,BBmb);

Para copiar o buffer UPPER_BAND (buffer 1) do identificador das Bandas de Bollinger para o array de destino:

CopyBuffer(hBB[xx],1,0,br,BBub);

Para copiar o buffer LOWER_BAND (buffer 2) do identificador das Bandas de Bollinger para o array de destino:

CopyBuffer(hBB[xx],2,0,br,BBlb);

Em seguida, a função GetOpenPosition() retorna os seguintes resultados:

  • Valor 0 - sinal desconhecido.
  • Valor 1 - sinal de compra.
  • Valor -1 - sinal de venda.

O Expert Advisor chama a função OpenBuy() quando a função GetOpenPosition() retorna o valor 1.

bool MCEA::OpenBuy(const string symbol) 
  {
//---
    ResetLastError();
    //--
    bool buyopen      = false;
    string ldComm     = GetCommentForOrder()+"_Buy";
    double ldLot      = MLots(symbol);
    ENUM_ORDER_TYPE type_req = ORDER_TYPE_BUY;
    //--
    MqlTradeRequest req={};
    MqlTradeResult  res={};
    MqlTradeCheckResult check={};
    //-- structure is set to zero
    ZeroMemory(req);
    ZeroMemory(res);
    ZeroMemory(check);
    //--
    CurrentSymbolSet(symbol);
    double SL=OrderSLSet(symbol,type_req,mc_symbol.Bid());
    double TP=OrderTPSet(symbol,type_req,mc_symbol.Ask());
    //--
    if(RefreshTick(symbol))
       buyopen=mc_trade.Buy(ldLot,symbol,mc_symbol.Ask(),SL,TP,ldComm);
    //--
    int error=GetLastError();
    if(buyopen||error==0)
      {
        string bsopen="Open BUY Order for "+symbol+" ~ Ticket= ["+(string)mc_trade.ResultOrder()+"] successfully..!";
        Do_Alerts(symbol,bsopen);
      }
    else
      {
        mc_trade.CheckResult(check);
        Do_Alerts(Symbol(),"Open BUY order for "+symbol+" FAILED!!. Return code= "+
                 (string)mc_trade.ResultRetcode()+". Code description: ["+mc_trade.ResultRetcodeDescription()+"]");
        return(false);   
      }
    //--
    return(buyopen);
    //--
//---
  } //-end OpenBuy
//---------//

Além disso, o Expert Advisor chama a função OpenSell() se a função GetOpenPosition() retornar o valor -1.

bool MCEA::OpenSell(const string symbol) 
  {
//---
    ResetLastError();
    //--
    bool selopen      = false;
    string sdComm     = GetCommentForOrder()+"_Sell";
    double sdLot      = MLots(symbol);
    ENUM_ORDER_TYPE type_req = ORDER_TYPE_SELL;
    //--
    MqlTradeRequest req={};
    MqlTradeResult  res={};
    MqlTradeCheckResult check={};
    //-- structure is set to zero
    ZeroMemory(req);
    ZeroMemory(res);
    ZeroMemory(check);
    //--
    CurrentSymbolSet(symbol);
    double SL=OrderSLSet(symbol,type_req,mc_symbol.Ask());
    double TP=OrderTPSet(symbol,type_req,mc_symbol.Bid());
    //--
    if(RefreshTick(symbol))
       selopen=mc_trade.Sell(sdLot,symbol,mc_symbol.Bid(),SL,TP,sdComm);
    //--
    int error=GetLastError();
    if(selopen||error==0)
      {
        string bsopen="Open SELL Order for "+symbol+" ~ Ticket= ["+(string)mc_trade.ResultOrder()+"] successfully..!";
        Do_Alerts(symbol,bsopen);
      }
    else
      {
        mc_trade.CheckResult(check);
        Do_Alerts(Symbol(),"Open SELL order for "+symbol+" FAILED!!. Return code= "+
                 (string)mc_trade.ResultRetcode()+". Code description: ["+mc_trade.ResultRetcodeDescription()+"]");
        return(false);   
      }
    //--
    return(selopen);
    //--
//---
  } //-end OpenSell
//---------//

4. Função ChartEvent

Para maior eficiência dos robôs multimoedas, criaremos vários botões para gerenciar ordens e alterar gráficos ou símbolos.

//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//---
//--- handling CHARTEVENT_CLICK event ("Clicking the chart")
   ResetLastError();
   //--
   ENUM_TIMEFRAMES CCS=mc.TFt;
   //--
   if(id==CHARTEVENT_OBJECT_CLICK) 
     {
       int lensymbol=StringLen(Symbol());
       int lensparam=StringLen(sparam);
       //--
       //--- if "Set SL All Orders" button is click
       if(sparam=="Set SL/TP All Orders") 
         { 
           mc.SetSLTPOrders();
           Alert("-- "+mc.expname+" -- ",Symbol()," -- Set SL/TP All Orders");
           //--- unpress the button 
           ObjectSetInteger(0,"Set SL/TP All Orders",OBJPROP_STATE,false);
           ObjectSetInteger(0,"Set SL/TP All Orders",OBJPROP_ZORDER,0);
           CreateManualPanel();
         }
       //--- if "Close All Order" button is click
       if(sparam=="Close All Order") 
         { 
           mc.CloseAllOrders();
           Alert("-- "+mc.expname+" -- ",Symbol()," -- Close All Orders");
           //--- unpress the button 
           ObjectSetInteger(0,"Close All Order",OBJPROP_STATE,false);
           ObjectSetInteger(0,"Close All Order",OBJPROP_ZORDER,0);
           CreateManualPanel();
         }
       //--- if "Close All Profit" button is click
       if(sparam=="Close All Profit") 
         { 
           mc.ManualCloseAllProfit();
           Alert("-- "+mc.expname+" -- ",Symbol()," -- Close All Profit");
           //--- unpress the button 
           ObjectSetInteger(0,"Close All Profit",OBJPROP_STATE,false);
           ObjectSetInteger(0,"Close All Profit",OBJPROP_ZORDER,0);
           CreateManualPanel();
         }
       //--- if "X" button is click
       if(sparam=="X") 
         { 
           ObjectsDeleteAll(0,0,OBJ_BUTTON);
           ObjectsDeleteAll(0,0,OBJ_LABEL);
           ObjectsDeleteAll(0,0,OBJ_RECTANGLE_LABEL);
           //--- unpress the button 
           ObjectSetInteger(0,"X",OBJPROP_STATE,false);
           ObjectSetInteger(0,"X",OBJPROP_ZORDER,0);
           //--
           DeleteButtonX();
           mc.PanelExtra=false;
           DisplayManualButton();
         }
       //--- if "M" button is click
       if(sparam=="M") 
         { 
           //--- unpress the button 
           ObjectSetInteger(0,"M",OBJPROP_STATE,false);
           ObjectSetInteger(0,"M",OBJPROP_ZORDER,0);
           mc.PanelExtra=true;
           CreateManualPanel();
         }
       //--- if "C" button is click
       if(sparam=="C") 
         { 
           //--- unpress the button 
           ObjectSetInteger(0,"C",OBJPROP_STATE,false);
           ObjectSetInteger(0,"C",OBJPROP_ZORDER,0);
           mc.PanelExtra=true;
           CreateSymbolPanel();
         }
       //--- if "R" button is click
       if(sparam=="R") 
         { 
           Alert("-- "+mc.expname+" -- ",Symbol()," -- Expert Advisor will be Remove from the chart.");
           ExpertRemove();
           //--- unpress the button 
           ObjectSetInteger(0,"R",OBJPROP_STATE,false);
           ObjectSetInteger(0,"R",OBJPROP_ZORDER,0);
           if(!ChartSetSymbolPeriod(0,Symbol(),Period()))
             ChartSetSymbolPeriod(0,Symbol(),Period());
           DeletePanelButton();
           ChartRedraw(0);
         }
       //--- if Symbol button is click
       if(lensparam==lensymbol)
         {
           int sx=mc.ValidatePairs(sparam);
           ChangeChartSymbol(mc.AS30[sx],CCS);
           mc.PanelExtra=false;
         }
       //--
     }
    //--
    return;
//---
  } //-end OnChartEvent()
//---------//

No grupo de propriedades de entrada "Other" (Outros), o trader terá a opção de escolher se deseja exibir as informações de negociação no gráfico (Yes) ou não (No).

Se escolher "Yes", as informações de negociação serão exibidas no gráfico onde o robô está colocado, através da chamada da função TradeInfo().

Dentro da função TradeInfo(), também adicionamos uma função para descrever o tempo de acordo com as condições do fuso horário de negociação.

string MCEA::PosTimeZone(void)
  {
//---
    string tzpos="";
    //--
    if(ReqTime(zntm,day)>ThisTime(day))
     {
       tzpos=tz_opn+ " Next day to " +tz_cls + " Next day";
     }
    else
    if(TimeCurrent()<znop)
      {
        if(ThisTime(day)==ReqTime(znop,day) && ThisTime(day)==ReqTime(zncl,day))
          tzpos=tz_opn+" to " +tz_cls+ " Today";
        //else
        if(ThisTime(day)==ReqTime(znop,day) && ThisTime(day)<ReqTime(zncl,day))
          tzpos=tz_opn+ " Today to " +tz_cls+ " Next day";
      }
    else
    if(TimeCurrent()>=znop && TimeCurrent()<zncl)
      {
        if(ThisTime(day)<ReqTime(zncl,day))
          tzpos=tz_opn+ " Today to " +tz_cls+ " Next day";
        else
        if(ThisTime(day)==ReqTime(zncl,day))
          tzpos=tz_opn+" to " +tz_cls+ " Today";
      }
    else
    if(ThisTime(day)==ReqTime(znop,day) && ThisTime(day)<ReqTime(zncl,day))
      {
        tzpos=tz_opn+" Today to " +tz_cls+ " Next day";
      }
    //--
    return(tzpos);
//----
  } //-end PosTimeZone()
//---------//
void MCEA::TradeInfo(void) // function: write comments on the chart
  {
//----
   Pips(Symbol());
   double spread=SymbolInfoInteger(Symbol(),SYMBOL_SPREAD)/xpip;
   rem=zntm-TimeCurrent();
   string postime=PosTimeZone();
   string eawait=" - Waiting for active time..!";
   //--
   string comm="";
   TodayOrders();
   //--
   comm="\n     :: Server Date Time : "+string(ThisTime(year))+"."+string(ThisTime(mon))+"."+string(ThisTime(day))+ "   "+TimeToString(TimeCurrent(),TIME_SECONDS)+
        "\n     ------------------------------------------------------------"+
        "\n      :: Broker               :  "+ TerminalInfoString(TERMINAL_COMPANY)+
        "\n      :: Expert Name      :  "+ expname+
        "\n      :: Acc. Name         :  "+ mc_account.Name()+
        "\n      :: Acc. Number      :  "+ (string)mc_account.Login()+
        "\n      :: Acc. TradeMode :  "+ AccountMode()+
        "\n      :: Acc. Leverage    :  1 : "+ (string)mc_account.Leverage()+
        "\n      :: Acc. Equity       :  "+ DoubleToString(mc_account.Equity(),2)+
        "\n      :: Margin Mode     :  "+ (string)mc_account.MarginModeDescription()+
        "\n      :: Magic Number   :  "+ string(magicEA)+
        "\n      :: Trade on TF      :  "+ EnumToString(TFt)+
        "\n      :: Today Trading   :  "+ TradingDay()+" : "+hariini+
        "\n      :: Trading Session :  "+ tz_ses+
        "\n      :: Trading Time    :  "+ postime;
        if(TimeCurrent()<zntm)
          {
            comm=comm+
            "\n      :: Time Remaining :  "+(string)ReqTime(rem,hour)+":"+(string)ReqTime(rem,min)+":"+(string)ReqTime(rem,sec) + eawait;
          }
        comm=comm+
        "\n     ------------------------------------------------------------"+
        "\n      :: Trading Pairs     :  "+pairs+
        "\n      :: BUY Market      :  "+string(oBm)+
        "\n      :: SELL Market     :  "+string(oSm)+
        "\n      :: Total Order       :  "+string(oBm+oSm)+
        "\n      :: Order Profit      :  "+DoubleToString(floatprofit,2)+
        "\n      :: Fixed Profit       :  "+DoubleToString(fixclprofit,2)+
        "\n      :: Float Money     :  "+DoubleToString(floatprofit,2)+
        "\n      :: Nett Profit        :  "+DoubleToString(floatprofit+fixclprofit,2);
   //--
   Comment(comm);
   ChartRedraw(0);
   return;
//----
  } //-end TradeInfo()  
//---------//

A interface do EA multimoeda BBOnKeltnerChannel_MCea é a seguinte:

EA-looks

Como pode ver, abaixo do nome do Expert Advisor BBOnKeltnerChannel_MCea, há os botões M, C e R.

Ao clicar no botão M, a barra de botões de comando manual será exibida, conforme mostrado na imagem abaixo.

Expert_manual_button_01

O trader pode gerenciar ordens manualmente quando a barra de botões de comando manual é exibida:

1. Set SL / TP All Orders (definir SL/TP para todas as ordens) Conforme explicado anteriormente, se "Use Order Stop Loss" for "No" e/ou "Use Order Take Profit" for "No", mas o trader desejar usar stop-loss ou take-profit para todas as ordens, um clique no botão "Set SL/TP All Orders" alterará todas as ordens e aplicará stop-loss e/ou take-profit.

void MCEA::SetSLTPOrders(void) 
  {
//---
   ResetLastError();
   MqlTradeRequest req={};
   MqlTradeResult  res={};
   MqlTradeCheckResult check={};
   //--
   double modbuysl=0;
   double modselsl=0;
   double modbuytp=0;
   double modseltp=0;
   string position_symbol;
   int totalorder=PositionsTotal();
   //--    
   for(int i=totalorder-1; i>=0; i--) 
     {
       string symbol=PositionGetSymbol(i);
       position_symbol=symbol;
       if(mc_position.Magic()==magicEA)
         {
           ENUM_POSITION_TYPE opstype = mc_position.PositionType();
           if(opstype==POSITION_TYPE_BUY) 
             {
               Pips(symbol);
               RefreshTick(symbol);
               double price    = mc_position.PriceCurrent();
               double pos_open = mc_position.PriceOpen();
               double pos_stop = mc_position.StopLoss();
               double pos_take = mc_position.TakeProfit();
               modbuysl=SetOrderSL(symbol,opstype,pos_open);
               if(price<modbuysl) modbuysl=mc_symbol.NormalizePrice(price-slip*pip);
               modbuytp=SetOrderTP(symbol,opstype,pos_open);
               if(price>modbuytp) modbuytp=mc_symbol.NormalizePrice(price+slip*pip);
               //--
               if(pos_stop==0.0 || pos_take==0.0)
                 {
                   if(!mc_trade.PositionModify(position_symbol,modbuysl,modbuytp))
                     {
                       mc_trade.CheckResult(check);
                       Do_Alerts(symbol,"Set SL and TP for "+EnumToString(opstype)+" on "+symbol+" FAILED!!. Return code= "+
                                (string)mc_trade.ResultRetcode()+". Code description: ["+mc_trade.ResultRetcodeDescription()+"]");
                     }
                 }
             }
           if(opstype==POSITION_TYPE_SELL) 
             {
               Pips(symbol);
               RefreshTick(symbol);
               double price    = mc_position.PriceCurrent();
               double pos_open = mc_position.PriceOpen();
               double pos_stop = mc_position.StopLoss();
               double pos_take = mc_position.TakeProfit();
               modselsl=SetOrderSL(symbol,opstype,pos_open);
               if(price>modselsl) modselsl=mc_symbol.NormalizePrice(price+slip*pip);
               modseltp=SetOrderTP(symbol,opstype,pos_open);
               if(price<modseltp) modseltp=mc_symbol.NormalizePrice(price-slip*pip);
               //--
               if(pos_stop==0.0 || pos_take==0.0)
                 {
                   if(!mc_trade.PositionModify(position_symbol,modselsl,modseltp))
                     {
                       mc_trade.CheckResult(check);
                       Do_Alerts(symbol,"Set SL and TP for "+EnumToString(opstype)+" on "+symbol+" FAILED!!. Return code= "+
                                (string)mc_trade.ResultRetcode()+". Code description: ["+mc_trade.ResultRetcodeDescription()+"]");
                     }
                 }
             }
         }
     }
    //--
    return;
//---
  } //-end SetSLTPOrders
//---------//

2. Close All Orders - fechar todas as ordens.

void MCEA::CloseAllOrders(void) //-- function: close all order
   {
//----
    ResetLastError();
    //--
    MqlTradeRequest req={};
    MqlTradeResult  res={};
    MqlTradeCheckResult check={};
    //--
    int total=PositionsTotal(); // number of open positions
    //--- iterate over all open positions
    for(int i=total-1; i>=0; i--)
      {
        //--- if the MagicNumber matches
        if(mc_position.Magic()==magicEA)
          { 
            //--
            string position_Symbol   = PositionGetSymbol(i);  // symbol of the position
            ulong  position_ticket   = PositionGetTicket(i);  // ticket of the the opposite position
            ENUM_POSITION_TYPE  type = mc_position.PositionType();
            RefreshTick(position_Symbol);
            bool closepos = mc_trade.PositionClose(position_Symbol,slip);
            //--- output information about the closure
            PrintFormat("Close #%I64d %s %s",position_ticket,position_Symbol,EnumToString(type));
            //---
          }
      }
   //---
   return;
//----
   } //-end CloseAllOrders()
//---------//

3. Close All Profits (fechar todas as ordens lucrativas)

bool MCEA::ManualCloseAllProfit(void)
   {
//----
    ResetLastError();
    //--
    bool orclose=false;
    //--
    MqlTradeRequest req={};
    MqlTradeResult  res={};
    MqlTradeCheckResult check={};
    //--
    int ttlorder=PositionsTotal(); // number of open positions
    //--
    for(int x=0; x<arrsymbx; x++)
       {
         string symbol=DIRI[x];
         orclose=false;
         //--
         for(int i=ttlorder-1; i>=0; i--)
            {
              string position_Symbol   = PositionGetSymbol(i);
              ENUM_POSITION_TYPE  type = mc_position.PositionType();
              if((position_Symbol==symbol) && (mc_position.Magic()==magicEA))
                {
                  double pos_profit = mc_position.Profit();
                  double pos_swap   = mc_position.Swap();
                  double pos_comm   = mc_position.Commission();
                  double cur_profit = NormalizeDouble(pos_profit+pos_swap+pos_comm,2);
                  ulong  position_ticket = PositionGetTicket(i);
                  //---
                  if(type==POSITION_TYPE_BUY && cur_profit>0.02)
                    {
                      RefreshTick(position_Symbol);
                      orclose = mc_trade.PositionClose(position_Symbol,slip);
                      //--- output information about the closure
                      PrintFormat("Close #%I64d %s %s",position_ticket,position_Symbol,EnumToString(type));
                    }
                  if(type==POSITION_TYPE_SELL && cur_profit>0.02)
                    {
                      RefreshTick(position_Symbol);
                      orclose = mc_trade.PositionClose(position_Symbol,slip);
                      //--- output information about the closure
                      PrintFormat("Close #%I64d %s %s",position_ticket,position_Symbol,EnumToString(type));
                    }
                }
            }
       }
     //--
     return(orclose);
//----
   } //-end ManualCloseAllProfit()
//---------//

Ao pressionar C, um botão do painel com 30 nomes de símbolos ou pares é exibido, e os traders podem clicar em um dos nomes dos pares ou símbolos. Ao clicar em um dos nomes ou símbolos de pares, o símbolo do gráfico será substituído pelo símbolo cujo nome foi clicado.

Expert_manual_button_02

Nesse caso, a função OnChartEvent() é chamada pela função ChangeChartSymbol() ao clicar em um dos nomes dos símbolos.

       //--- if Symbol button is click
       if(lensparam==lensymbol)
         {
           int sx=mc.ValidatePairs(sparam);
           ChangeChartSymbol(mc.AS30[sx],CCS);
           mc.PanelExtra=false;
         }
       //--
void ChangeChartSymbol(string c_symbol,ENUM_TIMEFRAMES cstf)
  {
//---
   //--- unpress the button 
   ObjectSetInteger(0,c_symbol,OBJPROP_STATE,false);
   ObjectSetInteger(0,c_symbol,OBJPROP_ZORDER,0);
   ObjectsDeleteAll(0,0,OBJ_BUTTON);
   ObjectsDeleteAll(0,0,OBJ_LABEL);
   ObjectsDeleteAll(0,0,OBJ_RECTANGLE_LABEL);
   //--
   ChartSetSymbolPeriod(0,c_symbol,cstf);
   //--
   ChartRedraw(0);
   //--
   return;
//---
  } //-end ChangeChartSymbol()
//---------//

Clicar no botão R removerá o EA multimoeda BBOnKeltnerChannel_MCea do gráfico, então os traders não precisarão desativar o EA manualmente.

   if(id==CHARTEVENT_OBJECT_CLICK) 
     {
       //--
       //--- if "R" button is click
       if(sparam=="R") 
         { 
           Alert("-- "+mc.expname+" -- ",Symbol()," -- Expert Advisor will be Remove from the chart.");
           ExpertRemove();
           //--- unpress the button 
           ObjectSetInteger(0,"R",OBJPROP_STATE,false);
           ObjectSetInteger(0,"R",OBJPROP_ZORDER,0);
           if(!ChartSetSymbolPeriod(0,Symbol(),Period()))
             ChartSetSymbolPeriod(0,Symbol(),Period());
           DeletePanelButton();
           ChartRedraw(0);
         }
       //---
     }

Testador de estratégias

Como é conhecido, o testador de estratégias do terminal MetaTrader 5 suporta e permite testar estratégias multissímbolos ou negociação automática para todos os símbolos disponíveis e em todos os timeframes disponíveis. Portanto, na plataforma de teste de estratégias MetaTrader 5, testaremos o BBOnKeltnerChannel_MCea como um Expert Advisor multimoeda.

Neste teste, colocamos o BBOnKeltnerChannel_MCea no par XAGUSD H1 com um período de tempo personalizado de 04.09.2023 a 02.12.2023.

ST_tester-period

O teste foi realizado com dois parâmetros de entrada diferentes do grupo de parâmetros "Trade & Order Management" (gestão de negociações e ordens).

1. Parâmetros de entrada padrão.

ST_Default-Input1

ST_Default-Input-result

2. Parâmetros de entrada personalizados

ST_Custom-Input1

ST_Custom-Input-result

Considerações finais

Desenvolver um Expert Advisor multimoeda com sinais das Bandas de Bollinger no canal de Keltner usando MQL5 leva às seguintes conclusões:

  1. Criar um robô multimoeda no MQL5 é praticamente igual a desenvolver um para uma única moeda.
  2. Comparado ao MetaTrader 4, o uso do handle do indicador em MQL5 para obter valores e sinais dos indicadores é mais simples e prático.
  3. Criar um Expert Advisor multimoeda aumentará a eficiência e produtividade dos traders, já que não precisarão abrir múltiplos gráficos.
  4. A estratégia de negociação correta aumenta a probabilidade de lucro em comparação com o uso de um Expert Advisor de moeda única, pois as perdas em um par podem ser compensadas pelos lucros em outros pares.
  5. O Expert Advisor multimoeda BBOnKeltnerChannel_MCea serve apenas como um exemplo para o estudo e desenvolvimento de ideias próprias, e os resultados do teste no testador de estratégias ainda são insatisfatórios. Experimentando e testando em diferentes timeframes ou ajustando períodos dos indicadores, é possível obter resultados mais lucrativos.
  6. Os resultados dos testes do BBOnKeltnerChannel_MCea no testador de estratégias indicam que os resultados com parâmetros de entrada personalizados são melhores do que com os parâmetros de entrada padrão.

Espero que o artigo e o robô multimoeda sejam úteis para os traders ao estudar e desenvolver suas ideias.

Obrigado pela atenção!

Nota: Se você tiver uma ideia para criar um simples EA multimoeda baseado nos sinais padrão do indicador MQL5, compartilhe-a nos comentários.

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/13861

Rede neural na prática: Função de reta Rede neural na prática: Função de reta
Neste artigo, vamos passar rapidamente, por alguns métodos para conseguir a função que poderá representar os nossos dados no banco. Não irei me aprofundar em detalhes relacionados ao como usar estatísticas e estudos de probabilidade para interpretar os resultados. Deixo isto como dever de casa, para cada um que realmente deseja se aprofundar, na parte matemática da coisa. De qualquer forma, estudar tais coisas será primordial para que você de fato consiga compreender tudo que envolve estudos de redes neurais. Aqui irei pegar bem leve no tema.
Análise quantitativa no MQL5: implementando um algoritmo promissor Análise quantitativa no MQL5: implementando um algoritmo promissor
Vamos explorar o que é a análise quantitativa, como os grandes players a utilizam e criar um dos algoritmos de análise quantitativa na linguagem MQL5.
Buffers de cores em indicadores de vários símbolos e vários períodos Buffers de cores em indicadores de vários símbolos e vários períodos
Neste artigo, analisaremos a estrutura do buffer de indicador em indicadores com vários símbolos e vários períodos e geraremos a exibição dos buffers coloridos desses indicadores no gráfico.
Redes neurais de maneira fácil (Parte 67): Aprendendo com experiências passadas para resolver novos problemas Redes neurais de maneira fácil (Parte 67): Aprendendo com experiências passadas para resolver novos problemas
Neste artigo, continuaremos a falar sobre métodos de coleta de dados em uma amostra de treinamento. É claro que o processo de aprendizado requer constante interação com o ambiente. Mas as situações podem variar.