Instrumental para el comercio manual rápido: Trabajando con órdenes abiertas y órdenes pendientes
Contenido
Introducción
En el artículo anterior, creamos una funcionalidad básica diseñada para simplificar y acelerar el trabajo de los tráders que se dedican al comercio manual. En primer lugar, nos centramos en facilitar la definición de ciertas solicitudes, es decir, en conseguir un instrumental más afinado para el ingreso en el mercado, con su funcionalidad correspondiente. No obstante, ya sabemos desde hace mucho que cualquier estrategia comercial, sea esta manual o automática, debe contener tres etapas básicas para trabajar con los mercados. Nos referimos a las reglas de ingreso en el mercado, el monitoreo de las posiciones abiertas y las condiciones de cierre de estas. En cuanto a la funcionalidad actual, solo hemos desarrollado su primera etapa, así que resultaría lógico añadir más recursos para trabajar con las posiciones que ya están abiertas o están pendientes, y también ampliar las condiciones para el cierre de transacciones. En este caso, todos los cálculos deben ofrecerse al instrumental, mientras que la propia decisión se dejan al tráder.
Formulando la tarea
Vamos a empezar determinando el rango de tareas requeridas para implementar la nueva funcionalidad. Para ello, destacaremos las principales etapas que marcarán el posterior desarrollo de la aplicación actual:
- Rediseño de la estructura de la aplicación. Originalmente, la aplicación se creó como una ventana principal con un grupo de botones para tareas. Dos de ellos abrían ventanas modales con herramientas para colocar posiciones de mercado y órdenes pendientes. Los tres botones restantes ofrecían una funcionalidad sencilla para cerrar posiciones según el mercado y eliminar todas las órdenes pendientes. Ahora, necesitamos añadir una interfaz para gestionar las posiciones actuales y editar las órdenes pendientes.
- Ampliación de los modos de trabajo con órdenes. Anteriormente, podíamos cerrar o bien todas las órdenes rentables, o bien todas las órdenes con pérdidas. Ahora, necesitamos una funcionalidad más flexible, dividida según el tipo de posición, y que además proporcione la posibilidad de establecer varias condiciones tanto para cerrar todas las órdenes, como solo las seleccionadas. Respecto a las órdenes pendientes, solo teníamos la posibilidad de cerrar en masa las órdenes creadas anteriormente con el instrumental. Y esto no es suficiente: deberíamos poder eliminar una orden pendiente aparte o modificar esta. Asimismo, sería positivo tener listas separadas para las órdenes de mercado y las órdenes pendientes.
Vamos a analizar con mayor detalle cada una de las etapas por separado.
Rediseñando la estructura de la aplicación
Para comprender todo con mayor claridad, debemos recordar lo que tenemos en estos momentos. La fig.1 muestra los bloques principales, que realizan tareas en dos categorías: apertura/creación y cierre/eliminación. La tercera categoría, como hemos escrito más arriba, se encarga del acompañamiento y la edición manuales.
Fig.1 Bloques principales del instrumental
Así, introduciremos tres pestañas para las categorías. En la primera de ellas, dejaremos la funcionalidad representada en la fig.1, mientras que en las otras dos, se localizará el trabajo con las posiciones de mercado y las órdenes pendientes.
Fig.2 Nueva estructura de la aplicación
En la pestaña Trading tendremos todo lo que antes se encontraba en la ventana principal del instrumental. En la pestaña Control de Posiciones Abiertas se encontrará un recuadro con las posiciones ya existentes, abiertas con esta aplicación. Asimismo, tendremos la posibilidad de trabajar con ellas. El control de órdenes pendientes también contendrá solo aquellas posiciones creadas por el instrumental, así como los elementos de la interfaz para cerrar y editar las órdenes colocadas. Vamos a echar un vistazo más detallado.
Fig.3 Pestaña Control de Posiciones de Mercado
Bien, en la fig.3 se encuentra la Pestaña Control de Posiciones de Mercado Esta incluye los siguientes elementos:
- Recuadro de posiciones de mercado. Representa la información de todas las posiciones abiertas por esta aplicación. Es semejante al recuadro que se encuentra en la pestaña Comercio en el terminal MetaTrader 5.
- Tres campos de edición. Se corresponden y relacionan con las columnas de izquierda a derecha: Volumen, Stop Loss, Take Profit.
- Dos botones. Al pulsar en una línea del recuadro en los campos de edición, se representarán los parámetros de la posición que se puedan editar. Así, al pulsar en Cambiar, podremos modificar el Stop Loss o el Take Profit de una posición seleccionada en el recuadro, o eliminar estos. El botón Cerrar cierra una posición al precio actual. Debemos notar que, al realizar el cierre, se comprueba el campo de edición Volumen. Es decir, podemos seleccionar un valor de lote menor que el lote de la posición actual y cerrar la posición parcialmente.
Ahora, en la fig.4, vamos a analizar la pestaña Control de Órdenes Pendientes.
Fig.4 Control de Órdenes Pendientes.
Aquí, todo es prácticamente igual:
- Recuadro de órdenes pendientes. Contiene la lista de órdenes pendientes abiertas con este instrumental.
- Tres campos de edición. Aquí, al elegir una orden pendiente en el recuadro, podremos cambiar su precio de ejecución actual, y también modificar o eliminar un Stop-Loss o un Take-Profit.
- Dos botones. El botón Cambiar, a diferencia del recuadro de posiciones de mercado, accede a los tres campos de edición para editar la orden pendiente seleccionada. El botón Cerrar elimina la orden.
Vamos a volver a la pestaña Comercio, que también tendrá cambios importantes. En primer lugar, completaremos el instrumental existente para cerrar posiciones tanto rentables como con pérdidas. Ahora podremos cerrar solo posiciones largas o cortas. Asimismo, presentaremos los conmutadores de los modos de cierre.
Fig.5 Ampliando la funcionalidad de cierre de órdenes de mercado.
Como podemos ver en la fig.5, tenemos 4 nuevos botones: Cerrar BUY rentables, Cerrar SELL rentables, Cerrar BUY con pérdidas, Cerrar SELL con pérdidas. Hay conmutadores adicionales a la derecha de los botones; merece la pena analizar estos con más detalle. Los conmutadores resultan similares, por lo que las siguientes descripciones se aplicarán a todos ellos.
- All. Valor por defecto. Aplicado a los botones, no establece ninguna restricción, y cierra además todos los elementos seleccionados.
- >Point. Cierra todas las posiciones del tipo seleccionado con un beneficio/pérdidas mayores que el número de puntos especificado.
- >Currency. Cierra todas las posiciones del tipo seleccionado con un beneficio/pérdidas mayores que la cantidad especificada en la divisa de depósito.
- Sum>Points. Cierra todas las posiciones del tipo seleccionado con un beneficio/pérdidas totales mayores que la cantidad especificada en la divisa de depósito.
- Sum>Currency. Cierra todas las posiciones del tipo seleccionado con un beneficio/pérdidas total mayor que la cantidad especificada en la divisa de depósito.
Para mayor claridad, ofrecemos un ejemplo con la configuración de la Fig. 5: Cerrar Todas las Posiciones con Pérdidas y la opción sum>currency. En este caso, el instrumental encontrará todas las posiciones abiertas por él, sumará su beneficio y, si es superior a 10 unidades en la divisa del depósito, las cerrará todas.
Implementando los complementos del instrumental
Como base, usaremos el anterior proyecto al final del artículo Instrumental para el comercio manual rápido: Funcionalidad básica. En primer lugar, necesitaremos construir de nuevo la estructura de la ventana principal como se muestra en la figura 2. Para ello, añadiremos el elemento de interfaz Pestaña creando en la clase básica CProgram el método CreateTabs() e implementándolo en el archivo MainWindow.mqh.
//+------------------------------------------------------------------+ //| Create a group with tabs | //+------------------------------------------------------------------+ bool CFastTrading::CreateTabs(const int x_gap,const int y_gap) { //--- Store the pointer to the main control m_tab.MainPointer(m_main_window); //--- Properties m_tab.Font(m_base_font); m_tab.FontSize(m_base_font_size); m_tab.LabelColor(clrWhite); m_tab.LabelColorHover(clrWhite); m_tab.IsCenterText(true); m_tab.AutoXResizeMode(true); m_tab.AutoYResizeMode(true); m_tab.AutoXResizeRightOffset(5); m_tab.AutoYResizeBottomOffset(5); m_tab.TabsYSize(27); m_tab.GetButtonsGroupPointer().Font(m_base_font); m_tab.GetButtonsGroupPointer().FontSize(m_base_font_size); //--- Add tabs with the specified properties string tabs_names[3]; tabs_names[0]=TRADING; tabs_names[1]=CAPTION_M_CONTROL_NAME; tabs_names[2]=CAPTION_P_CONTROL_NAME; for(int i=0; i<3; i++) m_tab.AddTab(tabs_names[i],180); //--- Create a control element if(!m_tab.CreateTabs(x_gap,y_gap)) return(false); //--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(0,m_tab); return(true); }
En el cuerpo del método creado se han introducido tres nuevas macrosustituciones que sirven como encabezados de las pestañas, por eso debemos añadirlas en los dos idiomas al archivo Defines.mqh:
#define TRADING (m_language==RUSSIAN ? "Трейдинг" : "Trading") #define CAPTION_M_CONTROL_NAME (m_language==RUSSIAN ? "Контроль рыночных позиций" : "Market Control") #define CAPTION_P_CONTROL_NAME (m_language==RUSSIAN ? "Контроль отложенных ордеров" : "Pending Control")
Antes de aplicar el método que acabamos de crear, necesitaremos reconstruir los métodos que crean botones y vincular estos métodos a la primera pestaña. Para ello, vamos a buscar el método común CreateButton() que crea ambos y a editarlo de la forma siguiente:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreateButton(CButton &button,string text,color baseclr,int x_gap,int y_gap) { //--- Store the window pointer button.MainPointer(m_tab); //--- Attach to tab m_tab.AddToElementsArray(0,button); //--- Set properties before creation button.XSize(180); button.YSize(40); button.Font(m_base_font); button.FontSize(m_base_font_size); button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(baseclr); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(baseclr); button.LabelColor(clrWhite); button.LabelColorPressed(clrWhite); button.LabelColorHover(clrWhite); button.IsCenterText(true); //--- Create a control element if(!button.CreateButton(text,x_gap,y_gap)) return(false); //--- Add a pointer to the element to the database CWndContainer::AddToElementsArray(0,button); return(true); }
A continuación, aplicamos los cambios actuales en el método para crear la ventana principal.
//+------------------------------------------------------------------+ //| Create a form for orders | //+------------------------------------------------------------------+ bool CFastTrading::CreateMainWindow(void) { //--- Add a window pointer to the window array CWndContainer::AddWindow(m_main_window); //--- Properties m_main_window.XSize(600); m_main_window.YSize(375); //--- Coordinates int x=5; int y=20; m_main_window.CaptionHeight(22); m_main_window.IsMovable(true); m_main_window.CaptionColor(m_caption_color); m_main_window.CaptionColorLocked(m_caption_color); m_main_window.CaptionColorHover(m_caption_color); m_main_window.BackColor(m_background_color); m_main_window.FontSize(m_base_font_size); m_main_window.Font(m_base_font); //--- Create the form if(!m_main_window.CreateWindow(m_chart_id,m_subwin,CAPTION_NAME,x,y)) return(false); //--- Tabs if(!CreateTabs(5,22+27)) return(false); //--- if(!CreateButton(m_order_button[0],MARKET_ORDER_NAME+"(M)",C'87,128,255',20,10)) return(false); if(!CreateButton(m_order_button[1],PENDING_ORDER_NAME+"(P)",C'31,209,111',210,10)) return(false); if(!CreateButton(m_order_button[2],MARKET_ORDERS_PROFIT_CLOSE+"(C)",C'87,128,255',20,60)) return(false); if(!CreateButton(m_order_button[3],MARKET_ORDERS_LOSS_CLOSE+"(D)",C'87,128,255',20,110)) return(false); if(!CreateButton(m_order_button[4],PEND_ORDERS_ALL_CLOSE+"(R)",C'31,209,111',210,60)) return(false); //--- return(true); }
Como vemos aquí, primero hemos añadido las pestañas, luego hemos actualizado las llamadas a los métodos que crean los botones, y después hemos modificado el tamaño de la ventana principal. Tras de compilar el proyecto, obtendremos como resultado la transferencia de todos los botones a la primera de las tres pestañas creadas:
Fig.6 Creando las pestañas y transfiriendo los botones a la primera de ellas
De acuerdo con la figura 5, creamos los botones y campos de edición adicionales para implementar la funcionalidad requerida. Para los botones grandes, utilizaremos el método CreateButton() actualizado; para crear los campos de edición y los conmutadores, en cambio, necesitaremos introducir los métodos adicionales: CreateModeButton(), el conmutador de modo, y CreateModeEdit(), los campos de edición de los valores. Aquí tenemos su implementación completa:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreateModeButton(CButton &button,string text,int x_gap,int y_gap) { //--- Store the window pointer button.MainPointer(m_tab); //--- Attach to tab m_tab.AddToElementsArray(0,button); color baseclr=clrDarkViolet; //--- Set properties before creation button.XSize(80); button.YSize(20); button.Font(m_base_font); button.FontSize(9); button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(baseclr); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(baseclr); button.LabelColor(clrWhite); button.LabelColorPressed(clrWhite); button.LabelColorHover(clrWhite); button.IsCenterText(true); //--- Create a control element if(!button.CreateButton(text,x_gap,y_gap)) return(false); //--- Add a pointer to the element to the database CWndContainer::AddToElementsArray(0,button); return(true); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreateModeEdit(CTextEdit &text_edit,int x_gap,int y_gap) { //--- Store the window pointer text_edit.MainPointer(m_tab); //--- Attach to tab m_tab.AddToElementsArray(0,text_edit); //--- Properties text_edit.XSize(80); text_edit.YSize(20); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.SetDigits(2); text_edit.MaxValue(99999); text_edit.StepValue(0.01); text_edit.MinValue(0.01); text_edit.SpinEditMode(true); text_edit.GetTextBoxPointer().XGap(1); text_edit.GetTextBoxPointer().XSize(80); //--- Create a control element if(!text_edit.CreateTextEdit("",x_gap,y_gap)) return(false); text_edit.SetValue(string(0.01)); text_edit.IsLocked(true); //--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(0,text_edit); return(true); }
Más abajo, podemos ver el método para crear la ventana principal utilizando los métodos mecionados arriba y la adición de los botones de la fig.5:
//+------------------------------------------------------------------+ //| Create a form for orders | //+------------------------------------------------------------------+ bool CFastTrading::CreateMainWindow(void) { //--- Add a window pointer to the window array CWndContainer::AddWindow(m_main_window); //--- Properties m_main_window.XSize(600); m_main_window.YSize(375); //--- Coordinates int x=5; int y=20; m_main_window.CaptionHeight(22); m_main_window.IsMovable(true); m_main_window.CaptionColor(m_caption_color); m_main_window.CaptionColorLocked(m_caption_color); m_main_window.CaptionColorHover(m_caption_color); m_main_window.BackColor(m_background_color); m_main_window.FontSize(m_base_font_size); m_main_window.Font(m_base_font); //--- Create the form if(!m_main_window.CreateWindow(m_chart_id,m_subwin,CAPTION_NAME,x,y)) return(false); //--- Tabs if(!CreateTabs(5,22+27)) return(false); //--- if(!CreateButton(m_order_button[0],MARKET_ORDER_NAME+"(M)",C'87,128,255',10,10)) return(false); if(!CreateButton(m_order_button[1],PENDING_ORDER_NAME+"(P)",C'31,209,111',300,10)) return(false); if(!CreateButton(m_order_button[2],CLOSE_ALL_PROFIT+"(C)",C'87,128,255',10,10+45*1)) return(false); if(!CreateButton(m_order_button[3],PEND_ORDERS_ALL_CLOSE+"(R)",C'31,209,111',300,10+45*1)) return(false); if(!CreateButton(m_order_button[4],CLOSE_BUY_PROFIT,C'87,128,255',10,10+45*2)) return(false); if(!CreateButton(m_order_button[5],CLOSE_SELL_PROFIT,C'87,128,255',10,10+45*3)) return(false); if(!CreateButton(m_order_button[6],CLOSE_ALL_LOSS+"(D)",C'87,128,255',10,10+45*4)) return(false); if(!CreateButton(m_order_button[7],CLOSE_BUY_LOSS,C'87,128,255',10,10+45*5)) return(false); if(!CreateButton(m_order_button[8],CLOSE_SELL_LOSS,C'87,128,255',10,10+45*6)) return(false); //--- for(int i=0; i<6; i++) { if(!CreateModeButton(m_mode_button[i],"all",205-10,10+45*(i+1))) return(false); if(!CreateModeEdit(m_mode_edit[i],204-10,30+45*(i+1))) return(false); } //--- return(true); }
Aquí, se introducen también nuevas macrosustituciones, por eso añadiremos sus valores en ambos idiomas a Defines.mqh:
#define CLOSE_BUY_PROFIT (m_language==RUSSIAN ? "Закрыть BUY прибыльные" : "Close BUY Profit") #define CLOSE_SELL_PROFIT (m_language==RUSSIAN ? "Закрыть SELL прибыльные" : "Close SELL Profit") #define CLOSE_ALL_PROFIT (m_language==RUSSIAN ? "Закрыть ВСЕ прибыльные" : "Close ALL Profit") #define CLOSE_BUY_LOSS (m_language==RUSSIAN ? "Закрыть BUY убыточные" : "Close BUY Losing") #define CLOSE_SELL_LOSS (m_language==RUSSIAN ? "Закрыть SELL убыточные" : "Close SELL Losing") #define CLOSE_ALL_LOSS (m_language==RUSSIAN ? "Закрыть ВСЕ убыточные" : "Close ALL Losing")
Compilamos el proyecto y obtenemos el siguiente resultado intermedio:
Fig.7 Añadiendo los botones y los conmutadores de modos
No obstante, esto es tan solo una implementación visual, una fachada. El siguiente paso será asignar su propia tarea lógica a cada uno de los elementos añadidos. En primer lugar, deberemos configurar el mecanismo de conmutación, ya que todos los elementos posteriores se referirán a sus valores y estados. Para los objetos de botón, crearemos el nuevo método ModeButtonSwitch(). Su misión consistirá en cambiar los modos de pulsación de los botones.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::ModeButtonSwitch(CButton &button,long lparam,string &states[]) { if(lparam==button.Id()) { int size=ArraySize(states); for(int i=0; i<size; i++) { if(button.LabelText()==states[i]) { if(i==size-1) { SetButtonParam(button,states[0]); break; } else { SetButtonParam(button,states[i+1]); break; } } } } }
El segundo nuevo método, ModeEditSwitch(), es necesario para que los ajustes de los campos de edición se correspondan con el modo del botón seleccionado. Por ejemplo, los puntos son números enteros, y cuando usamos la divisa del depósito, los valores deberán tener 2 dígitos.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::ModeEditSwitch(long lparam,string &states[]) { for(int i=0; i<6; i++) { if(lparam==m_mode_button[i].Id()) { if(m_mode_button[i].LabelText()==states[1]) { m_mode_edit[i].IsLocked(false); m_mode_edit[i].SetDigits(0); m_mode_edit[i].StepValue(1); m_mode_edit[i].MaxValue(9999); m_mode_edit[i].MinValue(1); m_mode_edit[i].SetValue(string(20)); m_mode_edit[i].GetTextBoxPointer().Update(true); m_current_mode[i]=1; } else if(m_mode_button[i].LabelText()==states[2]) { m_mode_edit[i].IsLocked(false); m_mode_edit[i].SetDigits(2); m_mode_edit[i].StepValue(0.01); m_mode_edit[i].MaxValue(99999); m_mode_edit[i].MinValue(0.01); m_mode_edit[i].SetValue(string(0.1)); m_mode_edit[i].GetTextBoxPointer().Update(true); m_current_mode[i]=2; } else if(m_mode_button[i].LabelText()==states[3]) { m_mode_edit[i].IsLocked(false); m_mode_edit[i].SetDigits(0); m_mode_edit[i].StepValue(1); m_mode_edit[i].MaxValue(9999); m_mode_edit[i].MinValue(1); m_mode_edit[i].SetValue(string(20)); m_mode_edit[i].GetTextBoxPointer().Update(true); m_current_mode[i]=3; } else if(m_mode_button[i].LabelText()==states[4]) { m_mode_edit[i].IsLocked(false); m_mode_edit[i].SetDigits(2); m_mode_edit[i].StepValue(0.01); m_mode_edit[i].MaxValue(99999); m_mode_edit[i].MinValue(0.01); m_mode_edit[i].SetValue(string(0.1)); m_mode_edit[i].GetTextBoxPointer().Update(true); m_current_mode[i]=4; } else { m_mode_edit[i].IsLocked(true); m_current_mode[i]=0; } } } }
En la implementación actual, hemos introducido la matriz estática m_current_mode con el tamaño correspondiente al número de conmutadores de los modos, es decir, 6. En esta se registran los modos seleccionados por el usuario para cada botón que cierra la posición. Para que todo lo que acabamos de añadir funcione correctamente, vamos a pasar a nuestro manejador de eventos OnEvent() y añadir el siguiente código a la sección de eventos de pulsación de botón:
//--- string states[5]= {"all",">points",">currency","sum>points","sum>currency"}; for(int i=0; i<6; i++) ModeButtonSwitch(m_mode_button[i],lparam,states); //--- ModeEditSwitch(lparam,states);
Compilamos el proyecto. Ahora, en la fig. 8, podemos ver que al alternar el modo cambian también las propiedades de los campos de edición.
Fig.8 Alternando el modo de cierre de posiciones de mercado
El siguiente paso será implementar la lógica de acción según las descripciones de los botones, que también deberán estar vinculados a los modos añadidos anteriormente. Ya tenemos dos acciones: "Cerrar Todas las Posiciones Rentables" y "Cerrar Todas las Posiciones con Pérdidas". Ahora, deberíamos complementarlas teniendo en cuenta los nuevos modos de cierre. A estas acciones les corresponden los métodos CloseAllMarketProfit() y CloseAllMarketLoss().
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::CloseAllMarketProfit(int id,long lparam) { if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_order_button[2].Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_C)) { //--- declare the request and the result MqlTradeRequest request; MqlTradeResult result; int total=PositionsTotal(); // the number of open positions int sum_pp=0; double sum_cur=0.0; //--- Calculate profit for modes 4 and 5 if(m_current_mode[0]>2) { for(int i=total-1; i>=0; i--) { //--- order parameters ulong position_ticket=PositionGetTicket(i); // position ticket string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol ulong magic=PositionGetInteger(POSITION_MAGIC); // position MagicNumber ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // position type double profit_cur=PositionGetDouble(POSITION_PROFIT); double swap=PositionGetDouble(POSITION_SWAP); //--- int profit_pp; if(type==POSITION_TYPE_BUY) profit_pp=int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point); else profit_pp=int((PositionGetDouble(POSITION_PRICE_OPEN)-SymbolInfoDouble(position_symbol,SYMBOL_ASK))/_Point); //--- if(magic==m_magic_number) if(position_symbol==Symbol()) { switch(m_current_mode[0]) { case 3: sum_pp+=profit_pp; break; case 4: sum_cur+=profit_cur+swap; break; } } } } //--- iterate over open positions for(int i=total-1; i>=0; i--) { //--- order parameters ulong position_ticket=PositionGetTicket(i); // position ticket string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol int digits=(int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); // the number of decimal places ulong magic=PositionGetInteger(POSITION_MAGIC); // position MagicNumber double volume=PositionGetDouble(POSITION_VOLUME); // position volume ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // position type double profit_cur=PositionGetDouble(POSITION_PROFIT); double swap=PositionGetDouble(POSITION_SWAP); int profit_pp; //--- if(type==POSITION_TYPE_BUY) profit_pp=int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point); else profit_pp=int((PositionGetDouble(POSITION_PRICE_OPEN)-SymbolInfoDouble(position_symbol,SYMBOL_ASK))/_Point); //--- if MagicNumber matches if(magic==m_magic_number) if(position_symbol==Symbol()) { if( (m_current_mode[0]==0 && profit_cur+swap>0) || // Close all profitable positions (m_current_mode[0]==1 && profit_pp>int(m_mode_edit[0].GetValue())) || // Close all positions having profit more than N points (m_current_mode[0]==2 && profit_cur+swap>double(m_mode_edit[0].GetValue())) || // Close all positions having profit more than N deposit currency units (m_current_mode[0]==3 && sum_pp>int(m_mode_edit[0].GetValue())) || // Close all positions with the total profit more than N points (m_current_mode[0]==4 && sum_cur>double(m_mode_edit[0].GetValue())) // Close all positions with the total profit more than N deposit currency units ) { //--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); //--- set the operation parameters request.action =TRADE_ACTION_DEAL; // trading operation type request.position =position_ticket; // position ticket request.symbol =position_symbol; // symbol request.volume =volume; // position volume request.deviation=5; // allowable price deviation request.magic =m_magic_number; // position MagicNumber //--- Set order price and type depending on the position type if(type==POSITION_TYPE_BUY) { request.price=SymbolInfoDouble(position_symbol,SYMBOL_BID); request.type =ORDER_TYPE_SELL; } else { request.price=SymbolInfoDouble(position_symbol,SYMBOL_ASK); request.type =ORDER_TYPE_BUY; } //--- sending a request bool res=true; for(int j=0; j<5; j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) break; else PrintFormat("OrderSend error %d",GetLastError()); // if unable to send the request, output the error code } } } } } }
Se trata de una modificación del método Cierre de Todas las Posiciones de Mercado. Como recordaremos, antes hemos introducido la matriz estática m_current_mode, que controla la selección del modo para cada una de las acciones en el botón. Por eso, el cálculo se efectúa para los modos 4 y 5, en los que todas las posiciones se cierran según el beneficio total en puntos o en divisa del depósito. A continuación, elegimos las posiciones que pertenecen a nuestro instrumental y, dependiendo del modo de cierre seleccionado, elegimos las condiciones según las cuales deberán cerrarse todas las posiciones de mercado creadas por el instrumental.
De forma análoga, cambiamos también el segundo método que cierra todas las posiciones con pérdidas:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::CloseAllMarketLoss(int id,long lparam) { if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_order_button[6].Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_D)) { //--- declare the request and the result MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); int total=PositionsTotal(); // the number of open positions int sum_pp=0; double sum_cur=0.0; //--- Calculate losses if(m_current_mode[3]>2) { for(int i=total-1; i>=0; i--) { //--- order parameters ulong position_ticket=PositionGetTicket(i); // position ticket string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol ulong magic=PositionGetInteger(POSITION_MAGIC); // position MagicNumber ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // position type double loss_cur=PositionGetDouble(POSITION_PROFIT); double swap=PositionGetDouble(POSITION_SWAP); int loss_pp; //--- if(type==POSITION_TYPE_BUY) loss_pp=int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point); else loss_pp=int((PositionGetDouble(POSITION_PRICE_OPEN)-SymbolInfoDouble(position_symbol,SYMBOL_ASK))/_Point); //--- if(magic==m_magic_number) if(position_symbol==Symbol()) { switch(m_current_mode[3]) { case 3: sum_pp+=loss_pp; break; case 4: sum_cur+=loss_cur+swap; break; } } } } //--- iterate over open positions for(int i=total-1; i>=0; i--) { //--- order parameters ulong position_ticket=PositionGetTicket(i); // position ticket string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol int digits=(int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); // the number of decimal places ulong magic=PositionGetInteger(POSITION_MAGIC); // position MagicNumber double volume=PositionGetDouble(POSITION_VOLUME); // position volume ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // position type double loss_cur=PositionGetDouble(POSITION_PROFIT); double swap=PositionGetDouble(POSITION_SWAP); int loss_pp; if(type==POSITION_TYPE_BUY) loss_pp=int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point); else loss_pp=int((PositionGetDouble(POSITION_PRICE_OPEN)-SymbolInfoDouble(position_symbol,SYMBOL_ASK))/_Point); //--- if MagicNumber matches if(magic==m_magic_number) if(position_symbol==Symbol()) { if( (m_current_mode[3]==0 && loss_cur+swap<0) || (m_current_mode[3]==1 && loss_pp<-int(m_mode_edit[3].GetValue())) || (m_current_mode[3]==2 && loss_cur+swap<-double(m_mode_edit[3].GetValue())) || (m_current_mode[3]==3 && sum_pp<-int(m_mode_edit[3].GetValue())) || (m_current_mode[3]==4 && sum_cur<-double(m_mode_edit[3].GetValue())) ) { //--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); //--- set the operation parameters request.action=TRADE_ACTION_DEAL; // trading operation type request.position=position_ticket; // position ticket request.symbol=position_symbol; // symbol request.volume=volume; // position volume request.deviation=5; // allowable price deviation request.magic=m_magic_number; // position MagicNumber //--- Set order price and type depending on the position type if(type==POSITION_TYPE_BUY) { request.price=SymbolInfoDouble(position_symbol,SYMBOL_BID); request.type =ORDER_TYPE_SELL; } else { request.price=SymbolInfoDouble(position_symbol,SYMBOL_ASK); request.type =ORDER_TYPE_BUY; } //--- sending a request bool res=true; for(int j=0; j<5; j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) break; else PrintFormat("OrderSend error %d",GetLastError()); // if unable to send the request, output the error code } } } } } }
Además, se realiza el cálculo para los métodos de comprobación del valor de las pérdidas en todas las posiciones abiertas en puntos o divisa de depósito, y, a continuación, dependiendo del modo de cierre, se comprueban las condiciones según las cuales se cierran todas las posiciones creadas por la aplicación.
A continuación, vamos a pasar a las nuevas acciones que se realizan con las posiciones abiertas, es decir, el Cierre de posiciones Buy/Sell, o bien rentables, o bien con pérdidas. En esencia, son casos especiales de los dos métodos descritos anteriormente. En los actuales todo resultará igual, excepto que se añadirá el filtrado por tipo. Para comenzar, vamos a crear varios métodos cuya función será ejecutar las acciones especificadas:
- CloseBuyMarketProfit() — cierra todas las posiciones Buy rentables.
- CloseSellMarketProfit() — cierra todas las posiciones Sell rentables.
- CloseBuyMarketLoss() — cierra todas las posiciones Buy con pérdidas.
- CloseSellMarketLoss() — cierra todas las posiciones Sell con pérdidas.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::CloseBuyMarketProfit(int id,long lparam) { if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_order_button[4].Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_U)) { //--- declare the request and the result MqlTradeRequest request; MqlTradeResult result; int total=PositionsTotal(); // the number of open positions int sum_pp=0; double sum_cur=0.0; //--- Calculate profit for modes 4 and 5 if(m_current_mode[1]>2) { for(int i=total-1; i>=0; i--) { //--- order parameters ulong position_ticket=PositionGetTicket(i); // position ticket string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol ulong magic=PositionGetInteger(POSITION_MAGIC); // position MagicNumber ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // position type double profit_cur=PositionGetDouble(POSITION_PROFIT); double swap=PositionGetDouble(POSITION_SWAP); //--- if(magic==m_magic_number) if(position_symbol==Symbol()) if(type==POSITION_TYPE_BUY) { int profit_pp=int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point); switch(m_current_mode[1]) { case 3: sum_pp+=profit_pp; break; case 4: sum_cur+=profit_cur+swap; break; } } } } //--- iterate over open positions for(int i=total-1; i>=0; i--) { //--- order parameters ulong position_ticket=PositionGetTicket(i); // position ticket string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol int digits=(int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); // the number of decimal places ulong magic=PositionGetInteger(POSITION_MAGIC); // position MagicNumber double volume=PositionGetDouble(POSITION_VOLUME); // position volume ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // position type double profit_cur=PositionGetDouble(POSITION_PROFIT); double swap=PositionGetDouble(POSITION_SWAP); //--- if(magic==m_magic_number) if(position_symbol==Symbol()) if(type==POSITION_TYPE_BUY) { int profit_pp=int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point); if( (m_current_mode[1]==0 && profit_cur+swap>0) || // Close all profitable positions (m_current_mode[1]==1 && profit_pp>int(m_mode_edit[1].GetValue())) || // Close all positions having profit more than N points (m_current_mode[1]==2 && profit_cur+swap>double(m_mode_edit[1].GetValue())) || // Close all positions having profit more than N deposit currency units (m_current_mode[1]==3 && sum_pp>int(m_mode_edit[1].GetValue())) || // Close all positions with the total profit more than N points (m_current_mode[1]==4 && sum_cur>double(m_mode_edit[1].GetValue())) // Close all positions with the total profit more than N deposit currency units ) { //--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); //--- set the operation parameters request.action =TRADE_ACTION_DEAL; // trading operation type request.position =position_ticket; // position ticket request.symbol =position_symbol; // symbol request.volume =volume; // position volume request.deviation=5; // allowable price deviation request.magic =m_magic_number; // position MagicNumber //--- Set order price and type depending on the position type request.price=SymbolInfoDouble(position_symbol,SYMBOL_BID); request.type =ORDER_TYPE_SELL; //--- sending a request bool res=true; for(int j=0; j<5; j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) break; else PrintFormat("OrderSend error %d",GetLastError()); // if unable to send the request, output the error code } } } } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::CloseBuyMarketLoss(int id,long lparam) { if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_order_button[7].Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_H)) { //--- declare the request and the result MqlTradeRequest request; MqlTradeResult result; int total=PositionsTotal(); // the number of open positions int sum_pp=0; double sum_cur=0.0; //--- Calculate profit for modes 4 and 5 if(m_current_mode[4]>2) { for(int i=total-1; i>=0; i--) { //--- order parameters ulong position_ticket=PositionGetTicket(i); // position ticket string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol ulong magic=PositionGetInteger(POSITION_MAGIC); // position MagicNumber ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // position type double profit_cur=PositionGetDouble(POSITION_PROFIT); double swap=PositionGetDouble(POSITION_SWAP); //--- if(magic==m_magic_number) if(position_symbol==Symbol()) if(type==POSITION_TYPE_BUY) { int profit_pp=int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point); switch(m_current_mode[1]) { case 3: sum_pp+=profit_pp; break; case 4: sum_cur+=profit_cur+swap; break; } } } } //--- iterate over open positions for(int i=total-1; i>=0; i--) { //--- order parameters ulong position_ticket=PositionGetTicket(i); // position ticket string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol int digits=(int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); // the number of decimal places ulong magic=PositionGetInteger(POSITION_MAGIC); // position MagicNumber double volume=PositionGetDouble(POSITION_VOLUME); // position volume ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // position type double profit_cur=PositionGetDouble(POSITION_PROFIT); double swap=PositionGetDouble(POSITION_SWAP); //--- if(magic==m_magic_number) if(position_symbol==Symbol()) if(type==POSITION_TYPE_BUY) { int profit_pp=int((SymbolInfoDouble(position_symbol,SYMBOL_BID)-PositionGetDouble(POSITION_PRICE_OPEN))/_Point); if( (m_current_mode[4]==0 && profit_cur+swap<0) || // Close all profitable positions (m_current_mode[4]==1 && profit_pp<-int(m_mode_edit[4].GetValue())) || // Close all positions having profit more than N points (m_current_mode[4]==2 && profit_cur+swap<-double(m_mode_edit[4].GetValue())) || // Close all positions having profit more than N deposit currency units (m_current_mode[4]==3 && sum_pp<-int(m_mode_edit[4].GetValue())) || // Close all positions with the total profit more than N points (m_current_mode[4]==4 && sum_cur<-double(m_mode_edit[4].GetValue())) // Close all positions with the total profit more than N deposit currency units ) { //--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); //--- set the operation parameters request.action =TRADE_ACTION_DEAL; // trading operation type request.position =position_ticket; // position ticket request.symbol =position_symbol; // symbol request.volume =volume; // position volume request.deviation=5; // allowable price deviation request.magic =m_magic_number; // position MagicNumber //--- Set order price and type depending on the position type request.price=SymbolInfoDouble(position_symbol,SYMBOL_BID); request.type =ORDER_TYPE_SELL; //--- sending a request bool res=true; for(int j=0; j<5; j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) break; else PrintFormat("OrderSend error %d",GetLastError()); // if unable to send the request, output the error code } } } } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::CloseSellMarketProfit(int id,long lparam) { if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_order_button[5].Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_J)) { //--- declare the request and the result MqlTradeRequest request; MqlTradeResult result; int total=PositionsTotal(); // the number of open positions int sum_pp=0; double sum_cur=0.0; //--- Calculate profit for modes 4 and 5 if(m_current_mode[2]>2) { for(int i=total-1; i>=0; i--) { //--- order parameters ulong position_ticket=PositionGetTicket(i); // position ticket string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol ulong magic=PositionGetInteger(POSITION_MAGIC); // position MagicNumber ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // position type double profit_cur=PositionGetDouble(POSITION_PROFIT); double swap=PositionGetDouble(POSITION_SWAP); //--- if(magic==m_magic_number) if(position_symbol==Symbol()) if(type==POSITION_TYPE_SELL) { int profit_pp=int((PositionGetDouble(POSITION_PRICE_OPEN)-SymbolInfoDouble(position_symbol,SYMBOL_ASK))/_Point); switch(m_current_mode[2]) { case 3: sum_pp+=profit_pp; break; case 4: sum_cur+=profit_cur+swap; break; } } } } //--- iterate over open positions for(int i=total-1; i>=0; i--) { //--- order parameters ulong position_ticket=PositionGetTicket(i); // position ticket string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol int digits=(int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); // the number of decimal places ulong magic=PositionGetInteger(POSITION_MAGIC); // position MagicNumber double volume=PositionGetDouble(POSITION_VOLUME); // position volume ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // position type double profit_cur=PositionGetDouble(POSITION_PROFIT); double swap=PositionGetDouble(POSITION_SWAP); //--- if(magic==m_magic_number) if(position_symbol==Symbol()) if(type==POSITION_TYPE_SELL) { int profit_pp=int((PositionGetDouble(POSITION_PRICE_OPEN)-SymbolInfoDouble(position_symbol,SYMBOL_ASK))/_Point); if( (m_current_mode[2]==0 && profit_cur+swap>0) || // Close all profitable positions (m_current_mode[2]==1 && profit_pp>int(m_mode_edit[2].GetValue())) || // Close all positions having profit more than N points (m_current_mode[2]==2 && profit_cur+swap>double(m_mode_edit[2].GetValue())) || // Close all positions having profit more than N deposit currency units (m_current_mode[2]==3 && sum_pp>int(m_mode_edit[2].GetValue())) || // Close all positions with the total profit more than N points (m_current_mode[2]==4 && sum_cur>double(m_mode_edit[2].GetValue())) // Close all positions with the total profit more than N deposit currency units ) { //--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); //--- set the operation parameters request.action =TRADE_ACTION_DEAL; // trading operation type request.position =position_ticket; // position ticket request.symbol =position_symbol; // symbol request.volume =volume; // position volume request.deviation=5; // allowable price deviation request.magic =m_magic_number; // position MagicNumber //--- Set order price and type depending on the position type request.price=SymbolInfoDouble(position_symbol,SYMBOL_ASK); request.type =ORDER_TYPE_BUY; //--- sending a request bool res=true; for(int j=0; j<5; j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) break; else PrintFormat("OrderSend error %d",GetLastError()); // if unable to send the request, output the error code } } } } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::CloseSellMarketLoss(int id,long lparam) { if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_order_button[8].Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_L)) { //--- declare the request and the result MqlTradeRequest request; MqlTradeResult result; int total=PositionsTotal(); // the number of open positions int sum_pp=0; double sum_cur=0.0; //--- Calculate profit for modes 4 and 5 if(m_current_mode[5]>2) { for(int i=total-1; i>=0; i--) { //--- order parameters ulong position_ticket=PositionGetTicket(i); // position ticket string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol ulong magic=PositionGetInteger(POSITION_MAGIC); // position MagicNumber ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // position type double profit_cur=PositionGetDouble(POSITION_PROFIT); double swap=PositionGetDouble(POSITION_SWAP); //--- if(magic==m_magic_number) if(position_symbol==Symbol()) if(type==POSITION_TYPE_SELL) { int profit_pp=int((PositionGetDouble(POSITION_PRICE_OPEN)-SymbolInfoDouble(position_symbol,SYMBOL_ASK))/_Point); switch(m_current_mode[1]) { case 3: sum_pp+=profit_pp; break; case 4: sum_cur+=profit_cur+swap; break; } } } } //--- iterate over open positions for(int i=total-1; i>=0; i--) { //--- order parameters ulong position_ticket=PositionGetTicket(i); // position ticket string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol int digits=(int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); // the number of decimal places ulong magic=PositionGetInteger(POSITION_MAGIC); // position MagicNumber double volume=PositionGetDouble(POSITION_VOLUME); // position volume ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // position type double profit_cur=PositionGetDouble(POSITION_PROFIT); double swap=PositionGetDouble(POSITION_SWAP); //--- if(magic==m_magic_number) if(position_symbol==Symbol()) if(type==POSITION_TYPE_SELL) { int profit_pp=int((PositionGetDouble(POSITION_PRICE_OPEN)-SymbolInfoDouble(position_symbol,SYMBOL_ASK))/_Point); if( (m_current_mode[5]==0 && profit_cur+swap<0) || // Close all profitable positions (m_current_mode[5]==1 && profit_pp<-int(m_mode_edit[5].GetValue())) || // Close all positions having profit more than N points (m_current_mode[5]==2 && profit_cur+swap<-double(m_mode_edit[5].GetValue())) || // Close all positions having profit more than N deposit currency units (m_current_mode[5]==3 && sum_pp<-int(m_mode_edit[5].GetValue())) || // Close all positions with the total profit more than N points (m_current_mode[5]==4 && sum_cur<-double(m_mode_edit[5].GetValue())) // Close all positions with the total profit more than N deposit currency units ) { //--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); //--- set the operation parameters request.action =TRADE_ACTION_DEAL; // trading operation type request.position =position_ticket; // position ticket request.symbol =position_symbol; // symbol request.volume =volume; // position volume request.deviation=5; // allowable price deviation request.magic =m_magic_number; // position MagicNumber //--- Set order price and type depending on the position type request.price=SymbolInfoDouble(position_symbol,SYMBOL_ASK); request.type =ORDER_TYPE_BUY; //--- sending a request bool res=true; for(int j=0; j<5; j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) break; else PrintFormat("OrderSend error %d",GetLastError()); // if unable to send the request, output the error code } } } } } }
Después de crearlos e implementarlos, los llamaremos en el cuerpo del manejador de eventos OnEvent(), fuera de cualquier sección.
//---
CloseBuyMarketProfit(id,lparam);
CloseSellMarketProfit(id,lparam);
CloseBuyMarketLoss(id,lparam);
CloseSellMarketLoss(id,lparam);
Para cada una de las acciones creadas, aparte de la pulsación del botón, hemos creado eventos de pulsación de atajos de teclado. El lector podrá reasignarlos en el código. Para mayor claridad, mostraremos sus valores junto a los nombres de las acciones en los botones.
//--- if(!CreateButton(m_order_button[0],MARKET_ORDER_NAME+"(M)",C'87,128,255',10,10)) return(false); if(!CreateButton(m_order_button[1],PENDING_ORDER_NAME+"(P)",C'31,209,111',300,10)) return(false); if(!CreateButton(m_order_button[2],CLOSE_ALL_PROFIT+"(C)",C'87,128,255',10,10+45*1)) return(false); if(!CreateButton(m_order_button[3],PEND_ORDERS_ALL_CLOSE+"(R)",C'31,209,111',300,10+45*1)) return(false); if(!CreateButton(m_order_button[4],CLOSE_BUY_PROFIT+"(U)",C'87,128,255',10,10+45*2)) return(false); if(!CreateButton(m_order_button[5],CLOSE_SELL_PROFIT+"(J)",C'87,128,255',10,10+45*3)) return(false); if(!CreateButton(m_order_button[6],CLOSE_ALL_LOSS+"(D)",C'87,128,255',10,10+45*4)) return(false); if(!CreateButton(m_order_button[7],CLOSE_BUY_LOSS+"(H)",C'87,128,255',10,10+45*5)) return(false); if(!CreateButton(m_order_button[8],CLOSE_SELL_LOSS+"(L)",C'87,128,255',10,10+45*6)) return(false);
Como resultado, obtendremos los siguientes valores:
Fig.9 Asignando los atajos de teclado a las acciones añadidas
Ya hemos implementado la funcionalidad de la pestaña Comercio. Ahora, vamos a pasar a la siguiente etapa: la creación de un recuadro con las posiciones abiertas por el instrumental y la adición de la posibilidad de gestionar las posiciones en la pestaña "Control de Posiciones de Mercado". La fig.3 al inicio del artículo, contiene un esquema visual para crear los elementos de la interfaz, que consta de tres campos de edición, dos botones y un recuadro. Comenzaremos por la pestaña. En primer lugar, crearemos tres campos de edición para editar el lote, el Stop Loss y el Take Profit de las posiciones abiertas. Hablamos de los métodos CreateLotControl(), CreateStopLossControl(), CreateTakeProfitControl().
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreateLotControl(CTextEdit &text_edit,const int x_gap,const int y_gap,int tab) { //--- Store the pointer to the main control text_edit.MainPointer(m_tab); //--- Attach to tab m_tab.AddToElementsArray(tab,text_edit); //--- Properties text_edit.XSize(80); text_edit.YSize(24); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.MaxValue(9999); text_edit.StepValue(_Point); text_edit.SetDigits(_Digits); text_edit.SpinEditMode(true); text_edit.GetTextBoxPointer().XGap(1); text_edit.GetTextBoxPointer().XSize(80); //--- Create a control element if(!text_edit.CreateTextEdit("",x_gap,y_gap)) return(false); text_edit.SetValue(string(0)); //--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(0,text_edit); return(true); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreateTakeProfitControl(CTextEdit &text_edit,const int x_gap,const int y_gap,int tab) { //--- Store the pointer to the main control text_edit.MainPointer(m_tab); //--- Attach to tab m_tab.AddToElementsArray(tab,text_edit); //--- Properties text_edit.XSize(80); text_edit.YSize(24); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.MaxValue(9999); text_edit.StepValue(_Point); text_edit.SetDigits(_Digits); text_edit.SpinEditMode(true); text_edit.GetTextBoxPointer().XGap(1); text_edit.GetTextBoxPointer().XSize(80); //--- Create a control element if(!text_edit.CreateTextEdit("",x_gap,y_gap)) return(false); text_edit.SetValue(string(0)); //--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(0,text_edit); return(true); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreateStopLossControl(CTextEdit &text_edit,const int x_gap,const int y_gap,int tab) { //--- Store the pointer to the main control text_edit.MainPointer(m_tab); //--- Attach to tab m_tab.AddToElementsArray(tab,text_edit); //--- Properties text_edit.XSize(80); text_edit.YSize(24); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.MaxValue(9999); text_edit.StepValue(_Point); text_edit.SetDigits(_Digits); text_edit.MinValue(0); text_edit.SpinEditMode(true); text_edit.GetTextBoxPointer().XGap(1); text_edit.GetTextBoxPointer().XSize(80); //--- Create a control element if(!text_edit.CreateTextEdit("",x_gap,y_gap)) return(false); text_edit.SetValue(string(0)); //--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(0,text_edit); return(true); }
Y llamamos a los nuevos métodos en el cuerpo de CreateMainWindow().
//--- Input field for editing open positions if(!CreateLotControl(m_lot_edit[6],375,3,1)) return(false); if(!CreateStopLossControl(m_sl_edit[6],375+80,3,1)) return(false); if(!CreateTakeProfitControl(m_tp_edit[6],375+83*2,3,1)) return(false);
Para los botones Cambiar y Cerrar también crearemos dos nuevos métodos encargados de implementarlos: CreateModifyButton(), CreateCloseButton().
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreateModifyButton(CButton &button,string text,int x_gap,int y_gap,int tab) { //--- Store the window pointer button.MainPointer(m_tab); //--- Attach to tab m_tab.AddToElementsArray(tab,button); color baseclr=clrDarkOrange; color pressclr=clrOrange; //--- Set properties before creation button.XSize(80); button.YSize(24); button.Font(m_base_font); button.FontSize(m_base_font_size); button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(pressclr); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(pressclr); button.LabelColor(clrWhite); button.LabelColorPressed(clrWhite); button.LabelColorHover(clrWhite); button.IsCenterText(true); //--- Create a control element if(!button.CreateButton(text,x_gap,y_gap)) return(false); //--- Add a pointer to the element to the database CWndContainer::AddToElementsArray(0,button); return(true); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreateCloseButton(CButton &button,string text,int x_gap,int y_gap,int tab) { //--- Store the window pointer button.MainPointer(m_tab); //--- Attach to tab m_tab.AddToElementsArray(tab,button); color baseclr=clrCrimson; color pressclr=clrFireBrick; //--- Set properties before creation button.XSize(80); button.YSize(24); button.Font(m_base_font); button.FontSize(m_base_font_size); button.BackColor(baseclr); button.BackColorHover(baseclr); button.BackColorPressed(pressclr); button.BorderColor(baseclr); button.BorderColorHover(baseclr); button.BorderColorPressed(pressclr); button.LabelColor(clrWhite); button.LabelColorPressed(clrWhite); button.LabelColorHover(clrWhite); button.IsCenterText(true); //--- Create a control element if(!button.CreateButton(text,x_gap,y_gap)) return(false); //--- Add a pointer to the element to the database CWndContainer::AddToElementsArray(0,button); return(true); }
De esta forma, los añadiremos al método para crear la ventana principal:
//--- Position editing/closing buttons if(!CreateModifyButton(m_small_button[0],MODIFY,622,3,1)) return(false); if(!CreateCloseButton(m_small_button[1],CLOSE,622+80,3,1)) return(false);
Aquí, tenemos dos macrosustituciones para la localización de la interfaz, por eso, pasamos a Defines.mqh y añadimos sus valores:
#define MODIFY (m_language==RUSSIAN ? "Изменить" : "Modify") #define CLOSE (m_language==RUSSIAN ? "Закрыть" : "Close")
Ahora, vamos a pasar al propio recuadro. Para ello, crearemos el método CreatePositionsTable(), lo implementaremos y también lo añadiremos al método de la ventana principal de la aplicación.
//+------------------------------------------------------------------+ //| Create a table of positions | //+------------------------------------------------------------------+ bool CFastTrading::CreatePositionsTable(CTable &table,const int x_gap,const int y_gap) { #define COLUMNS2_TOTAL 9 //--- Store the pointer to the main control table.MainPointer(m_tab); //--- Attach to tab m_tab.AddToElementsArray(1,table); //--- Array of column widths int width[COLUMNS2_TOTAL]; ::ArrayInitialize(width,80); width[0]=100; width[1]=110; width[2]=100; width[3]=60; width[6]=90; //--- Array of text alignment in columns ENUM_ALIGN_MODE align[COLUMNS2_TOTAL]; ::ArrayInitialize(align,ALIGN_CENTER); //--- Array of text offset along the X axis in the columns int text_x_offset[COLUMNS2_TOTAL]; ::ArrayInitialize(text_x_offset,7); //--- Array of column image offsets along the X axis int image_x_offset[COLUMNS2_TOTAL]; ::ArrayInitialize(image_x_offset,3); //--- Array of column image offsets along the Y axis int image_y_offset[COLUMNS2_TOTAL]; ::ArrayInitialize(image_y_offset,2); //--- Properties table.Font(m_base_font); table.FontSize(m_base_font_size); table.XSize(782); table.CellYSize(24); table.TableSize(COLUMNS2_TOTAL,1); table.TextAlign(align); table.ColumnsWidth(width); table.TextXOffset(text_x_offset); table.ImageXOffset(image_x_offset); table.ImageYOffset(image_y_offset); table.ShowHeaders(true); table.HeadersColor(C'87,128,255'); table.HeadersColorHover(clrCornflowerBlue); table.HeadersTextColor(clrWhite); table.IsSortMode(false); table.LightsHover(true); table.SelectableRow(true); table.IsZebraFormatRows(clrWhiteSmoke); table.DataType(0,TYPE_LONG); table.AutoYResizeMode(true); table.AutoYResizeBottomOffset(5); //--- Create a control element if(!table.CreateTable(x_gap,y_gap)) return(false); //--- Set the header titles table.SetHeaderText(0,TICKET); table.SetHeaderText(1,SYMBOL); table.SetHeaderText(2,TYPE_POS); table.SetHeaderText(3,PRICE); table.SetHeaderText(4,VOLUME); table.SetHeaderText(5,SL); table.SetHeaderText(6,TP); table.SetHeaderText(7,SWAP); table.SetHeaderText(8,PROFIT); //--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(0,table); return(true); }
El recuadro tendrá 9 columnas. Asimismo, ajustaremos en él la anchura de las columnas, dado que los nombres de estas tienen distinta longitud. Y establecemos los encabezados de las columnas aplicando la localización, es decir, creamos para cada columna su propia macrosustitución en el archivo Defines.mqh.
#define SYMBOL (m_language==RUSSIAN ? "Символ" : "Symbol") #define VOLUME (m_language==RUSSIAN ? "Объем" : "Volume") #define TYPE_POS (m_language==RUSSIAN ? "Тип позиции" : "Position Type") #define SWAP (m_language==RUSSIAN ? "Своп" : "Swap") #define PROFIT (m_language==RUSSIAN ? "Прибыль" : "Profit")
No obstante, si intentamos compilar el proyecto ahora, veremos que el recuadro, los botones y los campos se salen del borde derecho de la ventana. Por eso, necesitaremos añadir un mecanismo que ajuste la anchura de la ventana principal según su contenido. Para ello, crearemos el método WindowRezise().
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::WindowResize(int x_size,int y_size) { m_main_window.GetCloseButtonPointer().Hide(); m_main_window.ChangeWindowWidth(x_size); m_main_window.ChangeWindowHeight(y_size); m_main_window.GetCloseButtonPointer().Show(); }
Y creamos en el manejador de eventos un nuevo evento de clic sobre la pestaña. En él, escribiremos la anchura de la ventana principal para cada pestaña.
//--- Tab switching event if(id==CHARTEVENT_CUSTOM+ON_CLICK_TAB) { if(m_tab.SelectedTab()==0) WindowResize(600,m_main_window.YSize()); if(m_tab.SelectedTab()==1) WindowResize(782+10,m_main_window.YSize()); if(m_tab.SelectedTab()==2) WindowResize(682+10,m_main_window.YSize()); }
Ahora, todo se mostrará correctamente, según la fig.3. El siguiente paso consistirá en obtener la información sobre las posiciones abiertas por la aplicación y mostrarla en el recuadro creado. Para ello, deberemos designar tres etapas de preparación de datos y su visualización en el recuadro:
- Inicialización. Aquí definiremos el número de posiciones que pertenecerán a nuestro instrumental y reconstruimos el recuadro para estos datos.
- Adición de datos. Añadiremos al recuadro los parámetros de cada posición abierta, según la descripción en los encabezados de las columnas.
- Actualización del recuadro con la información rellenada.
Para el primer paso de la inicialización, crearemos la función InitializePositionsTable() y crearemos una muestra de todas las posiciones abiertas según el símbolo actual y el número mágico. De esta manera, obtendremos el número de posiciones que cumplen con nuestras condiciones. Después, añadiremos al recuadro el mismo número de filas.
//+------------------------------------------------------------------+ //| Initializing the table of positions | //+------------------------------------------------------------------+ void CFastTrading::InitializePositionsTable(void) { //--- Get symbols of open positions int total=PositionsTotal(); // the number of open positions int cnt=0; //--- Delete all rows m_table_positions.DeleteAllRows(); //--- Set the number of rows equal to the number of positions for(int i=0; i<total; i++) { //--- order parameters ulong position_ticket=PositionGetTicket(i); // position ticket string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol ulong magic=PositionGetInteger(POSITION_MAGIC); // position MagicNumber //--- if MagicNumber matches if(magic==m_magic_number) if(position_symbol==Symbol()) { m_table_positions.AddRow(cnt); cnt++; } } //--- If there are positions if(cnt>0) { //--- Set the values in the table SetValuesToPositionsTable(); //--- Update the table UpdatePositionsTable(); } }
A continuación, se comprobará si se ha encontrado aunque sea una posición abierta por nuestro instrumental. En caso contrario, estableceremos la información sobre las posiciones abiertas en las líneas recién creadas con la ayuda del método SetValuePositionTable().
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::SetValuesToPositionsTable(void) { //--- int cnt=0; for(int i=PositionsTotal()-1; i>=0; i--) { //--- order parameters ulong position_ticket=PositionGetTicket(i); // position ticket string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol int digits=(int)SymbolInfoInteger(position_symbol,SYMBOL_DIGITS); // the number of decimal places ulong magic=PositionGetInteger(POSITION_MAGIC); // position MagicNumber double volume=PositionGetDouble(POSITION_VOLUME); // position volume ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // position type double stoploss=PositionGetDouble(POSITION_SL); double takeprofit=PositionGetDouble(POSITION_TP); double swap=PositionGetDouble(POSITION_SWAP); double profit=PositionGetDouble(POSITION_PROFIT); double openprice=PositionGetDouble(POSITION_PRICE_OPEN); string pos=(type==POSITION_TYPE_BUY)?"BUY":"SELL"; profit+=swap; //--- if MagicNumber matches if(magic==m_magic_number) if(position_symbol==Symbol()) { m_table_positions.SetValue(0,i,string(position_ticket)); m_table_positions.SetValue(1,i,string(position_symbol)); m_table_positions.SetValue(2,i,pos); m_table_positions.SetValue(3,i,string(openprice)); m_table_positions.SetValue(4,i,string(volume)); m_table_positions.SetValue(5,i,string(stoploss)); m_table_positions.SetValue(6,i,string(takeprofit)); m_table_positions.SetValue(7,i,string(swap)); m_table_positions.SetValue(8,i,DoubleToString(profit,2)); //--- m_table_positions.TextColor(2,i,(pos=="BUY")? clrForestGreen : clrCrimson); m_table_positions.TextColor(8,i,(profit!=0)? (profit>0)? clrForestGreen : clrCrimson : clrSilver); cnt++; } } }
Y después de rellenar los datos, actualizaremos el recuadro con la ayuda de UpdatePositionsTable():
//+------------------------------------------------------------------+ //| Update the table of positions | //+------------------------------------------------------------------+ void CFastTrading::UpdatePositionsTable(void) { //--- Update the table m_table_positions.Update(true); m_table_positions.GetScrollVPointer().Update(true); }
Para que los cambios introducidos en el proyecto surtan efecto, deberemos ajustarlos correctamente. Para ello, pasaremos al archivo SimpleTrading.mq5, y añadiremos en la función OnInit()la llamada del método de inicialización de la clase de la aplicación:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- tick_counter=GetTickCount(); //--- Initialize class variables program.FontName("Trebuchet MS"); program.FontSize(Inp_BaseFont); program.BackgroundColor(Background); program.CaptionColor(Caption); program.SetLanguage(Language); program.SetMagicNumber(MagicNumber); //--- Set a trading panel if(!program.CreateGUI()) { Print(__FUNCTION__," > Failed to create graphical interface!"); return(INIT_FAILED); } program.OnInitEvent(); //--- return(INIT_SUCCEEDED); }
Esto debe hacerse estrictamente después de crear la aplicación CreateGUI(). Ahora, pasamos al cuerpo de OnInitEvent() y llamamos ahí a la inicialización del recuadro.
//+------------------------------------------------------------------+ //| Initialization | //+------------------------------------------------------------------+ void CFastTrading::OnInitEvent(void) { InitializePositionsTable(); }
Ahora, en el recuadro se muestra correctamente toda la información sobre las posiciones abiertas. No obstante, dicha información solo es relevante cuando se inicia la aplicación, y deberá actualizarse constantemente. Esto debe hacerse en el manejador de eventos comerciales OnTrade(), así como en la función OnTick(). En los eventos comerciales, monitorearemos el número de posiciones abiertas actuales y sus parámetros, mientras que en el tick actualizaremos la información sobre el beneficio actual de cada orden.
Para ello, creamos el método OnTradeEvent() en la sección pública de la clase base y llamamos a la inicialización del recuadro en su cuerpo.
//+------------------------------------------------------------------+ //| Trade event | //+------------------------------------------------------------------+ void CFastTrading::OnTradeEvent(void) { //--- If a new trade InitializePositionsTable(); }
El propio método nuevo lo llamamos en el manejador del evento comercial:
//+------------------------------------------------------------------+ //| Trade function | //+------------------------------------------------------------------+ void OnTrade(void) { program.OnTradeEvent(); }
Con las acciones realizadas, hemos ajustado la actualidad de la representación de las posiciones abiertas. Para actualizar el beneficio, creamos en la sección pública de la clase CFastTrading el método UpdatePositionProfit():
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::UpdatePositionProfit(void) { //--- int cnt=0; for(int i=PositionsTotal()-1; i>=0; i--) { //--- order parameters ulong position_ticket=PositionGetTicket(i); // position ticket string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol ulong magic=PositionGetInteger(POSITION_MAGIC); // position MagicNumber double swap=PositionGetDouble(POSITION_SWAP); double profit=PositionGetDouble(POSITION_PROFIT); profit+=swap; //--- if MagicNumber matches if(magic==m_magic_number) if(position_symbol==Symbol()) { m_table_positions.SetValue(8,i,DoubleToString(profit,2)); //--- m_table_positions.TextColor(8,i,(profit!=0)? (profit>0)? clrForestGreen : clrCrimson : clrSilver); cnt++; } } //--- if(cnt>0) UpdatePositionsTable(); }
Y lo llamamos en OnTick():
void OnTick() { program.UpdatePositionProfit(); }
Con esto, podemos considerar completa la implementación del recuadro. Ahora, vamos a crear la posibilidad de editar y cerrar las posiciones abiertas disponibles en la lista actual. Para ello, debemos lograr que al clicar en una fila del recuadro, los campos de edición muestren el lote, el Take Profit y el Stop Loss de la posición seleccionada.
//--- Event of clicking on a table row if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM) { //--- Check the element ID if(lparam==m_table_positions.Id()) { //--- int row=m_table_positions.SelectedItem(); m_lot_edit[6].SetValue(string(m_table_positions.GetValue(4,row))); m_sl_edit[6].SetValue(string(m_table_positions.GetValue(5,row))); m_tp_edit[6].SetValue(string(m_table_positions.GetValue(6,row))); m_lot_edit[6].GetTextBoxPointer().Update(true); m_sl_edit[6].GetTextBoxPointer().Update(true); m_tp_edit[6].GetTextBoxPointer().Update(true); } }
Compilamos el proyecto y vemos (como se muestra en la fig.10) que los valores de la posición de mercado seleccionada se representan en los campos de edición.
Fig.10 Seleccionado una posición abierta para su posterior edición.
A continuación, creamos dos métodos: ModifyPosition() y ClosePosition(), que, al pulsar en los botones Cambiar y Cerrar, aplicarán las acciones correspondientes a la posición abierta seleccionada.
//+------------------------------------------------------------------+ //| Modifying a selected open position | //+------------------------------------------------------------------+ bool CFastTrading::ModifyPosition(long lparam) { //--- Check the element ID if(lparam==m_small_button[0].Id()) { //--- Get index and symbol if(m_table_positions.SelectedItem()==WRONG_VALUE) return(false); int row=m_table_positions.SelectedItem(); ulong ticket=(ulong)m_table_positions.GetValue(0,row); //--- if(PositionSelectByTicket(ticket)) { //--- declare the request and the result MqlTradeRequest request; MqlTradeResult result; //--- calculation and rounding of the Stop Loss and Take Profit values double sl=NormalizeDouble((double)m_sl_edit[6].GetValue(),_Digits); double tp=NormalizeDouble((double)m_tp_edit[6].GetValue(),_Digits); //--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); //--- set the operation parameters request.action =TRADE_ACTION_SLTP; // trading operation type request.position=ticket; // position ticket request.symbol=Symbol(); // symbol request.sl =sl; // position Stop Loss request.tp =tp; // position Take Profit request.magic=m_magic_number; // position MagicNumber //--- sending a request bool res=true; for(int j=0; j<5; j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) return(true); else PrintFormat("OrderSend error %d",GetLastError()); // if unable to send the request, output the error code } } } //--- return(false); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::ClosePosition(long lparam) { //--- Check the element ID if(lparam==m_small_button[1].Id()) { //--- Get index and symbol if(m_table_positions.SelectedItem()==WRONG_VALUE) return(false); int row=m_table_positions.SelectedItem(); ulong ticket=(ulong)m_table_positions.GetValue(0,row); //--- if(PositionSelectByTicket(ticket)) { ENUM_POSITION_TYPE type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); // position type string position_symbol=PositionGetString(POSITION_SYMBOL); // symbol //--- declare the request and the result MqlTradeRequest request; MqlTradeResult result; //--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); //--- Set order price and type depending on the position type if(type==POSITION_TYPE_BUY) { request.price=SymbolInfoDouble(position_symbol,SYMBOL_BID); request.type =ORDER_TYPE_SELL; } else if(type==POSITION_TYPE_SELL) { request.price=SymbolInfoDouble(position_symbol,SYMBOL_ASK); request.type =ORDER_TYPE_BUY; } //--- check volume double position_volume=PositionGetDouble(POSITION_VOLUME); double closing_volume=(double)m_lot_edit[6].GetValue(); if(closing_volume>position_volume) closing_volume=position_volume; //--- setting request request.action =TRADE_ACTION_DEAL; request.position =ticket; request.symbol =Symbol(); request.volume =NormalizeLot(Symbol(),closing_volume); request.magic =m_magic_number; request.deviation=5; //--- sending a request bool res=true; for(int j=0; j<5; j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) return(true); else PrintFormat("OrderSend error %d",GetLastError()); // if unable to send the request, output the error code } } } //--- return(false); }
Con esto, podemos dar por finalizada la implementación de tareas en la pestaña Control de Posiciones de Mercado. Vamos a pasar al desarrollo de la pestaña Control de Órdenes Pendientes. Para ello, al final del cuerpo del método de la ventana principal CreateMainWindow(), añadimos un código que agregará la misma funcionalidad que en la pestaña anterior, concretamente: un recuadro de registro de órdenes pendientes, así como los botones y los campos de edición para su edición.
//--- Create a table of pending orders if(!CreateOrdersTable(m_table_orders,0,22+5)) return(false); //--- Input fields for editing pending orders if(!CreateLotControl(m_pr_edit[4],360,3,2)) return(false); if(!CreateStopLossControl(m_sl_edit[7],360+80,3,2)) return(false); if(!CreateTakeProfitControl(m_tp_edit[7],360+80*2,3,2)) return(false); //--- Pending order modifying/deleting orders if(!CreateModifyButton(m_small_button[2],MODIFY,361+80*3,3,2)) return(false); if(!CreateCloseButton(m_small_button[3],REMOVE,361+80*3,3+24,2)) return(false);
En esta adición, tenemos un nuevo método que añade un recuadro para registrar las órdenes pendientes, y también una nueva macrosustitución para el botón que elimina la orden seleccionada. Veamos con mayor detalle la creación del recuadro:
//+------------------------------------------------------------------+ //| Create a table of pending orders | //+------------------------------------------------------------------+ //--- bool CFastTrading::CreateOrdersTable(CTable &table,const int x_gap,const int y_gap) { #define COLUMNS1_TOTAL 7 //--- Store the pointer to the main control table.MainPointer(m_tab); //--- Attach to tab m_tab.AddToElementsArray(2,table); //--- Array of column widths int width[COLUMNS1_TOTAL]; ::ArrayInitialize(width,80); width[0]=100; width[2]=100; //--- Array of text offset along the X axis in the columns int text_x_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(text_x_offset,7); //--- Array of column image offsets along the X axis int image_x_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(image_x_offset,3); //--- Array of column image offsets along the Y axis int image_y_offset[COLUMNS1_TOTAL]; ::ArrayInitialize(image_y_offset,2); //--- Array of text alignment in columns ENUM_ALIGN_MODE align[COLUMNS1_TOTAL]; ::ArrayInitialize(align,ALIGN_CENTER); align[6]=ALIGN_LEFT; //--- Properties table.Font(m_base_font); table.FontSize(m_base_font_size); table.XSize(602); table.CellYSize(24); table.TableSize(COLUMNS1_TOTAL,1); table.TextAlign(align); table.ColumnsWidth(width); table.TextXOffset(text_x_offset); table.ImageXOffset(image_x_offset); table.ImageYOffset(image_x_offset); table.ShowHeaders(true); table.HeadersColor(C'87,128,255'); table.HeadersColorHover(clrCornflowerBlue); table.HeadersTextColor(clrWhite); table.IsSortMode(false); table.LightsHover(true); table.SelectableRow(true); table.IsZebraFormatRows(clrWhiteSmoke); table.AutoYResizeMode(true); table.AutoYResizeBottomOffset(5); table.DataType(0,TYPE_LONG); table.DataType(1,TYPE_STRING); table.DataType(2,TYPE_STRING); table.DataType(3,TYPE_DOUBLE); table.DataType(4,TYPE_DOUBLE); table.DataType(5,TYPE_DOUBLE); //--- Create a control element if(!table.CreateTable(x_gap,y_gap)) return(false); //--- Set the header titles table.SetHeaderText(0,TICKET); table.SetHeaderText(1,SYMBOL); table.SetHeaderText(2,TYPE_POS); table.SetHeaderText(3,VOLUME); table.SetHeaderText(4,PRICE); table.SetHeaderText(5,SL); table.SetHeaderText(6,TP); //--- Add the object to the common array of object groups CWndContainer::AddToElementsArray(0,table); return(true); }
Aquí, cambia el número de columnas y su orden. Al editar las posiciones de mercado abiertas, podemos modificar el lote, así como los valores de Take Profit y Stop Loss. En cuanto a las órdenes pendientes, aquí podemos modificar el precio de apertura en lugar del lote. Compilamos el proyecto y verificamos el resultado en la pestaña Control de Órdenes Pendientes:
Fig.11 Interfaz para trabajar con órdenes pendientes.
En esencia, el trabajo posterior es muy semejante a la creación de la pestaña anterior. En primer lugar, encontramos todas las órdenes que pertenecen a nuestro instrumental con la ayuda de InitializeOrdersTable():
//+------------------------------------------------------------------+ //| Initializing the table of positions | //+------------------------------------------------------------------+ void CFastTrading::InitializeOrdersTable(void) { //--- int total=OrdersTotal(); int cnt=0; //--- Delete all rows m_table_orders.DeleteAllRows(); //--- Set the number of rows equal to the number of positions for(int i=0; i<total; i++) { //--- order parameters ulong order_ticket=OrderGetTicket(i); // order ticket string order_symbol=OrderGetString(ORDER_SYMBOL); // symbol ulong magic=OrderGetInteger(ORDER_MAGIC); // position MagicNumber //--- if MagicNumber matches if(magic==m_magic_number) if(order_symbol==Symbol()) { m_table_orders.AddRow(cnt); cnt++; } } //--- If there are positions if(cnt>0) { //--- Set the values in the table SetValuesToOrderTable(); //--- Update the table UpdateOrdersTable(); } }
Si no hemos encontrado órdenes pendientes, introducimos la información sobre ellas en el recuadro a través del método SetValuesToOrderTable():
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::SetValuesToOrderTable(void) { //--- int cnt=0; ulong ticket; for(int i=0; i<OrdersTotal(); i++) { //--- order parameters if((ticket=OrderGetTicket(i))>0) { string position_symbol=OrderGetString(ORDER_SYMBOL); // symbol ulong magic=OrderGetInteger(ORDER_MAGIC); // order MagicNumber double volume=OrderGetDouble(ORDER_VOLUME_INITIAL); // order volume ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); // order type double price=OrderGetDouble(ORDER_PRICE_OPEN); double stoploss=OrderGetDouble(ORDER_SL); double takeprofit=OrderGetDouble(ORDER_TP); string pos=""; if(type==ORDER_TYPE_BUY_LIMIT) pos="Buy Limit"; else if(type==ORDER_TYPE_SELL_LIMIT) pos="Sell Limit"; else if(type==ORDER_TYPE_BUY_STOP) pos="Buy Stop"; else if(type==ORDER_TYPE_SELL_STOP) pos="Sell Stop"; //--- if(magic==m_magic_number) if(position_symbol==Symbol()) { m_table_orders.SetValue(0,i,string(ticket)); m_table_orders.SetValue(1,i,string(position_symbol)); m_table_orders.SetValue(2,i,pos); m_table_orders.SetValue(3,i,string(volume)); m_table_orders.SetValue(4,i,string(price)); m_table_orders.SetValue(5,i,string(stoploss)); m_table_orders.SetValue(6,i,string(takeprofit)); cnt++; } } } }
Y actualizamos los datos establecidos utilizando el método UpdateOrdersTable():
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::UpdateOrdersTable(void) { //--- Update the table m_table_orders.Update(true); m_table_orders.GetScrollVPointer().Update(true); }
Para incluir todo en la aplicación, ejecutamos las mismas acciones de la pestaña anterior. Concretamente, inicializamos el recuadro con órdenes pendientes:
//+------------------------------------------------------------------+ //| Initialization | //+------------------------------------------------------------------+ void CFastTrading::OnInitEvent(void) { InitializeOrdersTable(); InitializePositionsTable(); }
Y repetimos lo mismo en el manejador de eventos comerciales:
//+------------------------------------------------------------------+ //| Trade event | //+------------------------------------------------------------------+ void CFastTrading::OnTradeEvent(void) { //--- InitializePositionsTable(); InitializeOrdersTable(); }
Para que, al clicar sobre una fila del recuadro, los parámetros de la orden pendiente se transfieran a los campos de edición, vamos a añadir un código en la sección correspondiente del manejador de eventos:
//--- Event of clicking on a table row if(id==CHARTEVENT_CUSTOM+ON_CLICK_LIST_ITEM) { //--- Check the element ID if(lparam==m_table_positions.Id()) { //--- int row=m_table_positions.SelectedItem(); m_lot_edit[6].SetValue(string(m_table_positions.GetValue(4,row))); m_sl_edit[6].SetValue(string(m_table_positions.GetValue(5,row))); m_tp_edit[6].SetValue(string(m_table_positions.GetValue(6,row))); m_lot_edit[6].GetTextBoxPointer().Update(true); m_sl_edit[6].GetTextBoxPointer().Update(true); m_tp_edit[6].GetTextBoxPointer().Update(true); } //--- Check the element ID if(lparam==m_table_orders.Id()) { //--- int row=m_table_orders.SelectedItem(); m_pr_edit[4].SetValue(string(m_table_orders.GetValue(4,row))); m_sl_edit[7].SetValue(string(m_table_orders.GetValue(5,row))); m_tp_edit[7].SetValue(string(m_table_orders.GetValue(6,row))); m_pr_edit[4].GetTextBoxPointer().Update(true); m_sl_edit[7].GetTextBoxPointer().Update(true); m_tp_edit[7].GetTextBoxPointer().Update(true); } }
Lo último que nos queda por hacer es asignar las acciones necesarias a los botones Cambiar y Eliminar. Para ello, crearemos los métodos ModifyOrder() y RemoveOrder().
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::ModifyOrder(long lparam) { //--- Check the element ID if(lparam==m_small_button[2].Id()) { //--- Get index and symbol if(m_table_orders.SelectedItem()==WRONG_VALUE) return(false); int row=m_table_orders.SelectedItem(); ulong ticket=(ulong)m_table_orders.GetValue(0,row); //--- if(OrderSelect(ticket)) { string position_symbol=OrderGetString(ORDER_SYMBOL); // symbol ulong magic=OrderGetInteger(ORDER_MAGIC); // order MagicNumber ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); // order type //--- calculation and rounding of the Stop Loss and Take Profit values double sl=NormalizeDouble((double)m_sl_edit[7].GetValue(),_Digits); double tp=NormalizeDouble((double)m_tp_edit[7].GetValue(),_Digits); double price=NormalizeDouble((double)m_pr_edit[4].GetValue(),_Digits); //--- declare the request and the result MqlTradeRequest request; MqlTradeResult result; //--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); //--- set the operation parameters request.action=TRADE_ACTION_MODIFY; // trading operation type request.order = ticket; // order ticket request.symbol=Symbol(); // symbol request.sl =sl; // position Stop Loss request.tp =tp; // position Take Profit request.price=price; // new price request.magic=m_magic_number; // position MagicNumber //--- sending a request bool res=true; for(int j=0; j<5; j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) return(true); else PrintFormat("OrderSend error %d",GetLastError()); // if unable to send the request, output the error code } } } //--- return(false); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::RemoveOrder(long lparam) { //--- Check the element ID if(lparam==m_small_button[3].Id()) { //--- Get index and symbol if(m_table_orders.SelectedItem()==WRONG_VALUE) return(false); int row=m_table_orders.SelectedItem(); ulong ticket=(ulong)m_table_orders.GetValue(0,row); //--- if(OrderSelect(ticket)) { string position_symbol=OrderGetString(ORDER_SYMBOL); // symbol ulong magic=OrderGetInteger(ORDER_MAGIC); // order MagicNumber ENUM_ORDER_TYPE type=(ENUM_ORDER_TYPE)OrderGetInteger(ORDER_TYPE); // order type //--- declare the request and the result MqlTradeRequest request; MqlTradeResult result; //--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); //--- set the operation parameters request.action=TRADE_ACTION_REMOVE; // trading operation type request.order = ticket; // order ticket //--- sending a request bool res=true; for(int j=0; j<5; j++) { res=OrderSend(request,result); if(res && result.retcode==TRADE_RETCODE_DONE) return(true); else PrintFormat("OrderSend error %d",GetLastError()); // if unable to send the request, output the error code } } } //--- return(false); }
Y luego los llamaremos en el manejador de eventos fuera de cualquier sección:
//--- if(ModifyOrder(lparam)) return; if(RemoveOrder(lparam)) return;
Querríamos añadir otra característica conveniente para trabajar con órdenes pendientes. Esta ofrece la posibilidad de establecer el precio de apertura de una orden pendiente previamente seleccionada, con solo clicar en el gráfico. Para mayor claridad, esta acción se muestra en la figura 12:
Fig.12 Estableciendo el precio de apertura de una orden pendiente.
La secuencia a seguir sería:
- Clicando con el ratón en un campo de edición, activamos su edición.
- Después, transladamos el cursor del ratón a cualquier lugar del gráfico y nos orientamos según el eje de ordenadas. Para mayor comodidad, podemos pulsar la ruleta del ratón y seleccionar el precio necesario a través de la cruceta.
- Tras detener el cursor del ratón en el precio necesario, clicamos en el gráfico, y el precio se trasladará al campo de edición.
Con este método, podremos establecer rápida y cómodamente el precio de una orden pendiente, especialmente si debemos hacerlo a ojo. Si necesitamos establecer el precio de una forma más precisa, podremos introducirlo en el campo de edición, utilizando para ello el teclado. La implementación es muy sencilla. Creamos en el manejador de la clase básica una sección con el evento de clic sobre el gráfico y añadimos el siguiente código:
//--- The event of clicking on the chart if(id==CHARTEVENT_CLICK) { for(int i=0; i<4; i++) { if(m_pr_edit[i].GetTextBoxPointer().TextEditState()) { m_last_index=i; break; } else { if(m_last_index>=0) { //--- datetime dt =0; int window=0; //--- convert X and Y coordinates to date/time if(ChartXYToTimePrice(0,(int)lparam,(int)dparam,window,dt,m_xy_price)) { m_pr_edit[m_last_index].SetValue(DoubleToString(m_xy_price)); m_pr_edit[m_last_index].GetTextBoxPointer().Update(true); m_last_index=-1; } } } } }
Aquí, determinaremos cuál de los campos se está editando; luego lo guardaremos, obtendremos el valor haciendo clic en el gráfico e insertaremos este valor en el campo de edición. Podrá ver las principales posibilidades e innovaciones en el vídeo a continuación.
Conclusión
Al final del artículo se adjunta un fichero con todos los archivos enumerados, clasificados por carpetas. Por eso, para que funcione correctamente, basta con colocar la carpeta MQL5 en la carpeta raíz del terminal. Para encontrar la carpeta raíz del terminal en la que se encuentra la carpeta MQL5, debemos pulsar en MetaTarder 5 la combinación de Ctrl+Shift+D o utilizar el menú contextual como se muestra en la fig.13, más abajo.
Fig.13 Abriendo la carpeta MQL5 en la carpeta raíz del terminal MetaTrader 5.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/7981
- 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