English Русский 中文 Deutsch 日本語 Português
preview
Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 5): Bandas de Bollinger en el Canal de Keltner - Señales de Indicador

Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 5): Bandas de Bollinger en el Canal de Keltner - Señales de Indicador

MetaTrader 5Trading | 6 junio 2024, 17:02
365 0
Roberto Jacobs
Roberto Jacobs

Introducción

En este artículo, entenderemos por asesor multidivisa un asesor o robot comercial que puede comerciar (abrir/cerrar órdenes, gestionar órdenes, por ejemplo, trailing-stop y trailing-profit, etc.) con más de un par de símbolos de un gráfico. En este artículo, el asesor comerciará con 30 pares.

En este artículo, usaremos las señales de dos indicadores, las Bandas de Bollinger® y el Canal de Keltner.

En MetaTrader 4, dichas señales se usaban mediante la función iBandsOnArray.

En el Manual de Referencia de MQL4, la explicación a la función iBandsOnArray indica que, a diferencia de iBands(...), la función iBandsOnArray() no toma datos sobre el nombre del símbolo, el marco temporal y el precio aplicado. Los datos sobre precios deberán prepararse previamente....

En las discusiones del foro MQL5, me he encontrado con opiniones que iBandOnArray() no existe en MQL5.
Efectivamente, iBandOnArray() no se encuentra en la lista de funciones MQL5, pero usando el manejador del indicador iBands(), podemos crear fácilmente iBandOnArray() en MQL5. En mi opinión, el uso del manejador del indicador en MQL5 es más cómodo y fácil que el uso de la función iBandsOnArray() en MetaTrader 4.

Como ya hemos demostrado en artículos anteriores, el trading multidivisa tanto en el terminal comercial como en el simulador de estrategias es posible gracias a MQL5.

Por ello, el objetivo consistirá en satisfacer las necesidades básicas de los tráders que necesitan robots comerciales eficaces. Apoyándonos en los puntos fuertes y capacidades de MQL5, podemos crear un sencillo asesor multidivisa, que en este artículo utilizará las señales de dos indicadores para abrir órdenes: las Bandas de Bollinger en el Canal de Keltner. Las Bandas de Bollinger usarán los datos de precio del canal de Keltner. Seguiremos usando el indicador Parabolic SAR (iSAR) para los trailing stops.

Particularidades

1. Pares comerciales.

El asesor comerciará con los siguientes 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

Más 2 pares de metales: XAUUSD (oro) y XAGUSD (plata).

Con esos, tendremos un total de 30 pares.

Al igual que en el artículo anterior, hemos simplificado la tarea añadiendo propiedades de entrada especiales para el prefijo y el sufijo del nombre del par. A continuación, usaremos una función sencilla para procesar el prefijo y/o sufijo del nombre del par en combinación con los 30 nombres de pares registrados para que el asesor funcione con normalidad si el bróker utiliza nombres con caracteres especiales.

Una desventaja de la función para detectar nombres de símbolos con prefijos y sufijos es que solo funciona con pares o nombres de símbolos para divisas y metales en MetaTrader 5, pero no funciona con símbolos especiales e índices. Además, deben evitarse los errores tipográficos (deberán introducirse distinguiendo entre mayúsculas y minúsculas) en el nombre del prefijo y/o sufijo del par. Por consiguiente, se requerirá precisión y exactitud al introducir el prefijo y/o el sufijo del nombre del par.

Como en el artículo anterior, también hemos añadido 10 opciones sobre pares negociados a este asesor. Uno de los 10 pares de opciones que se negociarán son los Trader's Desired Pairs (los pares deseados por el tráder), donde los pares negociados deberán ser introducidos manualmente por el tráder en las propiedades del asesor. No olvide que el nombre del par introducido deberá figurar ya en la lista de 30 pares.

En esta versión del asesor también hemos añadido una opción de sesión comercial (zona horaria), por lo que los pares negociados se corresponderán con la hora de la sesión comercial.

