English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Simulación Rápida de Ideas de Trading en el Gráfico

Simulación Rápida de Ideas de Trading en el Gráfico

MetaTrader 5Sistemas comerciales | 28 mayo 2014, 08:52
1 402 0
Vladimir Kustikov
Vladimir Kustikov

Introducción

El sexto Automated Trading Championship (Campeonato de Trading Automatizado, o ATC 2012) ya ha empezado, por fin. Toda la emoción inicial se ha disipado, y ahora finalmente podemos relajarnos un poco y examinar los robots de trading presentados. He decidido investigar un poco para descubrir las cualidades más notables de los robots de trading modernos y definir qué podemos esperar de sus operaciones de trading.

Una tarea que no resultó nada fácil. Por tanto, mis cálculos no se pueden considerar totalmente precisos ni completos con el material del que disponía de las descripciones del Asesor Experto y los comentarios poco frecuentes de los creadores. No obstante, podemos extraer algunas conclusiones, y debajo puede ver los resultados de mis cálculos: 451 Asesores Expertos participan en el campeonato, pero solo 316 de ellos contienen descripciones significativas. Los creadores de los demás Asesores llenaron los campos de descripción con dedicatorias a sus amigos y familia, mensajes a civilizaciones extraterrestres o halagos por su propia labor.

Las estrategias más populares del ATC 2012:

  • trading usando varias construcciones gráficas (niveles de precio importantes, niveles de soporte-resistencia, canales) – 55;
  • análisis de movimiento de precios (para varios intervalos cronológicos) – 33;
  • sistemas de rastreo de tendencias (supongo que este increíble concepto oculta alguna combinación super-optimizada de medias móviles, pero podría estar equivocado :) ) – 31;
  • patrones de precio estadísticos – 10:
  • arbitración, análisis de correlación de símbolos – 8;
  • análisis de volatilidad – 8;
  • redes neuronales – 7;
  • análisis de velas – 5;
  • mediadores – 5;
  • conjuntos de estrategia – 5;
  • tiempo de sesión de trading – 4;
  • generador de números aleatorio – 4;
  • trading conforme a las noticias de actualidad – 3,
  • Ondas de Elliott – 2.

Las estrategias de indicador son tradicionalmente las más populares, por supuesto. Es difícil definir el papel de cada indicador particular en un Asesor Experto concreto, pero es posible calcular el número absoluto de su uso:

  • Media Móvil (MA) – 75;
  • MACD – 54;
  • Oscilador Estocástico – 25;
  • RSI – 23;
  • Bollinger Bands – 19;
  • Fractals – 8;
  • CCI, ATR – 7 indicadores cada uno;
  • Zigzag, SAR Parabólico – 6 indicadores cada uno;
  • ADX – 5;
  • Momentum – 4;
  • indicadores personalizados (cuánta intriga :) ) – 4;
  • Ichimoku, AO – 3 indicadores cada uno;
  • ROC, WPR, StdDev, Volumes – 2 indicadores cada uno.

Los datos sugieren las siguientes conclusiones: la mayoría de los participantes practican trading siguiendo estrategias con indicadores. Es posible que me dejara algo al recopilar los datos, y que, al contrario de mis suposiciones, veamos la llegada de varios genios en el campo del trading automatizado, pero creo que las posibilidades son escasas por ahora. En mi opinión, el principal problema es que los recién llegados, atraídos por el mercado, en la mayoría de los casos adquieren reglas en lugar de conocimiento.

Por ejemplo: “Aquí están las reglas del uso de MACD, aquí están las señales: ahora optimice los parámetros y gane dinero". ¿Dónde quedó el usar la cabeza un poco? ¡No tiene sentido! ¡Los estándares ya se han desarrollado! ¿Por qué seguir dándoles vueltas? No obstante, a menudo olvidamos que los indicadores que son tan populares ahora mismo también los inventaron traders como usted y como yo. Ellos también tenían sus estándares y autoridades. A lo mejor, un nuevo indicador con su nombre se convertirá en uno estándar en diez años.

Me gustaría compartir mi método de búsqueda de ideas de trading, así como el método que uso para la simulación rápida de estas ideas.


Descripción del Método

Todos los análisis técnicos se basan en un simple axioma: los precios consideran todo. Pero hay un problema: a esta declaración le falta dinamismo. Al mirar el gráfico, vemos una imagen estática: el precio lo ha considerado realmente todo. Sin embargo, queremos saber qué considerará el precio en un determinado periodo de tiempo en el futuro y hacia dónde irá para poder sacar un beneficio. Los indicadores derivados del precio se han diseñado exactamente para predecir posibles movimientos futuros.

Como ya sabemos por la física, la derivada de primer orden de la magnitud es la velocidad. Por tanto, los indicadores calculan la velocidad de cambio del precio actual. También sabemos que magnitudes significativas tienen una inercia para evitar en la velocidad cambios drásticos de su valor sin la intervención de fuerzas externas considerables. Así es como enfocamos gradualmente el concepto de una tendencia: el estado del precio cuando su derivada de primer orden (velocidad) mantiene su valor durante el período de tiempo en el que fuerzas externas (noticias, políticas de los bancos centrales, etc) no afectan al mercado.

Pero volvamos al principio: los precios consideran todo. Para desarrollar nuevas ideas deberíamos examinar el comportamiento del precio y sus derivadas en el mismo intervalo cronológico. Solo el análisis detallado de gráficos de precio llevará a su estrategia de trading de la fe ciega al nivel del entendimiento genuino.

Puede que esto no lleve a cambios inmediatos en los resultados de trading, pero la capacidad de responder a numerosas preguntas esenciales le reportará ventajas antes o después. Además, el análisis visual de gráficos e indicadores le permitirá encontrar correlaciones nuevas entre precios e indicadores completamente imprevistas por sus creadores.

Imagine que encuentra una nueva correlación que aparentemente juega a su favor. ¿Qué viene después? La forma más fácil es escribir un Asesor Experto y simularlo en datos del historial, asegurándose de que sus suposiciones son correctas. Si no es el caso, deberemos elegir una forma común de optimizar parámetros. Lo peor de todo esto es que no pudimos responder al porqué. ¿Por qué nuestro Asesor Experto resultó ser rentable/no rentable? ¿Por qué tiene semejante reducción? Sin las respuestas, no podrá implementar su idea de forma eficiente.

Yo llevo a cabo las siguientes acciones para visualizar los resultados de una correlación obtenida dentro del gráfico:

  1. Creo o cambio el indicador necesario para que genere una señal: -1 para vender y 1 para comprar.
  2. Conecto el indicador de saldo que muestra los puntos de entrada y salida del gráfico. El indicador también muestra los cambios del saldo y beneficio (en puntos) al procesar la señal.
  3. Analizo en qué casos y circunstancias mis suposiciones son correctas.

El método tiene ciertas ventajas.

  • En primer lugar, el indicador de saldo se calcula completamente usando el método OnCalculate, que facilita la mayor velocidad de cálculo y disponibilidad automática de datos del historial en los arrays de cálculo de entrada.
  • En segundo lugar, añadir la señal al indicador existente es un paso intermedio entre la creación de un Asesor Experto a través de un Wizard y su desarrollo por su cuenta.
  • En tercer lugar, una idea y el resultado final se pueden ver en un mismo gráfico. Por supuesto, el método tiene algunas limitaciones: una señal está vinculada al precio de cierre de la barra, el saldo se calcula para el lote constante, no hay opciones para trading usando órdenes pendientes. No obstante, todas estas limitaciones se pueden arreglar o mejorar fácilmente.


Implementación

Desarrollemos un indicador de señal simple para entender cómo funciona y evaluar la conveniencia del método. Hace mucho que oí hablar de los patrones de velas. ¿De modo que por qué no ver cómo funcionan en la práctica? He seleccionado los patrones inversos "hammer" y "shooting star" como señales de compra y venta respectivamente. Las imágenes de abajo muestran sus aspectos esquemáticos:

Figura 1. Patrones de velas "Hammer" y "shooting star".

Figura 1. Patrones de velas "Hammer" y "shooting star".

Ahora definamos las reglas de entrada al mercado cuando aparece el patrón "hammer".

  1. El valor mínimo de la vela debe ser menor que los valores de las cinco velas anteriores;
  2. El cuerpo de la vela no debe superar el 50% de su altura total;
  3. La sombra superior de la vela no debe superar el 0% de su altura total;
  4. La altura de la vela no debe ser menor del 100% de la altura media de las cinco velas anteriores;
  5. El precio de cierre del patrón debe ser menor que la Media Móvil de 10 períodos.

Si estas condiciones se cumplen, debemos abrir una posición larga. Las mismas reglas se aplican al patrón "shooting star". La única diferencia es que, en este caso, debemos abrir una posición corta:

  1. El valor máximo de la vela debe ser mayor que los valores de las cinco velas anteriores;
  2. El cuerpo de la vela no debe superar el 50% de su altura total;
  3. La sombra inferior de la vela no debe superar el 0% de su altura total;
  4. La altura de la vela no debe ser menor del 100% de la altura media de las cinco velas anteriores;
  5. El precio de cierre del patrón debe ser mayor que la Media Móvil de 10 períodos.

Usé negrita para los parámetros que basé en dibujos que se pueden optimizar en el futuro (si el patrón muestra resultados aceptables). Las limitaciones que quiero implementar nos permiten filtrar los patrones que tienen una apariencia inapropiada (pp. 1-3), así como los que sabemos que son débiles y no pueden aceptarse como señales.

Además, deberíamos determinar los momentos de salida. Puesto que los patrones mencionados aparecen como señales de inversión de tendencia, la tendencia existe en el momento en que aparece la vela inapropiada. Por tanto, la media móvil que persigue el precio también estará presente. La señal de salida se forma con el cruce del precio y su media móvil de 10 períodos.

Ahora es el momento de hacer algo de programación. Desarrollemos un nuevo indicador personalizado en MQL5 Wizard, llamémoslo PivotCandles y describamos su comportamiento. Definamos los valores devueltos para conectar el indicador de saldo:

  • -1 – abrir una posición de venta;
  • -2 – cerrar una posición de compra;
  • 0 – sin señal;
  • 1 – abrir una posición de compra;
  • 2 – cerrar una posición de venta.

Como ya sabe, los programadores genuinos no buscan maneras fáciles. Buscan las más fáciles. :) Yo no soy una excepción. Mientras escuchaba música por los auriculares y me tomaba un aromático café, creé el archivo con la clase para implementar en un indicador y en un Asesor Experto (en caso de que decidiera desarrollarlo basado en el indicador). Quizás incluso se pueda modificar para otros patrones de velas. El código no contiene nada especialmente nuevo. Creo que los comentarios implementados en el código pueden responder a todas las posibles preguntas.

//+------------------------------------------------------------------+
//|                                            PivotCandlesClass.mqh |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
//+------------------------------------------------------------------+
//| Input parameters                                                 |
//+------------------------------------------------------------------+
input int      iMaxBodySize            = 50;  // Maximum candle body, %
input int      iMaxShadowSize          = 0;   // Maximum allowed candle shadow, %
input int      iVolatilityCandlesCount = 5;   // Number of previous bars for calculation of an average volatility
input int      iPrevCandlesCount       = 5;   // Number of previous bars, for which the current bar should be an extremum
input int      iVolatilityPercent      = 100; // Correlation of a signal candle with a previous volatility, %
input int      iMAPeriod               = 10;  // Period of a simple signal moving average
//+------------------------------------------------------------------+
//| Class definition                                                 |
//+------------------------------------------------------------------+
class CPivotCandlesClass
  {
private:
   MqlRates          m_candles[];              // Array for storing the history necessary for calculations
   int               m_history_depth;          // Array length for storing the history
   int               m_handled_candles_count;  // Number of the already processed candles
   
   double            m_ma_value;               // Current calculated moving average value
   double            m_prev_ma_value;          // Previous calculated moving average value
   bool              m_is_highest;             // Check if the current candle is the highest one
   bool              m_is_lowest;              // Check if the current candle is the lowest one
   double            m_volatility;             // Average volatility
   int               m_candle_pattern;         // Current recognized pattern
   
   void              PrepareArrayForNewCandle();        // Prepare the array for accepting the new candle
   int               CheckCandleSize(MqlRates &candle); // Check the candle for conformity with patterns
   void              PrepareCalculation();
protected:
   int               DoAnalizeNewCandle();              // Calculation function
public:
   void              CPivotCandlesClass(); 
   
   void              CleanupHistory();                  // Clean up all calculation variables  
   double            MAValue() {return m_ma_value;}     // Current value of the moving average
   int               AnalizeNewCandle(MqlRates& candle);
   int               AnalizeNewCandle( const datetime time,
                                       const double open,
                                       const double high,
                                       const double low,
                                       const double close,
                                       const long tick_volume,
                                       const long volume,
                                       const int spread );
  };
//+------------------------------------------------------------------+
//| CPivotCandlesClass                                               |
//+------------------------------------------------------------------+
//| Class initialization                                             |
//+------------------------------------------------------------------+ 
void CPivotCandlesClass::CPivotCandlesClass()
  {
   // History depth should be enough for all calculations
   m_history_depth = (int)MathMax(MathMax(
      iVolatilityCandlesCount + 1, iPrevCandlesCount + 1), iMAPeriod);
   m_handled_candles_count = 0;
   m_prev_ma_value = 0;
   m_ma_value = 0;
   
   ArrayResize(m_candles, m_history_depth);
  }  
//+------------------------------------------------------------------+
//| CleanupHistory                                                   |
//+------------------------------------------------------------------+
//| Clean up the candle buffer for recalculation                     |
//+------------------------------------------------------------------+
void CPivotCandlesClass::CleanupHistory()
  {
   // Clean up the array
   ArrayFree(m_candles);
   ArrayResize(m_candles, m_history_depth);
   
   // Null calculation variables
   m_handled_candles_count = 0;
   m_prev_ma_value = 0;
   m_ma_value = 0;   
  }
//+-------------------------------------------------------------------+
//| AnalizeNewCandle                                                  |
//+-------------------------------------------------------------------+
//| Preparations for analyzing the new candle and the analysis itself |
//| based on candle's separate parameter values                       |
//+-------------------------------------------------------------------+
int CPivotCandlesClass::AnalizeNewCandle( const datetime time,
                                          const double open,
                                          const double high,
                                          const double low,
                                          const double close,
                                          const long tick_volume,
                                          const long volume,
                                          const int spread )
  {
   // Prepare the array for the new candle
   PrepareArrayForNewCandle();

   // Fill out the current value of the candle
   m_candles[0].time          = time;
   m_candles[0].open          = open;
   m_candles[0].high          = high;
   m_candles[0].low           = low;
   m_candles[0].close         = close;
   m_candles[0].tick_volume   = tick_volume;
   m_candles[0].real_volume   = volume;
   m_candles[0].spread        = spread;

   // Check if there is enough data for calculation
   if (m_handled_candles_count < m_history_depth)
      return 0;
   else
      return DoAnalizeNewCandle();
  }  
