![Instrumental para el comercio manual rápido: Funcionalidad básica](https://c.mql5.com/i/articles/overlay.png)
Instrumental para el comercio manual rápido: Funcionalidad básica
Contenido
Introducción
En la actualidad, cada vez son más los tráders que dan el salto a los sistemas comerciales automáticos. Muchos de ellos, o bien demandan una configuración inicial, o bien (una parte de los mismos) que los sistemas ya estén totalmente automatizados. No obstante, queda una parte significativa de tráders que comercian manualmente, a la antigua, dejando en sus sistemas comerciales espacio para la valoración humana a la hora de tomar decisiones en cada situación comercial concreta. A veces, estas situaciones se desarrollan a una gran velocidad, o bien, con comercio manual (por ejemplo, con scalping), resulta importante la precisión y la puntualidad en la entrada en el mercado. Para estas situaciones, resulta imprescindible tener herramientas que posibiliten implementar lo más rápidamente posible la acción pensada para entrar en el mercado o salir del mismo. Por eso, hemos decidido implementar nuestra funcionalidad básica para estas necesidades.
Concepto del instrumental
En primer lugar, debemos definir el conjunto de acciones básicas que podemos necesitar al comerciar manualmente, y también desarrollar un instrumento que permita ejecutar esto de la forma más rápida y cómoda posible. El comercio manual tiene lugar según un sistema comercial establecido, que presupone la entrada en el mercado mediante uno de los dos métodos: órdenes de mercado u órdenes pendientes. Por eso, las categorías básicas del instrumental abarcarán precisamente el trabajo con estos dos tipos de orden. Asimsimo, debemos elegir tareas que el tráder pueda realizar durante el comercio, así como reducir el tiempo y el número de acciones necesarias para su ejecución.
Fig.1 Ventana principal del instrumental
En la fig.1, podemos ver que aquí se presentan las dos categorías: una orden de mercado y una orden pendiente. Asimismo, hemos seleccionado tres tareas básicas que deberemos resolver rápidamente en ocasiones y, lo más importante, que no se pueden realizar en una sola acción. Como ya sabemos, en muchas aplicaciones, incluyendo el terminal MetaTrader 5, existe un conjunto propio de atajos de teclado para llamar rápidamente un comando o acción determinados. En esta aplicación, también tendremos en cuenta este punto. Entre paréntesis, indicamos el atajo de teclado cuya activación mediante un clic provoca una acción específica o, en el caso de trabajar con órdenes, hace que se abra una ventana para trabajar con ella. Pero también dispondremos de una funcionalidad para el ratón. Es decir, por ejemplo, podemos cerrar todas las órdenes rentables al mismo tiempo con el atajo de teclado C (ingl.) o clicando en el botón "Cerrar todas las posiciones rentables".
Presionando el atajo de teclado M (ingl.) o clicando en el botón "Orden de mercado", se abrirá la ventana "Ajustes: Orden de mercado". En esta, dispondremos del instrumental necesario para introducir los datos para colocar órdenes de compra o venta en el mercado.
Fig.2 Ventana de ajustes y creación de órdenes de mercado.
Aparte de los ajustes principales, semejantes a los del terminal, ofrecemos la posibilidad de seleccionar el lote no solo en valor numérico, sino también partiendo del valor porcentual del balance de la cuenta comercial. Asimismo, es posible colocar el Take Profit y el Stop Loss no solo en formato de precio, sino también en puntos. Las acciones Comprar y Vender, al igual que sucede arriba, se pueden ejecutar de dos formas: pulsando el botón correspondiente o el atajo de teclado indicado entre paréntesis.
Ahora, al pulsar P (ingl.), entraremos en la ventana de ajustes de las órdenes pendientes. Aquí se muestran cuatro.
Fig.3 Ventana de ajustes y creación de órdenes pendientes.
De la misma forma que en la ventana de órdenes de mercado, en las órdenes pendientes, es posible seleccionar el lote en valor numérico o tanto por ciento del balance; asimismo, el Take Profit y el Stop Loss se puede elegir en precio o puntos.
Implementando las herramientas
Para comenzar, vamos a crear la estructura inicial del proyecto. En la carpeta Experts, creamos la carpeta Simple Trading, y en esta, creamos varios archivos de la forma que se indica en la fig.4, un poco más abajo.
Fig.4 Estructura de archivos del proyecto
Vamos a analizar la misión de los archivos creados:
- SimpleTrading.mq5: archivo del experto en el que se encontrarán la construcción de la interfaz gráfica y los ajustes de la aplicación.
- Program.mqh: archivo de inclusión en el experto, contendrá la clase CFastTrading, sus campos y métodos, así como parte de su implementación.
- MainWindow.mqh: archivo de inclusión en Program.mqh, contendrá la implementación de los métodos de los elementos de la interfaz gráfica.
- Defines.mqh: también se incluye en Program.mqh, y contiene un conjunto de macrosustituciones en los elementos de la interfaz para implementar la versión rusa e inglesa.
Para comenzar, entraremos en Program.mqh, incluiremos las bibliotecas necesarias para implementar la interfaz y las funciones comerciales, y también crearemos la clase CFastTrading.
//+------------------------------------------------------------------+ //| Program.mqh | //| Alex2356 | //| https://www.mql5.com/en/users/alex2356/seller | //+------------------------------------------------------------------+ #include <EasyAndFastGUI\WndEvents.mqh> #include <DoEasy25\Engine.mqh> #include "Defines.mqh" //+------------------------------------------------------------------+ //| Enumeration for switching the interface language | //+------------------------------------------------------------------+ enum LANG { RUSSIAN, // Russian ENGLISH // English }; //+------------------------------------------------------------------+ //| Clase para crear la aplicación | //+------------------------------------------------------------------+ class CFastTrading : public CWndEvents { public: CFastTrading(void); ~CFastTrading(void); //--- Inicialización/desinicialización void OnInitEvent(void); void OnDeinitEvent(const int reason); //--- Timer void OnTimerEvent(void); //--- Manejador de eventos del gráfico virtual void OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); }; //+------------------------------------------------------------------+ //| Adding GUI elements | //+------------------------------------------------------------------+ #include "MainWindow.mqh" //+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CFastTrading::CFastTrading(void) { } //+------------------------------------------------------------------+ //| Destructor | //+------------------------------------------------------------------+ CFastTrading::~CFastTrading(void) { } //+------------------------------------------------------------------+ //| Initialization | //+------------------------------------------------------------------+ void CFastTrading::OnInitEvent(void) { } //+------------------------------------------------------------------+ //| Desinicialización | //+------------------------------------------------------------------+ void CFastTrading::OnDeinitEvent(const int reason) { //--- Remove the interface CWndEvents::Destroy(); } //+------------------------------------------------------------------+ //| Temporizador | //+------------------------------------------------------------------+ void CFastTrading::OnTimerEvent(void) { CWndEvents::OnTimerEvent(); //--- } //+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void CFastTrading::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { } //+------------------------------------------------------------------+
Ahora, vamos a pasar al archivo del experto SimpleTrading.mq5, incluyendo en el mismo Program.mqh y creando una instancia de la clase que acabamos de crear. Asimismo, vamos a establecer los parámetros de entrada, entre los cuales tendremos:
- Base FontSize — tamaño básico de la fuente de la aplicación.
- Caption Color — color del encabezado de la ventana principal de la aplicación.
- Back color — color del fondo.
- Interface language — idioma de la interfaz.
- Magic Number — número único de las órdenes creadas por este experto.
//+------------------------------------------------------------------+ //| SimpleTrading.mq5 | //| Alex2356 | //| https://www.mql5.com/en/users/alex2356/seller | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, Alexander Fedosov" #property link "https://www.mql5.com/en/users/alex2356" #property version "1.00" //--- Include application class #include "Program.mqh" //+------------------------------------------------------------------+ //| Parámetros de entrada del experto | //+------------------------------------------------------------------+ input int Inp_BaseFont = 10; // Base FontSize input color Caption = C'0,130,225'; // Caption Color input color Background = clrWhite; // Back color input LANG Language = ENGLISH; // Interface language input ulong MagicNumber = 1111; // Magic Number //--- CFastTrading program; ulong tick_counter; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- tick_counter=GetTickCount(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { program.OnDeinitEvent(reason); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer(void) { program.OnTimerEvent(); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { program.ChartEvent(id,lparam,dparam,sparam); //--- if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI) { Print("End in ",GetTickCount()-tick_counter," ms"); } } //+------------------------------------------------------------------+
Para que los parámetros de entrada del experto estén disponibles para esta clase, deberemos crear en ella las variables a las que se asignarán los valores de sus ajustes en el experto, así como los métodos que posibilitarán la implementación de esto. Creamos las variables en la sección privada de la clase.
private: //--- color m_caption_color; color m_background_color; //--- int m_base_font_size; int m_m_edit_index; int m_p_edit_index; //--- ulong m_magic_number; //--- string m_base_font; //--- LANG m_language;
Y los métodos de la sección pública:
//--- Caption color void CaptionColor(const color clr); //--- Background color void BackgroundColor(const color clr); //--- Font size void FontSize(const int font_size); //--- Font name void FontName(const string font_name); //--- Setting the interface language void SetLanguage(const LANG lang); //--- Setting the magic number void SetMagicNumber(ulong magic_number);
Y los implementamos:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::CaptionColor(const color clr) { m_caption_color=clr; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::BackgroundColor(const color clr) { m_background_color=clr; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::FontSize(const int font_size) { m_base_font_size=font_size; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::FontName(const string font_name) { m_base_font=font_name; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::SetLanguage(const LANG lang) { m_language=lang; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::SetMagicNumber(const ulong magic_number) { m_magic_number=magic_number; }
Ahora, los aplicamos en la inicialización del experto:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- tick_counter=GetTickCount(); //--- Initialize class variables program.FontSize(Inp_BaseFont); program.BackgroundColor(Background); program.CaptionColor(Caption); program.SetLanguage(Language); program.SetMagicNumber(MagicNumber); //--- return(INIT_SUCCEEDED); }
Añadimos el método CreateGUI(), que creará la interfaz completa; esta permanecerá por el momento vacía. Se irá rellenando a medida que creemos los elementos de la interfaz.
//--- Creando la interfaz gráfica del programa bool CreateGUI(void);
Pero ya podemos añadirla a la inicialización del experto:
//+------------------------------------------------------------------+ //| 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 up the trading panel if(!program.CreateGUI()) { Print(__FUNCTION__," > ¡No se ha logrado crear la interfaz gráfica!"); return(INIT_FAILED); } //--- return(INIT_SUCCEEDED); }
Ahora, vamos a crear la ventana principal de la aplicación. Para ello, añadiremos el método CreateMainWindow() en la sección protegida (protected) de nuestra clase.
protected: //--- forms bool CreateMainWindow(void);
A continuación, ubicamos su implementación en el archivo MainWindow.mqh y lo llamamos en CreateGUI(). Ya hemos utilizado la macrosustitución CAPTION_NAME en la implementación del presente método, por eso, debemos crearla en un archivo especialmente creado para ello, Defines.mqh.
//+------------------------------------------------------------------+ //| Creates a form for orders | //+------------------------------------------------------------------+ bool CFastTrading::CreateMainWindow(void) { //--- Añadimos el puntero de la ventana a la matriz de ventanas CWndContainer::AddWindow(m_main_window); //--- Propiedades m_main_window.XSize(400); m_main_window.YSize(182); //--- Coordenadas 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); //--- Creando el formulario if(!m_main_window.CreateWindow(m_chart_id,m_subwin,CAPTION_NAME,x,y)) return(false); //--- return(true); } //+------------------------------------------------------------------+ //| Creates the graphical interface of the program | //+------------------------------------------------------------------+ bool CFastTrading::CreateGUI(void) { //--- Create the main application window if(!CreateMainWindow()) return(false); //--- Finalizando la creación de GUI CWndEvents::CompletedGUI(); return(true); }
Ahora, creamos la nueva macrosustitución.
//+------------------------------------------------------------------+ //| Macrosustituciones | //+------------------------------------------------------------------+ #include "Program.mqh" #define CAPTION_NAME (m_language==RUSSIAN ? "Быстрый трейдинг" : "Fast Trading")
Tras compilar el proyecto, obtendremos la ventana principal de la aplicación. Ahora, vamos a añadir los botones (Fig.1) que ejecutarán las acciones descritas más arriba. Para implementarlos, crearemos en la sección protegida de nuestra clase el método universal CreateButton().
//--- Botones bool CreateButton(CWindow &window,CButton &button,string text,color baseclr,int x_gap,int y_gap,int w_number);
Lo implementamos en MainWindow.mqhy lo aplicamos en el cuerpo del método para crear la ventana principal.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreateButton(CWindow &window,CButton &button,string text,color baseclr,int x_gap,int y_gap,int w_number) { //--- Guardamos el puntero a la ventana button.MainPointer(window); //--- Set properties before creation button.XSize(170); 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); //--- Creamos el elemento de control if(!button.CreateButton(text,x_gap,window.CaptionHeight()+y_gap)) return(false); //--- Añadimos el puntero al elemento en la base CWndContainer::AddToElementsArray(w_number,button); return(true); } //+------------------------------------------------------------------+ //| Creates a form for orders | //+------------------------------------------------------------------+ bool CFastTrading::CreateMainWindow(void) { //--- Añadimos el puntero de la ventana a la matriz de ventanas CWndContainer::AddWindow(m_main_window); //--- Propiedades m_main_window.XSize(400); m_main_window.YSize(182); //--- Coordenadas 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); m_main_window.TooltipsButtonIsUsed(true); //--- Creando el formulario if(!m_main_window.CreateWindow(m_chart_id,m_subwin,CAPTION_NAME,x,y)) return(false); //--- if(!CreateButton(m_main_window,m_order_button[0],MARKET_ORDER_NAME+"(M)",C'87,128,255',20,10,0)) return(false); if(!CreateButton(m_main_window,m_order_button[1],PENDING_ORDER_NAME+"(P)",C'31,209,111',210,10,0)) return(false); if(!CreateButton(m_main_window,m_order_button[2],MARKET_ORDERS_PROFIT_CLOSE+"(C)",C'87,128,255',20,60,0)) return(false); if(!CreateButton(m_main_window,m_order_button[3],MARKET_ORDERS_LOSS_CLOSE+"(D)",C'87,128,255',20,110,0)) return(false); if(!CreateButton(m_main_window,m_order_button[4],PEND_ORDERS_ALL_CLOSE+"(R)",C'31,209,111',210,60,0)) return(false); return(true); }
Pero aquí también se usan nuevas macrosustituciones, por eso, vamos a Defines.mqh y las escribimos en los dos idiomas.
//+------------------------------------------------------------------+ //| Macrosustituciones | //+------------------------------------------------------------------+ #include "Program.mqh" #define CAPTION_NAME (m_language==RUSSIAN ? "Быстрый трейдинг" : "Fast Trading System") #define MARKET_ORDER_NAME (m_language==RUSSIAN ? "Рыночный ордер" : "Marker Order") #define PENDING_ORDER_NAME (m_language==RUSSIAN ? "Отложенный ордер" : "Pending Order") #define MARKET_ORDERS_PROFIT_CLOSE (m_language==RUSSIAN ? "Закрыть все прибыльные" : "Close all profitable") #define MARKET_ORDERS_LOSS_CLOSE (m_language==RUSSIAN ? "Закрыть все убыточные" : "Close all losing") #define PEND_ORDERS_ALL_CLOSE (m_language==RUSSIAN ? "Закрыть все отложенные" : "Close all pending")
Como resultado, al seleccionar el inglés, obtendremos la siguiente variante.
Fig.5 Ventana principal de la aplicación.
La variante rusa se ha mostrado en la fig.1, más arriba. Como indicamos en el concepto, en la aplicación deberemos tener dos ventanas, para poder trabajar con las órdenes de mercado y las órdenes pendientes. Por eso, vamos a crearlas y vincularlas con los botones Market Order y Pending Order, como hemos establecido antes: pulsando estos, y también con la ayuda de los atajos de teclado М y Р, respectivamente. Para ello, añadimos los métodos CreateMarketOrdersWindow() y CreatePendingOrdersWindow() a la sección protegida de la clase y los implementamos en el archivo MainWindow.mqh.
bool CreateMarketOrdersWindow(void); bool CreatePendingOrdersWindow(void); //+------------------------------------------------------------------+ //| Market order creation and editing window | //+------------------------------------------------------------------+ bool CFastTrading::CreateMarketOrdersWindow(void) { //--- Añadimos el puntero de la ventana a la matriz de ventanas CWndContainer::AddWindow(m_orders_windows[0]); //--- Propiedades m_orders_windows[0].XSize(450); m_orders_windows[0].YSize(242+58); //--- Coordenadas int x=m_order_button[0].XGap(); int y=m_order_button[0].YGap()+60; //--- color clrmain=C'87,128,255'; //--- m_orders_windows[0].CaptionHeight(22); m_orders_windows[0].IsMovable(true); m_orders_windows[0].CaptionColor(clrmain); m_orders_windows[0].CaptionColorLocked(clrmain); m_orders_windows[0].CaptionColorHover(clrmain); m_orders_windows[0].BackColor(m_background_color); m_orders_windows[0].BorderColor(clrmain); m_orders_windows[0].FontSize(m_base_font_size); m_orders_windows[0].Font(m_base_font); m_orders_windows[0].WindowType(W_DIALOG); //--- Creando el formulario if(!m_orders_windows[0].CreateWindow(m_chart_id,m_subwin,CAPTION_M_ORD_NAME,x,y)) return(false); return(true); } //+------------------------------------------------------------------+ //| Pending order creation and editing window | //+------------------------------------------------------------------+ bool CFastTrading::CreatePendingOrdersWindow(void) { //--- Añadimos el puntero de la ventana a la matriz de ventanas CWndContainer::AddWindow(m_orders_windows[1]); //--- Propiedades m_orders_windows[1].XSize(600); m_orders_windows[1].YSize(580); //--- Coordenadas int x=m_order_button[0].XGap(); int y=m_order_button[0].YGap()+60; //--- color clrmain=C'31,209,111'; //--- m_orders_windows[1].CaptionHeight(22); m_orders_windows[1].IsMovable(true); m_orders_windows[1].CaptionColor(clrmain); m_orders_windows[1].CaptionColorLocked(clrmain); m_orders_windows[1].CaptionColorHover(clrmain); m_orders_windows[1].BackColor(m_background_color); m_orders_windows[1].BorderColor(clrmain); m_orders_windows[1].FontSize(m_base_font_size); m_orders_windows[1].Font(m_base_font); m_orders_windows[1].WindowType(W_DIALOG); //--- Creando el formulario if(!m_orders_windows[1].CreateWindow(m_chart_id,m_subwin,CAPTION_P_ORD_NAME,x,y)) return(false); return(true); }
En ambos casos, se usan las nuevas macrosustituciones, por eso, añadimos estas al archivo correspondiente:
#define CAPTION_M_ORD_NAME (m_language==RUSSIAN ? "Настройка: Рыночный Ордер" : "Setting: Market Order") #define CAPTION_P_ORD_NAME (m_language==RUSSIAN ? "Настройка: Отложенный Ордер" : "Setting: Pending Order")
Ahora, llamamos a las ventanas recién creadas en el método básico CreateGUI() para crear la interfaz de la aplicación:
//+------------------------------------------------------------------+ //| Creates the graphical interface of the program | //+------------------------------------------------------------------+ bool CFastTrading::CreateGUI(void) { //--- Create the main application window if(!CreateMainWindow()) return(false); if(!CreateMarketOrdersWindow()) return(false); if(!CreatePendingOrdersWindow()) return(false); //--- Finalizando la creación de GUI CWndEvents::CompletedGUI(); return(true); }
Como ambas ventanas creadas son de diálogo, al iniciar la aplicación, veremos que no están, por eso, deberemos crear un mecanismo para representarlas. Sin embargo, ya hemos decidido cuáles serán: la apertura al pulsar los botones Orden de mercado y Orden pendiente, y los atajos de teclado. Para ello, tenemos que encontrar en la clase básica el cuerpo del método OnEvent() y escribir las condiciones según nuestra tarea.
//+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void CFastTrading::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Pressing the button event if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { //--- if(lparam==m_order_button[0].Id()) m_orders_windows[0].OpenWindow(); //--- if(lparam==m_order_button[1].Id()) m_orders_windows[1].OpenWindow(); } //--- Key press event if(id==CHARTEVENT_KEYDOWN) { //--- Opening a market order window if(lparam==KEY_M) { if(m_orders_windows[0].IsVisible()) { m_orders_windows[0].CloseDialogBox(); } else { if(m_orders_windows[1].IsVisible()) { m_orders_windows[1].CloseDialogBox(); } //--- m_orders_windows[0].OpenWindow(); } } //--- Opening a pending order window if(lparam==KEY_P) { if(m_orders_windows[1].IsVisible()) { m_orders_windows[1].CloseDialogBox(); } else { if(m_orders_windows[0].IsVisible()) { m_orders_windows[0].CloseDialogBox(); } //--- m_orders_windows[1].OpenWindow(); } } } }
Ahora, podemos compilar el proyecto. Vamos a intentar abrir las ventas de diálogo con la ayuda de los atajos de teclado. Como resultado, deberemos obtener lo mostrado en la fig. 6:
Fig.6 Abriendo y alternando las ventanas con la ayuda de atajos de teclado.
Aquí, también notaremos que no es necesario cerrar una ventana de diálogo para abrir otra. Se produce el salto de una a otra, ahorrándole tiempo al usuario y permitiéndole, si fuera necesario, pasar de órdenes de mercado a órdenes pendientes con solo presionar un botón. Como pequeño detalle adicional, hemos decidido añadir el cierre de las ventanas de diálogo usando la tecla Esc . Tras probar la apertura un par de veces, apatece cerrarlas con este botón, así que añadiremos el siguiente código a la sección Evento al presionar un botón del teclado:
//--- Exiting the order placing window if(lparam==KEY_ESC) { if(m_orders_windows[0].IsVisible()) { m_orders_windows[0].CloseDialogBox(); } else if(m_orders_windows[1].IsVisible()) { m_orders_windows[1].CloseDialogBox(); } }
Vamos a trabajar a fondo con las ventanas creadas: comenzaremos con la ventana de las órdenes de mercado. Como podemos recordar mirando la fig.2, debemos crear dos bloques para la gestión de las órdenes de compra y venta. Para comenzar, crearemos dos elementos de la interfaz: los frames con el nuevo método CreateFrame().
bool CreateFrame(CWindow &window,CFrame &frame,const int x_gap,const int y_gap,string caption,int w_number);
Su implementación será la siguiente:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreateFrame(CWindow &window,CFrame &frame,const int x_gap,const int y_gap,string caption,int w_number) { //--- Guardamos el puntero al elemento principal frame.MainPointer(window); //--- color clrmain=clrNONE; if(caption==BUY_ORDER) clrmain=C'88,212,210'; else if(caption==SELL_ORDER) clrmain=C'236,85,79'; //--- frame.YSize(110); frame.LabelColor(clrmain); frame.BorderColor(clrmain); frame.BackColor(m_background_color); frame.GetTextLabelPointer().BackColor(m_background_color); frame.Font(m_base_font); frame.FontSize(m_base_font_size); frame.AutoXResizeMode(true); frame.AutoXResizeRightOffset(10); //--- Creamos el elemento de control if(!frame.CreateFrame(caption,x_gap,window.CaptionHeight()+y_gap)) return(false); //--- Añadimos el objeto a la matriz general de grupos de objetos CWndContainer::AddToElementsArray(w_number,frame); return(true); }
A la hora de implementar un frame, tendremos dos nuevas macrosustituciones para los encabezados de los frames, por eso, las añadimos a Defines.mqh:
#define BUY_ORDER (m_language==RUSSIAN ? "Buy-ордер" : "Buy-order") #define SELL_ORDER (m_language==RUSSIAN ? "Sell-ордер" : "Sell-order")
Para aplicar esta método, tenemos que ir al final del cuerpo de CreateMarketOrdersWindow() para la creación de la ventana de órdenes de mercado y añadir lo siguiente:
... //--- Creando el formulario if(!m_orders_windows[0].CreateWindow(m_chart_id,m_subwin,CAPTION_M_ORD_NAME,x,y)) return(false); //--- BUY BLOCK if(!CreateFrame(m_orders_windows[0],m_frame[0],10,20,BUY_ORDER,1)) return(false); //--- SELL BLOCK if(!CreateFrame(m_orders_windows[0],m_frame[1],10,160,SELL_ORDER,1)) return(false); return(true); }
Veamos qué hemos obtenido:
Fig.7 Bloques de órdenes de mercado.
En cada uno de los bloques en la fig.7 se encontrarán las 4 categorías principales de la interfaz:
- Encabezados de texto. Hablamos del Lote, el Take Profit, el Stop Loss y
- los botones de radio. Para el lote, será Lote/% del Depósito; para el Take Profit y el Stop Loss, será el modo de precio o puntos.
- Campos de edición. Lote, Take Profit y Stop Loss.
- Botón de acción. Comprar o vender.
Vamos a pasar a la implementación por etapas de cada categoría. Para los encabezados de texto, crearemos el método universal CreateLabel().
//+------------------------------------------------------------------+ //| Creates the text label | //+------------------------------------------------------------------+ bool CFastTrading::CreateLabel(CWindow &window,CTextLabel &text_label,const int x_gap,const int y_gap,string label_text,int w_number) { //--- Guardamos el puntero a la ventana text_label.MainPointer(window); //--- text_label.Font(m_base_font); text_label.FontSize(m_base_font_size); text_label.XSize(80); text_label.BackColor(m_background_color); text_label.IsCenterText(true); //--- Creating a label if(!text_label.CreateTextLabel(label_text,x_gap,window.CaptionHeight()+y_gap)) return(false); //--- Añadimos el puntero al elemento en la base CWndContainer::AddToElementsArray(w_number,text_label); return(true); }
Un poco más arriba, completamos el método de creación de la ventana de la orden de mercado, ahora vamos a completarlo teniendo en cuenta la implementación actual.
//--- BUY BLOCK if(!CreateFrame(m_orders_windows[0],m_frame[0],10,20,BUY_ORDER,1)) return(false); //--- Encabezados if(!CreateLabel(m_orders_windows[0],m_m_text_labels[0],20,30,LOT,1)) return(false); if(!CreateLabel(m_orders_windows[0],m_m_text_labels[1],20+80+20,30,TP,1)) return(false); if(!CreateLabel(m_orders_windows[0],m_m_text_labels[2],20+(80+20)*2,30,SL,1)) return(false); //--- SELL BLOCK if(!CreateFrame(m_orders_windows[0],m_frame[1],10,160,SELL_ORDER,1)) return(false); //--- Encabezados if(!CreateLabel(m_orders_windows[0],m_m_text_labels[3],20,170,LOT,1)) return(false); if(!CreateLabel(m_orders_windows[0],m_m_text_labels[4],20+80+20,170,TP,1)) return(false); if(!CreateLabel(m_orders_windows[0],m_m_text_labels[5],20+(80+20)*2,170,SL,1)) return(false); return(true); }
Aquí encontramos otras tres nuevas macrosustituciones para el Lote, el Take Profit y el Stop Loss. Por eso, vamos a añadir sus nombres en los dos idiomas:
#define LOT (m_language==RUSSIAN ? "Лот" : "Lot") #define TP (m_language==RUSSIAN ? "Тейк профит" : "Take Profit") #define SL (m_language==RUSSIAN ? "Стоп лосс" : "Stop Loss")
Los botones de radio son la siguiente categoría. Para ellos, crearemos especialmente el método CreateSwitchButton().
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreateSwitchButton(CWindow &window,CButton &button,string text,int x_gap,int y_gap,int w_number) { //--- Guardamos el puntero a la ventana button.MainPointer(window); color baseclr=clrSlateBlue; color pressclr=clrIndigo; //--- 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); button.TwoState(true); //--- Creamos el elemento de control if(!button.CreateButton(text,x_gap,window.CaptionHeight()+y_gap)) return(false); //--- Añadimos el puntero al elemento en la base CWndContainer::AddToElementsArray(w_number,button); return(true); }
Este método lo utilizaremos igualmente para los dos bloques, en el propio método CreateMarketWindow():
... //--- BUY BLOCK if(!CreateFrame(m_orders_windows[0],m_frame[0],10,20,BUY_ORDER,1)) return(false); //--- Encabezados if(!CreateLabel(m_orders_windows[0],m_m_text_labels[0],20,30,LOT,1)) return(false); if(!CreateLabel(m_orders_windows[0],m_m_text_labels[1],20+80+20,30,TP,1)) return(false); if(!CreateLabel(m_orders_windows[0],m_m_text_labels[2],20+(80+20)*2,30,SL,1)) return(false); //--- Toggle buttons for(int i=0; i<3; i++) if(!CreateSwitchButton(m_orders_windows[0],m_switch_button[i],"-",20+(80+20)*i,60,1)) return(false); //--- SELL BLOCK if(!CreateFrame(m_orders_windows[0],m_frame[1],10,160,SELL_ORDER,1)) return(false); //--- Encabezados if(!CreateLabel(m_orders_windows[0],m_m_text_labels[3],20,170,LOT,1)) return(false); if(!CreateLabel(m_orders_windows[0],m_m_text_labels[4],20+80+20,170,TP,1)) return(false); if(!CreateLabel(m_orders_windows[0],m_m_text_labels[5],20+(80+20)*2,170,SL,1)) return(false); //--- Toggle buttons for(int i=3; i<6; i++) if(!CreateSwitchButton(m_orders_windows[0],m_switch_button[i],"-",20+(80+20)*(i-3),170+30,1)) return(false); return(true); }
Deberemos detenernos en la siguiente categoría de Campos de edición con más detalle, pues requerirá que comprendamos adicionalmente que estos elementos de la interfaz deben tener ciertas restricciones de uso. Por ejemplo, debemos considerar que los valores para el campo de entrada Lote deben estar limitados por la especificación del símbolo actual, así como por las características específicas de la cuenta comercial. Es decir, a la hora de editar este campo, será necesario considerar parámetros tales como el lote mínimo y máximo posibles, además del salto mínimo para cambiarlos. Partiendo de ello, creamos CreateLotEdit() y configuramos su campo de edición de la forma correspondiente:
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreateLotEdit(CWindow &window,CTextEdit &text_edit,const int x_gap,const int y_gap,int w_number) { //--- Guardamos el puntero al elemento principal text_edit.MainPointer(window); //--- Propiedades text_edit.XSize(80); text_edit.YSize(24); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.MaxValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX)); text_edit.StepValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP)); text_edit.MinValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN)); text_edit.SpinEditMode(true); text_edit.SetDigits(2); text_edit.GetTextBoxPointer().XGap(1); text_edit.GetTextBoxPointer().XSize(80); //--- Creamos el elemento de control if(!text_edit.CreateTextEdit("",x_gap,window.CaptionHeight()+y_gap)) return(false); text_edit.SetValue(string(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN))); //--- Añadimos el objeto a la matriz general de grupos de objetos CWndContainer::AddToElementsArray(w_number,text_edit); return(true); }
Asimismo, creamos los campos de edición para el Stop Loss y el Take Profit y añadimos todo lo creado en el mismo método para crear las ventanas de las órdenes de mercado.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreateTakeProfitEdit(CWindow &window,CTextEdit &text_edit,const int x_gap,const int y_gap,int w_number) { //--- Guardamos el puntero al elemento principal text_edit.MainPointer(window); //--- Propiedades 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(1); text_edit.MinValue(0); text_edit.SpinEditMode(true); text_edit.GetTextBoxPointer().XGap(1); text_edit.GetTextBoxPointer().XSize(80); //--- Creamos el elemento de control if(!text_edit.CreateTextEdit("",x_gap,window.CaptionHeight()+y_gap)) return(false); text_edit.SetValue(string(150)); //--- Añadimos el objeto a la matriz general de grupos de objetos CWndContainer::AddToElementsArray(w_number,text_edit); return(true); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreateStopLossEdit(CWindow &window,CTextEdit &text_edit,const int x_gap,const int y_gap,int w_number) { //--- Guardamos el puntero al elemento principal text_edit.MainPointer(window); //--- Propiedades 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(1); text_edit.MinValue(0); text_edit.SpinEditMode(true); text_edit.GetTextBoxPointer().XGap(1); text_edit.GetTextBoxPointer().XSize(80); //--- Creamos el elemento de control if(!text_edit.CreateTextEdit("",x_gap,window.CaptionHeight()+y_gap)) return(false); text_edit.SetValue(string(150)); //--- Añadimos el objeto a la matriz general de grupos de objetos CWndContainer::AddToElementsArray(w_number,text_edit); return(true); }
La última categoría de elementos de la interfaz de esta ventana serán los dos botones Comprar y Vender, así como los métodos que les corresponden para su adición CreateBuyButton() y CreateSellButton().
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreateBuyButton(CWindow &window,CButton &button,string text,int x_gap,int y_gap,int w_number) { //--- Guardamos el puntero a la ventana button.MainPointer(window); color baseclr=C'88,212,210'; //--- Set properties before creation button.XSize(120); 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); //--- Creamos el elemento de control if(!button.CreateButton(text,x_gap,y_gap)) return(false); //--- Añadimos el puntero al elemento en la base CWndContainer::AddToElementsArray(w_number,button); return(true); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreateSellButton(CWindow &window,CButton &button,string text,int x_gap,int y_gap,int w_number) { //--- Guardamos el puntero a la ventana button.MainPointer(window); color baseclr=C'236,85,79'; //--- Set properties before creation button.XSize(120); 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); //--- Creamos el elemento de control if(!button.CreateButton(text,x_gap,y_gap)) return(false); //--- Añadimos el puntero al elemento en la base CWndContainer::AddToElementsArray(w_number,button); return(true); }
Añadimos dos botones adicionales, y con ello finalizamos la implementación del método CreateMarketWindow():
//+------------------------------------------------------------------+ //| Market order creation and editing window | //+------------------------------------------------------------------+ bool CFastTrading::CreateMarketOrdersWindow(void) { //--- Añadimos el puntero de la ventana a la matriz de ventanas CWndContainer::AddWindow(m_orders_windows[0]); //--- Propiedades m_orders_windows[0].XSize(450); m_orders_windows[0].YSize(242+58); //--- Coordenadas int x=m_order_button[0].XGap(); int y=m_order_button[0].YGap()+60; //--- color clrmain=C'87,128,255'; //--- m_orders_windows[0].CaptionHeight(22); m_orders_windows[0].IsMovable(true); m_orders_windows[0].CaptionColor(clrmain); m_orders_windows[0].CaptionColorLocked(clrmain); m_orders_windows[0].CaptionColorHover(clrmain); m_orders_windows[0].BackColor(m_background_color); m_orders_windows[0].BorderColor(clrmain); m_orders_windows[0].FontSize(m_base_font_size); m_orders_windows[0].Font(m_base_font); m_orders_windows[0].WindowType(W_DIALOG); //--- Creando el formulario if(!m_orders_windows[0].CreateWindow(m_chart_id,m_subwin,CAPTION_M_ORD_NAME,x,y)) return(false); //--- BUY BLOCK if(!CreateFrame(m_orders_windows[0],m_frame[0],10,20,BUY_ORDER,1)) return(false); //--- Encabezados if(!CreateLabel(m_orders_windows[0],m_m_text_labels[0],20,30,LOT,1)) return(false); if(!CreateLabel(m_orders_windows[0],m_m_text_labels[1],20+80+20,30,TP,1)) return(false); if(!CreateLabel(m_orders_windows[0],m_m_text_labels[2],20+(80+20)*2,30,SL,1)) return(false); //--- Toggle buttons for(int i=0; i<3; i++) if(!CreateSwitchButton(m_orders_windows[0],m_switch_button[i],"-",20+(80+20)*i,60,1)) return(false); //--- Campo de edición if(!CreateLotEdit(m_orders_windows[0],m_lot_edit[0],20,60+34,1)) return(false); if(!CreateTakeProfitEdit(m_orders_windows[0],m_tp_edit[0],20+(80+20),60+34,1)) return(false); if(!CreateStopLossEdit(m_orders_windows[0],m_sl_edit[0],20+(80+20)*2,60+34,1)) return(false); //--- The Buy button if(!CreateBuyButton(m_orders_windows[0],m_buy_execute,BUY+"(B)",m_orders_windows[0].XSize()-(120+20),103,1)) return(false); //--- SELL BLOCK if(!CreateFrame(m_orders_windows[0],m_frame[1],10,160,SELL_ORDER,1)) return(false); //--- Encabezados if(!CreateLabel(m_orders_windows[0],m_m_text_labels[3],20,170,LOT,1)) return(false); if(!CreateLabel(m_orders_windows[0],m_m_text_labels[4],20+80+20,170,TP,1)) return(false); if(!CreateLabel(m_orders_windows[0],m_m_text_labels[5],20+(80+20)*2,170,SL,1)) return(false); //--- Toggle buttons for(int i=3; i<6; i++) if(!CreateSwitchButton(m_orders_windows[0],m_switch_button[i],"-",20+(80+20)*(i-3),170+30,1)) return(false); //--- Campo de edición if(!CreateLotEdit(m_orders_windows[0],m_lot_edit[1],20,170+30+35,1)) return(false); if(!CreateTakeProfitEdit(m_orders_windows[0],m_tp_edit[1],20+80+20,170+30+35,1)) return(false); if(!CreateStopLossEdit(m_orders_windows[0],m_sl_edit[1],20+(80+20)*2,170+30+35,1)) return(false); //--- The Sell button if(!CreateSellButton(m_orders_windows[0],m_sell_execute,SELL+"(S)",m_orders_windows[0].XSize()-(120+20),242,1)) return(false); return(true); }
Tampoco tenemos que olvidarnos de añadir las dos nuevas macrosustituciones:
#define BUY (m_language==RUSSIAN ? "Купить" : "Buy") #define SELL (m_language==RUSSIAN ? "Продать" : "Sell")
Compilamos el proyecto en esta etapa y obtenemos el siguiente resultado:
Fig.8 Interfaz completa de la ventana de creación de órdenes de mercado.
Pero, por el momento, esto es solo un borrador en forma de plantilla. Para desarrollarlo, ahora tenemos que resolver las siguientes tareas:
- Darle vida a los botones de radio.
- Dependiendo de su estado, tendremos que cambiar las propiedades de los campos de edición.
- Implementar la colocación de órdenes de mercado de acuerdo con los modos de los conmutadores y estos campos de edición.
Antes de comenzar a implementar el mecanismo de conmutación de los botones con el que se modificarán los nombres, deberemos establecer sus valores por defecto, dado que ahora en la fig.8 se ven solo rayas. Para establecer el nombre, introduciremos el método SetButtonParam(), tanto más que lo necesitaremos en el futuro para cambiar este.
//+------------------------------------------------------------------+ //| Setting the button text | //+------------------------------------------------------------------+ void CFastTrading::SetButtonParam(CButton &button,string text) { button.LabelText(text); button.Update(true); }
Pasamos al manejador de eventos e introducimos la sección Evento al terminar de crear la interfaz, y ahí, con la ayuda de nuestro método SetButtonParam(), asignamos los nombres a los botones de radio.
//--- UI creation completion if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI) { //--- SetButtonParam(m_switch_button[0],LOT); SetButtonParam(m_switch_button[1],POINTS); SetButtonParam(m_switch_button[2],POINTS); SetButtonParam(m_switch_button[3],LOT); SetButtonParam(m_switch_button[4],POINTS); SetButtonParam(m_switch_button[5],POINTS); }
Aquí, tendremos otra macrosustitución para el nombre de los botones. La escribimos en Defines.mqh:
#define POINTS (m_language==RUSSIAN ? "Пункты" : "Points")
Ya hemos finalizado la preparación para la creación del mecanismo. Creamos la función ButtonSwitch(), que monitoreará el estado del botón(pulsado/no pulsado) del conmutador, y que, partiendo de este, le asignará el nombre.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::ButtonSwitch(CButton &button,long lparam,string state1,string state2) { if(lparam==button.Id()) { if(!button.IsPressed()) SetButtonParam(button,state1); else SetButtonParam(button,state2); } }
Como la alternancia del nombre tiene lugar al darse el evento de pulsación del botón, llamaremos al método creado precisamente en la misma sección del manejador de eventos.
//--- Pressing the button event if(id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON) { //--- if(lparam==m_order_button[0].Id()) m_orders_windows[0].OpenWindow(); //--- if(lparam==m_order_button[1].Id()) m_orders_windows[1].OpenWindow(); //--- ButtonSwitch(m_switch_button[0],lparam,LOT,PERC_DEPO); ButtonSwitch(m_switch_button[1],lparam,POINTS,PRICE); ButtonSwitch(m_switch_button[2],lparam,POINTS,PRICE); ButtonSwitch(m_switch_button[3],lparam,LOT,PERC_DEPO); ButtonSwitch(m_switch_button[4],lparam,POINTS,PRICE); ButtonSwitch(m_switch_button[5],lparam,POINTS,PRICE); }
Asimismo, describimos las macrosustituciones en ambos idiomas:
#define PERC_DEPO (m_language==RUSSIAN ? "% Депозит" : "% Deposit") #define PRICE (m_language==RUSSIAN ? "Цена" : "Price")
Una vez más, compilamos el proyecto y obtenemos el cambio del nombre al pulsar el botón de conmutación:
Fig.9 Cambio de nombre de los botones de radio.
Continuamos. La siguiente tarea comprende la necesidad de modificar las propiedades de los campos de edición dependiendo del estado del botón de conmutación que les corresponda. Para ello, introducimos tres nuevos métodos para cada campo de edición: LotMarketSwitch(),TakeMarketSwitch(),StopMarketSwitch(). Para el primer método, será la conmutación del valor del lote al tanto por ciento del balance de la cuenta.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::LotMarketSwitch(long lparam) { for(int i=0; i<2; i++) { if(lparam==m_switch_button[i*3].Id()) { if(m_switch_button[i*3].IsPressed()) { m_lot_edit[i].SetDigits(0); m_lot_edit[i].StepValue(1); m_lot_edit[i].MaxValue(100); m_lot_edit[i].MinValue(1); m_lot_edit[i].SetValue(string(2)); m_lot_edit[i].GetTextBoxPointer().Update(true); } else { m_lot_edit[i].SetDigits(2); m_lot_edit[i].StepValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP)); m_lot_edit[i].MinValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN)); m_lot_edit[i].MaxValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX)); m_lot_edit[i].SetValue(string(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN))); m_lot_edit[i].GetTextBoxPointer().Update(true); } } } }
Para los otros métodos, del valor de establecimiento del nivel en puntos al valor de precio.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::TakeMarketSwitch(long lparam) { for(int i=0; i<2; i++) { if(lparam==m_switch_button[3*i+1].Id()) { if(m_switch_button[3*i+1].IsPressed()) { MqlTick tick; if(SymbolInfoTick(Symbol(),tick)) { m_tp_edit[i].SetDigits(_Digits); m_tp_edit[i].StepValue(_Point); m_tp_edit[i].SetValue(string(tick.ask)); m_tp_edit[i].GetTextBoxPointer().Update(true); } } else { m_tp_edit[i].SetDigits(0); m_tp_edit[i].StepValue(1); m_tp_edit[i].SetValue(string(150)); m_tp_edit[i].GetTextBoxPointer().Update(true); } } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::StopMarketSwitch(long lparam) { for(int i=0; i<2; i++) { if(lparam==m_switch_button[3*i+2].Id()) { if(m_switch_button[3*i+2].IsPressed()) { MqlTick tick; if(SymbolInfoTick(Symbol(),tick)) { m_sl_edit[i].SetDigits(_Digits); m_sl_edit[i].StepValue(_Point); m_sl_edit[i].SetValue(string(tick.bid)); m_sl_edit[i].GetTextBoxPointer().Update(true); } } else { m_sl_edit[i].SetDigits(0); m_sl_edit[i].StepValue(1); m_sl_edit[i].SetValue(string(150)); m_sl_edit[i].GetTextBoxPointer().Update(true); } } } }
Ahora, llamamos a los tres métodos al manejador de eventos en la sección Evento pulsando el botón:
// --- Switch Lot/Percent of balance LotMarketSwitch(lparam); //--- Switch Take Profit Points/Price TakeMarketSwitch(lparam); //--- Switch Stop Loss Points/Price StopMarketSwitch(lparam);
Y comprobamos qué hemos obtenido:
Fig.10 Alternancia de los modos de los campos de edición en la ventana de órdenes de mercado.
La última tarea consistirá en colocar las órdenes de mercado de acuerdo con los ajustes y los valores de los campos de edición con la ayuda de un botón o un atajo de teclado. Para ello, crearemos tres métodos nuevos:
- OnInitTrading() — define y configura las condiciones del entorno comercial.
- SetBuyOrder() — abre una orden de mercado de compra.
- SetSellOrder() — abre una orden de mercado de venta.
Los creamos en la sección privada de nuestra clase básica CFastTrading, implementándolos luego allí mismo.
//+------------------------------------------------------------------+ //| Trading Environment Initialization | //+------------------------------------------------------------------+ void CFastTrading::OnInitTrading() { string array_used_symbols[]; //--- Fill in the array of used symbols CreateUsedSymbolsArray(SYMBOLS_MODE_CURRENT,"",array_used_symbols); //--- Set the type of the used symbol list in the symbol collection and fill in the list of symbol timeseries m_trade.SetUsedSymbols(array_used_symbols); //--- Pass all existing collections to the trading class m_trade.TradingOnInit(); m_trade.TradingSetMagic(m_magic_number); m_trade.TradingSetLogLevel(LOG_LEVEL_ERROR_MSG); //--- Set synchronous passing of orders for all used symbols m_trade.TradingSetAsyncMode(false); //--- Set correct order expiration and filling types to all trading objects m_trade.TradingSetCorrectTypeExpiration(); m_trade.TradingSetCorrectTypeFilling(); }
Este método lo llamamos en el constructor de la clase:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CFastTrading::CFastTrading(void) { OnInitTrading(); }
Antes de implementar las funciones comerciales, deberemos tener en cuenta que la ejecución de estas se realizará según dos eventos diferentes.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::SetBuyOrder(int id,long lparam) { if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_buy_execute.Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_B)) { //--- double lot; if(m_switch_button[0].IsPressed()) lot=LotPercent(Symbol(),ORDER_TYPE_BUY,SymbolInfoDouble(Symbol(),SYMBOL_ASK),StringToDouble(m_lot_edit[0].GetValue())); else lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[0].GetValue())); if(m_switch_button[1].IsPressed() && m_switch_button[2].IsPressed()) { double tp=double(m_tp_edit[0].GetValue()); double sl=double(m_sl_edit[0].GetValue()); if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp)) return(true); } else if(!m_switch_button[1].IsPressed() && !m_switch_button[2].IsPressed()) { int tp=int(m_tp_edit[0].GetValue()); int sl=int(m_sl_edit[0].GetValue()); if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp)) return(true); } else if(m_switch_button[1].IsPressed() && !m_switch_button[2].IsPressed()) { double tp=double(m_tp_edit[0].GetValue()); int sl=int(m_sl_edit[0].GetValue()); if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp)) return(true); } else if(!m_switch_button[1].IsPressed() && m_switch_button[2].IsPressed()) { int tp=int(m_tp_edit[0].GetValue()); double sl=double(m_sl_edit[0].GetValue()); if(m_trade.OpenBuy(lot,Symbol(),m_magic_number,sl,tp)) return(true); } } return(false); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::SetSellOrder(int id,long lparam) { if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_sell_execute.Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_S)) { //--- double lot; if(m_switch_button[3].IsPressed()) lot=LotPercent(Symbol(),ORDER_TYPE_SELL,SymbolInfoDouble(Symbol(),SYMBOL_BID),StringToDouble(m_lot_edit[1].GetValue())); else lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[1].GetValue())); //--- if(m_switch_button[4].IsPressed() && m_switch_button[5].IsPressed()) { double tp=double(m_tp_edit[1].GetValue()); double sl=double(m_sl_edit[1].GetValue()); if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp)) return(true); } else if(!m_switch_button[4].IsPressed() && !m_switch_button[5].IsPressed()) { int tp=int(m_tp_edit[1].GetValue()); int sl=int(m_sl_edit[1].GetValue()); if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp)) return(true); } else if(!m_switch_button[4].IsPressed() && m_switch_button[5].IsPressed()) { int tp=int(m_tp_edit[1].GetValue()); double sl=double(m_sl_edit[1].GetValue()); if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp)) return(true); } else if(m_switch_button[4].IsPressed() && !m_switch_button[5].IsPressed()) { double tp=double(m_tp_edit[1].GetValue()); int sl=int(m_sl_edit[1].GetValue()); if(m_trade.OpenSell(lot,Symbol(),m_magic_number,sl,tp)) return(true); } } return(false); }
Aunque se producirá la llamada de ambos métodos comerciales, como sucede con los demás en el manejador de eventos, estos se encontrarán fuera de cualquiera de las secciones que identifican eventos por el motivo indicado anteriormente. La actual implementación de las funciones anteriores usa el método LotPercent() para calcular el tamaño del lote en el modo de tanto por ciento del saldo de la cuenta.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double CFastTrading::LotPercent(string symbol,ENUM_ORDER_TYPE trade_type,double price,double percent) { double margin=0.0; //--- checks if(symbol=="" || price<=0.0 || percent<1 || percent>100) return(0.0); //--- calculate margin requirements for 1 lot if(!OrderCalcMargin(trade_type,symbol,1.0,price,margin) || margin<0.0) return(0.0); //--- if(margin==0.0) return(0.0); //--- calculate maximum volume double volume=NormalizeDouble(AccountInfoDouble(ACCOUNT_BALANCE)*percent/100.0/margin,2); //--- normalize and check limits double stepvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP); if(stepvol>0.0) volume=stepvol*MathFloor(volume/stepvol); //--- double minvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MIN); if(volume<minvol) volume=0.0; //--- double maxvol=SymbolInfoDouble(symbol,SYMBOL_VOLUME_MAX); if(volume>maxvol) volume=maxvol; //--- return volume return(volume); }
Con esto, podemos dar por finalizado el trabajo con las órdenes de mercado. Ahora, vamos a dedicarnos a la ventana de creación de órdenes pendientes. Para ello, crearemos la interfaz por etapas en la misma secuencia que hemos seguido con la ventana de órdenes de mercado. No tiene demasiado sentido abarcar toda la secuencia de acciones, ya que hay muchos momentos que se repiten, pero sí que nos detendremos en los puntos nuevos.
//+------------------------------------------------------------------+ //| Pending order creation and editing window | //+------------------------------------------------------------------+ bool CFastTrading::CreatePendingOrdersWindow(void) { //--- Añadimos el puntero de la ventana a la matriz de ventanas CWndContainer::AddWindow(m_orders_windows[1]); //--- Propiedades m_orders_windows[1].XSize(600); m_orders_windows[1].YSize(580); //--- Coordenadas int x=m_order_button[0].XGap(); int y=m_order_button[0].YGap()+60; //--- color clrmain=C'31,209,111'; //--- m_orders_windows[1].CaptionHeight(22); m_orders_windows[1].IsMovable(true); m_orders_windows[1].CaptionColor(clrmain); m_orders_windows[1].CaptionColorLocked(clrmain); m_orders_windows[1].CaptionColorHover(clrmain); m_orders_windows[1].BackColor(m_background_color); m_orders_windows[1].BorderColor(clrmain); m_orders_windows[1].FontSize(m_base_font_size); m_orders_windows[1].Font(m_base_font); m_orders_windows[1].WindowType(W_DIALOG); //--- Creando el formulario if(!m_orders_windows[1].CreateWindow(m_chart_id,m_subwin,CAPTION_P_ORD_NAME,x,y)) return(false); //---BUY-STOP BLOCK if(!CreateFrame(m_orders_windows[1],m_frame[2],10,20,BUYSTOP_ORDER,2)) return(false); //--- Encabezados if(!CreateLabel(m_orders_windows[1],m_p_text_labels[0],20,60,PRICE,2)) return(false); if(!CreateLabel(m_orders_windows[1],m_p_text_labels[1],20+80+20,30,LOT,2)) return(false); if(!CreateLabel(m_orders_windows[1],m_p_text_labels[2],20+(80+20)*2,30,TP,2)) return(false); if(!CreateLabel(m_orders_windows[1],m_p_text_labels[3],20+(80+20)*3,30,SL,2)) return(false); //--- Switches for(int i=0; i<3; i++) if(!CreateSwitchButton(m_orders_windows[1],m_p_switch_button[i],"-",120+(80+20)*i,60,2)) return(false); //--- Campo de edición if(!CreatePriceEdit(m_orders_windows[1],m_pr_edit[0],20,60+35,2)) return(false); if(!CreateLotEdit(m_orders_windows[1],m_lot_edit[2],20+(80+20),60+35,2)) return(false); if(!CreateTakeProfitEdit(m_orders_windows[1],m_tp_edit[2],20+(80+20)*2,60+35,2)) return(false); if(!CreateStopLossEdit(m_orders_windows[1],m_sl_edit[2],20+(80+20)*3,60+35,2)) return(false); //--- Buy Stop placing button if(!CreateBuyButton(m_orders_windows[1],m_buystop_execute,"Buy Stop ( 1 )",m_orders_windows[1].XSize()-(120+20),103,2)) return(false); //---SELL-STOP BLOCK if(!CreateFrame(m_orders_windows[1],m_frame[3],10,160,SELLSTOP_ORDER,2)) return(false); //--- Encabezados if(!CreateLabel(m_orders_windows[1],m_p_text_labels[4],20,170+30,PRICE,2)) return(false); if(!CreateLabel(m_orders_windows[1],m_p_text_labels[5],20+80+20,170,LOT,2)) return(false); if(!CreateLabel(m_orders_windows[1],m_p_text_labels[6],20+(80+20)*2,170,TP,2)) return(false); if(!CreateLabel(m_orders_windows[1],m_p_text_labels[7],20+(80+20)*3,170,SL,2)) return(false); //--- Switches for(int i=3; i<6; i++) if(!CreateSwitchButton(m_orders_windows[1],m_p_switch_button[i],"-",120+(80+20)*(i-3),170+30,2)) return(false); //--- Campo de edición if(!CreatePriceEdit(m_orders_windows[1],m_pr_edit[1],20,170+30+35,2)) return(false); if(!CreateLotEdit(m_orders_windows[1],m_lot_edit[3],20+(80+20),170+30+35,2)) return(false); if(!CreateTakeProfitEdit(m_orders_windows[1],m_tp_edit[3],20+(80+20)*2,170+30+35,2)) return(false); if(!CreateStopLossEdit(m_orders_windows[1],m_sl_edit[3],20+(80+20)*3,170+30+35,2)) return(false); //--- Sell Stop placing button if(!CreateSellButton(m_orders_windows[1],m_sellstop_execute,"Sell Stop ( 2 )",m_orders_windows[1].XSize()-(120+20),242,2)) return(false); //---BUY-LIMIT BLOCK if(!CreateFrame(m_orders_windows[1],m_frame[4],10,300,BUYLIMIT_ORDER,2)) return(false); //--- Encabezados if(!CreateLabel(m_orders_windows[1],m_p_text_labels[8],20,330,PRICE,2)) return(false); if(!CreateLabel(m_orders_windows[1],m_p_text_labels[9],20+80+20,310,LOT,2)) return(false); if(!CreateLabel(m_orders_windows[1],m_p_text_labels[10],20+(80+20)*2,310,TP,2)) return(false); if(!CreateLabel(m_orders_windows[1],m_p_text_labels[11],20+(80+20)*3,310,SL,2)) return(false); //--- Switches for(int i=6; i<9; i++) if(!CreateSwitchButton(m_orders_windows[1],m_p_switch_button[i],"-",120+(80+20)*(i-6),330,2)) return(false); //--- Campo de edición if(!CreatePriceEdit(m_orders_windows[1],m_pr_edit[2],20,365,2)) return(false); if(!CreateLotEdit(m_orders_windows[1],m_lot_edit[4],20+(80+20),365,2)) return(false); if(!CreateTakeProfitEdit(m_orders_windows[1],m_tp_edit[4],20+(80+20)*2,365,2)) return(false); if(!CreateStopLossEdit(m_orders_windows[1],m_sl_edit[4],20+(80+20)*3,365,2)) return(false); //--- Buy Limit placing button if(!CreateBuyButton(m_orders_windows[1],m_buylimit_execute,"Buy Limit ( 3 )",m_orders_windows[1].XSize()-(120+20),382,2)) return(false); //---SELL-LIMIT BLOCK if(!CreateFrame(m_orders_windows[1],m_frame[5],10,440,SELLLIMIT_ORDER,2)) return(false); //--- Encabezados if(!CreateLabel(m_orders_windows[1],m_p_text_labels[12],20,470,PRICE,2)) return(false); if(!CreateLabel(m_orders_windows[1],m_p_text_labels[13],20+80+20,450,LOT,2)) return(false); if(!CreateLabel(m_orders_windows[1],m_p_text_labels[14],20+(80+20)*2,450,TP,2)) return(false); if(!CreateLabel(m_orders_windows[1],m_p_text_labels[15],20+(80+20)*3,450,SL,2)) return(false); //--- Switches for(int i=9; i<12; i++) if(!CreateSwitchButton(m_orders_windows[1],m_p_switch_button[i],"-",120+(80+20)*(i-9),470,2)) return(false); //--- Campo de edición if(!CreatePriceEdit(m_orders_windows[1],m_pr_edit[3],20,505,2)) return(false); if(!CreateLotEdit(m_orders_windows[1],m_lot_edit[5],20+(80+20),505,2)) return(false); if(!CreateTakeProfitEdit(m_orders_windows[1],m_tp_edit[5],20+(80+20)*2,505,2)) return(false); if(!CreateStopLossEdit(m_orders_windows[1],m_sl_edit[5],20+(80+20)*3,505,2)) return(false); //--- Sell Limit placing button if(!CreateSellButton(m_orders_windows[1],m_selllimit_execute,"Sell Limit ( 4 )",m_orders_windows[1].XSize()-(120+20),522,2)) return(false); //--- return(true); }
Como podemos ver por el listado de arriba, en ella se utilizan los mismos métodos que usamos al crear los elementos de la interfaz de la ventana de trabajo con órdenes de mercado. No obstante, sí que aparece un nuevo método. Hablamos de CreatePriceEdit(), y como podemos entender por el nombre, se trata del campo de edición para establecer el precio de una orden pendiente.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::CreatePriceEdit(CWindow &window,CTextEdit &text_edit,const int x_gap,const int y_gap,int w_number) { //--- Guardamos el puntero al elemento principal text_edit.MainPointer(window); //--- Propiedades text_edit.XSize(80); text_edit.YSize(24); text_edit.Font(m_base_font); text_edit.FontSize(m_base_font_size); text_edit.StepValue(_Point); text_edit.SpinEditMode(true); text_edit.SetDigits(_Digits); text_edit.GetTextBoxPointer().XGap(1); text_edit.GetTextBoxPointer().XSize(80); //--- Creamos el elemento de control if(!text_edit.CreateTextEdit("",x_gap,window.CaptionHeight()+y_gap)) return(false); //--- MqlTick tick; if(SymbolInfoTick(Symbol(),tick)) text_edit.SetValue(string(tick.bid)); //--- Añadimos el objeto a la matriz general de grupos de objetos CWndContainer::AddToElementsArray(w_number,text_edit); return(true); }
Después de implementarlo, la plantilla inicial estará preparada, pero la interfaz aún no funcionará, ya que necesitamos configurarla. Aquí, nos ceñiremos igualmente a la misma secuencia de antes: configuramos los botones de radio, vinculamos su estado con las propiedades de los campos de edición y creamos las funciones para colocar las órdenes pendientes.
Entramos en el manejador de eventos OnEvent() en la sección Finalización de la creación de la interfaz, e indicamos los nombres de los botones de radio:
//--- UI creation completion if(id==CHARTEVENT_CUSTOM+ON_END_CREATE_GUI) { //--- SetButtonParam(m_switch_button[0],LOT); SetButtonParam(m_switch_button[1],POINTS); SetButtonParam(m_switch_button[2],POINTS); SetButtonParam(m_switch_button[3],LOT); SetButtonParam(m_switch_button[4],POINTS); SetButtonParam(m_switch_button[5],POINTS); //--- SetButtonParam(m_p_switch_button[0],LOT); SetButtonParam(m_p_switch_button[1],POINTS); SetButtonParam(m_p_switch_button[2],POINTS); SetButtonParam(m_p_switch_button[3],LOT); SetButtonParam(m_p_switch_button[4],POINTS); SetButtonParam(m_p_switch_button[5],POINTS); SetButtonParam(m_p_switch_button[6],LOT); SetButtonParam(m_p_switch_button[7],POINTS); SetButtonParam(m_p_switch_button[8],POINTS); SetButtonParam(m_p_switch_button[9],LOT); SetButtonParam(m_p_switch_button[10],POINTS); SetButtonParam(m_p_switch_button[11],POINTS); }
Pasamos a la sección del Evento de pulsación del botón e implementamos el mecanismo de conmutación del estado de los botones con cambio de nombre:
//--- ButtonSwitch(m_p_switch_button[0],lparam,LOT,PERC_DEPO); ButtonSwitch(m_p_switch_button[1],lparam,POINTS,PRICE); ButtonSwitch(m_p_switch_button[2],lparam,POINTS,PRICE); ButtonSwitch(m_p_switch_button[3],lparam,LOT,PERC_DEPO); ButtonSwitch(m_p_switch_button[4],lparam,POINTS,PRICE); ButtonSwitch(m_p_switch_button[5],lparam,POINTS,PRICE); ButtonSwitch(m_p_switch_button[6],lparam,LOT,PERC_DEPO); ButtonSwitch(m_p_switch_button[7],lparam,POINTS,PRICE); ButtonSwitch(m_p_switch_button[8],lparam,POINTS,PRICE); ButtonSwitch(m_p_switch_button[9],lparam,LOT,PERC_DEPO); ButtonSwitch(m_p_switch_button[10],lparam,POINTS,PRICE); ButtonSwitch(m_p_switch_button[11],lparam,POINTS,PRICE);
Para vincular los estados de los botones de radio con los campos de edición y sus propiedades, vamos a crear tres nuevos métodos: LotPendingSwitch(), TakePendingSwitch(), StopPendingSwitch().
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::LotPendingSwitch(long lparam) { for(int i=0; i<4; i++) { if(lparam==m_p_switch_button[3*i].Id()) { if(m_p_switch_button[3*i].IsPressed()) { m_lot_edit[i+2].SetDigits(0); m_lot_edit[i+2].StepValue(1); m_lot_edit[i+2].MaxValue(100); m_lot_edit[i+2].MinValue(1); m_lot_edit[i+2].SetValue(string(2)); m_lot_edit[i+2].GetTextBoxPointer().Update(true); } else { m_lot_edit[i+2].SetDigits(2); m_lot_edit[i+2].StepValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP)); m_lot_edit[i+2].MinValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN)); m_lot_edit[i+2].MaxValue(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX)); m_lot_edit[i+2].SetValue(string(SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN))); m_lot_edit[i+2].GetTextBoxPointer().Update(true); } } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::TakePendingSwitch(long lparam) { for(int i=0; i<4; i++) { if(lparam==m_p_switch_button[3*i+1].Id()) { if(m_p_switch_button[3*i+1].IsPressed()) { MqlTick tick; if(SymbolInfoTick(Symbol(),tick)) { m_tp_edit[i+2].SetDigits(_Digits); m_tp_edit[i+2].StepValue(_Point); m_tp_edit[i+2].SetValue(string(tick.ask)); m_tp_edit[i+2].GetTextBoxPointer().Update(true); } } else { m_tp_edit[i+2].SetDigits(0); m_tp_edit[i+2].StepValue(1); m_tp_edit[i+2].SetValue(string(150)); m_tp_edit[i+2].GetTextBoxPointer().Update(true); } } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::StopPendingSwitch(long lparam) { for(int i=0; i<4; i++) { if(lparam==m_p_switch_button[3*i+2].Id()) { if(m_p_switch_button[3*i+2].IsPressed()) { MqlTick tick; if(SymbolInfoTick(Symbol(),tick)) { m_sl_edit[i+2].SetDigits(_Digits); m_sl_edit[i+2].StepValue(_Point); m_sl_edit[i+2].SetValue(string(tick.bid)); m_sl_edit[i+2].GetTextBoxPointer().Update(true); } } else { m_sl_edit[i+2].SetDigits(0); m_sl_edit[i+2].StepValue(1); m_sl_edit[i+2].SetValue(string(150)); m_sl_edit[i+2].GetTextBoxPointer().Update(true); } } } }
Ahora, los llamamos en el manejador de eventos del apartado al pulsar el botón:
// --- Switch Lot/Percent of balance LotPendingSwitch(lparam); //--- Switch Take Profit Points/Price TakePendingSwitch(lparam); //--- Switch Stop Loss Points/Price StopPendingSwitch(lparam);
Compilamos el proyecto y miramos lo que acabamos de crear:
Fig.11 Conmutación de los modos de los campos de edición en la ventana de órdenes pendientes.
Bien, los conmutadores de los modos de los campos de edición y el cambio de propiedades también funcionan. Esto significa que vamos a proceder a crear las funciones necesarias para colocar órdenes pendientes. Aquí, actuaremos igual que con las órdenes de mercado. Vamos a crear cuatro métodos según cada tipo.
bool SetBuyStopOrder(int id,long lparam); bool SetSellStopOrder(int id,long lparam); bool SetBuyLimitOrder(int id,long lparam); bool SetSellLimitOrder(int id,long lparam);
Acto seguido, los implementamos. Establecemos un atajo de teclado para cada una de las órdenes pendientes, aparte del evento de pulsación del botón creado.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::SetBuyStopOrder(int id,long lparam) { if(!m_orders_windows[1].IsVisible()) return(false); if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_buystop_execute.Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_1)) { //--- double lot; if(m_p_switch_button[0].IsPressed()) lot=LotPercent(Symbol(),ORDER_TYPE_BUY,SymbolInfoDouble(Symbol(),SYMBOL_ASK),StringToDouble(m_lot_edit[2].GetValue())); else lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[2].GetValue())); //--- double pr=double(m_pr_edit[0].GetValue()); //--- if(m_p_switch_button[1].IsPressed() && m_p_switch_button[2].IsPressed()) { double tp=double(m_tp_edit[2].GetValue()); double sl=double(m_sl_edit[2].GetValue()); if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number)) return(true); } else if(!m_p_switch_button[1].IsPressed() && !m_p_switch_button[2].IsPressed()) { int tp=int(m_tp_edit[2].GetValue()); int sl=int(m_sl_edit[2].GetValue()); if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number)) return(true); } else if(m_p_switch_button[1].IsPressed() && !m_p_switch_button[2].IsPressed()) { double tp=double(m_tp_edit[2].GetValue()); int sl=int(m_sl_edit[2].GetValue()); if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number)) return(true); } else if(!m_p_switch_button[1].IsPressed() && m_p_switch_button[2].IsPressed()) { int tp=int(m_tp_edit[2].GetValue()); double sl=double(m_sl_edit[2].GetValue()); if(m_trade.PlaceBuyStop(lot,Symbol(),pr,sl,tp,m_magic_number)) return(true); } } return(false); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::SetSellStopOrder(int id,long lparam) { if(!m_orders_windows[1].IsVisible()) return(false); if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_sellstop_execute.Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_2)) { //--- double lot; if(m_p_switch_button[3].IsPressed()) lot=LotPercent(Symbol(),ORDER_TYPE_SELL,SymbolInfoDouble(Symbol(),SYMBOL_BID),StringToDouble(m_lot_edit[3].GetValue())); else lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[3].GetValue())); //--- double pr=double(m_pr_edit[1].GetValue()); //--- if(m_p_switch_button[4].IsPressed() && m_p_switch_button[5].IsPressed()) { double tp=double(m_tp_edit[3].GetValue()); double sl=double(m_sl_edit[3].GetValue()); if(m_trade.PlaceSellStop(lot,Symbol(),pr,sl,tp,m_magic_number)) return(true); } else if(!m_p_switch_button[4].IsPressed() && !m_p_switch_button[5].IsPressed()) { int tp=int(m_tp_edit[3].GetValue()); int sl=int(m_sl_edit[3].GetValue()); if(m_trade.PlaceSellStop(lot,Symbol(),pr,sl,tp,m_magic_number)) return(true); } else if(m_p_switch_button[4].IsPressed() && !m_p_switch_button[5].IsPressed()) { double tp=double(m_tp_edit[3].GetValue()); int sl=int(m_sl_edit[3].GetValue()); if(m_trade.PlaceSellStop(lot,Symbol(),pr,sl,tp,m_magic_number)) return(true); } else if(!m_p_switch_button[4].IsPressed() && m_p_switch_button[5].IsPressed()) { int tp=int(m_tp_edit[3].GetValue()); double sl=double(m_sl_edit[3].GetValue()); if(m_trade.PlaceSellStop(lot,Symbol(),pr,sl,tp,m_magic_number)) return(true); } } return(false); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::SetBuyLimitOrder(int id,long lparam) { if(!m_orders_windows[1].IsVisible()) return(false); if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_buylimit_execute.Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_3)) { //--- double lot; if(m_p_switch_button[6].IsPressed()) lot=LotPercent(Symbol(),ORDER_TYPE_BUY,SymbolInfoDouble(Symbol(),SYMBOL_ASK),StringToDouble(m_lot_edit[4].GetValue())); else lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[4].GetValue())); //--- double pr=double(m_pr_edit[2].GetValue()); //--- if(m_p_switch_button[7].IsPressed() && m_p_switch_button[8].IsPressed()) { double tp=double(m_tp_edit[4].GetValue()); double sl=double(m_sl_edit[4].GetValue()); if(m_trade.PlaceBuyLimit(lot,Symbol(),pr,sl,tp,m_magic_number)) return(true); } else if(!m_p_switch_button[7].IsPressed() && !m_p_switch_button[8].IsPressed()) { int tp=int(m_tp_edit[4].GetValue()); int sl=int(m_sl_edit[4].GetValue()); if(m_trade.PlaceBuyLimit(lot,Symbol(),pr,sl,tp,m_magic_number)) return(true); } else if(m_p_switch_button[7].IsPressed() && !m_p_switch_button[8].IsPressed()) { double tp=double(m_tp_edit[4].GetValue()); int sl=int(m_sl_edit[4].GetValue()); if(m_trade.PlaceBuyLimit(lot,Symbol(),pr,sl,tp,m_magic_number)) return(true); } else if(!m_p_switch_button[7].IsPressed() && m_p_switch_button[8].IsPressed()) { int tp=int(m_tp_edit[4].GetValue()); double sl=double(m_sl_edit[4].GetValue()); if(m_trade.PlaceBuyLimit(lot,Symbol(),pr,sl,tp,m_magic_number)) return(true); } } return(false); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CFastTrading::SetSellLimitOrder(int id,long lparam) { if(!m_orders_windows[1].IsVisible()) return(false); if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_selllimit_execute.Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_4)) { //--- double lot; if(m_p_switch_button[9].IsPressed()) lot=LotPercent(Symbol(),ORDER_TYPE_SELL,SymbolInfoDouble(Symbol(),SYMBOL_BID),StringToDouble(m_lot_edit[5].GetValue())); else lot=NormalizeLot(Symbol(),StringToDouble(m_lot_edit[5].GetValue())); //--- double pr=double(m_pr_edit[3].GetValue()); //--- if(m_p_switch_button[10].IsPressed() && m_p_switch_button[11].IsPressed()) { double tp=double(m_tp_edit[5].GetValue()); double sl=double(m_sl_edit[5].GetValue()); if(m_trade.PlaceSellStop(lot,Symbol(),pr,sl,tp,m_magic_number)) return(true); } else if(!m_p_switch_button[10].IsPressed() && !m_p_switch_button[11].IsPressed()) { int tp=int(m_tp_edit[5].GetValue()); int sl=int(m_sl_edit[5].GetValue()); if(m_trade.PlaceSellStop(lot,Symbol(),pr,sl,tp,m_magic_number)) return(true); } else if(m_p_switch_button[10].IsPressed() && !m_p_switch_button[11].IsPressed()) { double tp=double(m_tp_edit[5].GetValue()); int sl=int(m_sl_edit[5].GetValue()); if(m_trade.PlaceSellStop(lot,Symbol(),pr,sl,tp,m_magic_number)) return(true); } else if(!m_p_switch_button[10].IsPressed() && m_p_switch_button[11].IsPressed()) { int tp=int(m_tp_edit[5].GetValue()); double sl=double(m_sl_edit[5].GetValue()); if(m_trade.PlaceSellStop(lot,Symbol(),pr,sl,tp,m_magic_number)) return(true); } } return(false); }
Ahora, pasamos al manejador de eventos y completamos al inicio su llamada con los métodos recién creados:
//+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void CFastTrading::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- SetBuyOrder(id,lparam); SetSellOrder(id,lparam); //--- SetBuyStopOrder(id,lparam); SetSellStopOrder(id,lparam); SetBuyLimitOrder(id,lparam); SetSellLimitOrder(id,lparam);
Ya hemos completado la funcionalidad básica para trabajar con todo tipo de órdenes. No obstante, como la esencia de esta aplicación consiste en hacer que el comercio manual sea más rápido y favorable, hemos decidido añadir una pequeña función que acelerará la colocación de órdenes de mercado y pendientes. Su principio es el siguiente: al configurar los valores de los campos de entrada en diferentes modos, su modificación utilizando las flechas hacia arriba y hacia abajo a la izquierda del campo cambiará el número en el salto especificado. Pero, si, por ejemplo, el precio de una orden pendiente se coloca de esta forma presionando hacia arriba o hacia abajo, el cambio se dará en EURUSD de 1.08500 a 1.85001. Esto resulta lento y no muy adecuado. Por consiguiente, al resaltar el campo de edición para editar un valor presionando los atajos de teclado Arriba/Abajo, añadiremos el cambio del valor en un salto multiplicado por 10. Para hacerlo, vamos a crear el método ArrowSwitch().
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::ArrowSwitch(long lparam) { //--- Prices for(int i=0; i<4; i++) { if(m_pr_edit[i].GetTextBoxPointer().TextEditState()) { if(lparam==KEY_UP) { //--- Get the new value double value=StringToDouble(m_pr_edit[i].GetValue())+m_pr_edit[i].StepValue()*10; //--- Increase by one step and check for exceeding the limit m_pr_edit[i].SetValue(DoubleToString(value),false); } else if(lparam==KEY_DOWN) { //--- Get the new value double value=StringToDouble(m_pr_edit[i].GetValue())-m_pr_edit[i].StepValue()*10; //--- Increase by one step and check for exceeding the limit m_pr_edit[i].SetValue(DoubleToString(value),false); } } } //--- Lot for(int i=0; i<6; i++) { if(m_lot_edit[i].GetTextBoxPointer().TextEditState()) { if(lparam==KEY_UP) { //--- Get the new value double value=StringToDouble(m_lot_edit[i].GetValue())+m_lot_edit[i].StepValue()*10; //--- Increase by one step and check for exceeding the limit m_lot_edit[i].SetValue(DoubleToString(value),false); } else if(lparam==KEY_DOWN) { //--- Get the new value double value=StringToDouble(m_lot_edit[i].GetValue())-m_lot_edit[i].StepValue()*10; //--- Increase by one step and check for exceeding the limit m_lot_edit[i].SetValue(DoubleToString(value),false); } } } //--- Take Profit, Stop Loss for(int i=0; i<6; i++) { //--- if(m_tp_edit[i].GetTextBoxPointer().TextEditState()) { if(lparam==KEY_UP) { //--- Get the new value double value=StringToDouble(m_tp_edit[i].GetValue())+m_tp_edit[i].StepValue()*10; //--- Increase by one step and check for exceeding the limit m_tp_edit[i].SetValue(DoubleToString(value),false); } else if(lparam==KEY_DOWN) { //--- Get the new value double value=StringToDouble(m_tp_edit[i].GetValue())-m_tp_edit[i].StepValue()*10; //--- Increase by one step and check for exceeding the limit m_tp_edit[i].SetValue(DoubleToString(value),false); } } //--- if(m_sl_edit[i].GetTextBoxPointer().TextEditState()) { if(lparam==KEY_UP) { //--- Get the new value double value=StringToDouble(m_sl_edit[i].GetValue())+m_sl_edit[i].StepValue()*10; //--- Increase by one step and check for exceeding the limit m_sl_edit[i].SetValue(DoubleToString(value),false); } else if(lparam==KEY_DOWN) { //--- Get the new value double value=StringToDouble(m_sl_edit[i].GetValue())-m_sl_edit[i].StepValue()*10; //--- Increase by one step and check for exceeding the limit m_sl_edit[i].SetValue(DoubleToString(value),false); } } } }
Después, lo llamamos en el manejador de eventos, en la sección Pulsación del botón en el teclado.
//--- Keypress if(id==CHARTEVENT_KEYDOWN) { //--- ArrowSwitch(lparam); ....
En la fig.12, podemos observar la diferencia entre la velocidad de cambio del valor editado y el necesario.
Fig.12 Cambio rápido de los valores de los campos de edición con atajos de teclado.
Ya hemos implementado el instrumental necesario para la colocación de órdenes de mercado y órdenes pendientes; ahora nos queda ir a la ventana principal de la aplicación y complementarla con varias acciones sobre las órdenes existentes, como mostramos en la primera figura al comienzo del artículo. Estas incluyen tres: cerrar todas las órdenes de mercado rentables, cerrar todas las órdenes con pérdidas y eliminar todas las órdenes pendientes actuales. Obviamente, esto solo se aplica a las órdenes colocadas por nuestra aplicación con un número mágico preestablecido en la configuración. Asimismo, debemos recordar que al colocar órdenes con el número mágico actual y luego cambiar este a cualquier otro en las propiedades del indicador, las órdenes existentes según el número anterior serán ignoradas, porque ya no estarán relacionadas con la aplicación actual.
Vamos a implementar las acciones indicadas. Creamos los tres nuevos métodos:
void CloseAllMarketProfit(int id,long lparam); void CloseAllMarketLoss(int id,long lparam); void DeleteAllPending(int id,long lparam);
E implementamos cada uno de ellos.
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ 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 //--- iterar todas las posiciones abiertas 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=PositionGetDouble(POSITION_PROFIT); double swap=PositionGetDouble(POSITION_SWAP); //--- if MagicNumber matches if(magic==m_magic_number) if(position_symbol==Symbol()) if(profit+swap>0) { //--- 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; } //--- envío de la solicitud if(!OrderSend(request,result)) PrintFormat("OrderSend error %d",GetLastError()); // if unable to send the request, output the error code } } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::CloseAllMarketLoss(int id,long lparam) { if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_order_button[3].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 //--- iterar todas las posiciones abiertas 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=PositionGetDouble(POSITION_PROFIT); double swap=PositionGetDouble(POSITION_SWAP); //--- if MagicNumber matches if(magic==m_magic_number) if(position_symbol==Symbol()) if(profit+swap<0) { //--- 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; } //--- envío de la solicitud if(!OrderSend(request,result)) PrintFormat("OrderSend error %d",GetLastError()); // if unable to send the request, output the error code } } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CFastTrading::DeleteAllPending(int id,long lparam) { if((id==CHARTEVENT_CUSTOM+ON_CLICK_BUTTON && lparam==m_order_button[4].Id()) || (id==CHARTEVENT_KEYDOWN && lparam==KEY_R)) { //--- declare and initialize the trade request and result of trade request MqlTradeRequest request; MqlTradeResult result; //--- iterate over all placed pending orders for(int i=OrdersTotal()-1; i>=0; i--) { ulong order_ticket=OrderGetTicket(i); // order ticket ulong magic=OrderGetInteger(ORDER_MAGIC); // order MagicNumber //--- if MagicNumber matches if(magic==m_magic_number) { //--- zeroing the request and result values ZeroMemory(request); ZeroMemory(result); //--- set the operation parameters request.action= TRADE_ACTION_REMOVE; // trading operation type request.order = order_ticket; // order ticket //--- envío de la solicitud if(!OrderSend(request,result)) PrintFormat("OrderSend error %d",GetLastError()); // if unable to send the request, output the error code } } } }
Debemos destacar que en los métodos creados actúa igualmente la regla tocante a la ejecución de una acción según dos eventos distintos: al pulsar un botón en la ventana principal y con el atajo de teclado. La llamada de las tres funciones se encontrará en el manejador de eventos, fuera de todas las secciones.
//+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void CFastTrading::OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- SetBuyOrder(id,lparam); SetSellOrder(id,lparam); //--- SetBuyStopOrder(id,lparam); SetSellStopOrder(id,lparam); SetBuyLimitOrder(id,lparam); SetSellLimitOrder(id,lparam); //--- CloseAllMarketProfit(id,lparam); CloseAllMarketLoss(id,lparam); //--- DeleteAllPending(id,lparam);
Con esto, podemos dar por concluido el desarrollo de la funcionalidad para el trabajo manual rápido: el lector podrá ver una demostración del funcionamiento de la aplicación creada en el vídeo de abajo.
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/7892
![Trabajando con las series temporales en la biblioteca DoEasy (Parte 45): Búferes de indicador de periodo múltiple](https://c.mql5.com/2/39/MQL5-avatar-doeasy-library__1.png)
![Cálculo de expresiones matemáticas (Parte 2). Parsers de Pratt y shunting yard](https://c.mql5.com/2/39/MQL5-avatar-analysis__1.png)
![Trabajando con las series temporales en la biblioteca DoEasy (Parte 46): Búferes de indicador de periodo y símbolo múltiples](https://c.mql5.com/2/39/MQL5-avatar-doeasy-library__2.png)
![Cálculo de expresiones matemáticas (Parte 1). Parsers de descenso recursivo](https://c.mql5.com/2/39/MQL5-avatar-analysis.png)
![MQL5 - Lenguaje de estrategias comerciales para el terminal de cliente MetaTrader 5](https://c.mql5.com/i/registerlandings/logo-2.png)
- 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