English Русский 中文 Deutsch 日本語 Português
La estrategia comercial 'Momentum Pinball'

La estrategia comercial 'Momentum Pinball'

MetaTrader 5Ejemplos | 8 enero 2018, 09:10
1 726 0
Alexander Puzanov
Alexander Puzanov

Introducción

En este artículo ofrecemos la programación de las estrategias de negociación descritas en el libro de Linda Raschke y Laurence Connors Street Smarts: High Probability Short-Term Trading Strategies, dedicado a la simulación del precio de los límites del rango. El último de los sistemas comerciales completos en el apartado es 'Momentum Pinball' que explota un patrón que consta de dos barras de día. De acuerdo con la primera barra, se define la dirección del comercio en el segundo día, mientras que el movimiento del precio en el comienzo de la segunda barra debe indicar los niveles comerciales específicos para las entradas y salidas del mercado.

El objetivo de este artíclo es mostrar a los programadores que ya han asimilado el lenguaje MQL5 una de las variantes de implementación del sistema comercial 'Momentum Pinball', en el que se usarán métodos simplificados de programación orientada a objetos. El código se diferenciará de la POO completa por la ausencia de clases, estas serán sustituidas por estructuras. A diferencia de las clases, la formalización en el código y el uso de los objetos de este tipo apenas se distinguen de la acostumbrada mayoría de programadores principiantes de programación por procedimientos. Por otro lado, las oportunidades ofrecidas por las estructuras son más que suficientes para resolver los problemas de este tipo.

Al igual que en el artículo anterior, primero crearemos el módulo de la unidad de señales, a continuación, un indicador para comerciar manualmente y marcar la historia, que utilizará este módulo. El tercer programa será un asesor comercial automatizado, que también usará el módulo de señales. Por último, probaremos el asesor con cotizaciones recientes, porque los autores del libro han trabajado con cotizaciones de 20 años de antigüedad.


Reglas del sistema comercial 'Momentum Pinball'

Linda Raschke y Laurence Connors se encontraron con ciertas indeterminaciones al usar la tecnología comercial explicada por George Taylor, lo cual los impulsó a crear las reglas de esta estrategia comercial. La estrategia de Taylor antes del comienzo del día siguiente define la dirección del comercio: si nos encontramos ante un día de ventas o de compras. Sin embargo, el comercio práctico propuesto por el maestro infringe dicho estado, lo cual, en opinión del autor, ha enredado las reglas comerciales.

Para definir con mayor precisión la dirección del comercio del día siguiente, los autores usaron el indicador ROC (Rate Of Change — Índice de cambio de precio). A sus indicaciones se aplicó el oscilador RSI (Relative Strenght Index — Índice de fuerza relativa), con lo que se hizo perfectamente visible el carácter cíclico de las indicaciones de ROC. En conclusión, los autores añadieron niveles de señal al sistema comercial, zonas fronterizas de sobrecompra y sobreventa en el RSI. Se considera que es precisamente el hallazgo de la línea de ese indicador (llamado LBR/RSI, de Linda Bradford Raschke) en concordancia con la zona, el mecanismo que está llamado a detectar los días más probables de venta o compra. Posteriormente analizaremos con más detalle LBR/RSI.

Las reglas completas del sistema comercial Momentum Pinball para las entradas y las salidas se formulan de la forma siguiente.

  1. En el marco temporal D1, el valor de los indicadores LBR/RSI del último día cerrado debe encontrarse en la zona de sobreventa, por debajo de 30.
  2. Después de cerrar la primera barra de hora del nuevo día, coloque una orden pendiente de compra por encima del máximo de la barra.
  3. Después de que se active la orden pendiente, coloque el Stop Loss de la posición en el mínimo de la primera barra de hora.
  4. Si la posición se cierra con pérdidas, coloque de nuevo una orden pendiente de venta en el nivel anterior.
  5. Al final del día, la posición tendrá beneficios, así que déjela hasta el día siguiente. El segundo día, la posición deberá ser necesariamente cerrada.

La visualización de las reglas de entrada con la ayuda de los dos indicadores descritos más arriba, tendrá el aspecto siguiente:

— LBR/RSI en el marco temporal de un día se encuentra en la zona de sobreventa (ver 30 de octubre 2017)


— el indicador TS_Momentum_Pinball en un marco temporal aleatorio (de M1 a D1) representa los niveles comerciales y el rango de precios de la primera hora del día, sobre cuya base se calculan estos niveles:


Las reglas de salida del mercado no se definen claramente en el libro: según los autores, puede darse tanto con el uso del trailing, como con el cierre a la mañana siguiente, como con la salida por encima del máximo del primer día de cotización.

Las normas de entrada en la compra son análogas, LBR/RSI debe estar en la zona de sobrecompra (por encima de 70), deberemos establecer la orden pendiente en el mínimo de la primera barra de hora.



Indicador LBR/RSI

Por supuesto, todos los cálculos necesarios para obtener la señal se pueden llevar a cabo en el módulo de señales, pero, aparte del plan de comercio automático, en este artículo se ofrece el manual. Para facilitar la identificación visual del patrón de la versión manual, sería útil disponer de un indicador LBR/RSI independiente con iluminación de las zonas de sobrecompra/sobreventa. Para automatizar nuestros esfuerzos, no vamos a programar dos versiones separadas de cálculo de LBR/RSI (una 'con búfer' para el indicador y otra 'sin búfer' para el robot). Aprovecharemos la oportunidad de conectar un indicador externo al módulo de señales utilizando la función estándar iCustom. Este indicador no producirá cálculos con un gran gasto de recursos, tampoco es necesario sondearlo en cada tick: en el sistema comercial se usa el valor del indicador en la barra diaria cerrada, el valor actual, en constante cambio, no nos interesa en absoluto. Por lo tanto, no hay obstáculos significativos para tal solución.

En este indicador, combinaremos los algoritmos de cálculo de ROC y RSI, que dibujarán la curva resultante del oscilador, y para mayor comodidad a la hora de detectar los valores necesarios, añadiremos el rellenado de las zonas de sobrecompra y sobreventa con diferentes colores. Para todo ello, necesitaremos cinco búferes para la representación, y cuatro más para los cálculos auxiliares.

A los ajustes estándar (el periodo RSI y los valores de los límites de las dos zonas) añadiremos uno más, que no se encuentra presente en las normas originales del sistema comercial. Nos dará la posibilidad de utilizar para los cálculos no solo el precio de cierre de la barra de día, sino también el precio mediano, típico o ponderado, que es más informativo. Propiamente, el usuario podrá elegir para sus experimentos cualquiera de las siete variantes contempladas por el tipo ENUM_APPLIED_PRICE.

La declaración de los búferes, los campos de edición personalizados y el bloque de inicialización tendrán el aspecto que sigue:

#property indicator_separate_window
#property indicator_buffers  9
#property indicator_plots    3
#property indicator_label1  “Overbought area"
#property indicator_type1   DRAW_FILLING
#property indicator_color1  C'255,208,234'
#property indicator_width1  1
#property indicator_label2  “Oversold area"
#property indicator_type2   DRAW_FILLING
#property indicator_color2  C'179,217,255'
#property indicator_width2  1
#property indicator_label3  "RSI от ROC"
#property indicator_type3   DRAW_LINE
#property indicator_style3  STYLE_SOLID
#property indicator_color3  clrTeal
#property indicator_width3  2
#property indicator_minimum 0
#property indicator_maximum 100
input ENUM_APPLIED_PRICE  TS_MomPin_Applied_Price = PRICE_CLOSE;  // Price for ROC calculation
input uint    TS_MomPin_RSI_Period = 3;                           // RSI Period
input double  TS_MomPin_RSI_Overbought = 70;                      // RSI Oversold level
input double  TS_MomPin_RSI_Oversold = 30;                        // RSI overbought level
double
  buff_Overbought_High[], buff_Overbought_Low[],                  // overbought area background
  buff_Oversold_High[], buff_Oversold_Low[],                      // oversold area background
  buff_Price[],                                                   // array of bar calculated prices
  buff_ROC[],                                                     // ROC array from calculated prices
  buff_RSI[],                                                     // RSI from ROC
  buff_Positive[], buff_Negative[]                                // auxiliary arrays for RSI calculation
;
int OnInit() {
  // designation of indicator buffers:
  
  // overbought area
  SetIndexBuffer(0, buff_Overbought_High, INDICATOR_DATA);
    PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
    PlotIndexSetInteger(0, PLOT_SHOW_DATA, false);
  SetIndexBuffer(1, buff_Overbought_Low, INDICATOR_DATA);
  
  // oversold area
  SetIndexBuffer(2, buff_Oversold_High, INDICATOR_DATA);
    PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE);
    PlotIndexSetInteger(1, PLOT_SHOW_DATA, false);
  SetIndexBuffer(3, buff_Oversold_Low, INDICATOR_DATA);
  
  // RSI curve
  SetIndexBuffer(4, buff_RSI, INDICATOR_DATA);
    PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE);
  
  // auxiliary buffers for RSI calculation
  SetIndexBuffer(5, buff_Price, INDICATOR_CALCULATIONS);
  SetIndexBuffer(6, buff_ROC, INDICATOR_CALCULATIONS);
  SetIndexBuffer(7, buff_Negative, INDICATOR_CALCULATIONS);
  SetIndexBuffer(8, buff_Positive, INDICATOR_CALCULATIONS);
  
  IndicatorSetInteger(INDICATOR_DIGITS, 2);
  IndicatorSetString(INDICATOR_SHORTNAME, "LBR/RSI");
  
  return(INIT_SUCCEEDED);
  }

En el procesador estándar OnCalculate organizamos dos ciclos separados, el primero prepara la matriz ROC de datos, el segundo, calcula los valores del oscilador basándose en los datos de esta matriz.

En la versión de Rates Of Change propuesta por Linda Raschke, hay que comparar no los precios que van secuencialmente con las barras, sino los que omiten una barra entre ellos. Es decir, en el sistema comercial se usan los cambios de precio de los días que se retrasan con respecto al día comercial en uno o tres días, respectivamente. Hacer esto no es complicado, y además, en este mismo ciclo organizaremos el rellando del fondo de las zonas de sobrecompra y sobreventa. Además, no nos olvidaremos de implementar la posibilidad de elegir el tipo de precio:

int
  i_RSI_Period = int(TS_MomPin_RSI_Period),         // transfer of RSI period into int type
  i_Bar, i_Period_Bar                               // two bar indices for simultaneous application
;
double
  d_Sum_Negative, d_Sum_Positive,                   // auxiliary variables for RSI calculation
  d_Change                                          // auxiliary variable for ROC calculation
;
// Fill in ROC buffer and fill areas:
i_Period_Bar = 1;
while(++i_Period_Bar < rates_total && !IsStopped()) {
// calculated bar price:
  switch(TS_MomPin_Applied_Price) {
    case PRICE_CLOSE:     buff_Price[i_Period_Bar] = Close[i_Period_Bar]; break;
    case PRICE_OPEN:      buff_Price[i_Period_Bar] = Open[i_Period_Bar]; break;
    case PRICE_HIGH:      buff_Price[i_Period_Bar] = High[i_Period_Bar]; break;
    case PRICE_LOW:       buff_Price[i_Period_Bar] = Low[i_Period_Bar]; break;
    case PRICE_MEDIAN:    buff_Price[i_Period_Bar] = 0.50000 * (High[i_Period_Bar] + Low[i_Period_Bar]); break;
    case PRICE_TYPICAL:   buff_Price[i_Period_Bar] = 0.33333 * (High[i_Period_Bar] + Low[i_Period_Bar] + Open[i_Period_Bar]); break;
    case PRICE_WEIGHTED:  buff_Price[i_Period_Bar] = 0.25000 * (High[i_Period_Bar] + Low[i_Period_Bar] + Open[i_Period_Bar] + Open[i_Period_Bar]); break;
  }
  // difference of bar calculated prices (ROC value):
  if(i_Period_Bar > 1) buff_ROC[i_Period_Bar] = buff_Price[i_Period_Bar] - buff_Price[i_Period_Bar - 2];
  
  // background filling:
  buff_Overbought_High[i_Period_Bar] = 100;
  buff_Overbought_Low[i_Period_Bar] = TS_MomPin_RSI_Overbought;
  buff_Oversold_High[i_Period_Bar] = TS_MomPin_RSI_Oversold;
  buff_Oversold_Low[i_Period_Bar] = 0;
    }

El segundo ciclo (cálculo de RSI) no tiene ninguna particularidad, repite prácticamente al pie de la letra el algoritmo del oscilador estándar de este tipo:

i_Period_Bar = prev_calculated - 1;
if(i_Period_Bar <= i_RSI_Period) {
  buff_RSI[0] = buff_Positive[0] = buff_Negative[0] = d_Sum_Positive = d_Sum_Negative = 0;
  i_Bar = 0;
  while(i_Bar++ < i_RSI_Period) {
    buff_RSI[0] = buff_Positive[0] = buff_Negative[0] = 0;
    d_Change = buff_ROC[i_Bar] - buff_ROC[i_Bar - 1];
    d_Sum_Positive += (d_Change > 0 ? d_Change : 0);
    d_Sum_Negative += (d_Change < 0 ? -d_Change : 0);
  }
  buff_Positive[i_RSI_Period] = d_Sum_Positive / i_RSI_Period;
  buff_Negative[i_RSI_Period] = d_Sum_Negative / i_RSI_Period;
  
  if(buff_Negative[i_RSI_Period] != 0)
    buff_RSI[i_RSI_Period] = 100 - (100 / (1. + buff_Positive[i_RSI_Period] / buff_Negative[i_RSI_Period]));
  else
    buff_RSI[i_RSI_Period] = buff_Positive[i_RSI_Period] != 0 ? 100 : 50;
  
  i_Period_Bar = i_RSI_Period + 1;
}

i_Bar = i_Period_Bar - 1;
while(++i_Bar < rates_total && !IsStopped()) {
  d_Change = buff_ROC[i_Bar] - buff_ROC[i_Bar - 1];
  
  buff_Positive[i_Bar] = (buff_Positive[i_Bar - 1] * (i_RSI_Period - 1) + (d_Change> 0 ? d_Change : 0)) / i_RSI_Period;
  buff_Negative[i_Bar] = (buff_Negative[i_Bar - 1] * (i_RSI_Period - 1) + (d_Change <0 ? -d_Change : 0)) / i_RSI_Period;
  
  if(buff_Negative[i_Bar] != 0)
    buff_RSI[i_Bar] = 100 - 100. / (1. + buff_Positive[i_Bar] / buff_Negative[i_Bar]);
  else
    buff_RSI[i_Bar] = buff_Positive[i_Bar] != 0 ? 100 : 50;
}

Llamaremos al indicador LBR_RSI.mq5 y lo ubicaremos en la carpeta estándar de indicadores del catálogo de datos del terminal. Precisamente eso se escribirá en la función iCustom del módulo de señales, por ello, no merece la pena cambiarlo.

Módulo de señales

En el módulo de señales conectado al asesor y al indicador ubicamos los ajustes de usuario de la estrategia comerical "Momentum Pinball". Los autores fijan los valores para el cálculo del indicador LBR/RSI (periodo RSI = 3, nivel de sobrecompra = 30, nivel de sobreventa = 70). Pero para los experimentos, nosotros los haremos modificables, al igual que los métodos de cierre de posición: en el libro se mencionan tres variantes. Los programamos todos, y el usuario tendrá la posibilidad de elegir la opción necesaria:

  • cerrar la posición según el trailing del nivel Stop Loss;
  • cerrarla a la mañana del día siguiente;
  • esperar al segundo día la ruptura del extremo del día de apertura de posición.

El concepto de 'mañana' es bastante escurridizo, para formalizar las normas, hay que tener una definición más exacta. Raschke y Connors no dicen nada al respecto, pero es lógico suponer que la vinculación - usada en las otras reglas del sistema comercial - a la primera barra del nuevo día indicará la marca 'mañana' en la escala temporal del día.

No debemos olvidar otros dos ajustes del sistema comercial: los espacios con respecto a los límites de la primera hora del día, que deben definir los niveles de colocación de la orden pendiente y el nivel de StopLoss:

enum ENUM_EXIT_MODE {     // List of exit methods
  CLOSE_ON_SL_TRAIL,      // only by trailing
  CLOSE_ON_NEW_1ST_CLOSE, // by closing of the 1st bar of the following day
  CLOSE_ON_DAY_BREAK      // by break-through of extremum of the position opening day
};
// user settings
input ENUM_APPLIED_PRICE  TS_MomPin_Applied_Price = PRICE_CLOSE;     // Momentum Pinball: Prices for ROC calculation
input uint    TS_MomPin_RSI_Period = 3;                              // Momentum Pinball: RSI period
input double  TS_MomPin_RSI_Overbought = 70;                         // Momentum Pinball: RSI oversold level
input double  TS_MomPin_RSI_Oversold = 30;                           // Momentum Pinball: RSI overbought level
input uint    TS_MomPin_Entry_Offset = 10;                           // Momentum Pinball: Offset of entry level from borders H1 (in points)
input uint    TS_MomPin_Exit_Offset = 10;                            // Momentum Pinball: Offset of exit level from borders H1 (in points)
  input ENUM_EXIT_MODE  TS_MomPin_Exit_Mode = CLOSE_ON_SL_TRAIL;       // Momentum Pinball: Profitable position closing method

La función principal del módulo fe_Get_Entry_Signal se unificará con la función del módulo de señales de la anterior estrategia comercial del libro de Raschke y Connors, y también con los subsecuentes módulos análogos de otros sistemas comerciales descritos en esta fuente. Esto significa que la función deberá tener un conjunto así de parámetros transmitidos, enlaces a las variables y el mismo tipo de valor retornado:

ENUM_ENTRY_SIGNAL fe_Get_Entry_Signal(      // Two-candle pattern analysis (D1 + H1)
  datetime  t_Time,                         // current time
  double&    d_Entry_Level,                 // entry level (link to the variable)
  double&    d_SL,                          // StopLoss level (link to the variable)
  double&    d_TP,                          // TakeProfit level (link to the variable)
  double&    d_Range_High,                  // high of the range's 1st hourly bar (link to the variable)
  double&    d_Range_Low                    // low of the range's 1st hourly bar (link to the variable)
) {
  // function body
  }

Al igual que en la versión anterior, no vamos a recalcular todo de nuevo tick a tick al llamar la función desde el robot, sino que almacenaremos entre ticks los niveles calculados en las variables estáticas. Sin embargo, la organización del trabajo con esta función en el indicador para el comercio manual tendrá diferencias sustanciales, y deberemos prever el reseteo de las variables estáticas al llamar la función desde el indicador. Para diferenciar la llamada desde el indicador de la llamada desde el robot, utilizaremos la variable t_Time, el indicador la invertirá, es decir, hará negativo su valor:

static ENUM_ENTRY_SIGNAL se_Trade_Direction = ENTRY_UNKNOWN;   // trading direction for today
static double
  // variables for storing calculated levels between ticks
  sd_Entry_Level = 0,
  sd_SL = 0, sd_TP = 0,
  sd_Range_High = 0, sd_Range_Low = 0
;
if(t_Time < 0) {                                               // only for call from indicator
  sd_Entry_Level = sd_SL = sd_TP = sd_Range_High = sd_Range_Low = 0;
  se_Trade_Direction = ENTRY_UNKNOWN;
}
// by default apply earlier saved levels of entries/exits:
    d_Entry_Level = sd_Entry_Level; d_SL = sd_SL; d_TP = sd_TP; d_Range_High = sd_Range_High; d_Range_Low = sd_Range_Low;

A continuación, viene el código para obtener el manejador del indicador LBR/RSI con la primera llamada de la función:

static int si_Indicator_Handle = INVALID_HANDLE;
if(si_Indicator_Handle == INVALID_HANDLE) {
  // to receive indicator handle when calling the function for the first time:
  si_Indicator_Handle = iCustom(_Symbol, PERIOD_D1, "LBR_RSI",
    TS_MomPin_Applied_Price,
    TS_MomPin_RSI_Period,
    TS_MomPin_RSI_Overbought,
    TS_MomPin_RSI_Oversold
  );
  
  if(si_Indicator_Handle == INVALID_HANDLE) { // indicator handle not received
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: error of receiving LBR_RSI indicator handle #%u", __FUNCTION__, _LastError);
    return(ENTRY_INTERNAL_ERROR);
  }
  }

Una vez al día, el robot tiene que analizar el valor del indicador en la última barra de día cerrada y definir la dirección permitida del comercio en la actualidad. O bien debe fijar la prohibición del comercio, si el valor de LBR/RSI se encuentra en la zona neutra. Aquí podemos ver el código para extraer este valor de la memoria intermedia del indicador y después analizarlo con las funciones de registro, y teniendo en cuenta los posibles errores y peculiaridades del indicador de comercio manual:

static int si_Indicator_Handle = INVALID_HANDLE;
if(si_Indicator_Handle == INVALID_HANDLE) {
  // receiving indicator handle at first function call:
  si_Indicator_Handle = iCustom(_Symbol, PERIOD_D1, "LBR_RSI",
    TS_MomPin_Applied_Price,
    TS_MomPin_RSI_Period,
    TS_MomPin_RSI_Overbought,
    TS_MomPin_RSI_Oversold
  );
  
  if(si_Indicator_Handle == INVALID_HANDLE) {       // handle not received
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: error of indicator handle receipt LBR_RSI #%u", __FUNCTION__, _LastError);
    return(ENTRY_INTERNAL_ERROR);
  }
}
// to find out the time of previous day daily bar:
datetime ta_Bar_Time[];
if(CopyTime(_Symbol, PERIOD_D1, fabs(t_Time), 2, ta_Bar_Time) < 2) {
  if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyTime: error #%u", __FUNCTION__, _LastError);
  return(ENTRY_INTERNAL_ERROR);
}
// previous day analysis, if this is the 1st call today:
static datetime st_Prev_Day = 0;
if(t_Time < 0) st_Prev_Day = 0;                     // only for call from indicator
if(st_Prev_Day < ta_Bar_Time[0]) {
  // zeroing of previous day parameters:
  se_Trade_Direction = ENTRY_UNKNOWN;
  d_Entry_Level = sd_Entry_Level = d_SL = sd_SL = d_TP = sd_TP = d_Range_High = sd_Range_High = d_Range_Low = sd_Range_Low = 0;
  
  // retrieve value LBR/RSI of previous day:
  double da_Indicator_Value[];
  if(1 > CopyBuffer(si_Indicator_Handle, 4, ta_Bar_Time[0], 1, da_Indicator_Value)) {
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyBuffer: error #%u", __FUNCTION__, _LastError);
    return(ENTRY_INTERNAL_ERROR);
  }
  
  // if anything wrong with  LBR/RSI value:
  if(da_Indicator_Value[0] > 100. || da_Indicator_Value[0] < 0.) {
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: Indicator buffer value error (%f)", __FUNCTION__, da_Indicator_Value[0]);
    return(ENTRY_UNKNOWN);
  }
  
  st_Prev_Day = ta_Bar_Time[0];                     // attempt counted
  
  // remember trading direction for today:
  if(da_Indicator_Value[0] > TS_MomPin_RSI_Overbought) se_Trade_Direction = ENTRY_SELL;
  else se_Trade_Direction = da_Indicator_Value[0] > TS_MomPin_RSI_Oversold ? ENTRY_NONE : ENTRY_BUY;
  
  // to log:
  if(Log_Level == LOG_LEVEL_DEBUG) PrintFormat("%s: Trading direction for %s: %s. LBR/RSI: (%.2f)",
    __FUNCTION__,
    TimeToString(ta_Bar_Time[1], TIME_DATE),
    StringSubstr(EnumToString(se_Trade_Direction), 6),
    da_Indicator_Value[0]
  );
  }

Hemos encontrado la dirección del comercio permitida. La siguiente tarea será identificar los niveles de entrada y la limitación de las pérdidas (Stop Loss). Basta con hacer esto una vez al día, inmediatamente después del cierre de la primera barra del día en el marco temporal de una hora. Pero, teniendo en cuenta las peculiaridades del funcionamiento del indicador de comercio manual, el algoritmo lo tendrá un poco más difícil. Esto se debe a que el indicador no solo debe identificar la señal de los niveles en tiempo real, sino también hacer las marcas en la historia:

// no signal search today
if(se_Trade_Direction == ENTRY_NONE) return(ENTRY_NONE);
// analysis of today’s first bar H1, unless this is already done:
if(sd_Entry_Level == 0.) {
  // to receive data of last 24 bars H1:
  MqlRates oa_H1_Rates[];
  int i_Price_Bars = CopyRates(_Symbol, PERIOD_H1, fabs(t_Time), 24, oa_H1_Rates);
  if(i_Price_Bars == WRONG_VALUE) {                      // handling of CopyRates function error
    if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyRates: error #%u", __FUNCTION__, _LastError);
    return(ENTRY_INTERNAL_ERROR);
  }
  
  // among 24 bars to find the 1st bar of today and to remember High, Low:
  int i_Bar = i_Price_Bars;
  while(i_Bar-- > 0) {
    if(oa_H1_Rates[i_Bar].time < ta_Bar_Time[1]) break;      // last bar H1 of previous day
    
    // borders of H1 1st bar range:
    sd_Range_High = d_Range_High = oa_H1_Rates[i_Bar].high;
    sd_Range_Low = d_Range_Low = oa_H1_Rates[i_Bar].low;
  }
  // H1 1st bar is not closed yet:
  if(i_Price_Bars - i_Bar < 3) return(ENTRY_UNKNOWN);
  
  // to calculate trading levels:
  
  // level of market entry:  
  d_Entry_Level = _Point * TS_MomPin_Entry_Offset;           // auxiliary calculations
  sd_Entry_Level = d_Entry_Level = se_Trade_Direction == ENTRY_SELL ? d_Range_Low - d_Entry_Level : d_Range_High + d_Entry_Level;
  // initial level SL:  
  d_SL = _Point * TS_MomPin_Exit_Offset;                     // auxiliary calculations
  sd_SL = d_SL = se_Trade_Direction == ENTRY_BUY ? d_Range_Low - d_SL : d_Range_High + d_SL;
  }

Después de ello, solo quedará concluir el funcionamiento de la función retornando la dirección de comercio descubierta:

return(se_Trade_Direction);

Ahora vamos a programar el análisis de las condiciones para la señal de cierre de posición. Tenemos tres opciones, una de las cuales (el nivel de trailing stop) ya está implementada en las versiones anteriores de código del asesor. Las otras dos opciones, en suma, exigirán para los cálculos el precio y la hora de entrada, además de la dirección de la posición. Las transmitiremos a fe_Get_Exit_Signal junto con la hora actual y el método de cierre elegido:

ENUM_EXIT_SIGNAL fe_Get_Exit_Signal(    // Detection of position closing signal
  double            d_Entry_Level,      // entry level
  datetime          t_Entry_Time,       // entry time
  ENUM_ENTRY_SIGNAL e_Trade_Direction,  // trade direction
  datetime          t_Current_Time,     // current time
  ENUM_EXIT_MODE    e_Exit_Mode         // exit mode
) {
  static MqlRates soa_Prev_D1_Rate[];   // data of D1 bar for previous day
  static int si_Price_Bars = 0;         // auxiliary counter
  if(t_Current_Time < 0) {              // to distinguish a call from indicator and a call from Expert Advisor
    t_Current_Time = -t_Current_Time;
    si_Price_Bars = 0;
  }
  double
    d_Curr_Entry_Level,
    d_SL, d_TP,
    d_Range_High,  d_Range_Low
  ;
  
  if(e_Trade_Direction < 1) {          // no positions, to zero everything
    si_Price_Bars = 0;
  }
  
  switch(e_Exit_Mode) {
    case CLOSE_ON_SL_TRAIL:            // only on trail
            return(EXIT_NONE);
                      
    case CLOSE_ON_NEW_1ST_CLOSE:       // on closing of next day 1st bar
            if((t_Current_Time - t_Current_Time % 86400)
              ==
              (t_Entry_Time - t_Current_Time % 86400)
            ) return(EXIT_NONE);       // day of position opening not finished yet
            
            if(fe_Get_Entry_Signal(t_Current_Time, d_Curr_Entry_Level, d_SL, d_TP, d_Range_High, d_Range_Low)
              < ENTRY_UNKNOWN
            ) {
              if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: 1st bar of the following day is closed", __FUNCTION__);
              return(EXIT_ALL);
            }
            return(EXIT_NONE);         // not closed
            
    case CLOSE_ON_DAY_BREAK:           // upon break-through of extremum of the position opening day
            if((t_Current_Time - t_Current_Time % 86400)
              ==
              (t_Entry_Time - t_Current_Time % 86400)
            ) return(EXIT_NONE);       // position opening day not finished yet
            
            if(t_Current_Time % 86400 > 36000) return(EXIT_ALL); // time out
            
            if(si_Price_Bars < 1) {
              si_Price_Bars = CopyRates(_Symbol, PERIOD_D1, t_Current_Time, 2, soa_Prev_D1_Rate);
              if(si_Price_Bars == WRONG_VALUE) { // handling of CopyRates function error
                if(Log_Level > LOG_LEVEL_NONE) PrintFormat("%s: CopyRates: error #%u", __FUNCTION__, _LastError);
                return(EXIT_UNKNOWN);
              }
              
              if(e_Trade_Direction == ENTRY_BUY) {
                if(soa_Prev_D1_Rate[1].high < soa_Prev_D1_Rate[0].high) return(EXIT_NONE);        // did not break-through
                
                if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: price broke-through yesterday’s High: %s > %s", __FUNCTION__, DoubleToString(soa_Prev_D1_Rate[1].high, _Digits), DoubleToString(soa_Prev_D1_Rate[0].high, _Digits));
                return(EXIT_BUY);
              } else {
                if(soa_Prev_D1_Rate[1].low > soa_Prev_D1_Rate[0].low) return(EXIT_NONE);          // did not break through
                
                if(Log_Level > LOG_LEVEL_ERR) PrintFormat("%s: price broke through yesterday’s Low: %s < %s", __FUNCTION__, DoubleToString(soa_Prev_D1_Rate[1].low, _Digits), DoubleToString(soa_Prev_D1_Rate[0].low, _Digits));
                return(EXIT_SELL);
              }
            }
            
            return(EXIT_NONE); // for each
  }
  
  return(EXIT_UNKNOWN);
  }

Aquí tenemos un 'tope' en el caso de que esté elegida la opción 'salida por trailing', la función retorna la ausencia de señal sin ningún análisis. Para las otras dos opciones se detecta el evento 'ya es de día' y 'se ha roto el extremo de ayer'. Las variantes de las funciones retornadas de los valores del tipo ENUM_EXIT_SIGNAL son muy semejantes a la lista análoga de los valores de los señales de entrada (ENUM_ENTRY_SIGNAL):

enum ENUM_EXIT_SIGNAL {  // The list of exit signals
  EXIT_UNKNOWN,          // not identified
  EXIT_BUY,              // close buys
  EXIT_SELL,             // close sells
  EXIT_ALL,              // close all
  EXIT_NONE              // close nothing
  };

Indicador para el comercio manual

El módulo de señales descrito anteriormente está diseñado para el uso en un robot de comercio automatizado. Veremos con más detalle el modo de aplicación un poco más tarde. En primer lugar, vamos a crear una herramienta para ver de forma más visual las características del sistema comercial en los gráficos en el terminal. Este indicador utiliza un módulo de señales sin ninguna modificación, y representa los niveles comerciales calculados en él: el nivel de colocación de la orden pendiente y el nivel de Stop Loss. El cierre de la transacción con beneficios en este indicador se muestra en una forma de realización simplificada: al alcanzar el nivel preestablecido (TakeProfit). Como recordará, hemos programado en el módulo algoritmos más complejos para identificar las señales de salida de la transacción, pero dejaremos su implementación para el robot.

Aparte de los niveles comerciales, el indicador destacará el fondo de las barras de la primera hora del día, para que quede claro por qué se usan precisamente estos niveles. Esta marca ayudará a valorar visualmente las ventajas y desventajas de la mayoría de reglas de la estrategia 'Momentum Pinball': servirá para detectar aquello que es imposible obtener del simulador de estrategias. El análisis visual, complementado por la estadística de simulación, permitirá componer las reglas del sistema comercial de forma más efectiva.

Para que el indicador se puede utilizar también en el comercio manual convencional, añadiremos al mismo un sistema de alertas al tráder en tiempo real. Esta notificación contendrá para el módulo de señales la dirección recomendada de entrada junto con los niveles de colocación de órdenes pendientes y la salida de emergemcia (Stop Loss). Habrá tres métodos de envío de notificaciones: la ventana emergente estándar con texto y señal acústica, el mensaje al correo electrónico y la notificación push al dispositivo móvil.

Ya se han enumerado todos los requisitos del indicador. Por lo tanto, podemos comenzar a programar. Para dibujar en el gráfico todos los objetos que hemos planeado, en el indicador debe haber un búfer del tipo DRAW_FILLING (para rellenar el rango de barras de la primera hora del día) y tres búferes para representar los niveles comerciales (nivel de entrada, nivel de fijación del beneficio y nivel de limitación de pérdidas). Uno de ellos (el nivel de colocación de órdenes pendientes) debe tener la posibilidad de cambiar el color (tipo DRAW_COLOR_LINE), dependiendo de la dirección del comercio; para los otros dos, bastará el tipo monocromo DRAW_LINE:

#property indicator_chart_window
#property indicator_buffers  6
#property indicator_plots    4
#property indicator_label1  “1st hour of a day"
#property indicator_type1   DRAW_FILLING
#property indicator_color1  C'255,208,234', C'179,217,255'
#property indicator_width1  1
#property indicator_label2  “Entry level"
#property indicator_type2   DRAW_COLOR_LINE
#property indicator_style2  STYLE_DASHDOT
#property indicator_color2  clrDodgerBlue, clrDeepPink
#property indicator_width2  2
#property indicator_label3  "Stop Loss"
#property indicator_type3   DRAW_LINE
#property indicator_style3  STYLE_DASHDOTDOT
#property indicator_color3  clrCrimson
#property indicator_width3  1
#property indicator_label4  "Take Profit"
#property indicator_type4   DRAW_LINE
#property indicator_color4  clrGreen
  #property indicator_width4  1

Ahora tenemos que declarar las listas, una parte de las cuales no son necesarias en el indicador (solo son usadas por el asesor), pero que participan en las funciones del módulo de señales. Estas variables del tipo enum son necesarias para trabajar con el registro y con varios métodos de cierre de posición que omitiremos también en el indicador: recuerde, aquí solo habrá una simple imitación de la fijación de las ganancias en un nivel dado (Take Profit). Tras el anunciar estas variables, se puede conectar un módulo externo enumerar los ajustes personalizados y declarar las variables globales:

enum ENUM_LOG_LEVEL {  // The list of logging levels
  LOG_LEVEL_NONE,      // logging disabled
  LOG_LEVEL_ERR,       // only info about errors
  LOG_LEVEL_INFO,      // errors + robot comments
  LOG_LEVEL_DEBUG      // all without exclusions
};
enum ENUM_ENTRY_SIGNAL {  // List of entry signals
  ENTRY_BUY,              // buy signal
  ENTRY_SELL,             // sell signal
  ENTRY_NONE,             // no signal
  ENTRY_UNKNOWN,          // status indefinite
  ENTRY_INTERNAL_ERROR    // internal function error
};
enum ENUM_EXIT_SIGNAL {  // The list of exit signals
  EXIT_UNKNOWN,          // not identified
  EXIT_BUY,              // close buys
  EXIT_SELL,             // close sells
  EXIT_ALL,              // close all
  EXIT_NONE              // close nothing
};
#include <Expert\Signal\Signal_Momentum_Pinball.mqh>     // signal module of ‘Momentum Pinball’ TS
input uint    TS_MomPin_Take_Profit = 10;                // Momentum Pinball: Take Profit (in points)
input bool    Show_1st_H1_Bar = true;                    // Show day 1st hourly bar range?
input bool    Alert_Popup = true;                        // Alert: Show pop-up window?
input bool    Alert_Email = false;                       // Alert: Send e-mail?
input string  Alert_Email_Subj = "";                     // Alert: Subjects of e-mail alert
input bool    Alert_Push = true;                         // Alert: Send push-notification?
input uint  Days_Limit = 7;                              // History layout depth (calendar days)
ENUM_LOG_LEVEL  Log_Level = LOG_LEVEL_DEBUG;             // Logging mode
double
  buff_1st_H1_Bar[], buff_1st_H1_Bar_Zero[],             // buffers for filling day 1st hourly bar range
  buff_Entry[], buff_Entry_Color[],                      // buffers of pending order line
  buff_SL[],                                             // buffer of StopLoss line
  buff_TP[],                                             // buffer of TakeProfit line
  gd_Entry_Offset = 0,                                   // TS_MomPin_Entry_Offset in symbol prices
  gd_Exit_Offset = 0                                     // TS_MomPin_Exit_Offset in symbol prices
  ;

En la función de inicialización no hay nada extraordinario, asignamos los índices de los búferes de indicador a las matrices declaradas por sus nombres para estos búferes. También en este caso, pasamos los ajustes personalizados de puntos al precio del instrumento, para reducir aunque sea un poco el consumo de recursos y no hacer semejante transformación miles de veces mientras funciona el programa principal:

int OnInit() {
  // converting points to symbol prices:
  gd_Entry_Offset = TS_MomPin_Entry_Offset * _Point;
  gd_Exit_Offset = TS_MomPin_Exit_Offset * _Point;
  
  // designation of indicator buffers:
  
  // day 1st hourly bar range rectangle
  SetIndexBuffer(0, buff_1st_H1_Bar, INDICATOR_DATA);
    PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, 0);
  SetIndexBuffer(1, buff_1st_H1_Bar_Zero, INDICATOR_DATA);
    PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0);
  
  // pending order placement line
  SetIndexBuffer(2, buff_Entry, INDICATOR_DATA);
    PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, 0);
  SetIndexBuffer(3, buff_Entry_Color, INDICATOR_COLOR_INDEX);
  
  // line SL
  SetIndexBuffer(4, buff_SL, INDICATOR_DATA);
    PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, 0);
  
  // line TP
  SetIndexBuffer(5, buff_TP, INDICATOR_DATA);
    PlotIndexSetDouble(3, PLOT_EMPTY_VALUE, 0);
  
  IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
  IndicatorSetString(INDICATOR_SHORTNAME, "Momentum Pinball");
  
  return(INIT_SUCCEEDED);
  }

En el código del indicador del anterior artículo de esta serie se creó una cierta entidad programática cuyo cometido era guardar información de cualquier tipo entre ticks. En el mencionado artículo podrá leer más información sobre su misión y su composición: aquí nos limitaremos a usarla sin ningún cambio. En esta versión del indicador, de toda la funcionalidad de 'domovói' solo se usará la bandera de comienzo de una nueva barra. Pero si deseamos hacer más avanzado el indicador para el comercio manual, nos vendrán muy bien otras funciones del 'domovói'. Podemos ver el código completo de la estructura de go_Brownie al final del archivo del código fuente del indicador (TS_Momentum_Pinball.mq5), en los materiales adjuntos a este artículo. Allí podemos ver también el código de la función de envío de notificaciones f_Do_Alert, en ella tampoco hay ningún cambio en comparación con el anterior indicador de esta serie de artículos, por eso no vamos a analizarlo con detalle.

Dentro del manejador estándar del evento de llegada del tick (OnCalculate), hay que declarar las variables necesarias antes de que comience el ciclo principal del programa. Si no se trata de a primera llamada del ciclo principal, hay que limitar el rango de recálculo solo al de las barras actuales de este momento: para esta estrategia comercial, serían las barras de los días de hoy y ayer. Si se trata de la primera llamada del ciclo tras la inicialización, habrá que limpiar los búferes del indicador de datos restantes. Si no lo hacemos, al cambiar de marco temporal quedarán rellenadas las áreas no actuales. Además, merece la pena limitar la llamada de la función principal a una vez por barra. Es muy cómodo hacer todo esto con la ayuda de la estucturago_Brownie (domovói):

go_Brownie.f_Update(prev_calculated, prev_calculated);     // “feed” data to Brownie
datetime t_Time = TimeCurrent();                           // last known server time
int
  i_Period_Bar = 0,                                        // auxiliary counter
  i_Current_TF_Bar = 0                                     // loop beginning bar index
;
if(go_Brownie.b_First_Run) {                               // if this is the 1st launch
  i_Current_TF_Bar = rates_total — Bars(_Symbol, PERIOD_CURRENT, t_Time — t_Time % 8640086400 * Days_Limit, t_Time);
  // clearing buffer at re-initialization:
  ArrayInitialize(buff_1st_H1_Bar, 0); ArrayInitialize(buff_1st_H1_Bar_Zero, 0);
  ArrayInitialize(buff_Entry, 0); ArrayInitialize(buff_Entry_Color, 0);
  ArrayInitialize(buff_TP, 0);
  ArrayInitialize(buff_SL, 0);
} else if(!go_Brownie.b_Is_New_Bar) return(rates_total);   // waiting for bar closing
else {                                                     // new bar
  // minimum re-calculation depth - from day beginning:
  i_Current_TF_Bar = rates_total — Bars(_Symbol, PERIOD_CURRENT, t_Time — t_Time % 86400, t_Time);
}
ENUM_ENTRY_SIGNAL e_Entry_Signal = ENTRY_UNKNOWN;          // entry signal
double
  d_SL = WRONG_VALUE,                                      // SL level
  d_TP = WRONG_VALUE,                                      // TP level
  d_Entry_Level = WRONG_VALUE,                             // entry level
  d_Range_High = WRONG_VALUE, d_Range_Low = WRONG_VALUE    // pattern's 1 st bar range borders
;
datetime
  t_Curr_D1_Bar = 0,                                       // current D1 bar time (pattern's 2 nd bar)
  t_Last_D1_Bar = 0,                                       // time of the last bar D1, on which signal was available
  t_Entry_Bar = 0                                          // pending order placement bar time
;
// making sure that initial re-calculation bar index is within allowed frames:
  i_Current_TF_Bar = int(fmax(0, fmin(i_Current_TF_Bar, rates_total — 1)));

Ahora vamos a programar el ciclo principal de trabajo. Al comienzo de cada iteración, es necesario extraer los datos desde el módulo de señales, controlar el resultado de la presencia de errores y gestionar la transición a la siguiente iteración del bucle, si no hay señal:

while(++i_Current_TF_Bar < rates_total && !IsStopped()) {                // iterate over the current TF bars
  // receiving data from signal module:
  e_Entry_Signal = fe_Get_Entry_Signal(-Time[i_Current_TF_Bar], d_Entry_Level, d_SL, d_TP, d_Range_High, d_Range_Low);
  if(e_Entry_Signal == ENTRY_INTERNAL_ERROR) {                           // error of data copying from external indicator buffer
    // calculations and drawing should be repeated on the next tick:
    go_Brownie.f_Reset();
    return(rates_total);
  }
    if(e_Entry_Signal > 1) continue;                                       // no active signal on this bar

Si el módulo ha detectado la presencia de una señal en la barra analizada y ha retornado el nivel de entrada, primero calculamos el nivel de fijación de beneficio (Take Profit):

t_Curr_D1_Bar = Time[i_Current_TF_Bar] - Time[i_Current_TF_Bar] % 86400; // comienzo del día al que pertenece esta barra

A continuación, marcamos en la historia esta transacción en su desarrollo, si se trata de la primera barra del nuevo día:

t_Curr_D1_Bar = Time[i_Current_TF_Bar] - Time[i_Current_TF_Bar] % 86400;            // start of the day the bar belongs to
if(t_Last_D1_Bar < t_Curr_D1_Bar) {                                                 // this is 1st bar of the day, on which signal is available
    t_Entry_Bar = Time[i_Current_TF_Bar];                                             // remember the time of trading start

Vamos a comenzar con el rellando de las barras de la primera hora del día, que son utilizadas en los cálculos de los niveles:

// Background filling 1st hour bars:
if(Show_1st_H1_Bar) {
  i_Period_Bar = i_Current_TF_Bar;
  while(Time[--i_Period_Bar] >= t_Curr_D1_Bar && i_Period_Bar > 0)
    if(e_Entry_Signal == ENTRY_BUY) {                               // bullish pattern
      
      buff_1st_H1_Bar_Zero[i_Period_Bar] = d_Range_High;
      buff_1st_H1_Bar[i_Period_Bar] = d_Range_Low;
    } else {                                                        // bearish pattern
      buff_1st_H1_Bar[i_Period_Bar] = d_Range_High;
      buff_1st_H1_Bar_Zero[i_Period_Bar] = d_Range_Low;
    }
  }

Después, dibujamos la línea de colocación de órdenes pendientes hasta el momento en que la orden pendiente se convierta en posición abierta, es decir, hasta que el precio toque este nivel:

// Entry line till crossed by a bar:
i_Period_Bar = i_Current_TF_Bar - 1;
if(e_Entry_Signal == ENTRY_BUY) {                               // bullish pattern
  while(++i_Period_Bar < rates_total) {
    if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) {            // day end
      e_Entry_Signal = ENTRY_NONE;                              // pending order did not trigger
      break;
    }
    
    // extend line:
    buff_Entry[i_Period_Bar] = d_Entry_Level;
    buff_Entry_Color[i_Period_Bar] = 0;
    
    if(d_Entry_Level <= High[i_Period_Bar]) break;               // entry was on this bar
  }
} else {                                                         // bearish pattern
  while(++i_Period_Bar < rates_total) {
    if(Time[i_Period_Bar] > t_Curr_D1_Bar + 86399) {             // day end
      e_Entry_Signal = ENTRY_NONE;                               // pending order did not trigger
      break;
    }
    
    // extend line:
    buff_Entry[i_Period_Bar] = d_Entry_Level;
    buff_Entry_Color[i_Period_Bar] = 1;
    
    if(d_Entry_Level >= Low[i_Period_Bar]) break;               // entry was on this bar
  }
  }

Si el precio no ha alcanzado el nivel de cálculo hasta el final del día, saltaremos al paso siguiente del ciclo principal:

if(e_Entry_Signal == ENTRY_NONE) {                 // pending order did not trigger before the day end
  i_Current_TF_Bar = i_Period_Bar;                 // this day bars are not interesting to us any more
  continue;
  }

Si ese día no se ha completado y el destino de la orden pendiente no está definido, no hay ninguna razón para continuar con el bucle principal del programa:

if(i_Period_Bar >= rates_total - 1) break;        // el día actual (no finalizado) ha sido procesado

Después de estos dos filtros, solo queda una posible opción de desarrollo de los eventos: la orden pendiente se ha activado. Vamos a encontrar la barra de ejecución de la orden pendiente y, comenzando por esta barra, dibujaremos los niveles de Take Profit y Stop Loss hasta que el precio cruce uno de ellos, es decir, hasta el cierre de la posición. En este caso, es necesario prever la situación cuando la apertura y el cierre de la posición se producen en la misma barra, en este caso, es necesario prolongar la línea en la barra hacia el pasado, de manera que pueda llegar a ser visible en el gráfico:

// order triggered, find position closing bar:
i_Period_Bar = fmin(i_Period_Bar, rates_total - 1);
buff_SL[i_Period_Bar] = d_SL;
while(++i_Period_Bar < rates_total) {
  if(TS_MomPin_Exit_Mode == CLOSE_ON_SL_TRAIL) {
    if(Time[i_Period_Bar] >= t_Curr_D1_Bar + 86400) break;        // this is the following day bar
    
    // Lines TP and SL until the bar crossing one of them:
    buff_SL[i_Period_Bar] = d_SL;
    buff_TP[i_Period_Bar] = d_TP;
    
    if((
      e_Entry_Signal == ENTRY_BUY && d_SL >= Low[i_Period_Bar]
      ) || (
      e_Entry_Signal == ENTRY_SELL && d_SL <= High[i_Period_Bar]
    )) {                                                          // SL exit
      if(buff_SL[int(fmax(0, i_Period_Bar - 1))] == 0.) {
   // beginning and end on a single bar, extend it by 1 bar to the past
        buff_SL[int(fmax(0, i_Period_Bar - 1))] = d_SL;
        buff_TP[int(fmax(0, i_Period_Bar - 1))] = d_TP;
      }
      break;
    }
    
    if((
      e_Entry_Signal == ENTRY_BUY && d_TP <= High[i_Period_Bar]
      ) || (
      e_Entry_Signal == ENTRY_SELL && d_SL >= Low[i_Period_Bar]
    )) {                                                         // TP exit
      if(buff_TP[int(fmax(0, i_Period_Bar - 1))] == 0.) {
        // beginning and end on a single bar, extend it by 1 bar to the past
        buff_SL[int(fmax(0, i_Period_Bar - 1))] = d_SL;
        buff_TP[int(fmax(0, i_Period_Bar - 1))] = d_TP;
      }
      break;
    }
  }
  }

Después de cerrar la posición, las barras restantes del día se pueden omitir en el ciclo principal del programa:

i_Period_Bar = i_Current_TF_Bar;
t_Curr_D1_Bar = Time[i_Period_Bar] - Time[i_Period_Bar] % 86400;
while(
  ++i_Period_Bar < rates_total
  &&
  t_Curr_D1_Bar == Time[i_Period_Bar] - Time[i_Period_Bar] % 86400
  ) i_Current_TF_Bar = i_Period_Bar;

Con esto, podemos dar por concluido el código del ciclo principal. Solo queda organizar las notificaciones, si la señal se ha detactado en la barra actual:

i_Period_Bar = rates_total - 1;                                            // current bar
if(Alert_Popup + Alert_Email + Alert_Push == 0) return(rates_total);       // all disabled
if(t_Entry_Bar != Time[i_Period_Bar]) return(rates_total);                 // no signal on this bar
// message wording:
string s_Message = StringFormat("ТС Momentum Pinball: needed %s @ %s, SL: %s",
  e_Entry_Signal == ENTRY_BUY ? "BuyStop" : "SellStop",
  DoubleToString(d_Entry_Level, _Digits),
  DoubleToString(d_SL, _Digits)
);
// alert:
  f_Do_Alert(s_Message, Alert_Popup, false, Alert_Email, Alert_Push, Alert_Email_Subj);

El código completo del indicador se puede ver en el archivo TS_Momentum_Pinball.mq5, de los materiales adjuntos a este artículo.

Asesor para la simulación del sistema comercial 'Momentum Pinball'

La funcionalidad básica del experto debería ampliarse ligeramente al preparar la simulación de la próxima estrategia comercial del libro de Raschke y Connors. En el artículo anterior podrá encontrar el código fuente tomado como base para esta versión, con una descripción detallada. No vamos a repetir lo mismo una vez más, solo vamos a analizar los cambios y adiciones sustanciales: solo son dos.

La primera adición es la lista de señales de salida, que no estaba presente en la anterior versión del robot comercial. Además, se ha añadido el estado ENTRY_INTERNAL_ERROR a la lista de señales de entrada. Estas listas numeradas no se diferencian en nada de las mismas listas enum en el indicador analizado más arriba. Las hemos ubicado en el código del robot antes de la línea de inclusión de la clase de las transacciones comerciales de la biblioteca estándar. En el archivo Street_Smarts_Bot_MomPin.mq5, de los materiales adjuntos al artículo, son las líneas 24..32.

El segundo cambio está relacionado con el hecho de que el módulo de señales ahora también da señales de cierre de posición. Añadimos el bloque de código correspondiente para que también funcione con esta señal. En la anterior versión del robot existe un operador condicional if para comprobar si la actual posición es nueva (línea 139): la comprobación se usa para calcular y colocar el nivel StopLoss inicial. En esta versión, añadiremos al operador if a través de un else alternativo el bloque de código correspondiente para invocar el módulo de señales. Si el resultado de la invocación requiere de ello, el asesor deberá cerrar la posición:

} else {                       // not new position
                               // conditions for position closing ready?
  ENUM_EXIT_SIGNAL e_Exit_Signal = fe_Get_Exit_Signal(d_Entry_Level, datetime(PositionGetInteger(POSITION_TIME)), e_Entry_Signal, TimeCurrent(), TS_MomPin_Exit_Mode);
  if((
      e_Exit_Signal == EXIT_BUY && e_Entry_Signal == ENTRY_BUY
    ) || (
      e_Exit_Signal == EXIT_SELL && e_Entry_Signal == ENTRY_SELL
    ) || e_Exit_Signal == EXIT_ALL
  ) {
                              // it must be closed
    CTrade o_Trade;
    o_Trade.LogLevel(LOG_LEVEL_ERRORS);
    o_Trade.PositionClose(_Symbol);
    return;
  }
  }

