English Русский 中文 Deutsch 日本語 Português
Mini emulador del mercado o Probador de estrategias manual

Mini emulador del mercado o Probador de estrategias manual

MetaTrader 5Probador | 14 diciembre 2017, 14:39
3 551 0
Dmitriy Zabudskiy
Dmitriy Zabudskiy

Introducción

El trabajo en el mercado Forex se empieza con el estudio de los principios básicos: estrategias de ganar, modos del análisis de los datos, modelos exitosos del trading. Todos los traders principiantes persiguen la misma idea— todo el mundo desea ganar dinero. Pero cada uno determina sus propias prioridades, plazos, posibilidades, objetivos, etc.

Hay varios escenarios del comportamiento de un trader principiante.

  • La variante «Todo y enseguida»: la mayor parte de los novatos quiere ganar mucho y rápido. Muchos se dejan convencer por la publicidad seductora de una estrategia mágica e implicable, la cual se puede usar por poco dinero e incluso gratis. Todo eso parece rápido y fácil, pero el depósito también se pierde de forma rápida y fácil.
  • La variante «Estudiar, estudiar y otra vez estudiar»: hay principiantes que abordan el asunto de un modo serio, sin creer en los cuentos. Ellos estudian las leyes del mercado y los sistemas comerciales detalladamente. Por fin empiezan a operar en una cuenta real, pero el beneficio resulta ser menos de lo esperado según los manuales. ¿Cómo puede ser eso y qué es lo que hay que hacer después?

Cuando ocurre la primera situación de éstas, los neófitos se deepcionan del trabajo en los mercados financieros para siempre. Los principiantes del segundo escenario continuan estudiar la teoría y sus estrategias prácticas.

Este artículo está dirigido principalmente a los principiantes que están ardiendo de impaciencia de tradear en la cuenta demo y probar su estrategia. Aquí también hay dos opciones:

  • Un grupo quiere probar la estrategia estudiada a corto plazo. Pero si sus miembros trabajan a tiempo completo, les quedan sólo las horas nocturnas, ya que el domingo el mercado está cerrado.
  • Otra categoría de los traders usa las estrategias a corto y medio plazo, y seguramente que ellos no tienen ganas de perfeccionar su estrategia en la cuenta demo.

Claro que me preguntarán, ¿para qué todas estas complicaciones, pues existe el gráfico del historial en el que se puede probar cualquier estrategia de forma rápida y eficaz? Pero en la práctica, eso no siempre funciona: a menudo ocurre que la estrategia que mostraba unos espléndidos resultados en el gráfico de la simulación en el pasado (backtesting), por alguna razón, trabaja muy mal en el «mercado vivo». En cualquier caso, es mejor estudiar el trading en los sistemas más o menos cercanos a la realidad. Por ejemplo, los emuladores del mercado serán bastante convenientes (se puede encontrar estos programas en Internet).

En este artículo, me gustaría contar sobre mi propia versión de la implementación del semejante sistema en el terminal MetaTrader 5. He escrito el indicador «Mini emulador del mercado» con una funcionalidad limitada, en comparación con la versión completa del terminal. Está diseñado para la verificación teórica de las estrategias.

Funcionalidad de la aplicación

La aplicación dispone de su propio panel de control, así como de algunos botones del «sistema original», es decir, del terminal MetaTrader 5.

Aquí tenemos las acciones principales que pueden ser ejecutadas por el emulador:

  1. Se puede colocar sólo dos órdenes en direcciones opuestas: buy y sell. También soporta la colocación del Stop Loss y Take Profit hasta la colocación de la orden y su volumen. Después de colocar la orden, se puede modificarla arrastrando los niveles Stop.
  2. Hay sólo siete velocidades del modelado, se puede dividirlas en tres grupos. La primera es «de artífice» que supone el modelado a base de la generación de ticks usando los datos del marco temporal de un minuto (timeframe), casi igual como en el Probador de estrategias. La segunda considera los datos ву de un minuto, se construye sin la generación (este modo es más rápido pero menos preciso). El tercer modo es más rápido: independientemente del timeframe, se construye una vela por segundo.
  3. Se facilita la información sobre el trading actual: beneficio, número de los puntos y el volumen. Los datos se muestran para la orden actual y la anterior, así como para el trading general desde el inicio de la emulación.
  4. Todos los objetos gráficos estándar presentes en el terminal están disponibles.
  5. Los timeframes estándar están disponibles (se conmutan con los botones del panel del terminal).


Fig. 1. Controles y apariencia de la aplicación


Sistema de generación de ticks

El principio de la generación de ticks fue cogido del artículo «Algoritmo de generación de ticks en el téster de estrategias del terminal MetaTrader 5». Fue revisado de una manera constructiva y presentado como una versión alternativa.

Dos funciones son responsables de la generación de ticks: la principal y la auxiliar.

La función principal es Tick Generation. Se le pasan dos parámetros: la misma vela y el array para los datos de respuesta (ticks). Luego, si todos los cuatro precios de la vela son iguales en la entrada, el volumen de los ticks se iguala a un tick. Fue hecho así para eliminar la posibilidad del error de la división por cero en caso del traspaso de datos erroneos.

A continuación, se hace la formación de una vela nueva. Si hay 1 — 3 ticks dentro de la vela, el proceso de generación continua de acuerdo con la descripción en el artículo arriba mencionado.

Si hay más de 3 ticks, el proceso se hace más complicado. La vela traspasada se divide en tres partes no iguales (véase el principio de divisón en el código de abajo separadamente para la vela bajista o alcista). Luego, si resulta que en la parte de arriba y abajo no hay ticks, se hace la corrección. A continuación, dependiendo de la naturaleza de la vela, el control se pasa a la función auxiliar.