//+-------------------------------------------------------------------+
//| AnalizeNewCandle                                                  |
//+-------------------------------------------------------------------+
//| Preparations for analyzing the new candle and the analysis itself |
//| based on the received candle                                      |
//+-------------------------------------------------------------------+
int CPivotCandlesClass::AnalizeNewCandle(MqlRates& candle)
  {
   // Prepare the array for the new candle   
   PrepareArrayForNewCandle();

   // Add the candle 
   m_candles[0] = candle;

   // Check if there is enough data for calculation
   if (m_handled_candles_count < m_history_depth)
      return 0;
   else
      return DoAnalizeNewCandle();
  }
//+------------------------------------------------------------------+
//| PrepareArrayForNewCandle                                         |
//+------------------------------------------------------------------+ 
//| Prepare the array for the new candle                             |
//+------------------------------------------------------------------+ 
void CPivotCandlesClass::PrepareArrayForNewCandle()
  {
   // Shift the array by one position to write the new value there
   ArrayCopy(m_candles, m_candles, 1, 0, m_history_depth-1);
   
   // Increase the counter of added candles
   m_handled_candles_count++;
  }
//+------------------------------------------------------------------+
//| CalcMAValue                                                      |
//+------------------------------------------------------------------+ 
//| Calculate the current values of the Moving Average, volatility   |
//|   and the value extremality                                      |
//+------------------------------------------------------------------+ 
void CPivotCandlesClass::PrepareCalculation()
  {
   // Store the previous value
   m_prev_ma_value = m_ma_value;
   m_ma_value = 0;
   
   m_is_highest = true; 	// check if the current candle is the highest one
   m_is_lowest = true;  	// check if the current candle is the lowest one
   m_volatility = 0;  	// average volatility
   
   double price_sum = 0; // Variable for storing the sum
   for (int i=0; i<m_history_depth; i++)
     {
      if (i<iMAPeriod)
         price_sum += m_candles[i].close;
      if (i>0 && i<=iVolatilityCandlesCount)
         m_volatility += m_candles[i].high - m_candles[i].low;
      if (i>0 && i<=iPrevCandlesCount)
        {
         m_is_highest = m_is_highest && (m_candles[0].high > m_candles[i].high);
         m_is_lowest = m_is_lowest && (m_candles[0].low < m_candles[i].low);
        }
     }
   m_ma_value = price_sum / iMAPeriod;
   m_volatility /= iVolatilityCandlesCount;
   
   m_candle_pattern = CheckCandleSize(m_candles[0]);
  }
//+------------------------------------------------------------------+
//| CheckCandleSize                                                  |
//+------------------------------------------------------------------+
//| Check if the candle sizes comply with the patterns               |
//| The function returns:                                            |
//|   0 - if the candle does not comply with the patterns            |
//|   1 - if "hammer" pattern is detected                            |
//|   -1 - if "shooting star" pattern is detected                    |
//+------------------------------------------------------------------+ 
int CPivotCandlesClass::CheckCandleSize(MqlRates &candle)
  {
   double candle_height=candle.high-candle.low;          // candle's full height
   double candle_body=MathAbs(candle.close-candle.open); // candle's body height

   // Check if the candle has a small body
   if(candle_body/candle_height*100.0>iMaxBodySize)
      return 0;

   double candle_top_shadow=candle.high-MathMax(candle.open,candle.close);   // candle upper shadow height
   double candle_bottom_shadow=MathMin(candle.open,candle.close)-candle.low; // candle bottom shadow height

   // If the upper shadow is very small, that indicates the "hammer" pattern
   if(candle_top_shadow/candle_height*100.0<=iMaxShadowSize)
      return 1;
   // If the bottom shadow is very small, that indicates the "shooting star" pattern
   else if(candle_bottom_shadow/candle_height*100.0<=iMaxShadowSize)
      return -1;
   else
      return 0;
  }
//+------------------------------------------------------------------+
//| DoAnalizeNewCandle                                               |
//+------------------------------------------------------------------+
//| Real analysis of compliance with the patterns                    |
//+------------------------------------------------------------------+ 
int CPivotCandlesClass::DoAnalizeNewCandle()
  {
   // Prepare data for analyzing the current situation
   PrepareCalculation();
   
   // Process prepared data and set the exit signal
   int signal = 0;
   
   ///////////////////////////////////////////////////////////////////
   // EXIT SIGNALS                                                  //
   ///////////////////////////////////////////////////////////////////
   // If price crosses the moving average downwards, short position is closed
   if(m_candles[1].close > m_prev_ma_value && m_candles[0].close < m_ma_value)
      signal = 2;
   // If price crosses the moving average upwards, long position is closed 
   else if (m_candles[1].close < m_prev_ma_value && m_candles[0].close > m_ma_value)
      signal = -2;
      
   ///////////////////////////////////////////////////////////////////
   // ENTRY SIGNALS                                                 //
   ///////////////////////////////////////////////////////////////////
   // Check if the minimum volatility condition is met
   if (m_candles[0].high - m_candles[0].low >= iVolatilityPercent / 100.0 * m_volatility)
     {
      // Checks for "shooting star" pattern
      if (m_candle_pattern < 0 && m_is_highest && m_candles[0].close > m_ma_value)
         signal = -1;
      // Checks for "hammer" pattern
      else if (m_candle_pattern > 0 && m_is_lowest && m_candles[0].close < m_ma_value)
         signal = 1;
     }
     
   return signal;
  }
//+------------------------------------------------------------------+

Podemos ver que la parte entera del cálculo se lleva a cabo por la clase CPivotCandlesClass. Se considera una buena práctica de programación el separar la parte del cálculo de la visual, y yo trato de seguir esta recomendación en la medida de lo posible. Los beneficios no tardan en notarse. Abajo puede ver el código del indicador mismo:

//+------------------------------------------------------------------+
//|                                                 PivotCandles.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property indicator_chart_window

// Use four buffers, while drawing two
#property indicator_buffers 4
#property indicator_plots   2
//--- plot SlowMA
#property indicator_label1  "SlowMA"
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrAliceBlue
#property indicator_style1  STYLE_SOLID
#property indicator_width1  1
//--- plot ChartSignal
#property indicator_label2  "ChartSignal"
#property indicator_type2   DRAW_COLOR_ARROW
#property indicator_color2  clrLightSalmon,clrOrangeRed,clrBlack,clrSteelBlue,clrLightBlue
#property indicator_style2  STYLE_SOLID
#property indicator_width2  3

#include <PivotCandlesClass.mqh>
//+------------------------------------------------------------------+
//| Common arrays and structures                                     |
//+------------------------------------------------------------------+
//--- Indicator buffers                                                
double   SMA[];            // Values of the Moving Average
double   Signal[];         // Signal values
double   ChartSignal[];    // Location of signals on the chart
double   SignalColor[];    // Signal color array
//--- Calculation class
CPivotCandlesClass PivotCandlesClass;
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   SetIndexBuffer(0,SMA,INDICATOR_DATA);
   SetIndexBuffer(1,ChartSignal,INDICATOR_DATA);
   SetIndexBuffer(2,SignalColor,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(3,Signal,INDICATOR_CALCULATIONS);

//--- set 0 as an empty value
   PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,0);

   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
   // If there have not been calculations yet or (!) the new history is uploaded, clean up the calculation object
   if (prev_calculated == 0)
      PivotCandlesClass.CleanupHistory();
   
   int end_calc_edge = rates_total-1;   
   if (prev_calculated >= end_calc_edge)
      return end_calc_edge;
   
   for(int i=prev_calculated; i<end_calc_edge; i++)
     {
      int signal = PivotCandlesClass.AnalizeNewCandle(time[i],open[i],high[i],low[i],close[i],tick_volume[i],volume[i],spread[i]);
      Signal[i] = signal;
      SMA[i] = PivotCandlesClass.MAValue();
      
      // Signals are processed, display them on the chart
      // Set the location of our signals...
      if (signal < 0)
         ChartSignal[i]=high[i];
      else if (signal > 0)
         ChartSignal[i]=low[i];
      else
         ChartSignal[i]=0;
      // .. as well as their color
      // Signals have a range of [-2..2], while color indices - [0..4]. Align them 
      SignalColor[i]=signal+2;
     }
   
   // Set the Moving Average value similar to the previous one to prevent it from sharp fall
   SMA[end_calc_edge] = SMA[end_calc_edge-1];

//--- return value of prev_calculated for next call
   return(end_calc_edge);
  }
//+------------------------------------------------------------------+

El indicador está listo. Ahora pongámoslo a prueba en cualquiera de los gráficos. Para ello, instale el indicador compilado en el gráfico. Después, veremos algo parecido a lo que se muestra en la imagen de abajo.

Figura 2. Indicador de los patrones de velas "hammer" y "shooting star".

Figura 2. Indicador de los patrones de velas "hammer" y "shooting star".

Los puntos de colores indican las posibles entradas y salidas del mercado. Los colores se seleccionan de la siguiente manera:

  • rojo oscuro – venta;
  • azul oscuro – compra;
  • rojo claro – cierre de posición larga;
  • azul claro – cierre de posición corta.

Las señales de cierre se forman cada vez que el precio llega a su media móvil. La señal se ignora si no hay posiciones en ese momento.

Ahora pasemos al tema principal del artículo. Tenemos el indicador con el buffer de señal generando solo ciertas señales. Mostremos en una ventana separada del mismo gráfico lo rentable o no rentable que estas señales pueden resultar si se siguen de verdad. El indicador se ha desarrollado especialmente para ese caso. Puede conectarse a otro indicador y abrir/cerrar posiciones virtuales dependiendo de las señales que entran.

Al igual que en el indicador anterior, debemos dividir el código en dos partes: la parte del cálculo y la visual. Abajo puede ver el resultado de una noche de insomnio, pero espero que merezca la pena. :)

//+------------------------------------------------------------------+
//|                                                 BalanceClass.mqh |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
//+------------------------------------------------------------------+
//| Common structures                                                |
//+------------------------------------------------------------------+
// Structure for returning calculation results 
// using only return command;
struct BalanceResults
  {
   double balance;
   double equity;
  };
//+------------------------------------------------------------------+
//| Common function                                                  |
//+------------------------------------------------------------------+
//  Function for searching for the indicator handle by its name
int FindIndicatorHandle(string _name)
  {
   // Receive the number of open charts
   int windowsCount = (int)ChartGetInteger(0,CHART_WINDOWS_TOTAL);
   
   // Search all of them
   for(int w=windowsCount-1; w>=0; w--)
     {
      // How many indicators are attached to the current chart
      int indicatorsCount = ChartIndicatorsTotal(0,w);

      // Search by all chart indicators
      for(int i=0;i<indicatorsCount;i++)
        {
         string name = ChartIndicatorName(0,w,i);
         // If such an indicator is found, return its handle
         if (name == _name)
            return ChartIndicatorGet(0,w,name);
        }
     }  
     
   // If there is no such an indicator, return the incorrect handle 
   return -1;
  }
//+------------------------------------------------------------------+
//| Base calculation class                                           |
//+------------------------------------------------------------------+
class CBaseBalanceCalculator
  {
private:
   double            m_position_volume; // Current open position volume
   double            m_position_price;  // Position opening price
   double            m_symbol_points;   // Value of one point for the current symbol
   BalanceResults    m_results;         // Calculation results
public:
   void              CBaseBalanceCalculator(string symbol_name = "");
   void              Cleanup();
   BalanceResults    Calculate( const double _prev_balance, 
                                const int    _signal,
                                const double _next_open,
                                const double _next_spread );
  };
//+------------------------------------------------------------------+
//| CBaseBalanceCalculator                                           |
//+------------------------------------------------------------------+
void CBaseBalanceCalculator::CBaseBalanceCalculator(string symbol_name = "")
  {
   // Clean up state variables
   Cleanup();
   
   // Define point size (because we will calculate the profit in points)
   if (symbol_name == "")
      m_symbol_points = SymbolInfoDouble(Symbol(), SYMBOL_POINT);
   else 
      m_symbol_points = SymbolInfoDouble(symbol_name, SYMBOL_POINT);
  }
//+------------------------------------------------------------------+
//| Cleanup                                                          |
//+------------------------------------------------------------------+
//| Clean up data on positions and prices                            |
//+------------------------------------------------------------------+
void CBaseBalanceCalculator::Cleanup()
  {
   m_position_volume = 0;
   m_position_price = 0;  
  }
//+------------------------------------------------------------------+
//| Calculate                                                        |
//+------------------------------------------------------------------+
//| Main calculation block                                           |
//+------------------------------------------------------------------+
BalanceResults CBaseBalanceCalculator::Calculate(
                                       const double _prev_balance,
                                       const int _signal,
                                       const double _next_open,
                                       const double _next_spread )
  {
   // Clean up the output structure from the previous values
   ZeroMemory(m_results);
   
   // Initialize additional variables
   double current_price = 0; // current price (bid or ask depending on position direction)
   double profit = 0;        // profit calculated value
   
   // If there was no signal, the balance remains the same 
   if (_signal == 0)
      m_results.balance = _prev_balance;
   // the signal coincides with the direction or no positions are opened yet
   else if (_signal * m_position_volume >= 0)
     {
      // Position already exists, the signal is ignored
      if (m_position_volume != 0)
         // Balance is not changed
         m_results.balance = _prev_balance;
      // No positions yet, buy signal

      else if (_signal == 1)
        {
         // Calculate current ASK price, recalculate price, volume and balance
         current_price = _next_open + _next_spread * m_symbol_points;
         m_position_price = (m_position_volume * m_position_price + current_price) / (m_position_volume + 1);
         m_position_volume = m_position_volume + 1;
         m_results.balance = _prev_balance;
        }
      // No positions yet, sell signal
      else if (_signal == -1) 
        {
         // Calculate current BID price, recalculate price, volume and balance
         current_price = _next_open;
         m_position_price = (-m_position_volume * m_position_price + current_price) / (-m_position_volume + 1);
         m_position_volume = m_position_volume - 1; 
         m_results.balance = _prev_balance;      
        }
      else
         m_results.balance = _prev_balance;
     }
   // Position is set already, the opposite direction signal is received
   else 
     {
      // buy signal/close sell position
      if (_signal > 0)
        {
         // Close position by ASK price, recalculate profit and balance
         current_price = _next_open + _next_spread * m_symbol_points;
         profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
         m_results.balance = _prev_balance + profit;
          
         // If there is a signal for opening a new position, open it at once
         if (_signal == 1)
           {
            m_position_price = current_price;
            m_position_volume = 1;
           }
         else
            m_position_volume = 0;
        }
      // sell signal/close buy position
      else 
        {
         // Close position by BID price, recalculate profit and balance
         current_price = _next_open;
         profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
         m_results.balance = _prev_balance + profit;
         
         // If there is a signal for opening a new position, open it at once
         if (_signal == -1)
           {
            m_position_price = current_price;
            m_position_volume = -1;
           }
         else 
           m_position_volume = 0;
        }
     }
    
   // Calculate the current equity
   if (m_position_volume > 0)
     {
      current_price = _next_open;
      profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
      m_results.equity = m_results.balance + profit;
     }
   else if (m_position_volume < 0)
     {
      current_price = _next_open + _next_spread * m_symbol_points;
      profit = (current_price - m_position_price) / m_symbol_points * m_position_volume;
      m_results.equity = m_results.balance + profit;
     }
   else
      m_results.equity = m_results.balance;    
   
   return m_results;
  }
//+------------------------------------------------------------------+

La clase de cálculo está lista. Ahora debemos implementar la visualización del indicador para ver cómo funciona.

//+------------------------------------------------------------------+
//|                                                      Balance.mq5 |
//|                        Copyright 2012, MetaQuotes Software Corp. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2012, MetaQuotes Software Corp."
#property link      "http://www.mql5.com"
#property version   "1.00"
#property indicator_separate_window

#property indicator_buffers 4
#property indicator_plots   3
#property indicator_level1  0.0 
#property indicator_levelcolor Silver 
#property indicator_levelstyle STYLE_DOT
#property indicator_levelwidth 1  
//--- plot Balance
#property indicator_label1  "Balance"
#property indicator_type1   DRAW_COLOR_HISTOGRAM
#property indicator_color1  clrBlue,clrRed
#property indicator_style1  STYLE_DOT
#property indicator_width1  1
//--- plot Equity
#property indicator_label2  "Equity"
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrLime
#property indicator_style2  STYLE_SOLID
#property indicator_width2  1
//--- plot Zero
#property indicator_label3  "Zero"
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrGray
#property indicator_style3  STYLE_DOT
#property indicator_width3  1

#include <BalanceClass.mqh>
//+------------------------------------------------------------------+
//| Input and global variables                                       |
//+------------------------------------------------------------------+
input string   iParentName        = "";             // Indicator name for balance calculation
input int      iSignalBufferIndex = -1;            // Signal buffer's index number
input datetime iStartTime         = D'01.01.2012';  // Calculation start date
input datetime iEndTime           = 0;             // Calculation end date
//--- Indicator buffers 
double   Balance[];       // Balance values
double   BalanceColor[];  // Color index for drawing the balance
double   Equity[];        // Equity values
double   Zero[];          // Zero value for histogram's correct display
//--- Global variables
double   Signal[1];       // Array for receiving the current signal
int      parent_handle;   // Indicator handle, the signals of which are to be used 

CBaseBalanceCalculator calculator; // Object for calculating balance and equity
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {     
   // Binding indicator buffers
   SetIndexBuffer(0,Balance,INDICATOR_DATA);
   SetIndexBuffer(1,BalanceColor,INDICATOR_COLOR_INDEX);
   SetIndexBuffer(2,Equity,INDICATOR_DATA);
   SetIndexBuffer(3,Zero,INDICATOR_DATA);
  
   // Search for indicator handle by its name
   parent_handle = FindIndicatorHandle(iParentName);
   if (parent_handle < 0)
     {
      Print("Error! Parent indicator not found");
      return -1;
     } 
   
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {   
   // Set the borders for calculating the indicator
   int start_index = prev_calculated;
   int end_index = rates_total-1;
   
   // Calculate balance and equity values
   for(int i=start_index; i<end_index; i++)
     {
      // Check if the balance calculation corresponds the interval
      if (time[i] < iStartTime)
        {
         Balance[i] = 0;
         Equity[i] = 0; 
         continue;
        }
      if (time[i] > iEndTime && iEndTime != 0)
        {
         Equity[i] = (i==0) ? 0 : Equity[i-1];
         Balance[i] = Equity[i]; 
         continue;
        }
      
      // Request a signal from the parent indicator
      if(CopyBuffer(parent_handle,iSignalBufferIndex,time[i],1,Signal)==-1) // Copy the indicator main line data
        {
         Print("Data copy error: " + IntegerToString(GetLastError()));
         return(0);  // Finish the function operation and send indicator for the full recalculation
        }
      
      // Initialize balance and equity calculation
      // Since the signal is formed when the candle is closing, we will be able 
      //   to perform any operation only at the next candle's opening price
      BalanceResults results = calculator.Calculate(i==0?0:Balance[i-1], (int)Signal[0], open[i+1], spread[1+1]);
      
      // Fill out all indicator buffers
      Balance[i] = results.balance;
      Equity[i] = results.equity; 
      Zero[i] = 0;
      if (Balance[i] >= 0)
         BalanceColor[i] = 0;
      else
         BalanceColor[i] = 1;
     }
     
   // Fill out buffers for the last candle 
   Balance[end_index] = Balance[end_index-1];
   Equity[end_index] = Equity[end_index-1]; 
   BalanceColor[end_index] = BalanceColor[end_index-1];
   Zero[end_index] = 0;
     
   return rates_total;
  }
//+------------------------------------------------------------------+

¡Lo conseguimos! Compilémoslo y examinemos los resultados.


Instrucciones para su uso

Para evaluar la operación de nuestro indicador recién creado, debe adjuntarse a un gráfico que contenga al menos un indicador de señal. Si siguió correctamente todos los pasos, ya tendrá un indicador así: PivotCandles. De modo que debemos configurar los parámetros de entrada. Veamos qué tenemos que especificar:

  • Indicator name for balance calculation (Nombre del indicador para el cálculo de saldo) (cadena de caracteres) – debemos tener en cuenta que la vinculación del indicador de saldo se lleva a cabo según su nombre. Por tanto, este campo es obligatorio.
  • Signal buffer's index number (Número de índice del buffer de señal) (íntegro) – otro parámetro crítico. El indicador de señal puede generar varias señales según el algoritmo anteriormente definido. Por tanto, el indicador de saldo debe tener los datos referentes a la señal del buffer que debe calcular.
  • Calculation start date (Fecha de comienzo de cálculo) (fecha/hora) – fecha inicial del cálculo de saldo.
  • Calculation end date (Fecha de final de cálculo) (fecha/hora) – fecha final del cálculo de saldo. Si no se selecciona la fecha (es igual a cero), el cálculo se llevará a cabo desde la última barra.

La Figura 3 muestra la configuración de los dos primeros parámetros para adjuntar el indicador de saldo al tercer buffer del indicador PivotCandles. Los dos parámetros restantes se pueden configurar a su gusto.

Figura 3. Parámetros del indicador de saldo.

Figura 3. Parámetros del indicador de saldo.

Si siguió correctamente todos los pasos anteriores, debería ver una imagen muy similar a la que se muestra abajo.

Figura 4. Curvas de saldo y beneficio generadas usando las señales del indicador PivotCandles.

Figura 4. Curvas de saldo y beneficio generadas usando las señales del indicador PivotCandles.

Ahora podemos probar diferentes intervalos cronológicos y símbolos y descubrir las entradas más y menos rentables al mercado. Debo añadir que este enfoque ayuda a encontrar las correlaciones de mercado que afectan a sus resultados de trading.

Originalmente quería comparar el tiempo invertido en la simulación del Asesor Experto en las mismas señales con el tiempo invertido en el uso del método descrito arriba. Pero después abandoné la idea, puesto que el recálculo del indicador dura un segundo. Un tiempo tan corto desde luego no se puede conseguir de momento con el Asesor Experto, con su carga del historial y los algoritmos de generación de ticks.


Conclusión

El método descrito arriba es muy rápido. Además, facilita claridad en la simulación de indicadores que generan señales de apertura y cierre de posiciones. Permite a los traders analizar las señales y las respuestas del depósito a ellas en una misma ventana de gráfico. Pero todavía tiene algunas limitaciones que debemos tener en cuenta:

  • el buffer de señal de indicador analizado se debe preparar de forma preliminar;
  • las señales están vinculadas al momento de apertura de la nueva barra;
  • no hay MM al calcular el saldo;

No obstante, a pesar de ello, espero que los beneficios sean significativos, y que este método de simulación se posicione con las demás herramientas diseñadas para analizar el comportamiento del mercado y procesar las señales generadas por el mercado.

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/505

Archivos adjuntos |
balanceclass.mqh (8.07 KB)
balance.mq5 (5.57 KB)
pivotcandles.mq5 (4.14 KB)
Señales de Trading en MetaTrader 5: ¡Una Alternativa Mejor a las Cuentas PAMM! Señales de Trading en MetaTrader 5: ¡Una Alternativa Mejor a las Cuentas PAMM!
Nos complace anunciar que MetaTrader 5 ahora cuenta con Trading Signals (Señales de Trading), una poderosa herramienta para inversores y gestores. Mientras sigue las operaciones de un trader con éxito, ¡el terminal las reproducirá automáticamente en su cuenta!
Comunicándonos con Meta Trader 5 usando conexiones designadas sin utilizar DLL Comunicándonos con Meta Trader 5 usando conexiones designadas sin utilizar DLL
Muchos desarrolladores se enfrentan con el mismo problema: cómo llegar al módulo del terminal sin utilizar DLL poco seguras. Una de los métodos más sencillos y seguros es utilizar conexiones designadas que funcionan como operaciones de archivo normales. Estos nos permiten organizar la comunicación cliente-servidor entre procesadores entre los programas. Eche un vistazo a los ejemplos prácticos en C++ y MQL5 que incluyen el intercambio de datos entre cliente y servidor y la prueba de rendimiento.
Cambiar los Parámetros del Asesor Experto desde el Panel de Usuario "Sobre la Marcha" Cambiar los Parámetros del Asesor Experto desde el Panel de Usuario "Sobre la Marcha"
Este artículo facilita un pequeño ejemplo que demuestra la implementación de un Asesor Experto cuyos parámetros se pueden controlar desde el panel de usuario. Al cambiar los parámetros "sobre la marcha", el Asesor Experto escribe los valores obtenidos del panel de información a un archivo para seguir leyéndolos desde el archivo y mostrarlos de la forma correspondiente en el panel. Este artículo podría ser relevante para aquellos que realizan operaciones de trading manualmente o en modo semi-automático.
Redes neuronales: de la teoría a la práctica Redes neuronales: de la teoría a la práctica
Hoy en día, cualquier operador ha oído hablar de las redes neuronales y conoce las ventajas de su utilización. La mayoría de ellos creen que quien puede trabajar con redes neuronales es una especie de superman. En este artículo intentaré explicarle la arquitectura de la red neuronal, describir sus aplicaciones y dar ejemplos de su uso práctico.