En el código fuente del bot, son las líneas 171..186.

Existen ciertos cambios en el código de la función que controla que la distancia hasta los niveles comerciales fb_Is_Acceptable_Distance sea suficiente (líneas 424..434).


Simulación de la estrategia con datos históricos

Hemos creado un par de herramientas (indicador y asesor) para investigar el sistema comercial, que obtuvo reconocimiento gracias al libro de Raschke y Connors. El motivo especial de la pasada con los datos históricos es la comprobación de la funcionalidad del robot comercial, una de estas herramientas. Por eso no hemos optimizado los parámetros, la simulación se ha realizado con los ajustes por defecto.

Los resultados completos de todas las pasadas se pueden encontrar en el archivo adjunto, pero aquí solo mostraremos los gráficos de cambio en el balance. Solo como ilustración del segundo objetivo de simulación por importancia: la valoración general (sin optimización de parámetros) del funcionamiento del sistema comercial en las condiciones del mercado actual. Recordemos que los autores ilustraron la estrategia con gráficos de finales del siglo pasado.

Gráfico de cambio del balance al simular el asesor desde comienzos de 2014 con las cotizaciones del serivor demo de MetaQuotes. Instrumento — EURJPY, marco temporal — H1:


Gráfico análogo para el instrumento EURUSD, con el mismo marco temporal y el mismo periodo de simulación:


Al simular sin cambiar los ajustes, con las cotizaciones de uno de los metales (XAUUSD), con el mismo periodo y el mismo marco temporal, el gráfico de cambio del balance se convierte en:


Conclusión

Las reglas enumeradas en el libro Street Smarts: High Probability Short-Term Trading Strategies para el sistema comercial 'Momentum Pinball' han sido trasladadas al código del indicador y el asesor. Por desgracia, la descripción no es tan detallada como debería ser, y deja más de una opción para las reglas acompañamiento y el cierre de posiciones. Por lo tanto, aquellos que deseen estudiar con detalle las características del sistema comercial, dispondrán de un amplio campo para la selección de los parámetros y las acciones de los algoritmos óptimos del robot. El código generado permite tal posibilidad, pero aparte de eso, esperamos, el código fuente será útil en el desarrollo de la programación orientada a objetos.

Los códigos fuente, los archivos compilados y la biblioteca en el directorio MQL5.zip han sido descomprimidos en los catálogos correspondientes. Cometido de cada uno de ellos:

# Nombre del archivo Tipo  Descripción 
 1  LBR_RSI.mq5  Indicador Indicador que combina ROC y RSI. Se usa para determinar la dirección del comercio (o su prohibición) en el día comenzado
 2  TS_Momentum_Pinball.mq5  Indicador Indicador para el comercio manual con este sistema comercial. Representa los niveles calculados de las entradas y salidas, resalta el rango de la primera hora, sobre cuya base se realizan los cálculos
 3   Signal_Momentum_Pinball.mqh  biblioteca  Biblioteca de funciones, estructuras y configuraciones personalizadas Se usa con el indicador y el asesor
 4  Street_Smarts_Bot_MomPin.mq5   asesor Indicador para comerciar manualmente con este sistema comercial. 

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

Archivos adjuntos |
MPtest.zip (692.56 KB)
MQL5.zip (164.76 KB)
Creamos una nueva estrategia comercial usando una tecnología de colocación de entradas a los indicadores Creamos una nueva estrategia comercial usando una tecnología de colocación de entradas a los indicadores
En el artículo se expone una tecnología con cuya ayuda cualquiera podrá crear su propia estrategia comercial combinando un conjunto individual de indicadores, y también desarrollar sus propias señales para entrar en el mercado.
Comercio por los niveles de DiNapoli Comercio por los niveles de DiNapoli
En este artículo se considera una de las versiones de la implementación práctica del Asesor Experto para el comercio por los niveles de DiNapoli a través de las herramientas estándar MQL5. Ha sido realizado el testeo de sus resultados, y han sido sacadas conclusiones.
Cómo reducir los riesgos del tráder Cómo reducir los riesgos del tráder
El comercio en los mercados financieros se relaciona con una serie de riesgos que deben ser tenidos en cuenta en los algoritmos de los sistemas comerciales. La reducción de dichos riesgos es una tarea vital a la hora de obtener beneficios en el trading.
Selección automática de señales prometedoras Selección automática de señales prometedoras
Este artículo está dedicado al estudio de las señales comerciales para MetaTrader 5 con la ejecución automática en las cuentas de los suscriptores. Además, se considera el desarrollo de las herramientas para buscar las señales comerciales prometedoras directamente en el terminal.