//+------------------------------------------------------------------+
//| Func Tick Generation                                             |
//+------------------------------------------------------------------+
void func_tick_generation(
MqlRates &rates,      // datos sobre la vela
double &tick[]        // array dinámico de los ticks
)
{
 if(rates.open==rates.close && rates.high==rates.low && rates.open==rates.high){rates.tick_volume=1;}
 if(rates.tick_volume<4)// los ticks son menos de cuatro
 {
ArrayResize(tick,int(rates.tick_volume));         // cambiamos el tamaño del array por el número de los ticks
if(rates.tick_volume==1)tick[0]=rates.close;      // un tick
if(rates.tick_volume==2)                          // dos ticks
{
 tick[0]=rates.open;
 tick[1]=rates.close;
}
if(rates.tick_volume==3)                          // tre ticks
{
 tick[0]=rates.open;
 tick[2]=rates.close;
 if(rates.open==rates.close)                      // fuimos a un dirección y volvimos al nivel Open
 {
if(rates.high==rates.open)tick[1]=rates.low;
if(rates.low==rates.open)tick[1]=rates.high;
 }
 if(rates.close==rates.low && rates.open!=rates.high)tick[1]=rates.high;           // fuimos a una dirección, retrocedimos y rompimos el nivel  Open
 if(rates.close==rates.high && rates.open!=rates.low)tick[1]=rates.low;
 if(rates.open==rates.high && rates.close!=rates.low)tick[1]=rates.low;            // fuimos a una dirección, retrocedimos pero no rompimos el nivel Open
 if(rates.open==rates.low && rates.close!=rates.high)tick[1]=rates.high;
 if((rates.open==rates.low && rates.close==rates.high) || (rates.open==rates.high && rates.close==rates.low))
 {
tick[1]=NormalizeDouble((((rates.high-rates.low)/2)+rates.low),_Digits);       // algunos puntos a una dirección
 }
}
 }
 if(rates.tick_volume>3)      // más de tres ticks
 {

 // calculamos el tama;o de la vela por puntos
int candle_up=0;
int candle_down=0;
int candle_centre=0;
if(rates.open>rates.close)
{
 candle_up=int(MathRound((rates.high-rates.open)/_Point));
 candle_down=int(MathRound((rates.close-rates.low)/_Point));
}
if(rates.open<=rates.close)
{
 candle_up=int(MathRound((rates.high-rates.close)/_Point));
 candle_down=int(MathRound((rates.open-rates.low)/_Point));
}
candle_centre=int(MathRound((rates.high-rates.low)/_Point));
int candle_all=candle_up+candle_down+candle_centre;      // longitud total del movimiento
int point_max=int(MathRound(double(candle_all)/2));      // número máximo posible de los ticks
double share_up=double(candle_up)/double(candle_all);
double share_down=double(candle_down)/double(candle_all);
double share_centre=double(candle_centre)/double(candle_all);

// calculamos el número de los puntos de referencia en cada sección
char point=0;
if(rates.tick_volume<10)point=char(rates.tick_volume);
else point=10;
if(point>point_max)point=char(point_max);
char point_up=char(MathRound(point*share_up));
char point_down=char(MathRound(point*share_down));
char point_centre=char(MathRound(point*share_centre));

// comprobamos la presencia de los puntos de referencia en los rangos seleccionados
if(candle_up>0 && point_up==0)
{point_up=1;point_centre=point_centre-1;}
if(candle_down>0 && point_down==0)
{point_down=1;point_centre=point_centre-1;}

// cambiamos el tamaño del array de salida
ArrayResize(tick,11);
char p=0;                     // índice del array de ticks (tick[])
tick[p]=rates.open;           // el primer tick es igual al precio de apertura
if(rates.open>rates.close)    // descendiente
{
 func_tick_small(rates.high,1,candle_up,point_up,tick,p);
 func_tick_small(rates.low,-1,candle_centre,point_centre,tick,p);
 func_tick_small(rates.close,1,candle_down,point_down,tick,p);
 ArrayResize(tick,p+1);
}
if(rates.open<=rates.close)   // ascendiente o Doji
{
 func_tick_small(rates.low,-1,candle_down,point_down,tick,p);
 func_tick_small(rates.high,1,candle_centre,point_centre,tick,p);
 func_tick_small(rates.close,-1,candle_up,point_up,tick,p);
 ArrayResize(tick,p+1);
}
 }
}

Como indica su nombre, la función Tick Small realiza la menor generación de ticks. Recibe la información sobre el último tick, la dirección a seguir (hacia arriba o hacia abajo), el número necesario de pasos, el último precio, y pasa los pasos calculados en el array de los ticks arriba mencionado. Como resultado obtenemos el array que contiene no más de 11 ticks.

//+------------------------------------------------------------------+
//| Func Tick Small                                                  |
//+------------------------------------------------------------------+
void func_tick_small(
 double end,        // fin del movimiento
 char route,        // dirección del movimiento
 int candle,        // distancia del movimiento
 char point,        // número de puntos
 double &tick[],    // array de ticks
 char&i           // índice actual del array
 )
{
 if(point==1)
 {
i++;
if(i>10)i=10;       // corrección
tick[i]=end;
 }
 if(point>1)
 {
double wave_v=(point+1)/2;
double step_v=(candle-1)/MathFloor(wave_v)+1;
step_v=MathFloor(step_v);
for(char p_v=i+1,i_v=i; p_v<i_v+point;)
{
 i++;
 if(route==1)tick[i]=tick[i-1]+(step_v*_Point);
 if(route==-1)tick[i]=tick[i-1]-(step_v*_Point);
 p_v++;
 if(p_v<i_v+point)
 {
i++;
if(route==1)tick[i]=tick[i-1]-_Point;
if(route==-1) tick[i]=tick[i-1]+_Point;
 }
 p_v++;
}
if(NormalizeDouble(tick[i],_Digits)!=NormalizeDouble(end,_Digits))
{
 i++;
 if(i>10)i=10;    // corrección
 tick[i]=end;
}
 }
}

Es, por decirlo así, el corazón del modelado entero «de artífice» (en la conclusión será explicado por qué se llama «de artífice»). Ahora vamos a la esencia de la interacción del sistema.

Interracción e intercambio de datos

