
Recetas MQL5 - Asesor multidivisa y funcionamiento de órdenes pendientes en MQL5
Introducción
En esta ocasión veremos la creación de un asesor multidivisa, cuyo algoritmo de comercio será construido para trabajar con las órdenes pendientes Buy Stop y Sell Stop. Construiremos un esquema para el comercio/simulación interno. En el artículo veremos las siguientes cuestiones:
- El comercio en el diapasón temporal indicado. Haremos lo necesario para que se pueda indicar la hora de comienzo y finalización del comercio. Por ejemplo, se puede tratar de un diapasón temporal que abarque las sesiones comerciales europeas o americanas. Por supuesto, tendremos la posibilidad de elegir el mejor diapasón temporal durante la optimización de los parámetros del experto.
- Cómo establecer/modificar/eleminar órdenes pendientes.
- Procesamiento de eventos comerciales: comprobación de la última posición de Take profit o Stop Loss, control del historial de operaciones conforme a cada símbolo.
Proceso de desarrollo del experto
Como plantilla tomaremos el código del artículo "Recetas MQL5 - Experto multidivisa: ejemplo de un esquema sencillo, preciso y rápido. Los cambios serán significativos, pero la estructura básica del esquema permanecerá igual. Crearemos un asesor para comercio interno, pero este modo se podrá desconectar cuando el usuario lo desee. Las órdenes pendientes, en tal caso, siempre se establecerán directamente (conforme al evento "barra nueva"), si la posición estaba cerrada.
Comenzaremos con los parámetros externos del experto. Primero crearemos una nueva enumeración ENUM_HOURS en el archivo incorporable Enums.mqh. La cantidad de indicadores en esta enumeración se corresponde con la cantidad de horas en el día:
//--- Enumeración de las horas enum ENUM_HOURS { h00 = 0, // 00 : 00 h01 = 1, // 01 : 00 h02 = 2, // 02 : 00 h03 = 3, // 03 : 00 h04 = 4, // 04 : 00 h05 = 5, // 05 : 00 h06 = 6, // 06 : 00 h07 = 7, // 07 : 00 h08 = 8, // 08 : 00 h09 = 9, // 09 : 00 h10 = 10, // 10 : 00 h11 = 11, // 11 : 00 h12 = 12, // 12 : 00 h13 = 13, // 13 : 00 h14 = 14, // 14 : 00 h15 = 15, // 15 : 00 h16 = 16, // 16 : 00 h17 = 17, // 17 : 00 h18 = 18, // 18 : 00 h19 = 19, // 19 : 00 h20 = 20, // 20 : 00 h21 = 21, // 21 : 00 h22 = 22, // 22 : 00 h23 = 23 // 23 : 00 };
En la lista de parámetros externos crearemos cuatro parámetros relacionados con el comercio en el diapasón temporal:
- TradeInTimeRange - conexión/desconexión del modo. Como ya hemos dicho más arriba, crearemos la posibilidad de trabajar con un asesor comercial, no sólo en el diapasón temporal, sino en el régimen de veinticuatro horas (ininterrumpido).
- StartTrade - hora de comienzo del comercio. Cuando la hora del servidor coincida con el valor indicado, el experto establecerá las órdenes pendientes, siempre que el modo esté conectado.
- StopOpenOrders - hora de finalización del establecimiento de órdenes. En cuanto la hora del servidor coincida con este valor, el experto dejará de establecer órdenes pendientes, si la posición se cierra.
- EndTrade - hora de finalización del comercio. En cuanto la hora del servidor sea igual a al valor indicado, el experto dejará de comerciar. La posición abierta sobre el símbolo indicado se cerrará, y las órdenes pendientes serán eliminadas
La lista de parámetros externos, dentro del código, tendrá el aspecto que se muestra abajo (ejemplo para dos símbolos). En el parámetro PendingOrder se establece la distancia en puntos a partir del precio actual.
//--- Parámetros externos del experto sinput long MagicNumber = 777; // Número mágico sinput int Deviation = 10; // Deslizamiento //--- sinput string delimeter_00=""; // -------------------------------- sinput string Symbol_01 ="EURUSD"; // Símbolo 1 input bool TradeInTimeRange_01 =true; // | Comercio en el diapasón temporal input ENUM_HOURS StartTrade_01 = h10; // | Hora del comienzo del comercio input ENUM_HOURS StopOpenOrders_01 = h17; // | Hora de finalización del establecimiento de órdenes input ENUM_HOURS EndTrade_01 = h22; // | Hora finalización del comercio input double PendingOrder_01 = 50; // | Orden pendiente input double TakeProfit_01 = 100; // | Take profit input double StopLoss_01 = 50; // | Stop loss input double TrailingStop_01 = 10; // | Trading stop input bool Reverse_01 = true; // | Viraje de posición input double Lot_01 = 0.1; // | Lote //--- sinput string delimeter_01=""; // -------------------------------- sinput string Symbol_02 ="AUDUSD"; // Símbolo 2 input bool TradeInTimeRange_02 =true; // | Comercio en el diapasón temporal input ENUM_HOURS StartTrade_02 = h10; // | Hora del comienzo del comercio input ENUM_HOURS StopOpenOrders_02 = h17; // | Hora de finalización del establecimiento de órdenes input ENUM_HOURS EndTrade_02 = h22; // | Hora finalización del comercio input double PendingOrder_02 = 50; // | Orden pendiente input double TakeProfit_02 = 100; // | Take profit input double StopLoss_02 = 50; // | Stop loss input double TrailingStop_02 = 10; // | Trading stop input bool Reverse_02 = true; // | Viraje de posición input double Lot_02 = 0.1; // | Lote
Hay que efectuar los cambios correspondientes también en la lista de las matrices que se llenan con los valores de los parámetros externos:
//--- Matrices para almacenar los parámetros externos string Symbols[NUMBER_OF_SYMBOLS]; // Símbolo bool TradeInTimeRange[NUMBER_OF_SYMBOLS]; // Comercio en el diapasón temporal ENUM_HOURS StartTrade[NUMBER_OF_SYMBOLS]; // Hora del comienzo del comercio ENUM_HOURS StopOpenOrders[NUMBER_OF_SYMBOLS]; // Hora de finalización del establecimiento de órdenes ENUM_HOURS EndTrade[NUMBER_OF_SYMBOLS]; // Hora finalización del comercio double PendingOrder[NUMBER_OF_SYMBOLS]; // Orden pendiente double TakeProfit[NUMBER_OF_SYMBOLS]; // Take profit double StopLoss[NUMBER_OF_SYMBOLS]; // Stop loss double TrailingStop[NUMBER_OF_SYMBOLS]; // Trading stop bool Reverse[NUMBER_OF_SYMBOLS]; // Viraje de posición double Lot[NUMBER_OF_SYMBOLS]; // Lote
Haremos que durante el modo de viraje de posición (con el parámetro Reverse en la posición true), cuando se active una de las órdenes pendientes, la orden pendiente opuesta sea establecida de nuevo. No tenemos la posibilidad de cambiar el volumen de una orden pendiente, como sucede con sus niveles de precio (precio de la orden, Stop loss, Take profit), por eso hay que eliminarla y establecer una nueva orden pendiente con el volumen necesario.
Asímismo, si está conectado el modo de viraje de posición y se ha establecido simultáneamente el nivel de Trading stop, entonces, la orden pendiente se irá desplazando tras el precio, conforme este se mueva. Si además se ha establecido también el Stop loss, entonces se calculará y establecerá conforme a la orden pendiente.
A un nivel global en el programa, crearemos dos variables de línea para los comentarios a las órdenes pendientes:
//--- Comentarios a las órdenes pendientes string comment_top_order ="top_order"; string comment_bottom_order ="bottom_order";
Mientras se carga el experto, en el momento de la inicialización, en la función OnInit() comprobaremos la corrección de los parámetros externos. La comprobación consistirá en que mientras esté conectado el modo de comercio en el diapasón temporal, la hora del comienzo del comercio no deberá ser antes de una hora de la finalización del establecimiento de las órdenes pendientes, y la hora de establecimiento de las órdenes pendientes no deberá ser antes de una hora de la finalización del comercio. Escribimos la función CheckInputParameters(), que realizará esa comprobación:
//+------------------------------------------------------------------+ //| Comprueba los parámetros externos | //+------------------------------------------------------------------+ bool CheckInputParameters() { //--- Revisaremos todos los símbolos indicados for(int s=0; s<NUMBER_OF_SYMBOLS; s++) { //--- Si no hay símbolo o el comercio durante el diapasón temporal está desconectado, pasaremos al símbolo siguiente if(Symbols[s]=="" || !TradeInTimeRange[s]) continue; //--- Comprobamos que la hora de comienzo y finalización del comercio sean correctas if(StartTrade[s]>=EndTrade[s]) { Print(Symbols[s], ": La hora de comienzo del comercio ("+IntegerToString(StartTrade[s])+") " "debe ser menor a la hora de la finalización del comercio ("+IntegerToString(EndTrade[s])+")!"); return(false); } //--- Se puede comenzar el comercio no más tarde de una hora antes de la hora de establecimiento de las órdenes pendientes. // El establecimiento de las órdenes pendientes deberá tener lugar no más tarde de una hora antes de la finalización del comercio. if(StopOpenOrders[s]>=EndTrade[s] || StopOpenOrders[s]<=StartTrade[s]) { Print(Symbols[s], ": La hora de la finalización del establecimiento de órdenes ("+IntegerToString(StopOpenOrders[s])+") " "debe ser menor a la hora de finalización ("+IntegerToString(EndTrade[s])+") y " "mayor que la hora de comienzo del comercio ("+IntegerToString(StartTrade[s])+")!"); return(false); } } //--- Los parámetros son correctos return(true); }
Para la realización de nuestro esquema nos harán falta funciones, en las que se llevarán a cabo comprobaciones de localización en los diapasones temporales para el comercio y el establecimiento de órdenes pendientes. Llamaremos a estas funciones IsInTradeTimeRange() y IsInOpenOrdersTimeRange(). Su principio de funcionamiento es el mismo, la única diferencia se encuentra en el límite superior del diapasón comprobado. Más adelante les mostraremos en el artículo dónde hay que usar estas funciones.
//+------------------------------------------------------------------+ //| Comprueba si nos encontramos en el diapasón temporal comercial | //+------------------------------------------------------------------+ bool IsInTradeTimeRange(int symbol_number) { //--- Si está conectado el comercio en el diapasón temporal if(TradeInTimeRange[symbol_number]) { //--- Estructura de la fecha y la hora MqlDateTime last_date; //--- Obtenemos los últimos datos de fecha y hora TimeTradeServer(last_date); //--- Fuera del diapasón temporal permitido if(last_date.hour<StartTrade[symbol_number] || last_date.hour>=EndTrade[symbol_number]) return(false); } //---Dentro del diapasón temporal permitido return(true); } //+------------------------------------------------------------------+ //| Comprueba si nos encontramos en el diapasón temporal | //| de establecimiento de órdenes | //+------------------------------------------------------------------+ bool IsInOpenOrdersTimeRange(int symbol_number) { //--- Si está conectado el comercio en el diapasón temporal if(TradeInTimeRange[symbol_number]) { //--- Estructura de la fecha y la hora MqlDateTime last_date; //--- Obtenemos los últimos datos de fecha y hora TimeTradeServer(last_date); //--- Fuera del diapasón temporal permitido if(last_date.hour<StartTrade[symbol_number] || last_date.hour>=StopOpenOrders[symbol_number]) return(false); } //--- Dentro del diapasón temporal permitido return(true); }
En artículos anteriores ya hemos visto las funciones para obtener las propiedades de una posición, símbolo o del historial de operaciones. En este artículo necesitaremos una función análoga para obtener las propiedades de una orden pendiente. En el archivo incorporable Enums.mqh creamos una enumeración con las propiedades de la orden pendiente:
//--- Enumeración de las propiedades de una orden pendiente enum ENUM_ORDER_PROPERTIES { O_SYMBOL = 0, O_MAGIC = 1, O_COMMENT = 2, O_PRICE_OPEN = 3, O_PRICE_CURRENT = 4, O_PRICE_STOPLIMIT = 5, O_VOLUME_INITIAL = 6, O_VOLUME_CURRENT = 7, O_SL = 8, O_TP = 9, O_TIME_SETUP = 10, O_TIME_EXPIRATION = 11, O_TIME_SETUP_MSC = 12, O_TYPE_TIME = 13, O_TYPE = 14, O_ALL = 15 };
Después, en el archivo incorporable TradeFunctions.mqh hay que describir la estructura con las propiedades de la orden pendiente y crear su muestra:
//--- Propiedades de la orden pendiente struct pending_order_properties { string symbol; // Símbolo long magic; // Número magico string comment; // Comentarios double price_open; // Precio indicado en la orden double price_current; // Precio actual del símbolo de la orden double price_stoplimit; // El precio de la realización de la orden Limit cuando se active la orden StopLimit double volume_initial; // Volumen incial de la realización de la orden double volume_current; // Volumen sin cumplir double sl; // Nivel de Stop Loss double tp; // Nivel Take Profit datetime time_setup; // Hora de la realización de la orden datetime time_expiration; // Tiempo de expiración de la orden datetime time_setup_msc; // Tiempo de establecimiento de la orden para su ejecución en milisegundos desde 01.01.1970 datetime type_time; // Tiempo de vida útil de la orden ENUM_ORDER_TYPE type; // Tipo de posición }; //--- variable de las propiedades de la orden pending_order_properties ord;
Para ontener las propiedades de una orden pendiente o todas las propiedades de golpe, escribimos la función GetPendingOrderProperties(). Primero hay que elegir la orden y sólo después se podra usar esta función para obtener sus propiedades. Mostraremos cómo hacerlo más adelante en este artículo.
//+------------------------------------------------------------------+ //| Obtiene las propiedades de una orden pendiente | //| elegida por anticipado | //+------------------------------------------------------------------+ void GetPendingOrderProperties(ENUM_ORDER_PROPERTIES order_property) { switch(order_property) { case O_SYMBOL : ord.symbol=OrderGetString(ORDER_SYMBOL); break; case O_MAGIC : ord.magic=OrderGetInteger(ORDER_MAGIC); break; case O_COMMENT : ord.comment=OrderGetString(ORDER_COMMENT); break; case O_PRICE_OPEN : ord.price_open=OrderGetDouble(ORDER_PRICE_OPEN); break; case O_PRICE_CURRENT : ord.price_current=OrderGetDouble(ORDER_PRICE_CURRENT); break; case O_PRICE_STOPLIMIT : ord.price_stoplimit=OrderGetDouble(ORDER_PRICE_STOPLIMIT); break; case O_VOLUME_INITIAL : ord.volume_initial=OrderGetDouble(ORDER_VOLUME_INITIAL); break; case O_VOLUME_CURRENT : ord.volume_current=OrderGetDouble(ORDER_VOLUME_CURRENT); break; case O_SL : ord.sl=OrderGetDouble(ORDER_SL); break; case O_TP : ord.tp=OrderGetDouble(ORDER_TP); break; case O_TIME_SETUP : ord.time_setup=(datetime)OrderGetInteger(ORDER_TIME_SETUP); break; case O_TIME_EXPIRATION : ord.time_expiration=(datetime)OrderGetInteger(ORDER_TIME_EXPIRATION); break; case O_TIME_SETUP_MSC : ord.time_setup_msc=(datetime)OrderGetInteger(ORDER_TIME_SETUP_MSC); break; case O_TYPE_TIME : ord.type_time=(datetime)OrderGetInteger(ORDER_TYPE_TIME); break; case O_TYPE : ord.type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); break; case O_ALL : ord.symbol=OrderGetString(ORDER_SYMBOL); ord.magic=OrderGetInteger(ORDER_MAGIC); ord.comment=OrderGetString(ORDER_COMMENT); ord.price_open=OrderGetDouble(ORDER_PRICE_OPEN); ord.price_current=OrderGetDouble(ORDER_PRICE_CURRENT); ord.price_stoplimit=OrderGetDouble(ORDER_PRICE_STOPLIMIT); ord.volume_initial=OrderGetDouble(ORDER_VOLUME_INITIAL); ord.volume_current=OrderGetDouble(ORDER_VOLUME_CURRENT); ord.sl=OrderGetDouble(ORDER_SL); ord.tp=OrderGetDouble(ORDER_TP); ord.time_setup=(datetime)OrderGetInteger(ORDER_TIME_SETUP); ord.time_expiration=(datetime)OrderGetInteger(ORDER_TIME_EXPIRATION); ord.time_setup_msc=(datetime)OrderGetInteger(ORDER_TIME_SETUP_MSC); ord.type_time=(datetime)OrderGetInteger(ORDER_TYPE_TIME); ord.type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); break; //--- default: Print("¡La propiedad transmitida de la orden pendiente no se encuentra en la enumeración!"); return; } }
Ahora vamos a escribir las funciones básicas para establecer, modificar y eliminar órdenes pendientes. La función SetPendingOrder() establece una orden pendiente, y si no se consigue establecer la orden, muestra un mensaje en el registro con el código de error y su descripción:
//+------------------------------------------------------------------+ //| Establece una orden pospuesta | //+------------------------------------------------------------------+ void SetPendingOrder(int symbol_number, // Número del símbolo ENUM_ORDER_TYPE order_type, // Tipo de orden double lot, // Volumen double stoplimit_price, // Nivel de la orden StopLimit double price, // Precio double sl, // Stop loss double tp, // Take profit ENUM_ORDER_TYPE_TIME type_time, // Duración de la orden string comment) // Comentarios { //--- Establecemos el número mágico en la estructura comercial trade.SetExpertMagicNumber(MagicNumber); //--- Si no se ha logrado establecer la orden pendiente, mostrar un mensaje sobre ello if(!trade.OrderOpen(Symbols[symbol_number], order_type,lot,stoplimit_price,price,sl,tp,type_time,0,comment)) Print("Error al establecer la orden pendiente: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
La función ModifyPendingOrder() modifica la orden pendiente. Vamos a hacer posible que con esta función se pueda cambiar, no sólo el precio de la orden, sino también su volumen, transmitiéndoselo como último parámetro de la función. Si el valor del volumen transmitido es superior a cero, esto significa que la orden pendiente debe ser eliminada y hay que establecer una nueva con el volumen indicado. En todos los casos semejantes, tiene lugar sólo la modificación de la orden existente, con un cambio de precio.
//+------------------------------------------------------------------+ //| Cambiar la orden pendiente | //+------------------------------------------------------------------+ void ModifyPendingOrder(int symbol_number, // Número del símbolo ulong ticket, // Ticket de la orden ENUM_ORDER_TYPE type, // Tipo de la orden double price, // Precio de la orden double sl, // Stop loss de la orden double tp, // Take profit de la orden ENUM_ORDER_TYPE_TIME type_time, // Duración de la orden datetime time_expiration, // Fecha de expiración de la orden double stoplimit_price, // Precio string comment, // Comentarios double volume) // Volumen { //--- Si se ha transmido un valor distinto a cero, establecemos de nuevo la orden if(volume>0) { //--- Si no se ha logrado eliminar la orden, salimos if(!DeletePendingOrder(ticket)) return; //--- Establecemos la orden pendiente SetPendingOrder(symbol_number,type,volume,0,price,sl,tp,type_time,comment); //--- Corregimos las posiciones de Stop Loss con respecto a la orden CorrectStopLossByOrder(symbol_number,price,type); } //--- Si se transmite un volumen igual a cero, modificamos la orden else { //--- Si no se ha logrado cambiar la orden pendiente, se debe mostrar un mensaje sobre ello if(!trade.OrderModify(ticket,price,sl,tp,type_time,time_expiration,stoplimit_price)) Print("Error al cambiar el precio de la orden pendiente: ", GetLastError()," - ",ErrorDescription(GetLastError())); //--- De otro modo, corregimos las posiciones de Stop Loss con respecto a la orden else CorrectStopLossByOrder(symbol_number,price,type); } }
En el código de más arriba tenemos dos funciones más: DeletePendingOrder() y CorrectStopLossByOrder(). La primera elimina la orden pendiente, y la segunda corrige el nivel de la posición de Stop loss con respecto a la orden pendiente.
//+------------------------------------------------------------------+ //| Elimina la orden pendiente | //+------------------------------------------------------------------+ bool DeletePendingOrder(ulong ticket) { //--- Si no se ha logrado eliminar la orden pendiente, se muestra un mensaje sobre ello if(!trade.OrderDelete(ticket)) { Print("Error al eliminar la orden pendiente: ",GetLastError()," - ",ErrorDescription(GetLastError())); return(false); } //--- return(true); } //+------------------------------------------------------------------+ //| Corrige las posiciones Stop Loss con respecto | //| a la orden pendiente | //+------------------------------------------------------------------+ void CorrectStopLossByOrder(int symbol_number, // Número del símbolo double price, // Precio de la orden ENUM_ORDER_TYPE type) // Tipo de la orden { //--- Si Stop Loss está desconectado, salimos if(StopLoss[symbol_number]==0) return; //--- Si Stop Loss está conectado double new_sl=0.0; // Nuevo valor para Stop Loss //--- Obtenemos el valor de un punto y GetSymbolProperties(symbol_number,S_POINT); //--- la cantidad de símbolos en el precio tras la coma GetSymbolProperties(symbol_number,S_DIGITS); //--- Obtenemos las posiciones Take Profit GetPositionProperties(symbol_number,P_TP); //--- Calculamos con respecto al tipo de la orden switch(type) { case ORDER_TYPE_BUY_STOP : new_sl=NormalizeDouble(price+CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits); break; case ORDER_TYPE_SELL_STOP : new_sl=NormalizeDouble(price-CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits); break; } //--- Modificamos la posición if(!trade.PositionModify(Symbols[symbol_number],new_sl,pos.tp)) Print("Error al modificar la posición: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
Antes de establecer una orden pendiente, hay que comprobar de manera adicional si existe alguna orden comercial con el mismo comentario. Como ya dijimos al inicio del artículo, la orden Buy Stop superior la estableceremos con el comentario "top_order", y la orden Sell Stop, con el comentario "bottom_order". Para llevar a cabo esa comprobación, escribiremos la función CheckPendingOrderByComment():
//+------------------------------------------------------------------+ //| Comprueba la existencia de una orden pendiente | //| según el comentario | //+------------------------------------------------------------------+ bool CheckPendingOrderByComment(int symbol_number,string comment) { int total_orders =0; // Cantidad total de órdenes pendientes string order_symbol =""; // Símbolo de la orden string order_comment =""; // Comentarios de la orden //--- Obtenemos la cantidad de órdenes pendientes total_orders=OrdersTotal(); //--- Repasamos un ciclo por todas las órdenes for(int i=total_orders-1; i>=0; i--) { //--- Elegimos la orden conforme a su ticket if(OrderGetTicket(i)>0) { //--- Obtenemos el nombre del símbolo order_symbol=OrderGetString(ORDER_SYMBOL); //--- Si los símbolos son iguales if(order_symbol==Symbols[symbol_number]) { //--- Obtenemos los comentarios de la orden order_comment=OrderGetString(ORDER_COMMENT); //--- Si los comentarios coinciden if(order_comment==comment) return(true); } } } //--- No se ha encontrado la orden con el comentario indicado return(false); }
En el código de arriba se puede ver que la cantidad total de órdenes se puede obtener con la ayuda de la función incorporable OrdersTotal(). Para obtener la cantidad de órdenes sobre un símbolo en concreto, hay que escribir una función personalizada. La llamaremos OrdersTotalBySymbol():
//+------------------------------------------------------------------+ //| Retorna la cantidad de órdenes sobre el símbolo indicado | //+------------------------------------------------------------------+ int OrdersTotalBySymbol(string symbol) { int count =0; // Contador de órdenes int total_orders =0; // Cantidad de órdenes pendientes //--- Obtenemos la cantidad de órdenes pendientes total_orders=OrdersTotal(); //--- Repasamos un ciclo por todas las órdenes for(int i=total_orders-1; i>=0; i--) { //--- Si la orden ha sido elegida if(OrderGetTicket(i)>0) { //--- Obtenemos el símbolo de la orden GetOrderProperties(O_SYMBOL); //--- Si el símbolo de la orden y el símbolo indicado coinciden if(ord.symbol==symbol) //--- Aumentamos el contador count++; } } //--- Devolvemos la cantidad de órdenes return(count); }
Antes de establecer una orden pendiente, hay que calcular para ella el precio y, en caso necesario, los niveles de Stop loss y Take profit. Si tenemos conectado el viraje de posición, necesitaremos funciones personalizadas por separado para recalcular y tener en cuenta los cambios de nivel del trading stop.
Para calcular el precio de una orden pendiente, escribiremos la función CalculatePendingOrder():
//+------------------------------------------------------------------+ //| Calcula el nivel (precio) de una orden pendiente | //+------------------------------------------------------------------+ double CalculatePendingOrder(int symbol_number,ENUM_ORDER_TYPE order_type) { //--- Para el valor calculado de una orden pendiente double price=0.0; //--- Si hay que calcular el valor de una orden SELL STOP if(order_type==ORDER_TYPE_SELL_STOP) { //--- Calculamos el nivel price=NormalizeDouble(symb.bid-CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits); //--- Devolvemos el valor calculado, en caso de que sea inferior al límite inferior de stops level // Si el valor es mayor o igual, devolvemos el valor corregido return(price<symb.down_level ? price : symb.down_level-symb.offset); } //--- Si hay que calcular el valor para la orden BUY STOP if(order_type==ORDER_TYPE_BUY_STOP) { //--- Calculamos el nivel price=NormalizeDouble(symb.ask+CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits); //--- Devolvemos el valor calculado, en caso de que sea superior al límite superior de stops level // Si el valor es menor o igual, devolvemos el valor corregido return(price>symb.up_level ? price : symb.up_level+symb.offset); } //--- return(0.0); }
Más abajo tenemos el código de la función para el cálculo de los niveles de Stop loss y Take profit en una orden pendiente:
//+------------------------------------------------------------------+ //| Calcula el nivel de Stop loss para una orden pendiente | //+------------------------------------------------------------------+ double CalculatePendingOrderStopLoss(int symbol_number,ENUM_ORDER_TYPE order_type,double price) { //--- Si es necesario Stop Loss if(StopLoss[symbol_number]>0) { double sl =0.0; // Para el valor calculado de Stop Loss double up_level =0.0; // Nivel superior de Stop Levels double down_level =0.0; // Nivel inferior de Stop Levels //--- Si hay que calcular el valor para la orden BUY STOP if(order_type==ORDER_TYPE_BUY_STOP) { //--- Determinamos el umbral inferior down_level=NormalizeDouble(price-symb.stops_level*symb.point,symb.digits); //--- Calculamos el nivel sl=NormalizeDouble(price-CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits); //--- Devolvemos el valor calculado, en caso de que sea inferior al límite inferior de stops level // Si el valor es mayor o igual, devolvemos el valor corregido return(sl<down_level ? sl : NormalizeDouble(down_level-symb.offset,symb.digits)); } //--- Si hay que calcular el valor para la orden SELL STOP if(order_type==ORDER_TYPE_SELL_STOP) { //--- Determinamos el umbral superior up_level=NormalizeDouble(price+symb.stops_level*symb.point,symb.digits); //--- Calculamos el nivel sl=NormalizeDouble(price+CorrectValueBySymbolDigits(StopLoss[symbol_number]*symb.point),symb.digits); //--- Devolvemos el valor calculado, en caso de que sea superior al límite superior de stops level // Si el valor es menor o igual, devolvemos el valor corregido return(sl>up_level ? sl : NormalizeDouble(up_level+symb.offset,symb.digits)); } } //--- return(0.0); } //+------------------------------------------------------------------+ //| Calcula el nivel de Take Profit para un orden pendiente | //+------------------------------------------------------------------+ double CalculatePendingOrderTakeProfit(int symbol_number,ENUM_ORDER_TYPE order_type,double price) { //--- Si es necesario Take Profit if(TakeProfit[symbol_number]>0) { double tp =0.0; // Para el valor calculado de Take Profit double up_level =0.0; // Nivel superior de Stop Levels double down_level =0.0; // Nivel inferior de Stop Levels //--- Si hay que calcular el valor para la orden SELL STOP if(order_type==ORDER_TYPE_SELL_STOP) { //--- Determinamos el umbral inferior down_level=NormalizeDouble(price-symb.stops_level*symb.point,symb.digits); //--- Calculamos el nivel tp=NormalizeDouble(price-CorrectValueBySymbolDigits(TakeProfit[symbol_number]*symb.point),symb.digits); //--- Devolvemos el valor calculado, en caso de que sea inferior al límite inferior de stops level // Si el valor es mayor o igual, devolvemos el valor corregido return(tp<down_level ? tp : NormalizeDouble(down_level-symb.offset,symb.digits)); } //--- Si hay que calcular el valor para la orden BUY STOP if(order_type==ORDER_TYPE_BUY_STOP) { //--- Determinamos el umbral superior up_level=NormalizeDouble(price+symb.stops_level*symb.point,symb.digits); //--- Calculamos el nivel tp=NormalizeDouble(price+CorrectValueBySymbolDigits(TakeProfit[symbol_number]*symb.point),symb.digits); //--- Devolvemos el valor calculado, en caso de que sea superior al límite superior de stops level // Si el valor es menor o igual, devolvemos el valor corregido return(tp>up_level ? tp : NormalizeDouble(up_level+symb.offset,symb.digits)); } } //--- return(0.0); }
Para calcular el nivel de parada (precio) de una orden dirigida en dirección contraria y su segumiento tenemos CalculateReverseOrderTrailingStop() y ModifyPendingOrderTrailingStop(). Con el código de estas funciones se podrá familiarizar después.
Código de la función CalculateReverseOrderTrailingStop():
//+----------------------------------------------------------------------------+ //| Calcula el nivel de Trailing Stop para una orden dirigida | //| en dirección contraria | //+----------------------------------------------------------------------------+ double CalculateReverseOrderTrailingStop(int symbol_number,ENUM_POSITION_TYPE position_type) { //--- Variables para los cálculos double level =0.0; double buy_point =low[symbol_number].value[1]; // Valor Low para Buy double sell_point =high[symbol_number].value[1]; // Valor High para Sell //--- Calculamos el nivel para la posición BUY if(position_type==POSITION_TYPE_BUY) { //--- Mínimo de la barra menos la cantidad indicada puntos level=NormalizeDouble(buy_point-CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits); //--- Si el nivel calculado es inferior que el nivel inferior del límite (stops level), // entonces el cálculo se finalizará, devolvemos el valor actual del nivel if(level<symb.down_level) return(level); //--- De otra forma probaremos a calcular a partir del precio de bid else { level=NormalizeDouble(symb.bid-CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits); //--- Si el nivel calculado es inferior que el limitador, devolvemos el valor actual del nivel // De otra forma, estableceremos el más cercano posible return(level<symb.down_level ? level : symb.down_level-symb.offset); } } //--- Calculamos el nivel para la posición SELL if(position_type==POSITION_TYPE_SELL) { // El máximo de la barra más la cantidad indicada de puntos level=NormalizeDouble(sell_point+CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits); //--- Si el nivel calculado es mayor que el nivel superior de la limitación (stops level), // entonces el cálculo se finalizará, devolvemos el valor actual de nivel if(level>symb.up_level) return(level); //--- De otra forma probaremos a calcular a partir del precio de ask else { level=NormalizeDouble(symb.ask+CorrectValueBySymbolDigits(PendingOrder[symbol_number]*symb.point),symb.digits); //--- Si el nivel calculado es superior que el limitador, devolvemos el valor actual del nivel // De otra forma, estableceremos el más cercano posible return(level>symb.up_level ? level : symb.up_level+symb.offset); } } //--- return(0.0); }
Código de la función ModifyPendingOrderTrailingStop():
//+------------------------------------------------------------------+ //| Cambia el nivel de Trailing Stop para una orden pendiente | //+------------------------------------------------------------------+ void ModifyPendingOrderTrailingStop(int symbol_number) { //--- Salimos, si el viraje de posición se encuentra desactivado o el Trailing Stop no ha sido introducido if(!Reverse[symbol_number] || TrailingStop[symbol_number]==0) return; //--- double new_level =0.0; // Para el cálculo del nuevo nivel de la orden pendiente bool condition =false; // Para comprobar las condiciones de la modificación int total_orders =0; // Cantidad total de órdenes pendientes ulong order_ticket =0; // Ticket de la orden string opposite_order_comment =""; // Comentarios de la orden opuesta ENUM_ORDER_TYPE opposite_order_type =WRONG_VALUE; // Tipo de orden //--- Obtenemos la bandera sobre la presencia/ausencia de posición pos.exists=PositionSelect(Symbols[symbol_number]); //--- Si no hay posición if(!pos.exists) return; //--- Obtenemos cantidad de órdenes pendientes total_orders=OrdersTotal(); //--- Obtenemos las propiedades del símbolo GetSymbolProperties(symbol_number,S_ALL); //--- Obtenemos las propiedades de la posición GetPositionProperties(symbol_number,P_ALL); //--- Obtenemos el nivel para Stop Loss new_level=CalculateReverseOrderTrailingStop(symbol_number,pos.type); //--- Pasamos el ciclo por todas las órdenes desde la última a la primera for(int i=total_orders-1; i>=0; i--) { //--- Si se ha elegido la orden if((order_ticket=OrderGetTicket(i))>0) { //--- Obtenemos el símbolo de la orden GetPendingOrderProperties(O_SYMBOL); //--- Obtenemos los comentarios de la orden GetPendingOrderProperties(O_COMMENT); //--- Obtenemos el precio de la orden GetPendingOrderProperties(O_PRICE_OPEN); //--- Dependiendo del tipo de posición, comprobaremos que se correspondan las condiciones de modificación de Trailing Stop switch(pos.type) { case POSITION_TYPE_BUY : //--- Si el nuevo valor para la orden es mayor que el valor actual más el salto establecido, entonces la condición se ha cumplido condition=new_level>ord.price_open+CorrectValueBySymbolDigits(TrailingStop[symbol_number]*symb.point); //--- Determinamos el tipo y comentarios de la orden pendiente dirigida en dirección contraria, para su comprobación opposite_order_type =ORDER_TYPE_SELL_STOP; opposite_order_comment =comment_bottom_order; break; case POSITION_TYPE_SELL : //--- Si el nuevo valor para la orden es menor que el valor actual menos el salto establecido, entonces la condición se ha cumplido condition=new_level<ord.price_open-CorrectValueBySymbolDigits(TrailingStop[symbol_number]*symb.point); //--- Determinamos el tipo y comentarios de la orden pendiente dirigida en dirección contraria, para su comprobación opposite_order_type =ORDER_TYPE_BUY_STOP; opposite_order_comment =comment_top_order; break; } //--- Si las condiciones se cumplen, coinciden el símbolo de la orden y la posición // y también coinciden los comentarios de la orden y la orden dirigida en dirección opuesta if(condition && ord.symbol==Symbols[symbol_number] && ord.comment==opposite_order_comment) { double sl=0.0; // Stop loss double tp=0.0; // Take profit //--- Obtenemos los niveles de Take Profit y Stop Loss sl=CalculatePendingOrderStopLoss(symbol_number,opposite_order_type,new_level); tp=CalculatePendingOrderTakeProfit(symbol_number,opposite_order_type,new_level); //--- Cambiamos el orden ModifyPendingOrder(symbol_number,order_ticket,opposite_order_type,new_level,sl,tp, ORDER_TIME_GTC,ord.time_expiration,ord.price_stoplimit,ord.comment,0); return; } } } }
A veces puede resultar necesario determinar si ha sido cerrada la posición de Stop loss o Take profit. En nuestro caso se darán situaciones así, por eso escribiremos funciones que determinarán este evento sobre el comentario de la última operación. Para obtener los comentarios de la última operación sobre el símbolo indicado, escribiremos una función por separado, a la que llamaremos GetLastDealComment():
//+------------------------------------------------------------------+ //| Retorna los comentarios de la última operación sobre | //| el símbolo indicado | //+------------------------------------------------------------------+ string GetLastDealComment(int symbol_number) { int total_deals =0; // Total de operaciones en la lista de la historia elegida string deal_symbol =""; // Símbolo de la operación string deal_comment =""; // Comentarios de la operación //--- Si hemos obtenido el historial de operaciones if(HistorySelect(0,TimeCurrent())) { //--- Obtenemos la cantidad de operaciones en la lista obtenida total_deals=HistoryDealsTotal(); //--- Hacemos un repaso de todas las operaciones en la lista obtenida, desde la última operación a la primera for(int i=total_deals-1; i>=0; i--) { //--- Obtenemos los comentarios de la operación deal_comment=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_COMMENT); //--- Obtenemos el símbolo de la operación deal_symbol=HistoryDealGetString(HistoryDealGetTicket(i),DEAL_SYMBOL); //--- Si el símbolo de la operación y el símbolo actual son iguales, detenemos el ciclo if(deal_symbol==Symbols[symbol_number]) break; } } //--- return(deal_comment); }
Ahora vamos a escribir las funciones que determinan el motivo del cierre de la última posición en el símbolo indicado: es lo más facil del mundo. Más abajo tenemos el código de las funciones IsClosedByTakeProfit() y IsClosedByStopLoss():
//+------------------------------------------------------------------+ //| Retorma el motivo del cierre de la posición según Take Profit | //+------------------------------------------------------------------+ bool IsClosedByTakeProfit(int symbol_number) { string last_comment=""; //--- Obtenemos los comentarios de la última operación en el símbolo indicado last_comment=GetLastDealComment(symbol_number); //--- Si en el comentario tenemos la línea "tp" if(StringFind(last_comment,"tp",0)>-1) return(true); //--- Si no tenemos la línea "tp" return(false); } //+------------------------------------------------------------------+ //| Retorna el motivo del cierre de posición según Stop Loss | //+------------------------------------------------------------------+ bool IsClosedByStopLoss(int symbol_number) { string last_comment=""; //--- Obtenemos los comentarios de la última operación en el símbolo indicado last_comment=GetLastDealComment(symbol_number); //--- Si en el comentario tenemos la línea "sl" if(StringFind(last_comment,"sl",0)>-1) return(true); //--- Si no tenemos la línea "sl" return(false); }
Otra comprobación que vamos a hacer es si se trata de la última operación en la historia de operaciones en el símbolo indicado. El ticket de la última operación se almacenará en la memoria, para ello hay que declarar la variable a nivel global del programa:
//--- Matriz para comprobar el ticket de la últmia operación en cada símbolo ulong last_deal_ticket[NUMBER_OF_SYMBOLS];
La función IsLastDealTicket(), ideada para comprobar el ticket de la última operación, tendrá el aspecto siguiente:
//+------------------------------------------------------------------+ //| Retorna el evento de la última operación en el símbolo indicado | //+------------------------------------------------------------------+ bool IsLastDealTicket(int symbol_number) { int total_deals =0; // Total de operaciones en la lista de la historia elegida string deal_symbol =""; // Símbolo de la operación ulong deal_ticket =0; // Ticket de la operación //--- Si hemos obtenido la historia de las operaciones if(HistorySelect(0,TimeCurrent())) { //--- Obtenemos la cantidad de operaciones en la lista obtenida total_deals=HistoryDealsTotal(); //--- Hacemos un repsaso de todas las operaciones en la lista obtenida, desde la última a la primera for(int i=total_deals-1; i>=0; i--) { //--- Obtenemos el ticket de la operación deal_ticket=HistoryDealGetTicket(i); //--- Obtenemos el símbolo de la operación deal_symbol=HistoryDealGetString(deal_ticket,DEAL_SYMBOL); //--- Si el Símbolo de la operación y el símbolo actual coinciden, detenemos el ciclo if(deal_symbol==Symbols[symbol_number]) { //--- Si los tickets son iguales, salimos if(deal_ticket==last_deal_ticket[symbol_number]) return(false); //--- Si los tickets no son iguales, informamos sobre ello y else { //--- Guardamos el ticket de la última operación last_deal_ticket[symbol_number]=deal_ticket; return(true); } } } } //--- return(false); }
Si la hora actual se sale de los límites del diapasón temporal indicado, la posición se cerrará forzosamente, con independencia de si se halla en pérdidas o ganancias. Para cerrar la posición, crearemos la función ClosePosition():
//+------------------------------------------------------------------+ //| Cierra la posición | //+------------------------------------------------------------------+ void ClosePosition(int symbol_number) { //--- Vemos si hay posición pos.exists=PositionSelect(Symbols[symbol_number]); //--- Si no la hay, salimos if(!pos.exists) return; //--- Establecemos el tamaño del deslizamiento en puntos trade.SetDeviationInPoints(CorrectValueBySymbolDigits(Deviation)); //--- Si la posición no se ha cerrado, mostraremos un mensaje sobre ello if(!trade.PositionClose(Symbols[symbol_number])) Print("Error al cerrar la posición: ",GetLastError()," - ",ErrorDescription(GetLastError())); }
Cuando se cierra la posición al salir del diapsón temporal comercial, es indispensable eliminar las órdenes pendientes. Para ello escribiremos la función DeleteAllPendingOrders(), que eliminará todas las órdenes pendientes en el símbolo indicado:
//+------------------------------------------------------------------+ //| Elimina todas las ordenes pendientes | //+------------------------------------------------------------------+ void DeleteAllPendingOrders(int symbol_number) { int total_orders =0; // Cantidad de órdenes pendientes ulong order_ticket =0; // Ticket de la orden //--- Obtenemos la cantidad de órdenes pendientes total_orders=OrdersTotal(); //--- Hacemos un repaso en el ciclo por todas las órdenes for(int i=total_orders-1; i>=0; i--) { //--- Si la orden ha sido elegida if((order_ticket=OrderGetTicket(i))>0) { //--- Obtenemos el símbolo de la orden GetOrderProperties(O_SYMBOL); //--- Si el símbolo de la orden y el símbolo actual coinciden if(ord.symbol==Symbols[symbol_number]) //--- Eliminamos la orden DeletePendingOrder(order_ticket); } } }
Así, en el momento actual ya tenemos listas todas las funciones imprescindibles para construir el esquema. A continuación veremos la ya conocida función TradingBlock(), que ha cambiado significativamente, y la nueva función para gestionar las órdenes pendientes ManagePendingOrders(). En ella se llevará a cabo un control absoluto de la situación actual, con respecto a las órdenes pendientes.
La función TradingBlock() para el esquema actual, tiene este aspecto:
//+------------------------------------------------------------------+ //| Bloque comercial | //+------------------------------------------------------------------+ void TradingBlock(int symbol_number) { double tp=0.0; // Take Profit double sl=0.0; // Stop Loss double lot=0.0; // Volumen para el cálculo de la posición en caso de viraje double order_price=0.0; // Precio para el establecimiento de la orden ENUM_ORDER_TYPE order_type=WRONG_VALUE; // Tipo de orden para la apertura de posición //--- Si se encuentra fuera del diapasón temporal para el establecimiento de órdenes pendientes if(!IsInOpenOrdersTimeRange(symbol_number)) return; //--- Vemos si existe una posición abierta del símbolo pos.exists=PositionSelect(Symbols[symbol_number]); //--- Si no hay posición if(!pos.exists) { //--- Obtenemos las propiedades del símbolo GetSymbolProperties(symbol_number,S_ALL); //--- Corregimos el volumen lot=CalculateLot(symbol_number,Lot[symbol_number]); //--- Si no hay orden pendiente superior if(!CheckPendingOrderByComment(symbol_number,comment_top_order)) { //--- Obtenemos el precio para el establecimiento de la orden pendiente order_price=CalculatePendingOrder(symbol_number,ORDER_TYPE_BUY_STOP); //--- Obtenemos los niveles de Take Profit y Stop Loss sl=CalculatePendingOrderStopLoss(symbol_number,ORDER_TYPE_BUY_STOP,order_price); tp=CalculatePendingOrderTakeProfit(symbol_number,ORDER_TYPE_BUY_STOP,order_price); //--- Establecemos la orden pendiente SetPendingOrder(symbol_number,ORDER_TYPE_BUY_STOP,lot,0,order_price,sl,tp,ORDER_TIME_GTC,comment_top_order); } //--- Si no hay orden pendiente inferior if(!CheckPendingOrderByComment(symbol_number,comment_bottom_order)) { //--- Obtenemos el precio para el establecimiento de la orden pendiente order_price=CalculatePendingOrder(symbol_number,ORDER_TYPE_SELL_STOP); //--- Obtenemos los niveles de Take Profit y Stop Loss sl=CalculatePendingOrderStopLoss(symbol_number,ORDER_TYPE_SELL_STOP,order_price); tp=CalculatePendingOrderTakeProfit(symbol_number,ORDER_TYPE_SELL_STOP,order_price); //--- Establecemos la orden pendiente SetPendingOrder(symbol_number,ORDER_TYPE_SELL_STOP,lot,0,order_price,sl,tp,ORDER_TIME_GTC,comment_bottom_order); } } }
Código de la fucnión ManagePendingOrders() para la gestión de órdenes pendientes:
//+------------------------------------------------------------------+ //| Gestiona las órdenes pendientes | //+------------------------------------------------------------------+ void ManagePendingOrders() { //--- Repasamos el ciclo por todos los símbolos for(int s=0; s<NUMBER_OF_SYMBOLS; s++) { //--- Si no se permite comerciar conforme a ese símbolo, pasaremos al siguiente if(Symbols[s]=="") continue; //--- Vemos si existe alguna posición abierta del símbolo pos.exists=PositionSelect(Symbols[s]); //--- Si no la hay if(!pos.exists) { //--- Si la última operación ha sido sobre el símbolo actual y // la salida de la posición ha sido en Take Profit o Stop Loss if(IsLastDealTicket(s) && (IsClosedByStopLoss(s) || IsClosedByTakeProfit(s))) //--- Eliminamos todas las órdenes pendientes sobre el símbolo DeleteAllPendingOrders(s); //--- Pasamos al siguiente símbolo continue; } //--- Si hay posición ulong order_ticket =0; // Tiket de la orden int total_orders =0; // Cantidad de órdenes pendientes int symbol_total_orders =0; // Cantidad de órdenes pendientes sobre el símbolo indicado string opposite_order_comment =""; // Comentarios de la orden opuesta ENUM_ORDER_TYPE opposite_order_type =WRONG_VALUE; // Tipo de la orden //--- Obtenemos la cantidad total de órdenes pendientes total_orders=OrdersTotal(); //--- Obtenemos la cantidad de órdenes sobre el símbolo indicado symbol_total_orders=OrdersTotalBySymbol(Symbols[s]); //--- Obtenemos las propiedades del símbolo GetSymbolProperties(s,S_ASK); GetSymbolProperties(s,S_BID); //--- Obtenemos los comentarios de la posición elegida GetPositionProperties(s,P_COMMENT); //--- Si los comentarios de la posición provienen de la orden superior, // hay que eliminar/modificar/establecer la orden inferior if(pos.comment==comment_top_order) { opposite_order_type =ORDER_TYPE_SELL_STOP; opposite_order_comment =comment_bottom_order; } //--- Si los comentarios de la posición provienen de la orden inferior, // hay que eliminar/modificar/establecer la orden superior if(pos.comment==comment_bottom_order) { opposite_order_type =ORDER_TYPE_BUY_STOP; opposite_order_comment =comment_top_order; } //--- Si no hay órdenes pendientes sobre este símbolo if(symbol_total_orders==0) { //--- Si el viraje de posición está conectado, establecemos una orden dirigida en sentido opuesto if(Reverse[s]) { double tp=0.0; // Take Profit double sl=0.0; // Stop Loss double lot=0.0; // Volumen para el cálculo posición en caso de viraje de posición double order_price=0.0; // Precio para el establecimiento de la orden //--- Obtenemos el precio para el establecimiento de la orden pendiente order_price=CalculatePendingOrder(s,opposite_order_type); //--- Obtenemos los niveles de Take Profit y Stop Loss sl=CalculatePendingOrderStopLoss(s,opposite_order_type,order_price); tp=CalculatePendingOrderTakeProfit(s,opposite_order_type,order_price); //--- Calculamos un volumen doble lot=CalculateLot(s,pos.volume*2); //--- Establecemos la orden pendiente SetPendingOrder(s,opposite_order_type,lot,0,order_price,sl,tp,ORDER_TIME_GTC,opposite_order_comment); //--- Corregimos el Stop Loss con respecto a la orden CorrectStopLossByOrder(s,order_price,opposite_order_type); } return; } //--- Si hay órdenes pendientes sobre este símbolo, entonces, dependiendo de las condiciones, eliminamos // modificamos la orden dirigida en sentido opuesto if(symbol_total_orders>0) { //--- Hacemos una pasada en el ciclo por todas las órdenes, desde la última a la primera for(int i=total_orders-1; i>=0; i--) { //--- Si se ha elegido la orden if((order_ticket=OrderGetTicket(i))>0) { //--- Obtenemos el símbolo de la orden GetPendingOrderProperties(O_SYMBOL); //--- Obtenemos el comentario de la orden GetPendingOrderProperties(O_COMMENT); //--- Si coinciden el símbolo de la orden y de la posición, // e igualmente coinciden los comentarios de la orden y la orden dirigida en sentido contrario if(ord.symbol==Symbols[s] && ord.comment==opposite_order_comment) { //--- Si el viraje de posición se encuentra desconectado if(!Reverse[s]) //--- Eliminamos la orden DeletePendingOrder(order_ticket); //--- Si el viraje de posición se encuentra conectado else { double lot=0.0; //--- Obtenemos las propiedades de la orden actual GetPendingOrderProperties(O_ALL); //--- Obtenemos el volumen de la posición actual GetPositionProperties(s,P_VOLUME); //--- Si la orden ya ha sido modificada, salimos del ciclo if(ord.volume_initial>pos.volume) break; //--- Calculamos un volumen doble lot=CalculateLot(s,pos.volume*2); //--- Cambiamos (establecemos de nuevo) la orden ModifyPendingOrder(s,order_ticket,opposite_order_type, ord.price_open,ord.sl,ord.tp, ORDER_TIME_GTC,ord.time_expiration, ord.price_stoplimit,opposite_order_comment,lot); } } } } } } }
Queda introducir unos pequeños cambios en el archivo principal del programa. Añadimos el operador de eventos comerciales OnTrade(). En esta función se realizará la comprobación de la situación actual, con respecto a las órdenes pendientes sobre un evento comercial.
//+------------------------------------------------------------------+ //| Procesamiento de eventos comerciales | //+------------------------------------------------------------------+ void OnTrade() { //--- Comprobamos el estado de las órdenes pendientes ManagePendingOrders(); }
Asímismo, la función ManagePendingOrders() se usará en el operador de eventos personalizados OnChartEvent():
//+------------------------------------------------------------------+ //| Operador de eventos personalizados y eventos del gráfico | //+------------------------------------------------------------------+ void OnChartEvent(const int id, // Identificador del evento const long &lparam, // Parámetro del evento del tipo long const double &dparam, // Parámetro del evento del tipo double const string &sparam) // Parámetro del evento del tipo string { //--- Si se trata de un evento personalizado if(id>=CHARTEVENT_CUSTOM) { //--- Salir, en caso de que esté prohibido comerciar if(CheckTradingPermission()>0) return; //--- Si ha tenido lugar el evento "tick" if(lparam==CHARTEVENT_TICK) { //--- Comprobamos el estado de las órdenes pendientes ManagePendingOrders(); //--- Comprobamos las señales y comerciamos conforme a él CheckSignalsAndTrade(); return; } } }
Y, al fin, los cambios han tenido lugar en la función CheckSignalsAndTrade(). En el código se destacan las líneas con las funciones nuevas que hemos visto en este artículo.
//+------------------------------------------------------------------+ //| Comprueba las señales y comercia conforme al evento "nueva barra"| //+------------------------------------------------------------------+ void CheckSignalsAndTrade() { //--- Hacemos una pasada por todos los símbolos indicados for(int s=0; s<NUMBER_OF_SYMBOLS; s++) { //--- Si el comercio conforme a este símbolo no está permitido, salimos if(Symbols[s]=="") continue; //--- Si la barra no es nueva, pasamos al siguiente símbolo if(!CheckNewBar(s)) continue; //--- Si hay una nueva barra else { //--- Si se encuentra fuera del diapasón temporal if(!IsInTradeTimeRange(s)) { //--- Cerramos la posición ClosePosition(s); //--- Eliminamos todas las órdenes pendientes DeleteAllPendingOrders(s); //--- Pasamos al siguiente símbolo continue; } //--- Obtenemos los datos de las barras GetBarsData(s); //--- Comprobamos las condiciones y comerciamos TradingBlock(s); //--- Si está conectado el viraje de posición if(Reverse[s]) //--- Prolongamos el stop loss para la orden pendiente ModifyPendingOrderTrailingStop(s); //--- Si está desconectado el viraje de posición else //--- Prolongamos el stop loss ModifyTrailingStop(s); } } }
Ahora ya está todo listo y podemos probar a optimizar los parámetros de este asesor multidivisa. Estableceremos los siguientes ajustes en el Simulador de estrategias:
Dib. 1 - Ajustes del simulador para la optimización de parámetros
Efectuaremos la optimización de parámetros para la pareja de divisas EURUSD, y después para AUDUSD. En la captura de pantalla se muestran los parámetros que debemos establecer para optimizar EURUSD:
Dib. 2 - Establecimiento de los parámetros para optimizar el asesor multidivisa
Después de haber realizado la optimización de los parámetros para la pareja de divisas EURUSD, tendremos que optimizar exactamente los mismos parámetros para AUDUSD. Más abajo tenemos el resultado conjunto de estos símbolos. Los resultados han sido elegidos según el factor máximo de recuperación. Para la simulación sobre los dos símbolos para el lote, se ha establecido el valor 1.
Dib. 3 - Resultado conjunto sobre los dos símbolos
Conclusión
Con esto damos por terminado el artículo. Si disponemos de funciones ya listas, podemos concentrarnos en el desarrollo del propio concepto de toma de decisiones comerciales. En ese caso, los cambios principales se realizarán en las funciones TradingBlock() y ManagePendingOrders(). Para aquellos que hace poco que han empezado a estudiar programación en MQL5, les recomiendo, para practicar, que prueben a añadir una mayor cantidad de símbolos y a cambiar la lógica del algoritmo comercial.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/755





- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso