
Conjunto de instrumentos para el marcado manual de gráficos y comercio (Parte I). Preparación - descripción de la estructura y clase de funciones auxiliares
Introducción
El autor es un admirador confeso del análisis manual, en el sentido de que no analiza los gráficos con fórmulas complejas e indicadores, sino de forma "manual", usando los gráficos. Esto permite mayor flexibilidad al comerciar, notar cosas que son difíciles de formular, modificar fácilmente los marcos temporales cuando el movimiento se vuelve más fuerte o más débil, y también conocer el comportamiento esperado del precio mucho antes de entrar en el mercado.
Básicamente, para el análisis utilizamos líneas de tendencia en diferentes combinaciones (tridentes, abanicos, niveles, etcétera). Así que hemos creado una herramienta adecuada que permite dibujar muy rápidamente diferentes líneas de tendencia, literalmente con solo presionar una tecla. Ahora, queremos compartir esta herramienta con los demás.
Presentación de vídeo. Cómo funciona
Concepto general. Formulando la tarea
Bien, necesitamos una herramienta que nos ayude a ejecutar las operaciones más frecuentes utilizando los atajos de teclado.
¿De qué tareas estamos hablando? Por ejemplo, querríamos:
- que la línea simple horizontal se dibuje al pulsar las teclas "H" (del inglés "Horizontal") , y la vertical, al pulsar la tecla "i";
- dibujar niveles horizontales de una longitud determinada (NO infinitos) desde cualquier punto del gráfico;
- dibujar niveles verticales a una distancia determinada (aleatoria) respecto al punto inicial;
- alternar con las teclas el marco temporal y el orden de ubicación de las capas en el gráfico;
- colocar un abanico de Fibonacci con los niveles indicados por nosotros según los extremos más próximos;
- colocar líneas de tendencia según los extremos más próximos: su longitud deberá ser múltipla de la distancia entre los extremos, aunque, en ciertos casos, la línea podrá ser también un rayo;
- colocar un tridente de Andrews de diferentes tipos (normales, se Schiff, "tridentes de Schiff inversos" para las tendencias rápidas) (ver vídeo);
- para los extremos de los tridentes, las rectas y los abanicos, debería existir la posibilidad de configurar el "orden" de los extremos (el número de barras, aparte por la izquierda y por la derecha);
- una interfaz gráfica que permita configurar los parámetros de las líneas y extremos necesarios, sin abrir la ventana principal de ajustes;
- un conjunto de funciones para el acmpoñamiento de órdenes: apertura de órdenes en tanto por ciento del depósito, stop automático al abrir una orden según el precio de mercado o al activarse una orden pendiente sin stop, cierre parcial de posiciones según los niveles, para algunas estartegias, traling stop.
Para la mayoría de estas tareas, claro está, podemos crear scripts. Disponemos de varios, que compartiremos con el lector en los archivos adjuntos. Pero estamos a favor de otro enfoque.
En cualquier asesor o indicador, podemos crear el método OnChartEvent, en el que se describirá la reacción a cualquier evento: pulsación de teclas, movimiento del ratón, creación de un objeto gráfico y su eliminación.
Por eso, hemos decidido que el programa se ejecutará en forma de archivos de inclusión. Todas las funciones y variables han sido distribuidas por varias clases, para que resulte más cómodo recurrir a ellas. Ahora, necesitamos las clases solo para que resulte cómodo agrupar las funciones: por eso, en las primeras implementaciones, no habrá nada complicado, como la herencia o las "fábricas de clases". Se trata simplemente de una colección.
Y, por supuesto, deseamos que la clase sea multiplataforma y que funcione de una forma similar tanto en MQL4, como en MQL5.
Estructura del programa
La biblioteca contiene cinco archivos interrelacionados. Todos los archivos se ubican en la misma carpeta "Shortcuts", en el directorio Include. Sus nombres se pueden ver en la figura: GlobalVariables.mqh, Graphics.mqh, Mouse.mqh, Shortcuts.mqh, Utilites.mqh.
Archivo principal de la biblioteca(Shortcuts.mqh)
Archivo principal del programa — "Shortcuts.mqh". En él se implementará la lógica de reacción a las teclas. Y precisamente este archivo se incluirá en el experto. También se incluirán en él todos los archivos auxiliares.
//+------------------------------------------------------------------+ //| Shortcuts.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://www.mql5.com/es/articles/7468 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://www.mql5.com/en/articles/7468" #include "GlobalVariables.mqh" #include "Mouse.mqh" #include "Utilites.mqh" #include "Graphics.mqh" //+------------------------------------------------------------------+ //| The main control class of the program. It should be connected | //| to the Expert Advisor | //+------------------------------------------------------------------+ class CShortcuts { private: CGraphics m_graphics; // Object for drawing m_graphics public: CShortcuts(); void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam); }; //+------------------------------------------------------------------+ //| Default constructor | //+------------------------------------------------------------------+ CShortcuts::CShortcuts(void) { ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,true); } //+------------------------------------------------------------------+ //| Event handling function | //+------------------------------------------------------------------+ void CShortcuts::OnChartEvent( const int id, const long &lparam, const double &dparam, const string &sparam ) { //--- // This will contain the description of the events related to keystrokes // and mouse movements // ... } } CShortcuts shortcuts;
El archivo contiene la descripción de la clase CShortcuts.
Al inicio del archivo se incluyen todas las clases auxiliares.
En la clases solo hay dos métodos: el primero es el manejador de eventos OnChartEvent, en el que propiamente tendrá lugar el procesamiento de los eventos de las teclas y el movimiento del ratón; el segundo es el constructor por defecto, en el que se permite procesar el movimiento del ratón.
Después de la descripción de la clase, se crea la variable shortcuts,que debemos utilizar en el método OnChartEvent del experto principal al incluir la biblioteca.
La propia inclusión requiere dos líneas:
//+------------------------------------------------------------------+ //| The main Expert (file "Shortcuts-Main-Expert.mq5") | //+------------------------------------------------------------------+ #include <Shortcuts\Shortcuts.mqh> // ... //+------------------------------------------------------------------+ //| The ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- shortcuts.OnChartEvent(id,lparam,dparam,sparam); }
La primera línea incluye el archivo de la clase, la segunda, transmite a la clase el control del procesamiento de eventos.
Después de ello, el asesor estará listo para funcionar: podremos compilarlo y comenzar a dibujar las líneas de inmediato.
Clase para procesar los movimientos del ratón
La clase que guardará todos los parámetros principales de la ubicación actual del cursor: las coordenadas X, Y en píxeles y en precio/hora, el número de la barra en la que se encuentra el puntero, etcétera, todo se guarda en el archivo "Mouse.mqh".
//+------------------------------------------------------------------+ //| Mouse.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://www.mql5.com/es/articles/7468 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://www.mql5.com/en/articles/7468" //+------------------------------------------------------------------+ //| Mouse movement handling class | //+------------------------------------------------------------------+ class CMouse { //--- Members private: static int m_x; // X static int m_y; // Y static int m_barNumber; // Bar number static bool m_below; // Indication of a cursor above the price static bool m_above; // Indication of a cursor below the price static datetime m_currentTime; // Current time static double m_currentPrice;// Current price //--- Methods public: //--- Remembers the main parameters of the mouse cursor static void SetCurrentParameters( const int id, const long &lparam, const double &dparam, const string &sparam ); //--- Returns the X coordinate (in pixels) of the current cursor position static int X(void) {return m_x;} //--- Returns the Y coordinate (in pixels) of the current cursor position static int Y(void) {return m_y;} //--- Returns the price of the current cursor position static double Price(void) {return m_currentPrice;} //--- Returns the time of the current cursor position static datetime Time(void) {return m_currentTime;} //--- Returns the bar number of the current cursor position static int Bar(void) {return m_barNumber;} //--- Returns a flag showing that the price is below the Low of the current bar static bool Below(void) {return m_below;} //--- Returns a flag showing that the price is above the High of the current bar static bool Above(void) {return m_above;} }; //--- int CMouse::m_x=0; int CMouse::m_y=0; int CMouse::m_barNumber=0; bool CMouse::m_below=false; bool CMouse::m_above=false; datetime CMouse::m_currentTime=0; double CMouse::m_currentPrice=0; //+------------------------------------------------------------------+ //| Remembers the main parameters for a mouse movement: coordinates | //| of cursor in pixels and price/time, whether the cursor is above | //| or below price. | //|+-----------------------------------------------------------------+ static void CMouse::SetCurrentParameters( const int id, const long &lparam, const double &dparam, const string &sparam ) { //--- int window = 0; //--- ChartXYToTimePrice( 0, (int)lparam, (int)dparam, window, m_currentTime, m_currentPrice ); m_x=(int)lparam; m_y=(int)dparam; m_barNumber=iBarShift( Symbol(), PERIOD_CURRENT, m_currentTime ); m_below=m_currentPrice<iLow(Symbol(),PERIOD_CURRENT,m_barNumber); m_above=m_currentPrice>iHigh(Symbol(),PERIOD_CURRENT,m_barNumber); } //+------------------------------------------------------------------+
Esta clase puede utilizarse desde cualquier lugar del programa, ya que sus métodos han sido declarados estáticos. No es obligatorio crear una instancia de esta clase para usarla.
Descripción del bloque de ajustes del experto. Archivo "GlobalVariables.mqh"
Todos los ajustes disponibles para el usuario se guardan en el archivo "GlobalVariables.mqh".
En el siguiente artículo, describiremos muchos más ajustes, ya que aparecerán más objetos a dibujar.
Por el momento, describiremos el código que se usa en la versión actual:
//+------------------------------------------------------------------+ //| GlobalVariables.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://www.mql5.com/es/articles/7468 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://www.mql5.com/en/articles/7468" //+------------------------------------------------------------------+ //| File describing parameters available to the user | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Key settings | //+------------------------------------------------------------------+ input string Keys="=== Key settings ==="; input string Up_Key="U"; // Switch timeframe up input string Down_Key="D"; // Switch timeframe down input string Trend_Line_Key="T"; // Trend line input string Switch_Trend_Ray_Key="R"; // Indication of a trend line ray input string Z_Index_Key="Z"; // Indication of the chart on top //+------------------------------------------------------------------+ //| Size settings | //+------------------------------------------------------------------+ input string Dimensions="=== Size settings ==="; input int Trend_Line_Width=2; // Trend line width //+------------------------------------------------------------------+ //| Display styles | //+------------------------------------------------------------------+ input string Styles="=== Display styles ==="; input ENUM_LINE_STYLE Trend_Line_Style=STYLE_SOLID; // Trend line style //+------------------------------------------------------------------+ //| Other parameters | //+------------------------------------------------------------------+ input string Others="=== Other parameters ==="; input bool Is_Trend_Ray=false; // Trend line - ray input bool Is_Change_Timeframe_On_Create = true; // Hide objects on higher timeframes? // (true - hide, false - show) input bool Is_Select_On_Create=true; // Select upon creation input bool Is_Different_Colors=true; // Change colors for times // Number of bars on the left and on the right // for trend line and fan extreme points input int Fractal_Size_Left=1; // Size of the left fractal input int Fractal_Size_Right=1; // Size of the right fractal //+------------------------------------------------------------------+ //| Name prefixes of drawn shapes (can be change only in code, | //| not visible in EA parameters) | //+------------------------------------------------------------------+ // string Prefixes="=== Prefixes ==="; string Trend_Line_Prefix="Trend_"; // Trend line prefix //+------------------------------------------------------------------+ //| Colors for objects of one timeframe (can be changed only in code,| //| not visible in EA parameters) | //+------------------------------------------------------------------+ // string TimeframeColors="=== Time frame colors ==="; color mn1_color=clrCrimson; color w1_color=clrDarkOrange; color d1_color=clrGoldenrod; color h4_color=clrLimeGreen; color h1_color=clrLime; color m30_color=clrDeepSkyBlue; color m15_color=clrBlue; color m5_color=clrViolet; color m1_color=clrDarkViolet; color common_color=clrGray; //--- Auxiliary constant for displaying error messages #define DEBUG_MESSAGE_PREFIX "=== ",__FUNCTION__," === " //--- Constants for describing the main timeframes when drawing #define PERIOD_LOWER_M5 OBJ_PERIOD_M1|OBJ_PERIOD_M5 #define PERIOD_LOWER_M15 PERIOD_LOWER_M5|OBJ_PERIOD_M15 #define PERIOD_LOWER_M30 PERIOD_LOWER_M15|OBJ_PERIOD_M30 #define PERIOD_LOWER_H1 PERIOD_LOWER_M30|OBJ_PERIOD_H1 #define PERIOD_LOWER_H4 PERIOD_LOWER_H1|OBJ_PERIOD_H4 #define PERIOD_LOWER_D1 PERIOD_LOWER_H4|OBJ_PERIOD_D1 #define PERIOD_LOWER_W1 PERIOD_LOWER_D1|OBJ_PERIOD_W1 //+------------------------------------------------------------------+
Funciones auxiliares
En el programa hay multitud de funciones que no tienen relación directa con el dibujado, pero que, en cambio, ayudan a encontrar los extremos, alternar el marco temporal, etcétera. Todas ellas han sido introducidas en el archivo "Utilites.mqh".
Encabezado del archivo Utilites.mqh
//+------------------------------------------------------------------+ //| Utilites.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://www.mql5.com/es/articles/7468 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://www.mql5.com/en/articles/7468" //+------------------------------------------------------------------+ //| Class describing auxiliary functions | //+------------------------------------------------------------------+ class CUtilites { public: //--- Changes the timeframe to the next one on the toolbar static void ChangeTimeframes(bool isUp); //--- Converts string command constants to keycodes static int GetCurrentOperationChar(string keyString); //--- Switches layers in charts (the chart is on top of all objects) static void ChangeChartZIndex(void); //--- Returns the number of the nearest extreme bar static int GetNearestExtremumBarNumber( int starting_number=0, bool is_search_right=false, bool is_up=false, int left_side_bars=1, int right_side_bars=1, string symbol=NULL, ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT ); //--- Returns the color for the current timeframe static color GetTimeFrameColor(long allDownPeriodsValue); //--- Returns a list of all timeframes lower than the current one (including the current one) static long GetAllLowerTimeframes(int NeededTimeframe=PERIOD_CURRENT); //--- Coordinates of the straight line. Writes numbers of extreme bars to points p1 and p2 static void SetExtremumsBarsNumbers(bool _is_up,int &p1, int &p2); //--- Converts a string to to an array of floating point numbers static void StringToDoubleArray( string _haystack, double &_result[], const string _delimiter="," ); //--- Determines the name of the current object static string GetCurrentObjectName( const string _prefix, const ENUM_OBJECT _type=OBJ_TREND, int _number = -1 ); //--- Obtains the number of the next object static int GetNextObjectNumber( const string _prefix, const ENUM_OBJECT _object_type, bool true ); //--- Returns the distance, in screen pixels, between adjacent bars static int GetBarsPixelDistance(void); //--- Converts a numeric value of the timeframe to its string name static string GetTimeframeSymbolName( ENUM_TIMEFRAMES _timeframe=PERIOD_CURRENT // Desired timeframe ); };
Función que cambia secuencialmente el periodo del gráfico
Las primeras funciones en este archivo no son importantes. Por ejemplo, la función que cambia los periodos del gráfico secuencialmente uno tras otro tiene el aspecto siguiente:
//+------------------------------------------------------------------+ //| Sequentially changes the current chart period | //| | //| In this implementation, only the timeframes shown in the | //| standard panel are used. | //| | //| Parameters: | //| _isUp - timeframe switch direction: up (true) | //| or down (false) | //+------------------------------------------------------------------+ static void CUtilites::ChangeTimeframes(bool _isUp) { ENUM_TIMEFRAMES timeframes[] = { PERIOD_CURRENT, PERIOD_M1, PERIOD_M5, PERIOD_M15, PERIOD_M30, PERIOD_H1, PERIOD_H4, PERIOD_D1, PERIOD_W1, PERIOD_MN1 }; int period = Period(); int shift = ArrayBsearch(timeframes,period); if(_isUp && shift < ArraySize(timeframes)-1) { ChartSetSymbolPeriod(0,NULL,timeframes[++shift]); } else if(!_isUp && shift > 1) { ChartSetSymbolPeriod(0,NULL,timeframes[--shift]); } }
En primer lugar, se describe la matriz con todos los marcos temporales indicados en el panel estándar de control por defecto. Si usted necesita alternar entre todos los periodos temporales disponibles en MetaTrader 5, deberá simplemente terminar de escribir las constantes correspondientes en los lugares necesarios de la matriz. Bien es cierto que, en este caso, podría perderse la compatibilidad inversa, es decir, la biblioteca podría dejar de funcionar con MQL4.
A continuación, utilizamos las funciones estándar para obtener el periodo actual y encontrarlo en la lista.
Después, para alternar los periodos temporales del gráfico, utilizaremos la función estándar ChartSetSymbolPeriod, a la que se transmitirá el periodo que sigue al actual.
En lo que respecta a las demás funciones, no tiene especial sentido hacer comentarios adicionales al código. Es solo código.
Varias funciones sencillas
//+------------------------------------------------------------------+ //| Converts string command constants to keycodes | //+------------------------------------------------------------------+ static int CUtilites::GetCurrentOperationChar(string keyString) { string keyValue = keyString; StringToUpper(keyValue); return(StringGetCharacter(keyValue,0)); } //+------------------------------------------------------------------+ //| Switch chart position to display on top of other objects | //+------------------------------------------------------------------+ static void CUtilites::ChangeChartZIndex(void) { ChartSetInteger( 0, CHART_FOREGROUND, !(bool)ChartGetInteger(0,CHART_FOREGROUND) ); ChartRedraw(0); } //+------------------------------------------------------------------+ //| Returns a string name for any standard timeframe | //| Parameters: | //| _timeframe - ENUM_TIMEFRAMES numeric value for which we need | //| to find a string name | //| Return value: | //| string name of the required timeframe | //+------------------------------------------------------------------+ static string CUtilites::GetTimeframeSymbolName( ENUM_TIMEFRAMES _timeframe=PERIOD_CURRENT // Desired timeframe ) { ENUM_TIMEFRAMES current_timeframe; // current timeframe string result = ""; //--- if(_timeframe == PERIOD_CURRENT) { current_timeframe = Period(); } else { current_timeframe = _timeframe; } //--- switch(current_timeframe) { case PERIOD_M1: return "M1"; case PERIOD_M2: return "M2"; case PERIOD_M3: return "M3"; case PERIOD_M4: return "M4"; case PERIOD_M5: return "M5"; case PERIOD_M6: return "M6"; case PERIOD_M10: return "M10"; case PERIOD_M12: return "M12"; case PERIOD_M15: return "M15"; case PERIOD_M20: return "M20"; case PERIOD_M30: return "M30"; case PERIOD_H1: return "H1"; case PERIOD_H2: return "M1"; case PERIOD_H3: return "H3"; case PERIOD_H4: return "H4"; case PERIOD_H6: return "H6"; case PERIOD_H8: return "H8"; case PERIOD_D1: return "D1"; case PERIOD_W1: return "W1"; case PERIOD_MN1: return "MN1"; default: return "Unknown"; } } //+------------------------------------------------------------------+ //| Returns the standard color for the current timeframe | //+------------------------------------------------------------------+ static color CUtilites::GetTimeFrameColor(long _all_down_periods_value) { if(Is_Different_Colors) { switch((int)_all_down_periods_value) { case OBJ_PERIOD_M1: return (m1_color); case PERIOD_LOWER_M5: return (m5_color); case PERIOD_LOWER_M15: return (m15_color); case PERIOD_LOWER_M30: return (m30_color); case PERIOD_LOWER_H1: return (h1_color); case PERIOD_LOWER_H4: return (h4_color); case PERIOD_LOWER_D1: return (d1_color); case PERIOD_LOWER_W1: return (w1_color); case OBJ_ALL_PERIODS: return (mn1_color); default: return (common_color); } } else { return (common_color); } }
Función de búsqueda de los extremos y su aplicación
La siguiente función ayuda a buscar los extremos. Dependiendo de los parámetros, los extremos pueden localizarse tanto a la derecha como a la izquierda del indicador del ratón; también podemos establecer cuántas barras deberá haber a la izquierda del extremo, y cuántas a la derecha.
//+------------------------------------------------------------------+ //| Returns the number of the nearest fractal in the selected | //| direction | //| Parameters: | //| starting_number - bar number at which the search starts | //| is_search_right - search to the right (true) or left (false) | //| is_up - if "true", search by High levels, otherwise - Low | //| left_side_bars - number of bars on the left | //| right_side_bars - number of bars on the right | //| symbol - symbol name for search | //| timeframe - period for search | //| Return value: | //| the number of the extremum closest to starting_number, | //| matching the specified parameters | //+------------------------------------------------------------------+ static int CUtilites::GetNearestExtremumBarNumber( int starting_number=0, // Initial bar number const bool is_search_right=false, // Direction to the right const bool is_up=false, // Search by Highs const int left_side_bars=1, // Number of bars to the left const int right_side_bars=1, // Number of bars to the right const string symbol=NULL, // The required symbol const ENUM_TIMEFRAMES timeframe=PERIOD_CURRENT // The required timeframe ) { //--- int i, nextExtremum, sign = is_search_right ? -1 : 1; //--- If the starting bar is specified incorrectly //--- (is beyond the current chart borders) //--- and search - towards the border... if((starting_number-right_side_bars<0 && is_search_right) || (starting_number+left_side_bars>iBars(symbol,timeframe) && !is_search_right) ) { //--- ...it is necessary to display an error message if(Print_Warning_Messages) { Print(DEBUG_MESSAGE_PREFIX, "Can't find extremum: ", "wrong direction"); Print("left_side_bars = ",left_side_bars,"; ", "right_side_bars = ",right_side_bars); } return (-2); } else { //--- otherwise - the direction allows you to select the correct bar. //--- check all bars in the required direction, //--- as long as we are beyond the known chart borders while((starting_number-right_side_bars<0 && !is_search_right) || (starting_number+left_side_bars>iBars(symbol,timeframe) && is_search_right) ) { starting_number +=sign; } } //--- i=starting_number; //--- Preparation is complete. Proceed to search while(i-right_side_bars>=0 && i+left_side_bars<iBars(symbol,timeframe) ) { //--- Depending on the level, check the required extremum if(is_up) { //--- either the upper one nextExtremum = iHighest( Symbol(), Period(), MODE_HIGH, left_side_bars+right_side_bars+1, i-right_side_bars ); } else { //--- or the lower one nextExtremum = iLowest( Symbol(), Period(), MODE_LOW, left_side_bars+right_side_bars+1, i-right_side_bars ); } if(nextExtremum == i) // If the current bar is an extremum, { return nextExtremum; // the problem is solved } else // Otherwise - continue to shift the counter of the checked candlestick to the desired direction if(is_search_right) { if(nextExtremum<i) { i=nextExtremum; } else { i--; } } else { if(nextExtremum>i) { i=nextExtremum; } else { i++; } } } //--- If the edge is reached but no extremum has been found, if(Print_Warning_Messages) { //--- show an error message. Print(DEBUG_MESSAGE_PREFIX, "Can't find extremum: ", "an incorrect starting point or wrong border conditions."); Print("left_side_bars = ",left_side_bars,"; ", "right_side_bars = ",right_side_bars); } return (-1); }
Para dibujar las líneas de tendencia, necesitamos una función que encuentre los dos extremos más cercanos a la derecha del ratón. Esta función puede utilizar la anterior:
//+------------------------------------------------------------------+ //| Finds 2 nearest extreme points to the right of the current | //| mouse pointer position | //| Parameters: | //| _is_up - search by High (true) or Low (false) | //| int &_p1 - bar number of the first point | //| int &_p2 - bar number of the second point | //+------------------------------------------------------------------+ static void CUtilites::SetExtremumsBarsNumbers( bool _is_up, // search by High (true) or by Low (false) int &_p1, // bar number of the first point int &_p2 // bar number of second point ) { int dropped_bar_number=CMouse::Bar(); //--- _p1=CUtilites::GetNearestExtremumBarNumber( dropped_bar_number, true, _is_up, Fractal_Size_Left, Fractal_Size_Right ); _p2=CUtilites::GetNearestExtremumBarNumber( _p1-1, // Bar to the left of the previous found extremum true, _is_up, Fractal_Size_Left, Fractal_Size_Right ); if(_p2<0) { _p2=0; } }
Generando los nombres de los objetos
Para dibujar objetos iguales en series, deberemos darles el mismo nombre. Para ello, lo más sencillo es tomar algún prefijo que se corresponda con un tipo de objeto determinado, y después añadirle un número único. Los prefijos para los diferentes tipos de objeto se enumeran en el archivo "GlobalVariables.mqh".
Los números son generados por la función correspondiente.
//+------------------------------------------------------------------+ //| Returns the number of the next object in the series | //| Parameters: | //| prefix - name prefix for this group of objects. | //| object_type - the type of objects to search in. | //| only_prefixed - if "false", search in all objects | //| of this type, "true" - only the objects | //| with the specified prefix | //| Return value: | //| number of the next object in series. If search by prefix, | //| and the existing numbering has a gap, the next number | //| will be be at gap beginning. | //+------------------------------------------------------------------+ int CUtilites::GetNextObjectNumber( const string prefix, const ENUM_OBJECT object_type, bool true ) { int count = ObjectsTotal(0,0,object_type), i, current_element_number, total_elements = 0; string current_element_name = "", comment_text = ""; //--- if(only_prefixed) { for(i=0; i<count; i++) { current_element_name=ObjectName(0,i,0,object_type); if(StringSubstr(current_element_name,0,StringLen(prefix))==prefix) { current_element_number= (int)StringToInteger( StringSubstr(current_element_name, StringLen(prefix), -1) ); if(current_element_number!=total_elements) { break; } total_elements++; } } } else { total_elements = ObjectsTotal(0,-1,object_type); do { current_element_name = GetCurrentObjectName( prefix, object_type, total_elements ); if(ObjectFind(0,current_element_name)>=0) { total_elements++; } } while(ObjectFind(0,current_element_name)>=0); } //--- return(total_elements); }
En el código se han implementado dos algoritmos de búsqueda. El primero (y principal para esta biblioteca) itera por todos los objetos que se correspondan con el tipo y tengan el prefijo indicado. En cuanto encuentra un número libre, lo retorna al usuario. Esto permite cerrar los "agujeros" en la numeración.
No obstante, este algoritmo no es demasiado adecuado para los casos en los que pueden haber varios objetos con el mismo número, pero diferentes sufijos. En versiones anteriores, cuando aún lo utilizábamos para dibujar con scripts, así nombrábamos los conjuntos de tridentes.
Por ese motivo, en la biblioteca se ha implementado un segundo método de búsqueda. Se toma el número total de objetos de un tipo dado, y después se comprueba si existe un nombre que comience por el mismo prefijo y tenga el mismo número. De ser así, el número se aumenta en 1 hasta que se encuentre un valor libre.
Y cuando ya tenemos el número (o se puede obtener fácilmente con la ayuda de la función), crear el nombre se convierte en algo muy sencillo.
//+------------------------------------------------------------------+ //| Generates the current element name | //| Parameters: | //| _prefix - name prefix for this group of objects. | //| _type - the type of objects to search in. | //| _number - the number of the current object if it is ready. | //| Return value: | //| current object name string. | //+------------------------------------------------------------------+ string CUtilites::GetCurrentObjectName( string _prefix, ENUM_OBJECT _type=OBJ_TREND, int _number = -1 ) { int Current_Line_Number; //--- Addition to the prefix - current timeframe string Current_Line_Name=IntegerToString(PeriodSeconds()/60)+"_"+_prefix; //--- Get the element number if(_number<0) { Current_Line_Number = GetNextObjectNumber(Current_Line_Name,_type); } else { Current_Line_Number = _number; } //--- Generate the name Current_Line_Name +=IntegerToString(Current_Line_Number,4,StringGetCharacter("0",0)); //--- return (Current_Line_Name); }
Distancia entre barras colindantes (en píxeles)
A veces, resulta necesario calcular la distancia hasta un determinado punto en el futuro. Uno de los métodos más fiables para alcanzar d, consiste en calcular la distancia en píxeles entre dos barras colindantes, y después multiplicarla por el coeficiente necesario (cuántas barras queremos retroceder).
La distancia entre barras colindantes se puede calcular con la ayuda de la siguiente función:
//+------------------------------------------------------------------+ //| Calculates a distance in pixels between two adjacent bars | //+------------------------------------------------------------------+ int CUtilites::GetBarsPixelDistance(void) { double price; // arbitrary price on the chart (used for // standard functions calculating coordinates datetime time1,time2; // the time of the current and adjacent candlesticks int x1,x2,y1,y2; // screen coordinates of two points // on adjacent candlesticks int deltha; // result - the desired distance //--- Initial settings price = iHigh(Symbol(),PERIOD_CURRENT,CMouse::Bar()); time1 = CMouse::Time(); //--- Get the time of the current candlestick if(CMouse::Bar()<Bars(Symbol(),PERIOD_CURRENT)){ // if at the middle of the chart, time2 = time1+PeriodSeconds(); // take the candlestick on the left } else { // otherwise time2 = time1; time1 = time1-PeriodSeconds(); // take the candlestick on the right } //--- Convert coordinates form price/time values to screen pixels ChartTimePriceToXY(0,0,time1,price,x1,y1); ChartTimePriceToXY(0,0,time2,price,x2,y2); //--- Calculate the distance deltha = MathAbs(x2-x1); //--- return (deltha); }
Función que convierte una línea en una matriz de números double
Para configurar los niveles de Fibonacci con la ayuda de los parámetros del asesor, lo más sencillo es utilizar líneas que consten de valores separados por comas. Sin embargo, MQL requiere que se usen números double para establecer los niveles.
La siguiente función nos ayudará a extraer los números de la línea.
//+------------------------------------------------------------------+ //| Converts a string with separators to an array of double | //| numbers | //| Parameters: | //| _haystack - source string for conversion | //| _result[] - resulting array | //| _delimiter - separator character | //+------------------------------------------------------------------+ static void CUtilites::StringToDoubleArray( string _haystack, // source string double &_result[], // array of results const string _delimiter="," // separator ) { //--- string haystack_pieces[]; // Array of string fragments int pieces_count, // Number of fragments i; // Counter string current_number=""; // The current string fragment (estimated number) //--- Split the string into fragments pieces_count=StringSplit(_haystack,StringGetCharacter(_delimiter,0),haystack_pieces); //--- Convert if(pieces_count>0) { ArrayResize(_result,pieces_count); for(i=0; i<pieces_count; i++) { StringTrimLeft(haystack_pieces[i]); StringTrimRight(haystack_pieces[i]); _result[i]=StringToDouble(haystack_pieces[i]); } } else { ArrayResize(_result,1); _result[0]=0; } }
Clase de dibujado: ejemplo de uso de funciones utilitarias
El artículo está resultando muy voluminoso, por eso, dejaremos la descripción de la mayor parte de las funciones de dibujado para el artículo siguiente. No obstante, para poner a prueba las posibilidades de algunas funciones ya creadas, vamos a mostrar aquí un código que ayuda a dibujar líneas rectas simples (según dos extremos colindantes).
Encabezado del archivo de dibujado del gráfico Graphics.mqh
//+------------------------------------------------------------------+ //| Graphics.mqh | //| Copyright 2020, MetaQuotes Software Corp. | //| https://www.mql5.com/es/articles/7468 | //+------------------------------------------------------------------+ #property copyright "Copyright 2020, MetaQuotes Software Corp." #property link "https://www.mql5.com/en/articles/7468" //+------------------------------------------------------------------+ //| Class for plotting graphic objects | //+------------------------------------------------------------------+ class CGraphics { //--- Fields private: bool m_Is_Trend_Ray; bool m_Is_Change_Timeframe_On_Create; //--- Methods private: //--- Sets general parameters for any newly created object void CurrentObjectDecorate( const string _name, const color _color=clrNONE, const int _width = 1, const ENUM_LINE_STYLE _style = STYLE_SOLID ); public: //--- Default constructor CGraphics(); //--- Universal function for creating trend lines with specified parameters bool TrendCreate( const long chart_ID=0, // chart ID const string name="TrendLine", // line name const int sub_window=0, // subwindow number datetime time1=0, // time of the first point double price1=0, // price of the first point datetime time2=0, // time of the second point double price2=0, // price of the second point const color clr=clrRed, // line color const ENUM_LINE_STYLE style=STYLE_SOLID, // line style const int width=1, // line width const bool back=false, // in the background const bool selection=true, // if the line is selected const bool ray_right=false, // ray to the right const bool hidden=true, // hide in the list of objects const long z_order=0 // Z-Index ); //--- Plots a trend line based on the two nearest (to the right of the mouse) extreme points void DrawTrendLine(void); //--- Checks if the straight line is a ray bool IsRay() {return m_Is_Trend_Ray;} //--- Sets the ray indication (whether the line should be extended to the right) void IsRay(bool _is_ray) {m_Is_Trend_Ray = _is_ray;} //--- Checks if the display of objects created by the program should be changed on higher timeframes bool IsChangeTimeframe(void) {return m_Is_Change_Timeframe_On_Create;} //--- Sets flags for the display of objects created by the program on higher timeframes void IsChangeTimeframe(bool _is_tf_change) {m_Is_Change_Timeframe_On_Create = _is_tf_change;} };
Función que establece los parámetros generales para cualquier objeto gráfico creado de nuevo
//+------------------------------------------------------------------+ //| Sets general parameters for all new objects | //| Parameters: | //| _name - the name of the object being modified | //| _color - the color of the object being modified. If not set, | //| standard color of the current period is used | //| _width - object line width | //| _style - object line type | //+------------------------------------------------------------------+ void CGraphics::CurrentObjectDecorate( const string _name, // the name of the object being modified const color _color=clrNONE, // the color of the object being modified const int _width = 1, // object line width const ENUM_LINE_STYLE _style = STYLE_SOLID // object line style ) { long timeframes; // timeframes on which the object will be displayed color currentColor; // object color //--- Specify timeframes on which the object will be displayed if(Is_Change_Timeframe_On_Create) { timeframes = CUtilites::GetAllLowerTimeframes(); } else { timeframes = OBJ_ALL_PERIODS; } //--- Specify the object color if(_color != clrNONE) { currentColor = _color; } else { currentColor = CUtilites::GetTimeFrameColor(timeframes); } //--- Set attributes ObjectSetInteger(0,_name,OBJPROP_COLOR,currentColor); // Color ObjectSetInteger(0,_name,OBJPROP_TIMEFRAMES,timeframes); // Periods ObjectSetInteger(0,_name,OBJPROP_HIDDEN,true); // Hide in the list of objects ObjectSetInteger(0,_name,OBJPROP_SELECTABLE,true); // Ability to select ObjectSetInteger(0,_name,OBJPROP_SELECTED,Is_Select_On_Create); // Stay selected after creation? ObjectSetInteger(0,_name,OBJPROP_WIDTH,_width); // Line width ObjectSetInteger(0,_name,OBJPROP_STYLE,_style); // Line style }
Función de dibujado de rectas
//+------------------------------------------------------------------+ //| Universal function for creating trend lines with specified | //| parameters | //| Parameters: | //| chart_ID - chart ID | //| name - line name | //| sub_window - subwindow number | //| time1 - time of the first point | //| price1 - price of the first point | //| time2 - time of the second point | //| price2 - price of the second point | //| clr - line color | //| style - line style | //| width - line width | //| back - in the background | //| selection - if the line is selected | //| ray_right - ray to the right | //| hidden - hide in the list of objects | //| z_order - priority of mouse clicks (Z-Index) | //| Return value: | //| indication of success. If line drawing failed, | //| false is returned, otherwise - true | //+------------------------------------------------------------------+ bool CGraphics::TrendCreate( const long chart_ID=0, // chart ID const string name="TrendLine", // line name const int sub_window=0, // subwindow number datetime time1=0, // time of the first point double price1=0, // price of the first point datetime time2=0, // time of the second point double price2=0, // price of the second point const color clr=clrRed, // line color const ENUM_LINE_STYLE style=STYLE_SOLID, // line style const int width=1, // line width const bool back=false, // in the background const bool selection=true, // if the line is selected const bool ray_right=false, // ray to the right const bool hidden=true, // hide in the list of objects const long z_order=0 // Z-Index ) { //--- Reset the last error message ResetLastError(); //--- Create a line if(!ObjectCreate(chart_ID,name,OBJ_TREND,sub_window,time1,price1,time2,price2)) { if(Print_Warning_Messages) // if failed, show an error message { Print(__FUNCTION__, ": Can't create trend line! Error code = ",GetLastError()); } return(false); } //--- Set additional attributes CurrentObjectDecorate(name,clr,width,style); //--- line in the foreground (false) or in the background (true) ObjectSetInteger(chart_ID,name,OBJPROP_BACK,back); //--- ray to the right (true) or exact borders (false) ObjectSetInteger(chart_ID,name,OBJPROP_RAY_RIGHT,ray_right); //--- mouse click priority (Z-index) ObjectSetInteger(chart_ID,name,OBJPROP_ZORDER,z_order); //--- Update the chart image ChartRedraw(0); //--- Everything is good. The line has been drawn successfully. return(true); }
Usando esta función general como base, crearemos otra función que dibujará una recta según dos extremos colindantes.
//+------------------------------------------------------------------+ //| Draws a trend line by nearest extreme points in the right | //+------------------------------------------------------------------+ void CGraphics::DrawTrendLine(void) { int dropped_bar_number=CMouse::Bar(); // candlestick number under the mouse int p1=0,p2=0; // numbers of the first and seconds points string trend_name = // trend line name CUtilites::GetCurrentObjectName(Trend_Line_Prefix,OBJ_TREND); double price1=0, // price of the first point price2=0, // price of the second point tmp_price; // variable for temporary storing of the price datetime time1=0, // time of the first point time2=0, // time of the second point tmp_time; int x1,x2,y1,y2; // Point coordinates int window=0; // Subwindow number //--- Setting initial parameters if(CMouse::Below()) // If a mouse cursor is below the candlestick Low { //--- Find two extreme points below CUtilites::SetExtremumsBarsNumbers(false,p1,p2); //--- Determine point coordinates time1=iTime(Symbol(),PERIOD_CURRENT,p1); price1=iLow(Symbol(),PERIOD_CURRENT,p1); time2=iTime(Symbol(),PERIOD_CURRENT,p2); price2=iLow(Symbol(),PERIOD_CURRENT,p2); } else // otherwise if(CMouse::Above()) // If a mouse cursor is below the candlestick High { //--- Find two extreme points above CUtilites::SetExtremumsBarsNumbers(true,p1,p2); //--- Determine point coordinates time1=iTime(Symbol(),PERIOD_CURRENT,p1); price1=iHigh(Symbol(),PERIOD_CURRENT,p1); time2=iTime(Symbol(),PERIOD_CURRENT,p2); price2=iHigh(Symbol(),PERIOD_CURRENT,p2); } //--- Draw the line TrendCreate(0,trend_name,0, time1,price1,time2,price2, CUtilites::GetTimeFrameColor(CUtilites::GetAllLowerTimeframes()), 0,Trend_Line_Width,false,true,m_Is_Trend_Ray ); //--- Redraw the chart ChartRedraw(0); }
Querríamos llamar de nuevo la atención sobre la llamada de la función CUtilites::SetExtremumsBarsNumbers, que recibe los números de las barras para los puntos 1 y 2. Su código ha sido descrito más arriba. Lo demás parece obvio, y no requiere una descripción detallada.
La función resultante dibuja una recta según dos puntos. Dependiendo del parámetro global Is_Trend_Ray, (descrito en el archivo "GlobalVariables.mqh") la línea será, o bien un rayo prolongado hacia la derecha, o simplemente un segmento entre dos extremos.
También sacaremos al teclado la posibilidad de alternar la prolongación de una línea.
Creación de un bloque de control: configuración del método OnChartEvent
Ahora que hemos escrito las funciones principales, podemos configurar los atajos de teclado.
En "Shortcuts.mqh", escribimos el método CShortcuts::OnChartEvent.
//+------------------------------------------------------------------+ //| Event handling function | //+------------------------------------------------------------------+ void CShortcuts::OnChartEvent( const int id, const long &lparam, const double &dparam, const string &sparam ) { //--- switch(id) { //--- Save the coordinates of the mouse cursor case CHARTEVENT_MOUSE_MOVE: CMouse::SetCurrentParameters(id,lparam,dparam,sparam); break; //--- Handle keystrokes case CHARTEVENT_KEYDOWN: //--- Change the timeframe 1 level up if(CUtilites::GetCurrentOperationChar(Up_Key) == lparam) { CUtilites::ChangeTimeframes(true); }; //--- Change the timeframe 1 level down if(CUtilites::GetCurrentOperationChar(Down_Key) == lparam) { CUtilites::ChangeTimeframes(false); }; //--- Change the Z Index of the chart (chart on top of all objects) if(CUtilites::GetCurrentOperationChar(Z_Index_Key) == lparam) { CUtilites::ChangeChartZIndex(); } //--- Draw a trend line if(CUtilites::GetCurrentOperationChar(Trend_Line_Key) == lparam) { m_graphics.DrawTrendLine(); } //--- Switch the Is_Trend_Ray parameter if(CUtilites::GetCurrentOperationChar(Switch_Trend_Ray_Key) == lparam) { m_graphics.IsRay(!m_graphics.IsRay()); } break; //--- } }
Teclas utilizadas en la actual implementación de la biblioteca
Acción | Tecla | De la palabra en inglés |
---|---|---|
Ir al marco temporal superior por los periodos principales (del panel de periodos) | U | Up |
Ir al marco temporal inferior | D | Down |
Alternar el nivel Z del gráfico (gráfico encima o debajo de los objetos) | Z | Z order |
Dibujar una línea de tendencia inclinada según los dos extremos unidireccionales más próximos al ratón | T | Trend line |
Alternar el modo de rayo para las nuevas rectas | R key | Ray |
Conclusión
En el archivo adjunto hay un directorio con la versión actual de la biblioteca. Asimismo, también hay tres scripts
- El primero se llama Del-All-Graphics, y se encarga de eliminar todos los objetos gráficos en la ventana actual. En nuestro terminal, se le asignó el atajo de teclado Ctrl+A (All).
- El segundo es Del-All-Prefixed. Con su ayuda, podemos eliminar todos los objetos según el prefijo de un nombre (por ejemplo, todas las líneas de tendencia, o todos los objetos que comiencen por H1). En nuestro caso, lo llamamos con el atajo Alt+R (Remove).
- Y, finalmente, el script (DeselectAllObjects) permite quitar todas las selecciones de la ventana actual. Nuestro atajo para él es Ctrl+D (Deselect, como en Fotoshop).
Es mejor incluir la biblioteca en el experto, y no en el indicador, dado que, en el caso de incluirla en el indicador e intentar usar este indicador junto con algún otro asesor, sufriremos una ralentización terrible. Por lo menos, en nuestro caso ha sido así. Puede que no lo hayamos preparado correctamente.
Y, para finalizar, hablemos un poco de nuestros planes.
La segunda versión de la biblioteca contendrá el dibujado de los objetos útiles que hemos visto en el vídeo. Algunos de estos objetos son primitivos (como la línea vertical o la horizontal), y con algunos de ellos, como las líneas con la longitud necesaria, hemos tenido que trabajar más de la cuenta, y además, no siempre funcionan de forma ideal, debido los "errores en las entradas" u otros motivos. Hablaremos de nuestras soluciones al respecto, y, por supuesto, estaremos muy contentos de escuchar cualquier tipo de crítica.
La tercera versión contendrá la interfaz gráfica para configurar los parámetros de los instrumentos gráficos.
La cuarta, si es que la abordamos, será ya un asesor de acompañamiento completo, y permitirá facilitar el comercio manual. Aquí, queremos plantear una pregunta a la comunidad. No estamos seguros de aplicar ninguna solución nueva en comparación con los análogos existentes. La interfaz será de diseño propio, y podemos escuchar sugerencias al respecto, pero el comercio manual es comercio manual :-). Por consiguiente, ¿alguien necesitará esto?
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/7468






- 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