A primera vista, el código del sistema parece confuso. Las funciones no son de todo coherentes, pueden ser llamadas desde diferentes partes del programa. Es así, porque el sistema tiene que interactuar no sólo con el usuario, sino también con el terminal. Es el esquema aproximado de estas interacciónes (Fig. 2):


Fig. 2. Esquema de interacciones en la aplicación

Para reducir el número de objetos controladores en la ventana del indicador, el mecanismo para cambiar los períodos fue prestado desde el envoltorio del terminal. Pero puesto que cuando se conmuta un período, la aplicación se reinicia y todas las variables locales y globales se reescriben, el array de datos cada vez se copia tras la conmutación. En particular, se copian los datos de dos períodos: M1 y el selecionado. Los parámetros del procesamiento posterior de estos datos se eligen en el panel: se trata de la velocidad y la calidad del modelado («de artífice» o el modelado simple rápido). Cuando todo ya está listo, se comienza la construcción del gráfico.

Desde el panel control, se puede colocar y eliminar las órdenes. Para eso, el programa se dirige a la clase "COrder". Esta clase se usa para manejar las órdenes durante la construcción del gráfico.

Como ha sido mencionado antes, si se conmuta el período del gráfico, el indicador se reinicia. Por tanto, para la conexión de la estructura entera de la aplicación se usan las variables globales del terminal de cliente. Se las variables comunes, ellas se diferencian en el almacenamiento por largo tiempo (4 semanas) y la resistencia a los reinicios. La única desventaja es el tipo de datos, que puede ser solamente double. Pero en general, es más conveniente que crear cada vez un archivo separado, reescribir y leerlo.

Vamos directamente al código de las partes de la interacción.

Implementación en el código.

Inicio del código

Primero, se realizan los procedimientos estándar de la declaración de las variables. Luego, la función OnInit() inicializa los búferes, dibuja la interfaz del panel de control, se calcula el desplazamiento desde el comienzo de la emulación. El desplazamiento es necesario para no empezar el modelado con un gráfico vacío, sino en un determinado historial para empezar inmediatamente el testeo de la estrategia.

Aquí mismo copiamos los arrays de los datos y leemos la variable interconectora principal (con el nombre time_end). Es la hora a la que se detiene el modelado:

//--- establece el valor de la hora hasta la que se dibuja el indicador
 if(GlobalVariableCheck(time_end))end_time_indicator=datetime(GlobalVariableGet(time_end));

De esta manera, el indicador siembre «sabe» donde se ha parado. La función OnInit() se termina en la llamada al temporizador (Timer), que, en realidad, da el comando para la salida de un nuevo tick o la formación de una vela entera (dependiendo de la velocidad).

Función del temporizador

El estado del botón "play" en el panel de control se comprueba al principio de la función. Si está pulsado, se ejecuta el siguiente código.

Primero, se determina la barra del indicador en la que se ha detenido el modelado (respecto a la hora actual). La última hora end_time_indicator y la hora actual se toman como los puntos finales. Los datos se recalculan cada segundo porque el gráfico se mueve constantemente (a excepción del sabado y el domingo), y eso no está sincronizado en el tiempo. De esta manera, se realiza el seguimiento y el desplazamiento del gráfico por la función ChartNavigate().

Luego, se calculan las variables number_now_rates, bars_now_rates, all_bars_indicator. Después de eso, se comprueba el tiempo. Si no se ha terminado según los parámetros de entrada del indicador, se realiza el modelado a través de la función func_merger(). Luego, se comprueban las posiciones actuales y su rentabilidad, con la introducción de los valores en las variables globales y visualización en el bloque informativo del indicador.

Además, aquí se realiza el acceso a la clase "COrder", a saber, a sus partes responsables de la eliminación automática de la orden como resultado de las acciones del usuario (position.Delete) o la activación de los niveles Stop (position.Check).

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
{
//---
 if(button_play)
 {
end_bar_indicator=Bars(_Symbol,_Period,end_time_indicator,TimeCurrent());      // número de las barras, desde la más anterior hasta la actual
ChartNavigate(0,CHART_END,-end_bar_indicator);                                 // desplazamiento del gráfico (indicador) a la barra actual del modelado
number_now_rates=(Bars(_Symbol,_Period,real_start,end_time_indicator)-1);      // la barra actual utilizada para el modelado
bars_now_rates=(Bars(_Symbol,_Period,real_start,stop)-1);                      // número de barras utilizadas desde el historial para el períod actual
all_bars_indicator=(Bars(_Symbol,_Period,real_start,TimeCurrent()))-1;         // número de barras desde el comienzo del modelado hasta el momento actual

if(end_time_indicator<stop)                                                    // comprobación de la hora del modelado
{
 func_merger();
 ObjectSetDouble(0,line_bid,OBJPROP_PRICE,price_bid_now);
 if(ObjectFind(0,line_ask)>=0)
 {ObjectSetDouble(0,line_ask,OBJPROP_PRICE,price_ask_now);}

 //--- cálculos actuales para las órdenes
 0
 double vol_now=0;
 double money_now=0;
 if(ObjectFind(0,order_buy)>=0 && GlobalVariableGet(order_buy)>0)             // hay una orden de compra
 {
int p_now=int((price_bid_now-GlobalVariableGet(order_buy))*dig_pow);
double v_now=GlobalVariableGet(vol_buy);
double m_now=p_now*v_now*10;
point_now+=p_now;
vol_now+=v_now;
money_now+=m_now;
 }
 if(ObjectFind(0,order_sell)>=0 && GlobalVariableGet(order_sell)>0)           // hay orden de venta
 {
int p_now=int((GlobalVariableGet(order_sell)-price_ask_now)*dig_pow);
double v_now=GlobalVariableGet(vol_sell);
double m_now=p_now*v_now*10;
point_now+=p_now;
vol_now+=v_now;
money_now+=m_now;
 }
 GlobalVariableSet(info_point_now,point_now);
 GlobalVariableSet(info_vol_now,vol_now);
 GlobalVariableSet(info_money_now,money_now);
}

COrder position;    //objeto de la clase "COrder"
position.Delete(price_bid_now,price_ask_now,(-1));
position.Check(end_time_indicator,GlobalVariableGet(order_buy),GlobalVariableGet(tp_buy),GlobalVariableGet(sl_buy),
 GlobalVariableGet(order_sell),GlobalVariableGet(tp_sell),GlobalVariableGet(sl_sell));

func_info_print("Money All: ",info_money_all,2);
func_info_print("Money Last: ",info_money_last,2);
func_info_print("Money Now: ",info_money_now,2);
func_info_print("Volume All: ",info_vol_all,2);
func_info_print("Volume Last: ",info_vol_last,2);
func_info_print("Volume Now: ",info_vol_now,2);
func_info_print("Point All: ",info_point_all,0);
func_info_print("Point Last: ",info_point_last,0);
func_info_print("Point Now: ",info_point_now,0);

position.Modify();
 }
//--- control del botón Hide
 char x=char(GlobalVariableGet("hide"));
 if(x==1)
 {
ObjectSetInteger(0,"20",OBJPROP_STATE,false);
ObjectSetInteger(0,"14",OBJPROP_YDISTANCE,24);
ObjectSetInteger(0,"15",OBJPROP_YDISTANCE,24);
ObjectSetInteger(0,"16",OBJPROP_YDISTANCE,24);
ObjectSetInteger(0,"17",OBJPROP_YDISTANCE,24);
ObjectSetInteger(0,"18",OBJPROP_YDISTANCE,24);
ObjectSetInteger(0,"19",OBJPROP_YDISTANCE,24);
 }
 if(x==2)
 {
ObjectSetInteger(0,"20",OBJPROP_STATE,true);
ObjectSetInteger(0,"14",OBJPROP_YDISTANCE,-24);
ObjectSetInteger(0,"15",OBJPROP_YDISTANCE,-24);
ObjectSetInteger(0,"16",OBJPROP_YDISTANCE,-24);
ObjectSetInteger(0,"17",OBJPROP_YDISTANCE,-24);
ObjectSetInteger(0,"18",OBJPROP_YDISTANCE,-24);
ObjectSetInteger(0,"19",OBJPROP_YDISTANCE,-24);
 }
}