2. Indicadores de señal.

En esta versión del asesor utilizaremos señales de dos indicadores, en este caso las Bandas de Bollinger sobre el canal de Keltner. Utilizaremos el canal de Keltner como datos de precios para las Bandas de Bollinger.

2.1. Canal de Keltner.

El canal Keltner fue introducido por primera vez por Chester Keltner en la década de 1960. La fórmula original usaba medias móviles simples (SMA) y un rango de precios máximos y mínimos para calcular las bandas. En la década de 1980, se introdujo una nueva fórmula que usaba el rango medio verdadero (ATR). El método ATR está muy extendido en la actualidad.

Al construir el canal de Keltner usado para los asesores de este artículo, hemos utilizado un método popular hoy en día, a saber, una media móvil exponencial (EMA) con un periodo de 20 con bandas superior e inferior utilizando el indicador ATR con un periodo de 20.

Los parámetros de entrada del canal Keltner serán los siguientes:

BBOnKC_Keltner Channel Indicator-cr

2.2. Bandas de Bollinger

Las Bandas de Bollinger fueron creadas por John Bollinger en la década de 1980 y rápidamente se convirtieron en uno de los indicadores más populares de análisis técnico. Las bandas de Bollinger constan de tres bandas -superior, media e inferior- que se usan para marcar los extremos de precios a corto plazo en el mercado. La banda superior es señal de sobrecompra, mientras que la inferior es de sobreventa. La mayoría de los analistas financieros usan las Bandas de Bollinger y las combinan con otros indicadores para obtener una mejor visión analítica de las condiciones del mercado.

En el asesor para este artículo, utilizaremos las Bandas de Bollinger con un periodo de 38, que usarán los datos de precios del canal de Keltner.

Los parámetros de entrada de las Bandas de Bollinger será los siguientes:

BBOnKC_Bollinger Bands Indicator-cr

En las figuras 1 y 2 se muestra el canal de Keltner como datos de precios para las Bandas de Bollinger para señales de compra y venta.

BBOnKC_BUY signal

Fig. 1. Señal de compra:


BBOnKC_SELL signal

Fig. 2. Señal de venta:

En la ilustración anterior, solo se emite una señal cuando la línea media del canal de Keltner cruza la banda de Bollinger superior o inferior. Pero para el asesor de este artículo, la señal del indicador será en realidad la intersección de la línea media del canal de Keltner y las líneas superior, media e inferior de las Bandas de Bollinger.

  • Para señales de compra:

  1. Primera señal: La línea media del canal de Keltner cruza la banda inferior de Bollinger de abajo arriba; o bien
  2. Segunda señal: La línea media del canal de Keltner cruza la banda inferior de Bollinger de abajo arriba; o bien
  3. La tercera señal: la línea media del canal de Keltner cruza la banda superior de Bollinger de abajo a arriba.

  • Para señales de venta:

  1. Primera señal: La línea media del canal de Keltner cruza la banda superior de Bollinger de arriba abajo; o bien
  2. Segunda señal: La línea media del canal de Keltner cruza la banda superior de Bollinger de arriba abajo; o bien
  3. La tercera señal: la línea media del canal de Keltner cruza la banda inferior de Bollinger de arriba abajo.

3. Control de transacciones y órdenes.

El asesor multidivisa nos ofrece varias opciones para gestionar las transacciones:

3.1. Órdenes Stop Loss

Opciones: Use Order Stop Loss (Yes) o (No) - utilizar una orden Stop Loss: sí o no

  • Si seleccionamos Use Order Stop Loss (No), todas las órdenes se abrirán sin un Stop Loss.

  • Si seleccionamos Use Order Stop Loss (Yes), nuevamente aparecerá una opción: Use Automatic Calculation Stop Loss (Yes) o (No) - utilizar el Stop Loss calculado automáticamente: sí o no
  • Si seleccionamos Automatic Calculation Stop Loss (Yes), el asesor calculará el Stop Loss.
  • Si seleccionamos Automatic Calculation Stop Loss (No), el tráder deberá ingresar el valor del Stop Loss en pips.

  • Si seleccionamos Use Order Stop Loss (No), el asesor verificará si se cumplen las condiciones de la señal. Si se cumplen, se guardará la orden. Si la señal se ha debilitado, la orden deberá cerrarse para conservar el beneficio, o bien la condición de la señal ha cambiado de dirección y la orden deberá cerrarse y la pérdida deberá fijarse.

Nota: Antes de cerrar una operación debido a una señal débil, se solicitará la confirmación del usuario.

Si se establece No, aunque la señal se haya debilitado, la orden se guardará o no se cerrará para preservar el beneficio.

Si se establece Yes, las condiciones para el canal de Keltner y las Bandas de Bollinger serán las siguientes:

  • Para cerrar órdenes de compra: cuando la línea inferior del indicador del canal de Keltner cruce la línea superior de las bandas de Bollinger, se cerrará la orden de compra.
  • Para cerrar órdenes de venta: Cuando la línea superior del indicador del canal de Keltner cruce la línea superior de las bandas de Bollinger, se cerrará la orden de venta.

El código para establecer un stop loss será el siguiente:

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()
//---------//

Código para cerrar la operación y mantener el beneficio debido a una señal débil:

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. Órdenes Take Profit

Opciones: Use Order Take Profit (Yes) o (No) - usar órdenes de Take Profit: sí o no

  • Si seleccionamos Use Order Take Profit (No), todas las órdenes se abrirán sin Take Profit.

  • Si seleccionamos Use Order Take Profit (Yes), nuevamente aparecerá una opción: Use Automatic Calculation Order Take Profit (Yes) o (No) - utilizar el Take Profit calculado automáticamente: sí o no

  • Si seleccionamos Automatic Calculation Order Take Profit (Yes), el asesor calculará el Take Profit.

  • Si seleccionamos Automatic Calculation Order Take Profit (No), el tráder deberá ingresar el valor de Take Profit en pips.

El código para establecer el take profit será el siguiente:

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 y Trailing Take Profit

Opciones: Use Trailing SL/TP (Yes) o (No) - utilizar Traling Stop Loss/Take Profit: sí o no

  • Si se establece Use Trailing SL/TP option (No), el asesor no utilizará el Traling Stop Loss ni el Traling Take Profit.

  • Si se establece Use Trailing SL/TP (Yes), nuevamente aparecerá una opción: Use Automatic Trailing (Yes) o (No) - usar trailing automático: sí o no 

  • Si utilizamos el Trailing Automático (Yes), el asesor ejecutará un trailing stop utilizando el valor de Parabolic SAR (iSAR) simultáneamente con el cálculo de las señales del marco temporal y al mismo tiempo tomando el beneficio móvil basado en el valor de la variable TPmin (valor del trailing profit).
  • Si utilizamos Use Automatic Trailing (No), el asesor ejecutará el Trailing Stop utilizando el valor del parámetro de entrada.

Nota: El asesor ejecutará simultáneamente el Trailing Take Profit y el Trailing Stop.

Función 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()
//---------//

Cambio de la función 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. Gestión manual de órdenes.

Para mejorar la eficiencia, hemos añadido varios botones.

4.1. Set SL / TP All Orders (establecer Stop Loss/Take Profit para todas las órdenes)

Si establecemos Use Order Stop Loss (No) y/o Use Order Take Profit (No),
pero luego queremos usar Stop Loss o Take Profit para todas las órdenes, solo necesitaremos clicar en el botón

Set SL / TP All Orders para cambiar todas las órdenes y aplicar stop el loss y/o el take profit.

4.2. Close All Orders (cerrar todas las órdenes)

4.3. Cerrar todas las órdenes con beneficio: cierra todas las órdenes con beneficio.

5. Management Orders and Chart Symbols.

Una característica muy útil para los asesores multidivisa que comercian con 30 pares desde un mismo gráfico será la presencia de una barra de un solo botón para que los tráders puedan cambiar de gráfico o de símbolo con un solo clic para ver la precisión de la señal del indicador cuando el asesor abra o cierre una orden.

Implementamos la planificación en un programa MQL5

1. Encabezado del programa y parámetros de entrada.

Inclusión de un archivo de encabezado en 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;
//---

Enumeración para el uso de la zona horaria

//--
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
 };
//--

Enumeración para seleccionar las 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
  };
//--

Enumeración para seleccionar los minutos

//--
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
  };
//--

Enumeración para seleccionar pares de opciones para comerciar

//--
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
 };   
//--

La enumeración YN se usará para las opciones (Yes) o (No) en el parámetro del asesor.

Enumeración para usar el tamaño del lote en la gestión de capital

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

Enumeración para seleccionar el marco temporal que se usará en el cálculo de los indicadores de señales

//--
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: Al enumerar los TFUSE, limitamos el uso de los cálculos de tiempo para los asesores solo de TF-M5 a TF-D1

Parámetros de entrada del asesor

//---
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: Si el parámetro de entrada Expert ID (Magic Number) se deja vacío, el asesor podrá gestionar las órdenes abiertas manualmente.

En el grupo de propiedades de entrada Global Strategy EA Parameters del asesor se ofrece a los tráders elegir un marco temporal para calcular las señales del indicador e introducir parámetros para el canal de Keltner y las Bandas de Bollinger.

Al crear el asesor en este artículo, el canal de Keltner en el asesor tendrá exactamente los mismos parámetros de entrada que en el 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
//--

En el grupo de propiedades de los datos de entrada Select Pairs to Trade deberemos seleccionar un par para comerciar entre las 10 opciones proporcionadas. El valor por defecto será All Forex 30 Pairs.

Para configurar el par a comerciar, llamaremos a la función HandlingSymbolArrays(). Usando la función HandlingSymbolArrays(), manejaremos todos los pares comerciados.

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 de la función HandlingSymbolArrays(), llamaremos a la función SetSymbolNamePS(). Usando SetSymbolNamePS(), podremos gestionar nombres de símbolos con prefijo y/o sufijo.

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: El asesor comprobará los pares. Si un tráder comete un error al introducir el nombre de un par o el nombre de un prefijo/sufijo o falla la comprobación de pares, el asesor generará una advertencia y se eliminará del gráfico.

En el grupo de propiedades Trade on Specific Time input, el tráder deberá seleccionar Trade on Specific Time Zone (Yes) o (No) - comerciar en una zona horaria específica: sí o no. En caso afirmativo, deberemos seleccionar los parámetros de la enumeración:

  • Trading on Custom Session (sesión personalizada)
  • Trading on New Zealand Session (sesión de Nueva Zelanda)
  • Trading on Australia Sydney Session (sesión de Sídney)
  • Trading on Asia Tokyo Session (sesión de Tokio)
  • Trading on Europe London Session (sesión de Londres)
  • Trading on America New York Session (sesión de Nueva York)

Trading on Custom Session (sesión personalizada): Los tráders deben establecer una hora u horas y minutos para iniciar la negociación, así como las horas y minutos para cerrarla. Así, el asesor solo realizará acciones durante el tiempo especificado.

En otros casos, las horas de inicio y fin de la negociación las elige el asesor.

Clase para el funcionamiento del asesor experto

Para definir el diseño y la configuración del flujo de trabajo del asesor, crearemos una clase que declare todas las variables, objetos y funciones necesarios.

//+------------------------------------------------------------------+
//| 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
//---------//

La primera y más importante función en la operación del asesor multidivisa, que es llamada por OnInit(), será BBOnKeltnerChannel_MCEA_Config().

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

La función BBOnKeltnerChannel_MCEA_Config() configurará todos los símbolos utilizados, todos los indicadores de manejo utilizados y algunas funciones importantes del encabezado del archivo de inclusión.

Las líneas 468 a 484 explican cómo procesar el marco temporal y crear manejadores de indicadores para todos los indicadores usados.

//+------------------------------------------------------------------+
//| 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. Función Expert Tick

En la función Expert Tick (OnTick()) llamaremos a una de las funciones principales del asesor multidivisa, concretamente a la función ExpertActionTrade().

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

Secuencia del asesor dentro de esta función.

La función ExpertActionTrade() realizará todas las acciones y gestionará el comercio automático, incluidas las órdenes de apertura/cierre, el Trailing Stop, el Trailing Take Profit y otras acciones adicionales.

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()
//---------//

El grupo de propiedades Day Trading On/Off permite a los tráders comerciar en días específicos de domingo a sábado. Los tráders pueden activar o desactivar los asesores para comerciar en un día específico utilizando la opción Yes o 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)

Propiedades On/Off de Day Trading:

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: Las condiciones de activación/desactivación de la negociación diaria se mostrarán en la información comercial del gráfico.

En el grupo de propiedades del asesor Trade on Specific Time, los tráders pueden seleccionar la zona horaria.

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: En otros casos, las horas de inicio y fin de la negociación las elegirá el asesor. Así, en las propiedades Expert Entry, los tráders solo deberán establecer la hora y el minuto de inicio del comercio, y la hora y el minuto de finalización del comercio para la sesión personalizada.

Especialmente para las zonas horarias comerciales, hemos añadido una llamada de la función lógica Trade_session() a la función ExpertActionTrade().

Cuando Trade_session() sea true, el asesor trabajará hasta la finalización, y cuando sea false, el asesor solo realizará las tareas Close Trade and Save profit due to weak signal (Yes) (cerrar la transacción por señal débil (Sí)) y Trailing stop (Yes) (trailing stop (Sí)).

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. Obtención de señales comerciales para abrir/cerrar una posición

La función ExpertActionTrade() llama a la función GetOpenPosition() para obtener una señal.

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()
//---------//

La función GetOpenPosition() llama a las funciones BBOnKeltnerChannel() que realizan el cálculo de la señal.

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 de la función BBOnKeltnerChannel(), usaremos y llamaremos a una función, 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()
//---------//

La función PairsIdxArray() se utilizará para obtener el nombre del símbolo y los indicadores solicitados. A continuación, llamaremos al indicador correspondiente para obtener el valor del búfer de señal del canal de Keltner y las bandas de Bollinger de este marco temporal.

Luego deberemos averiguar el número de búfer del canal de Keltner. Los números de búfer en la función OnInit() serán los siguientes: 0 - banda media, 1 - banda superior, 2 - banda 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);
//---
  }
//---------//

En este caso, también necesitaremos conocer el número de búferes de las Bandas de Bollinger.

Al hacerlo, como se explica en la función iBands®:

"Los números del búfer serán los siguientes: 0 - LÍNEA_BASE, 1 - BANDA_SUPERIOR, 2 - BANDA_INFERIOR"

applied_price

[in]  Precio utilizado. Puede ser cualquier constante de precio ENUM_APPLIED_PRICE o el manejador de otro indicador.

En este artículo, los asesores utilizarán el precio aplicado para las Bandas de Bollinger del manejador del canal de Keltner.

Así, para obtener el valor del búfer para cada línea del canal de Keltner y la banda de Bollinger, copiaremos cada búfer del manejador de indicadores.

Para copiar el búfer de la línea media (búfer 0) desde el manejador del canal Keltner al array de destino:

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

Para copiar el búfer de la línea media (búfer 1) desde el manejador del canal Keltner al array de destino:

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

Para copiar el búfer de la línea media (búfer 2) desde el manejador del canal Keltner al array de destino:

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

Para copiar el búfer BASE_LINE (búfer 0) del manejador de Bandas de Bollinger al array destino:

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

Para copiar el búfer BASE_LINE (búfer 1) del manejador de Bandas de Bollinger al array destino:

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

Para copiar el búfer BASE_LINE (búfer 2) del manejador de Bandas de Bollinger al array destino:

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

A continuación, la función GetOpenPosition() retornará los siguientes resultados:

  • Valor 0 - señal desconocida.
  • Valor 1 - señal de compra.
  • Valor -1 - señal de venta.

El asesor llamará a la función OpenBuy() cuando la función GetOpenPosition() retorne el 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
//---------//

El asesor llamará a la función OpenSell() si la función GetOpenPosition() retorna -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. Función ChartEvent

Para que los asesores multidivisa sean más eficaces, crearemos varios botones para gestionar las órdenes y cambiar los gráficos o 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()
//---------//

En el grupo de propiedades Other (otras), el tráder tendrá la opción de elegir si desea mostrar la información de la transacción en el gráfico (Yes) o no (No).

Si la respuesta es Yes, la información de la transacción se mostrará en el gráfico en el que está colocado el asesor llamando a la función TradeInfo().

Dentro de TradeInfo(), también hemos añadido una función para describir la hora según las condiciones de la zona horaria comercial.

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()  
//---------//

La interfaz del asesor multidivisa BBOnKeltnerChannel_MCEA será la siguiente.

EA-looks

Como podemos ver, tenemos los botones M, C y R con el nombre del asesor BBOnKeltnerChannel_MCEA

Al pulsar el botón M, aparecerá la barra de botones de pulsación manual, como se muestra en la figura siguiente.

Expert_manual_button_01

El tráder podrá gestionar las órdenes manualmente cuando se muestre la barra de botones manual:

1. Set SL / TP All Orders (establecer Stop Loss/Take Profit para todas las órdenes) Como hemos explicado antes, si Use Order Stop Loss es igual a No y/o Use Order Take Profit es igual a No, pero el tráder tiene la intención de utilizar Stop Loss o Take Profit en todas las órdenes, un solo clic en el botón Set SL/TP All Orders cambiará todas las órdenes y aplicará un Stop Loss y/o 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 (cerrar todas las órdenes)

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 (cerrar todas las órdenes rentables)

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()
//---------//

Al clicar en C, se mostrará un botón de panel con los 30 nombres de los símbolos o pares, y los tráders podrán clicar en uno de los nombres de pares o símbolos. Clicando en uno de los nombres o símbolos de un par, se sustituirá inmediatamente el símbolo del gráfico por el símbolo cuyo nombre se ha pulsado.

Expert_manual_button_02

En este caso, OnChartEvent() será llamada por ChangeChartSymbol() al pulsar sobre uno de los nombres de símbolo.

       //--- 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()
//---------//

Al hacer clic en el botón R se eliminará el asesor multidivisa BBOnKeltnerChannel_MCEA del gráfico, por lo que los tráders no tendrán que desactivar los asesores 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);
         }
       //---
     }

Simulador de estrategias

Como usted sabrá, el simulador de estrategias del terminal MetaTrader 5 admite y permite probar estrategias, operar con varios símbolos o probar el trading automático para todos los símbolos disponibles y en todos los marcos temporales disponibles. Por lo tanto, vamos a probar BBOnKeltnerChannel_MCEA como asesor multidivisa en la plataforma del simulador de estrategias MetaTrader 5.

En esta prueba, hemos colocado BBOnKeltnerChannel_MCEA en el par XAGUSD H1 con un periodo de tiempo personalizado de 2023.09.04 - 2023.12.02.

ST_tester-period

La prueba se ha realizado con dos parámetros de entrada diferentes del grupo de parámetros Trade & Order Management (Gestión de transacciones y órdenes).

1. Parámetros de entrada por defecto.

ST_Default-Input1

ST_Default-Input-result

2. Parámetros de entrada personalizados.

ST_Custom-Input1

ST_Custom-Input-result

Conclusión

La creación de un asesor multidivisa con las señales de las Bandas de Bollinger en el canal de Keltner utilizando MQL5 lleva a las siguientes conclusiones:

  1. Crear un asesor multidivisa en MQL5 no resulta muy diferente de desarrollar un asesor monodivisa,
  2. En comparación con MetaTrader 4, en MQL5 resulta más fácil y cómodo utilizar el manejador del indicador para obtener los valores y señales del indicador.
  3. La creación de un asesor multidivisa aumentará la eficiencia de los tráders, ya que no necesitarán abrir muchos gráficos.
  4. Una estrategia comercial correcta, combinada con señales de indicadores de alta calidad, aumentará la probabilidad de obtener beneficios en comparación con el uso de un asesor monodivisa. Las pérdidas de un par se compensarán con los beneficios de otros pares.
  5. El asesor multidivisa ADXPSAR_MCEA es solo un ejemplo que permite estudiar y desarrollar nuestras propias ideas. Los resultados de las pruebas en el simulador de estrategias siguen pareciéndonos insatisfactorios. Experimentando y probando en diferentes marcos temporales o calculando diferentes periodos de los indicadores, podremos obtener resultados más rentables.
  6. Los resultados de BBOnKeltnerChannel_MCEA en el simulador de estrategias muestran que los resultados de las propiedades de los parámetros de entrada personalizados son mejores que los de las propiedades de los parámetros de entrada predeterminados.

Espero que el artículo y el asesor multidivisa resulten útiles a los tráders para estudiar y desarrollar ideas.

¡Gracias por su atención!

Nota: Si tiene una idea para crear un asesor multidivisa sencillo basado en las señales integradas de un indicador estándar de MQL5, expóngala en los comentarios.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/13861

Paradigmas de programación (Parte 1): Enfoque procedimental para el desarrollo de un asesor basado en la dinámica de precios Paradigmas de programación (Parte 1): Enfoque procedimental para el desarrollo de un asesor basado en la dinámica de precios
Conozca los paradigmas de programación y su aplicación en el código MQL5. En este artículo, analizaremos las características de la programación procedimental y ofreceremos ejemplos prácticos. Asimismo, aprenderemos a desarrollar un asesor basado en la acción del precio (Action Price) utilizando el indicador EMA y datos de velas. Además, el artículo introduce el paradigma de la programación funcional.
Análisis cuantitativo en MQL5: implementamos un algoritmo prometedor Análisis cuantitativo en MQL5: implementamos un algoritmo prometedor
Hoy veremos qué es el análisis cuantitativo, cómo lo utilizan los grandes jugadores y crearemos uno de los algoritmos de análisis cuantitativo en MQL5.
Asesor Experto Grid-Hedge Modificado en MQL5 (Parte I): Creamos un sencillo asesor de cobertura Asesor Experto Grid-Hedge Modificado en MQL5 (Parte I): Creamos un sencillo asesor de cobertura
Hoy crearemos un sencillo asesor de cobertura como base para nuestro asesor Grid-Hedge más avanzado, que será una mezcla de estrategias de rejilla y cobertura clásicas. Al final de este artículo, usted sabrá cómo crear una estrategia de cobertura simple y lo que la gente opina sobre la rentabilidad de esta estrategia.
Redes neuronales: así de sencillo (Parte 67): Utilizamos la experiencia adquirida para afrontar nuevos retos Redes neuronales: así de sencillo (Parte 67): Utilizamos la experiencia adquirida para afrontar nuevos retos
En este artículo, seguiremos hablando de los métodos de recopilación de datos en una muestra de entrenamiento. Obviamente, en el proceso de entrenamiento será necesaria una interacción constante con el entorno, aunque con frecuencia se dan situaciones diferentes.