Clase COrder

Esta clase contiene las funciones para abrir y cerrar las posiciones, modificar y comprobar el estado actual de las órdenes (controlar sus niveles de Take Profit y Stop Loss).

Empezamos con la colocación de la orden usando Placed. Seleccionamos el tipo de la orden (buy o sell) usando el operador-conmutador switch, introducimos los datos en la variable global (order_buy o order_sell). Si m_take_profit y m_stop_los han sido definidos antes, los guardamos en las variables globales correspondientes y dibujamos sus líneas en el gráfico. Las líneas se establecen por la función Line de esta clase.

//+------------------------------------------------------------------+
//| Class COrder                                                     |
//+------------------------------------------------------------------+
class COrder
{
public:
 void Placed(
 char m_type,//tipo de la orden (1-buy, 2-sell)
 double m_price_bid, //precio bid
 double m_price_ask, //precio ask
 int m_take_profit,//puntos hasta take profit
 int m_stop_loss //puntos hasta stop loss
 )
 {
switch(m_type)
{
 case 1:
 {
GlobalVariableSet(order_buy,m_price_ask);
Line(GlobalVariableGet(order_buy),order_buy,col_buy,STYLE_SOLID,1,true);
if(m_take_profit>0)
{
 GlobalVariableSet(tp_buy,(m_price_ask+(_Point*m_take_profit)));
 Line(GlobalVariableGet(tp_buy),tp_buy,col_tp,STYLE_DASH,1,true);
}
if(m_stop_loss>0)
{
 GlobalVariableSet(sl_buy,(m_price_ask-(_Point*m_stop_loss)));
 Line(GlobalVariableGet(sl_buy),sl_buy,col_sl,STYLE_DASH,1,true);
}
 }
 break;
 case 2:
 {
GlobalVariableSet(order_sell,m_price_bid);
Line(GlobalVariableGet(order_sell),order_sell,col_sell,STYLE_SOLID,1,true);
if(m_take_profit>0)
{
 GlobalVariableSet(tp_sell,(m_price_bid-(_Point*m_take_profit)));
 Line(GlobalVariableGet(tp_sell),tp_sell,col_tp,STYLE_DASH,1,true);
}
if(m_stop_loss>0)
{
 GlobalVariableSet(sl_sell,(m_price_bid+(_Point*m_stop_loss)));
 Line(GlobalVariableGet(sl_sell),sl_sell,col_sl,STYLE_DASH,1,true);
}
 }
 break;
}
 }

La función para eliminar la orden Delete será la siguiente. Otra vez el operador-conmutador selecciona de tres opciones: eliminación automática, buy o sell. En este caso, la eliminación automática es la situación cuando una orden se elimina del gráfico mediante la eliminación de su línea del gráfico.

Las funciones de la clase Small_del_buy y Small_del_sell se encargan de eso.

 void Delete(
 double m_price_bid,      //precio bid
 double m_price_ask,      //precio ask
 char m_del_manual        //tipo de eliminación (-1 auto, 1 buy, 2 sell)
 )
 {
switch(m_del_manual)
{
 case(-1):
if(ObjectFind(0,order_buy)<0 && GlobalVariableGet(order_buy)>0)
{Small_del_buy(m_price_bid);}
if(ObjectFind(0,order_sell)<0 && GlobalVariableGet(order_sell)>0)
{Small_del_sell(m_price_ask);}
break;
 case 1:
if(ObjectFind(0,order_buy)>=0)
{
 ObjectDelete(0,order_buy);
 Small_del_buy(m_price_bid);
}
break;
 case 2:
if(ObjectFind(0,order_sell)>=0)
{
 ObjectDelete(0,order_sell);
 Small_del_sell(m_price_ask);
}
break;
}
 }

Vamos a considerar una ellas— Small_del_sell.

Comprobamos la presencia de las líneas de Take Profit y Stop Loss. Si están presentes, las eliminamos. Luego, damos el valor cero a la variable global order_sell. Lo necesitaremos después si vamos a comprobar la presencia de las órdenes usando las variables globales.

La información sobre el beneficio de la orden también se guarda en las vriables globales (info_point_last, info_vol_last, info_money_last). De eso se encarga la función small_concatenation (es algo parecido al operador += en la expresión, pero con las variables globales). Sumamos el beneficio (volumen), y lo guardamos también en las variables globales (info_point_all, info_vol_all, info_money_all).

void Small_del_sell(double m_price_ask)
 {
if(ObjectFind(0,tp_sell)>=0)ObjectDelete(0,tp_sell);       // eliminamos la línea take profit 
 if(ObjectFind(0,sl_sell)>=0)ObjectDelete(0,sl_sell);      // eliminamos la línea stop loss 
 int point_plus=int(MathRound((GlobalVariableGet(order_sell)-m_price_ask)/_Point));      // calculamos el beneficio de la transacción
GlobalVariableSet(order_sell,0);                           //damos el valor cero a la variable del precio de la orden colocada
GlobalVariableSet(info_vol_last,GlobalVariableGet(vol_sell));
GlobalVariableSet(vol_sell,0);
GlobalVariableSet(info_point_last,point_plus);
GlobalVariableSet(info_money_last,(GlobalVariableGet(info_point_last)*GlobalVariableGet(info_vol_last)*10));
Small_concatenation(info_point_all,info_point_last);
Small_concatenation(info_vol_all,info_vol_last);
Small_concatenation(info_money_all,info_money_last);
 }

La modificación de una orden es el cambio de la posición de su línea usando el ratón. Eso se hace de dos maneras. La primera es el intento del arrastre de la línea de la apertura de la orden. Em este caso, se construyen las nuevas líneas del Take Profit y Stop Loss, dependiendo de la dirección del desplazamiento y el tipo de la orden. Esta función Small_mod también se implementa en la clase COrder. Su parámetros de entrada son los siguientes: el nombre del objeto, permiso para el desplazamiento del objeto y el tipo de la orden.

Al principio de la función Small_mod, se comprueba la presencia del objeto. Luego, si el desplazamiento de las líneas de Take Profit y Stop Loss está permitido, el cambio del precio se guarda en la variable global. Si el desplazamiento está prohibido (líneas buy y sell), entonces, dependiendo del tipo de la orden, la línea de Take Profit o Stop Loss aparece en la nueva posición de la línea, y la línea de la orden vuelve a su posición.

 void Small_mod(string m_name,      // nombre del objeto y de la variable global
bool m_mode,                        // permiso para cambiar posición
char m_type                         // 1 — buy, 2 — sell
)
 {
if(ObjectFind(0,m_name)>=0)
{
 double price_obj_double=ObjectGetDouble(0,m_name,OBJPROP_PRICE);
 int price_obj=int(price_obj_double*dig_pow);
 double price_glo_double=GlobalVariableGet(m_name);
 int price_glo=int(price_glo_double*dig_pow);
 if(price_obj!=price_glo && m_mode==true)
 {
GlobalVariableSet(m_name,(double(price_obj)/double(dig_pow)));
 }
 if(price_obj!=price_glo && m_mode==false)
 {
switch(m_type)
{
 case 1:                         // order buy
if(price_obj>price_glo)          //TP
{
 GlobalVariableSet(tp_buy,(double(price_obj)/double(dig_pow)));
 Line(GlobalVariableGet(tp_buy),tp_buy,col_tp,STYLE_DASH,1,true);
}
if(price_obj<price_glo)          //SL
{
 GlobalVariableSet(sl_buy,(double(price_obj)/double(dig_pow)));
 Line(GlobalVariableGet(sl_buy),sl_buy,col_sl,STYLE_DASH,1,true);
}
break;
 case 2:                        // order sell
if(price_obj>price_glo)         //SL
{
 GlobalVariableSet(sl_sell,(double(price_obj)/double(dig_pow)));
 Line(GlobalVariableGet(sl_sell),sl_sell,col_sl,STYLE_DASH,1,true);
}
if(price_obj<price_glo)         //TP
{
 GlobalVariableSet(tp_sell,(double(price_obj)/double(dig_pow)));
 Line(GlobalVariableGet(tp_sell),tp_sell,col_tp,STYLE_DASH,1,true);
}
break;
}
ObjectSetDouble(0,m_name,OBJPROP_PRICE,(double(price_glo)/double(dig_pow)));
 }
}
 }

Durante el proceso de la construcción del gráfico, las órdenes se comprueban constantemente por la función Check класса COrder. Todas las variables globales que almacenan la información sobre las órdenes se pasa a la función. También disponde de su propia variable global que contiene la información sobre la hora de la última llamada. Eso permite con cada llamada comprobar el rango entero del precio (timeframe de un minuto) en el intervalo entre la última llamada a la función y la hora actual del dibujado del gráfico.

Si durante este tiempo, el precio ha llegado hasta una de las líneas Stop o la ha roto, el control se pasa a la función de la eliminación de las órdenes (clase COrder, función Delete).

 void Check(
datetime m_time,
double m_price_buy,
double m_price_tp_buy,
double m_price_sl_buy,
double m_price_sell,
double m_price_tp_sell,
double m_price_sl_sell
)
 {
int start_of_z=0;
int end_of_z=0;
datetime time_end_check=datetime(GlobalVariableGet(time_end_order_check));
if(time_end_check<=0){time_end_check=m_time;}
GlobalVariableSet(time_end_order_check,m_time);
start_of_z=Bars(_Symbol,PERIOD_M1,real_start,time_end_check);
end_of_z=Bars(_Symbol,PERIOD_M1,real_start,m_time);
for(int z=start_of_z; z<end_of_z; z++)
{
 COrder del;
 double p_bid_high=period_m1[z].high;
 double p_bid_low=period_m1[z].low;
 double p_ask_high=p_bid_high+(spread*_Point);
 double p_ask_low=p_bid_low+(spread*_Point);
 if(m_price_buy>0)                                              // hay una orden BUY
 {
if(ObjectFind(0,tp_buy)>=0)
{
 if(m_price_tp_buy<=p_bid_high && m_price_tp_buy>=p_bid_low)    // se ha activado TP
 {del.Delete(m_price_tp_buy,0,1);}                              // cerramos por el precio TP
} 
if(ObjectFind(0,sl_buy)>=0)
{
 if(m_price_sl_buy>=p_bid_low && m_price_sl_buy<=p_bid_high)    // se ha activado SL
 {del.Delete(m_price_sl_buy,0,1);}                              // cerramos por el precio SL
}
 }
 if(m_price_sell>0)                                                   // hay una orden SELL
 {
if(ObjectFind(0,tp_sell)>=0)
{
 if(m_price_sl_sell<=p_ask_high && m_price_sl_sell>=p_ask_low)  // se ha activado SL
 {del.Delete(0,m_price_sl_sell,2);}                             // cerramos por el precio SL
}
if(ObjectFind(0,sl_sell)>=0)
{
 if(m_price_tp_sell>=p_ask_low && m_price_tp_sell<=p_ask_high)  // se ha activado TP
 {del.Delete(0,m_price_tp_sell,2);}                             // cerramos por el precio TP
}
 }
}
 }

Aquí, las funciones principales de la clase se acaban. Vamos a examinar las funciones que responden directamente del dibujado de las velas en el gráfico.

Función del rellenado func_filling()

Puesto que, cuando se cambia el período, el indicador se reinicia, cada vez hay que rellenar el gráfico y colocar las velas ya pasadas hasta el momento actual (así llamada «cola»). Esta función también se utiliza antes de la formación de la vela nueva, lo que permite normalizar la «cola» del gráfico y aumentar la precisión de la visualización.

A la función se le pasa el array de datos del período actual, la hora actual de la visualización, el número de todas las velas y la vela actual que se dibuja. Después de la ejecución, la función pasa la hora de la apertura de la última vela visualizada y la hora de la apertura de la vela que la sigue. También se rellena el array del indicador y se pasa la bandera de la finalización del trabajo de la función work_status.

La función usa el ciclo for para rellenar el búfer entero del indicador visualizado antes, hasta la vela actual dibujada, así como se establecen los valores de los precios de la vela actual dibujada (normalmente iguales al precio de apertura).

//+------------------------------------------------------------------+
//| Func Filling                                                     |
//+------------------------------------------------------------------+
void func_filling(MqlRates &input_rates[],                // datos de entrada (del período actual) para rellenar
datetime input_end_time_indicator,      // hora actual del indicador
int input_all_bars_indicator,           // número de todas las barras del indicador
datetime &output_time_end_filling,      // hora de apertura de la última barra
datetime &output_time_next_filling,     // hora de apertura de la barra siguiente
int input_end_bar_indicator,            // barra actual (dibujada) del indicador
double &output_o[],
double &output_h[],
double &output_l[],
double &output_c[],
double &output_col[],
char &work_status)                      // estatus del trabajo
{
 if(work_status==1)
 {
int stopped_rates_bar;
for(int x=input_all_bars_indicator,y=0;x>0;x--,y++)
{
 if(input_rates[y].time<input_end_time_indicator)
 {
output_o[x]=input_rates[y].open;
output_h[x]=input_rates[y].high;
output_l[x]=input_rates[y].low;
output_c[x]=input_rates[y].close;
if(output_o[x]>output_c[x])output_col[x]=0;
else output_col[x]=1;
output_time_end_filling=input_rates[y].time;
output_time_next_filling=input_rates[y+1].time;
input_end_bar_indicator=x;
stopped_rates_bar=y;
 }
 else break;
}
output_o[input_end_bar_indicator]=input_rates[stopped_rates_bar].open;
output_h[input_end_bar_indicator]=output_o[input_end_bar_indicator];
output_l[input_end_bar_indicator]=output_o[input_end_bar_indicator];
output_c[input_end_bar_indicator]=output_o[input_end_bar_indicator];
work_status=-1;
 }
}

Tras la ejecución, el control se pasa a una de tres funciones del dibujado de la vela actual. Vamos a considerarlas una por una, empezando por la más rápida.

Función para dibujar la vela cada segundo func_candle_per_seconds()

A diferencia de otras dos velas, aquí el contros no se pasa a otras funciones hasta el reinicio del indicadoro o hasta el cambio de la velocidad del dibujado del gráfico. Cada nueva llamada se realiza cada segundo por el temporizador, y en este momento, se realiza el dibujado de la vela actual (llenado con datos). Primero, los datos se copian desde el array pasado a la vela actual, luego, los datos iniciales se pasan a la siguiente vela. Al final del todo, la función pasa la hora cuando ha sido formada la última vela.

La función descrita responde de la «séptima velocidad» de la formación de la vela (véase el panel de control).

//+------------------------------------------------------------------+
//| Func Candle Per Seconds                                          |
//+------------------------------------------------------------------+
void func_candle_per_seconds(MqlRates &input_rates[],
 datetime &input_end_time_indicator,
 int input_bars_now_rates,
 int input_number_now_rates,
 int &input_end_bar_indicator,
 double &output_o[],
 double &output_h[],
 double &output_l[],
 double &output_c[],
 double &output_col[],
 char &work_status)
{
 if(work_status==-1)
 {
if(input_number_now_rates<input_bars_now_rates)
{
 if(input_number_now_rates!=0)
 {
output_o[input_end_bar_indicator]=input_rates[input_number_now_rates-1].open;
output_h[input_end_bar_indicator]=input_rates[input_number_now_rates-1].high;
output_l[input_end_bar_indicator]=input_rates[input_number_now_rates-1].low;
output_c[input_end_bar_indicator]=input_rates[input_number_now_rates-1].close;
if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0;
else output_col[input_end_bar_indicator]=1;
 }
 input_end_bar_indicator--;
 output_o[input_end_bar_indicator]=input_rates[input_number_now_rates].open;
 output_h[input_end_bar_indicator]=input_rates[input_number_now_rates].high;
 output_l[input_end_bar_indicator]=input_rates[input_number_now_rates].low;
 output_c[input_end_bar_indicator]=input_rates[input_number_now_rates].close;
 if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0;
 else output_col[input_end_bar_indicator]=1;
 input_end_time_indicator=input_rates[input_number_now_rates+1].time;
}
 }
}

Las siguientes dos funciones son muy parecidas una a otra. Una de ellas construye las velas por la hora, a pesar de los ticks, mientras que la segunda («de artífice») utiliza el generador de ticks descrito al principio del artículo para la emulación más completa del mercado.

Función de la formación de la vela func_of_form_candle()

Los parámetros de entrada son los mismos que antes (OHLC). En cuanto a la funcionalidad, aquí todo es bastante simple. Los precios se copian cíclicamente desde los datos del timeframe M1 a la vela actual, a partir de la hora recibida desde la función del rellenado func_filling(). Resulta que, cambiando la hora, formamos la vela gradualmente.Las velocidades de la segunda a la sexta están construidas de esta manera (véase el panel de control). Después de que el tiempo llegue hasta la formación completa de la vela en el timeframe actual, la bandera work_status se cambia, para que se invoque de nuevo la función func_filling() en el momento de la siguiente ejecución del temporizador.

//+------------------------------------------------------------------+
//| Func Of Form Candle                                              |
//+------------------------------------------------------------------+
void func_of_form_candle(MqlRates &input_rates[],
 int input_bars,
 datetime &input_time_end_filling,
 datetime &input_end_time_indicator,
 datetime &input_time_next_filling,
 int input_end_bar_indicator,
 double &output_o[],
 double &output_h[],
 double &output_l[],
 double &output_c[],
 double &output_col[],
 char &work_status)
{
 if(work_status==-1)
 {
int start_of_z=0;
int end_of_z=0;
start_of_z=Bars(_Symbol,PERIOD_M1,real_start,input_time_end_filling);
end_of_z=Bars(_Symbol,PERIOD_M1,real_start,input_end_time_indicator);
for(int z=start_of_z; z<end_of_z; z++)
{
 output_c[input_end_bar_indicator]=input_rates[z].close;
 if(output_h[input_end_bar_indicator]<input_rates[z].high)output_h[input_end_bar_indicator]=input_rates[z].high;
 if(output_l[input_end_bar_indicator]>input_rates[z].low)output_l[input_end_bar_indicator]=input_rates[z].low;
 if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0;
 else output_col[input_end_bar_indicator]=1;
}
if(input_end_time_indicator>=input_time_next_filling)work_status=1;
 }
}

Vamos ahora a la función que es capaz de formar una vela que está cerca del mercado al máximo.

Función del modelado «de artífice» de la vela func_of_form_jeweler_candle()

Al principio de la función, todo ocurre como en la versión anterior. Los datos del timeframe de un minuto rellenan completamente la vela actual, salvo el último minuto. Sus datos se pasan a la función de la generación de ticks func_tick_generation() que ha sido descrita al principio del artículo. Con cada llamada a la función, el array de los ticks recibido se pasa gradualmente con el precio de cierre de la vela actual, tomando en cuenta el ajuste de las «sombras». Cuando los «ticks» del array se acaban, el proceso se repite.

//+------------------------------------------------------------------+
//| Func Of Form Jeweler Candle                                      |
//+------------------------------------------------------------------+
void func_of_form_jeweler_candle(MqlRates &input_rates[],                    //información para la generación de ticks
 int input_bars,                             //tamaño del array de la información
 datetime &input_time_end_filling,           //hora de la finalización del llenado rápido
 datetime &input_end_time_indicator,         //hora de la última construcción del indicador
 datetime &input_time_next_filling,          //tiempo hasta la finalización de la formación de la barra completa del timeframe actual
 int input_end_bar_indicator,                //barra dibujada actual del indicador
 double &output_o[],
 double &output_h[],
 double &output_l[],
 double &output_c[],
 double &output_col[],
 char &work_status                           // tipo de finalización del trabajo (comando para la función del rellenado rápido)
 )
{
 if(work_status==-1)
 {
int start_of_z=0;
int current_of_z=0;
start_of_z=Bars(_Symbol,PERIOD_M1,real_start,input_time_end_filling)-1;
current_of_z=Bars(_Symbol,PERIOD_M1,real_start,input_end_time_indicator)-1;
if(start_of_z<current_of_z-1)
{
 for(int z=start_of_z; z<current_of_z-1; z++)
 {
output_c[input_end_bar_indicator]=input_rates[z].close;
if(output_h[input_end_bar_indicator]<input_rates[z].high)output_h[input_end_bar_indicator]=input_rates[z].high;
if(output_l[input_end_bar_indicator]>input_rates[z].low)output_l[input_end_bar_indicator]=input_rates[z].low;
if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0;
else output_col[input_end_bar_indicator]=1;
 }
 input_end_time_indicator=input_rates[current_of_z].time;
}
//recibimos los ticks en el array
static int x=0;                   // cálculo del array y la bandera del inicio
static double tick_current[];
static int tick_current_size=0;
if(x==0)
{
 func_tick_generation(input_rates[current_of_z-1],tick_current);
 tick_current_size=ArraySize(tick_current);
 if(output_h[input_end_bar_indicator]==0)
 {output_h[input_end_bar_indicator]=tick_current[x];}
 if(output_l[input_end_bar_indicator]==0)
 {output_l[input_end_bar_indicator]=tick_current[x];}
 output_c[input_end_bar_indicator]=tick_current[x];
}
if(x<tick_current_size)
{
 output_c[input_end_bar_indicator]=tick_current[x];
 if(tick_current[x]>output_h[input_end_bar_indicator])
 {output_h[input_end_bar_indicator]=tick_current[x];}
 if(tick_current[x]<output_l[input_end_bar_indicator])
 {output_l[input_end_bar_indicator]=tick_current[x];}
 if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0;
 else output_col[input_end_bar_indicator]=1;
 x++;
}
else
{
 input_end_time_indicator=input_rates[current_of_z+1].time;
 x=0;
 tick_current_size=0;
 ArrayFree(tick_current);
}
if(input_end_time_indicator>input_time_next_filling)
{work_status=1;}
 }
}

Las tres opciones de la formación de las velas se encuentran en la función Merger.

Función para combinar la formación func_merger()

Dependeindo de la velocidad seleccionada por el operador-conmutador switch, se determina la función que va a usarse en el trabajo. Hay tres tipos de esta función. Cualquier caso se empieza con la función func_filling(), luego el control se pasa a una de tres funciones de formación de la vela: func_of_form_jeweler_candle(), func_of_form_candle() o func_candle_per_seconds(). La hora se recalcula en cada paso de la segunda velocidad a la sexta, inclusive. La función func_calc_time() calcula la parte necesaria del timeframeactual y la añade a la hora actual. El precio Bid se coge del precio de cierre de la vela actual, y el precio Ask se calcula a base de los datos del spread recibidos del servidor comercial.

//+------------------------------------------------------------------+
//| Func Merger                                                      |
//+------------------------------------------------------------------+
void func_merger()
{
 switch(button_speed)
 {
case 1:
{
 func_filling(period_array,end_time_indicator,all_bars_indicator,time_open_end_rates,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 func_of_form_jeweler_candle(period_m1,bars_m1,time_open_end_rates,end_time_indicator,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 price_bid_now=TST_C_C[end_bar_indicator];
 price_ask_now=price_bid_now+(spread*_Point);
}
break;
case 2:
{
 func_filling(period_array,end_time_indicator,all_bars_indicator,time_open_end_rates,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 func_of_form_candle(period_m1,bars_m1,time_open_end_rates,end_time_indicator,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 price_bid_now=TST_C_C[end_bar_indicator];
 price_ask_now=price_bid_now+(spread*_Point);
 end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,13);
}
break;
case 3:
{
 ...
 end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,11);
}
break;
case 4:
{
 ...
 end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,9);
}
break;
case 5:
{
 ...
 end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,7);
}
break;
case 6:
{
 ...
 end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,5);
}
break;
case 7:
{
 func_filling(period_array,end_time_indicator,all_bars_indicator,time_open_end_rates,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 func_candle_per_seconds(period_array,end_time_indicator,bars_now_rates,number_now_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 price_bid_now=TST_C_C[end_bar_indicator];
 price_ask_now=price_bid_now+(spread*_Point);
}
break;
 }
}

Aplicación posible

Yo propongo usar este indicador para testear nuevas ideas y estrategias comerciales, modelar el comportamiento de un trader principiante en una determinada situación, practicar las entradas en el mercado y salidas de él. Eso concierne principalmente a las herramientas técnicas: por ejemplo, se puede usar este indicador para construir las Ontas de Elliott, los canales o testear el trabajo de las líneas de soporte/resistencia.

Ejemplos del trabajo del del indicador:

Conclusión

Ahora revelaré el secreto, ¿Porqué uno de los tipos deд modelado se llama «de artífice»?

Todo es muy simple. En el proceso del desarrollo de esta aplicación, he llegado a la conclusión que el modelado tan suave y preciso no es necesario para la simulación de la mayoría de las estrategias. Por esa razón, es un lujo, una joya. Estos ticks modelan las fluctuaciones casi comparables con el tamaño del spread, y no afectan mucho el flujo de la estrategia, más aún si se considera la velocidad de la simulación. Es poco probable que alguien va a perder varios días para capturar el punto de entrada teniendo la posibilidad de rebobinar hasta el siguiente punto conveniente.

En cuanto al código, no excluyo la posibilidad de diferentes fallos. No obstante, eso no debe influir en el análisis de la estrategia en general. Es que todas las acciones básicas se guardan en las variables globales, y se puede simplemente reiniciar el timeframe o el terminal (sin cerrar la ventana del indicador), y después de eso, continuar la emulación.

En la descripción del código, han sido omitidas muchas funciones auxiliares, ya que se comprenden sin explicaciones o están explicadas en la documentación. En cualquier caso, no duden en hacer preguntas si ven que algo no está claro. Como siempre, estaré encantado de recibir cualquier comentario.


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

Archivos adjuntos |
STSv1.1.ex5 (120.96 KB)
STSv1.1.mq5 (127.38 KB)
for_STS.zip (13.39 KB)
Comparación de diferentes tipos de media móvil en el comercio Comparación de diferentes tipos de media móvil en el comercio
Se han analizado 7 tipos de medias móviles (MA), asimismo, se ha desarrollado una estrategia comercial para trabajar con ellas. Se ha realizado la simulación y la comparación de diferentes MA en una estrategia comercial, dando una característica comparativa de la efectividad del uso de las diferentes MA.
Colocando las entradas por los indicadores Colocando las entradas por los indicadores
En la vida de cada trader pueden ocurrir diferentes situaciones. A menudo usamos el historial de transacciones rentables para intentar restablecer una estrategia, y usando el historial de pérdidas, tratamos de mejorarla. En ambos casos comparamos las transacciones con indicadores conocidos. En este artículo, se propone la técnica de comparación por lotes de las transacciones con una serie de indicadores.
Simulación de patrones que surgen al comerciar con cestas de parejas de divisas. Parte II Simulación de patrones que surgen al comerciar con cestas de parejas de divisas. Parte II
Seguimos con la simulación de los patrones y la comprobación de las metodologías descritas en los artículos sobre la negociación con cestas de parejas de divisas. Vamos a considerar en la práctica si se puede usar los patrones de la intersección de la media móvil por el gráfico de WPR combinado, y si se puede, de qué manera.
Asesor Experto multiplataforma: las clases CExpertAdvisor y CExpertAdvisor Asesor Experto multiplataforma: las clases CExpertAdvisor y CExpertAdvisor
En el artículo final de la serie sobre el asesor comercial multiplataforma, hablaremos sobre las clases CExpertAdvisor y CExpertAdvisors, que sirven de contendores para los componentes del experto anteriormente descritos. Asimismo, analizaremos la implementación del monitoreo de las nuevas barras y el guardado de datos.