Cómo crear gráficos 3D en DirectX en MetaTrader 5

27 abril 2020, 17:29
MetaQuotes
0
1 956

Los gráficos computerizados en 3D se ocupan de representar los objetos de un espacio tridimensional en la superficie plana de un monitor. En este caso, además, los propios objetos o la posición del observador pueden cambiar en el tiempo, por consiguiente, deberá también cambiar la imagen en dos dimensiones, creando la ilusión de profundidad de la imagen: el giro, la aproximación, el cambio de iluminación, etcétera. MQL5 permite crear y controlar los gráficos computerizados directamente en el terminal MetaTrader 5 con la ayuda de las funciones DirectX. Para trabajar con estas funciones, la tarjeta gráfica del usuario deberá soportar DX 11 y los sombreadores de la versión 5.0.


Modelo del objeto

Para dibujar un objeto tridimensional en una superficie plana, debemos crear un modelo de este objeto en las coordenadas espaciales X, Y y Z. Es decir, debemos describir cada punto en la superficie de este objeto, indicando sus coordenadas. De forma ideal, necesitaríamos describir un número infinito de puntos en la superficie del objeto, para que, fuera cual fuera el escalado, la calidad de la imagen no disminuyese. En la práctica, para describir un modelo tridimensional, se usa una red primitiva formada por polígonos. Cuanto mayor nivel de detalle tenga la red, más polígonos tendrá, y más real parecerá el modelo. Sin embargo, también será mayor el nivel de recursos de la computadora necesarios para construir un gráfico 3D.

Modelo de una tetera en forma de red de polígonos

Modelo de una tetera en forma de red de polígonos.

Al principio, cuando las computadoras y las tarjetas gráficas no eran tan potentes como ahora, cada polígono se dividía en triángulos, dado que, con la ayuda de un triángulo, se puede describir de forma unívoca la posición de una pequeña área de la superficie y calcular sobre ella parámetros tan necesarios como la iluminación y el reflejo de la luz incidente. Un conjunto de varios de estos pequeños triángulos permite crear una imagen realista en tres dimensiones de un objeto. Aquí y en lo sucesivo, triángulo y polígono van a actuar como sinónimos, ya que representar un triángulo resulta bastante más sencillo que representar un polígono con N vértices.


Cubo compuesto por triángulos.

De esta forma, para crear un modelo tridimensional de un objeto, basta con describir las coordenadas de cada vértice del triángulo, para después calcular las coordenadas de cada punto del objeto, incluso si el propio objeto se desplaza en el espacio o cambia la posición del observador. Los picos del triángulo se llaman vértices (vertex), los segmentos que los unen se llaman bordes (edge), y la superficie confinada entre los segmentos se llama cara (face). Conociendo la ubicación de un triángulo en el espacio, podremos construir el vector normal (el vector que parte desde la superficie y es perpendicular a la misma) con respecto al mismo según las leyes del álgebra lineal, y calcular de esta forma cómo la luz que incide en la cara procedente de una fuente coloreará la superficie y se reflejará en ella.


Ejemplos de objetos sencillos con vértices, bordes, caras y vectores normales. El vector normal es la flecha de color rojo.

Hay diferentes métodos para crear el modelo de un objeto, la topología, por ejemplo, describe la forma en que los polígonos forman un modelo 3D (mesh). La topología regular permite usar un número mínimo de polígonos para describir un objeto y, en algunos casos, hace más simple el desplazamiento y el giro del objeto en el espacio.

Modelo de esfera en las dos topologías

Modelo de esfera en las dos topologías.

Precisamente el juego de luz y sombra sobre los polígonos de un objeto le confiere volumen al mismo, y esta es exactamente la tarea de los gráficos computerizados: calcular para cada punto de un objeto su ubicación en el espacio, así como su color e iluminación, para después proyectar el objeto en la pantalla del monitor.

Creación de una figura

Vamos a escribir un sencillo programa que creará un cubo. Para ello, usaremos la clase CCanvas3D de la biblioteca de gráficos 3D.

La clase CCanvas3DWindow para el dibujado de la ventana 3D tiene un número mínimo de miembros y métodos, fáciles de comprender. A continuación, añadiremos todos los métodos nuevos con aclaraciones del concepto de gráficos 3D implementado en la función de trabajo con DirectX.

//+------------------------------------------------------------------+
//| Application window                                               |
//+------------------------------------------------------------------+
class CCanvas3DWindow
  {
protected:
   CCanvas3D         m_canvas;
   //--- dimensiones del lienzo
   int               m_width;
   int               m_height;
   //--- objeto Cubo
   CDXBox            m_box;

public:
                     CCanvas3DWindow(void) {}
                    ~CCanvas3DWindow(void) {m_box.Shutdown();}
   //-- creando la escena
   virtual bool      Create(const int width,const int height){}
   //--- calculando la escena
   void              Redraw(){}
   //--- procesando los eventos del gráfico
   void              OnChartChange(void) {}
  };

Al crear una escena, comenzamos por el lienzo. Después, establecemnos para la matriz de proyección:

  1. Un ángulo de visión de 30 grados (M_PI/6), desde el que miramos a la escena en 3D;
  2. La relación de aspecto (aspect ratio) del fotograma como la relación de la anchura respecto a la altura;
  3. Y, finalmente, la distancia hasta las superficies de cruce cercana (0.1f) y lejana (100.f).

Esto significa que en la matriz de proyección se representarán solo los objetos que se encuentren entre estas dos paredes virtuales (0.1f y 100.f); en este caso, además, el objeto también deberá entrar en el ángulo de visión horizontal, igual a 30 grados. En general, en los gráficos computerizados, tanto las distancias como las coordenadas son virtuales, Dado que son importantes, no las magnitudes absolutas, sino la relación entre las distancias y los tamaños.

   //+------------------------------------------------------------------+
   //| Create                                                           |
   //+------------------------------------------------------------------+
   virtual bool      Create(const int width,const int height)
     {
      //--- guardamos las dimensiones del lienzo
      m_width=width;
      m_height=height;
      //--- creamos el lienzo para dibujar la escena 3D sobre el mismo
      ResetLastError();
      if(!m_canvas.CreateBitmapLabel("3D Sample_1",0,0,m_width,m_height,COLOR_FORMAT_ARGB_NORMALIZE))
        {
         Print("Error creating canvas: ",GetLastError());
         return(false);
        }
      //--- establecemos los parámetros de la matriz de proyección: el ángulo de visión, la relación de los lados, la distancia hasta las superificies de corte cercana y lejana
      m_canvas.ProjectionMatrixSet((float)M_PI/6,(float)m_width/m_height,0.1f,100.0f);
      //--- creamos un cubo que transmitimos al gestor de recursos, los parámetros de la escena  y las coordenadas de los dos ángulos opuestos del cubo
      if(!m_box.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),DXVector3(-1.0,-1.0,5.0),DXVector3(1.0,1.0,7.0)))
        {
         m_canvas.Destroy();
         return(false);
        }
      //--- añadimos el cubo a la escena
      m_canvas.ObjectAdd(&m_box);
      //--- redibujamos la escena
      Redraw();
      //--- succeed
      return(true);
     }

Después de la matriz de proyección, se crea el propio objeto en 3D, en nuestro caso, el cubo basado en la clase CDXBox. Para crear el cubo, será necesario y suficiente indicar dos vectores que muestren los ángulos opuestos del cubo. Si usted monitorea la creación del cubo para su depuración, podrá ver cómo en el método DXComputeBox() se crean todos los vértices del cubo (sus coordenadas se anotan en la matriz vertices), y cómo las caras del cubo se dividen en triángulos que se enumerarn y guardan en la matriz indiсes. En total, el cubo tiene 8 vértices, 6 caras divididas en 12 triángulos, y 36 índices con los vértices enumerados de estos triángulos.

Aunque el cubo solo tiene 8 vértices, para su descripción se crean 24 vectores, ya que para cada una de las 6 caras es necesario indicar su propio conjunto de vértices con su propio vector normal. La dirección del vector normal influirá en lo sucesivo en el cálculo de la iluminación de cada cara. El orden de enumeración de los vértices del triángulo en el índice influye en el lado desde el que el triángulo será visible. El orden de rellenado de los vértices e índices se puede ver en el código DXUtils.mqh:

   for(int i=20; i<24; i++)
      vertices[i].normal=DXVector4(0.0,-1.0,0.0,0.0);

Además, en ese mismo lugar, se describen para cada cara las coordenadas de textura para el mapeado de texturas:

//--- texture coordinates
   for(int i=0; i<faces; i++)
     {
      vertices[i*4+0].tcoord=DXVector2(0.0f,0.0f);
      vertices[i*4+1].tcoord=DXVector2(1.0f,0.0f);
      vertices[i*4+2].tcoord=DXVector2(1.0f,1.0f);
      vertices[i*4+3].tcoord=DXVector2(0.0f,1.0f);
     }

Cada uno de los 4 vectores de la cara establece uno de los 4 ángulos de despliegue del mapeado de texturas. Esto significa que al realizar la renderización en cada cara del cubo se mapeará una estructura en forma de cuadrado para su dibujado. Si, por supuesto, se indica la textura.


Cálculo y dibujado de una escena

Con cado cambio de la escena 3D, será necesario realizar todos los cálculos de nuevo. Esto significa que deberemos calcular de forma consecutiva:

  • La posición del centro del objeto en el espacio en las coordenadas del mundo;
  • La posición de cada elemento del objeto, es decir, de cada vértice;
  • La profundidad del píxel y su visibilidad para el observador;
  • La posición de cada píxel en el polígono establecido por sus vértices;
  • El color de cada píxel en el polígono de acuerdo con la textura establecida;
  • La dirección del color incidente en el píxel y el reflejo del mismo;
  • Aplicar luz difusa a cada píxel;
  • La conversión de todas las coordenadas del mundo en coordenadas de la cámara;
  • La conversión de las coordenadas de la cámara en coordenadas en la matriz de proyección.
Todas estas operaciones se realizan en el método Render del objeto CCanvas3D. Después de la renderización, la imagen calculada se traslada de la matriz de proyección al lienzo llamando al método Update.
   //+------------------------------------------------------------------+
   //| actualizamos la escena                                           |
   //+------------------------------------------------------------------+
   void              Redraw()
     {
      //--- calculando la escena 3D
      m_canvas.Render(DX_CLEAR_COLOR|DX_CLEAR_DEPTH,ColorToARGB(clrBlack));
      //--- actualizando la imagen en el lienzo de acuerdo con la escena actual
      m_canvas.Update();
     }

En nuestro ejemplo, el cubo se crea solo una vez, y en lo sucesivo no sufre ningún cambio. Por eso, solo deberemos actualizar el fotograma en el lienzo en el caso de que haya cambios en el gráfico, como por ejemplo un cambio en sus dimensiones. En este caso, se reajustará el tamaño del lienzo según las dimensiones actuales del gráfico, se reestablecerá la matriz de proyección y se actualizará la imagen en el lienzo.

   //+------------------------------------------------------------------+
   //| Process chart change event                                       |
   //+------------------------------------------------------------------+
   void              OnChartChange(void)
     {
      //--- obteniendo las dimensiones actuales del gráfico
      int w=(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS);
      int h=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS);
      //--- actualizando las dimensiones del lienzo de acuerdo con las dimensiones del gráfico
      if(w!=m_width || h!=m_height)
        {
         m_width =w;
         m_height=h;
         //--- cambiando las dimensiones del lienzo
         m_canvas.Resize(w,h);
         DXContextSetSize(m_canvas.DXContext(),w,h);
         //--- actualizando la matriz de proyección de acuerdo con las dimensiones del lienzo
         m_canvas.ProjectionMatrixSet((float)M_PI/6,(float)m_width/m_height,0.1f,100.0f);
         //--- recalculamos la escena 3-D y la dibujamos en el lienzo
         Redraw();
        }
     }

Iniciamos el asesor "Step1 Create Box.mq5" y vemos un cuadrado blanco sobre un fondo negro. Como desconocemos la iluminación, el color blanco se establece por defecto para los objetos durante la creación.

Un cubo blanco y su esquema de ubicación en el espacio

Un cubo blanco y su esquema de ubicación en el espacio

En este caso, además, el eje X está orientado a la derecha, el eje Y, hacia arriba, y el Z, hacia el fondo de la escena 3D respecto a nosotros. Este sistema de coordenadas se conoce como zurdo.

El centro del cubo se encuentra en el punto con las coordenadas X=0, Y=0, Z=6. La posición desde la que observamos el cubo se encuentra en el centro de las coordenadas, este es el valor por defecto. Si queremos cambiar la posición del punto de vista sobre la escena en 3D, deberemos indicar las coordenadas explícitamente con la ayuda de la función ViewPositionSet().

Para finalizar el funcionamiento del programa, deberemos pulsar sobre la tecla "Escape".


Rotación de un objeto en el eje Z y ángulo de visión sobre una escena

Para avivar un poco la escena, transmitiremos al cubo la rotación alrededor del eje Z. Para ello, añadiremos un temporizador cuyos eventos marcarán la rotación del cubo en el sentido opuesto a las agujas del reloj.

Vamos a crear una matriz de giro alrdedor del eje Z en el ángulo establecido, usando para ello la función DXMatrixRotationZ(), y después la transmitiremos como parámetro al método TransformMatrixSet(): esto cmabiará la posición del cubo en el espacio. Para actualizar la imagen en el lienzo, llamaremos de nuevo Redraw().

   //+------------------------------------------------------------------+
   //| Timer handler                                                    |
   //+------------------------------------------------------------------+
   void              OnTimer(void)
     {
      //--- variables para calcular el ángulo de giro
      static ulong last_time=0;
      static float angle=0;
      //--- obtenemos la hora actual
      ulong current_time=GetMicrosecondCount();
      //--- calculamos delta
      float deltatime=(current_time-last_time)/1000000.0f;
      if(deltatime>0.1f)
         deltatime=0.1f;
      //--- aumentamos el ángulo de giro del cubo alrededor del eje Z
      angle+=deltatime;
      //--- guardamos la hora
      last_time=current_time;
      //--- establecemos para el cubo el ángulo de giro alrededor del eje Z
      DXMatrix rotation;
      DXMatrixRotationZ(rotation,angle);
      m_box.TransformMatrixSet(rotation);
      //--- recalculamos la escena 3-D y la dibujamos en el lienzo
      Redraw();
     }

Iniciamos y obtenemos un cuadrado blanco giratorio.

El cubo rota alrededor del eje Z en el sentido opuesto a las agujas del reloj

El código fuente de este ejemplo se encuentra en el archivo "Step2 Rotation Z.mq5". Debemos tener en cuenta que, para crear una escena, ahora se indica el ángulo M_PI/5, que es superior al ángulo=M_PI/6 del ejemplo anterior. 

      //--- establecemos los parámetros de la matriz de proyección: el ángulo de visión, la relación de los lados, la distancia hasta las superificies de corte cercana y lejana
      m_matrix_view_angle=(float)M_PI/5;
      m_canvas.ProjectionMatrixSet(m_matrix_view_angle,(float)m_width/m_height,0.1f,100.0f);
      //--- creamos un cubo que transmitimos al gestor de recursos, los parámetros de la escena  y las coordenadas de los dos ángulos opuestos del cubo

Pero, en este caso, las dimensiones del cubo en la pantalla son ahora visualmente menores. Cuanto menor sea el ángulo de visión que indicamos al establecer la matriz de proyección, mayor será la parte del fotograma que ocupa el objeto. El proceso se asemeja al funcionamiento de un telescopio, que muestra los objetos más grandes, aunque el ángulo de visión se reduzca.


Control de la posición de la cámara

La clase CCanvas3D dispone de 3 métodos -relacionados entre sí- para establecer los parámetros importantes de una escena 3D:

  • ViewPositionSet — establece la posición del punto de vista de la escena 3D
  • ViewTargetSet — establece las coordinadas del punto hacia el que está dirigida la vista
  • ViewUpDirectionSet — establece la dirección del borde superior del fotograma en el espacio

Todos estos métodos se usan de forma conjunta: esto significa que si queremos indicar cualquiera de estos parámetros en una escena 3D, deberemos inicializar los dos parámetros restantes, aunque sea en la etapa de generación de la escena. Vamos a mostrar esto en el ejemplo siguiente, donde balancearemos a izquierda y derecha el borde superior del fotograma. Para ello, añadiremos al método Create() 3 líneas:

   //+------------------------------------------------------------------+
   //| Create                                                           |
   //+------------------------------------------------------------------+
   virtual bool      Create(const int width,const int height)
     {
....       
      //--- añadimos el cubo a la escena
      m_canvas.ObjectAdd(&m_box);
      //--- establecemos los parámetros de la escena
      m_canvas.ViewUpDirectionSet(DXVector3(0,1,0));  // establecemos el vector de dirección hacia arriba a lo largo del eje Y  
      m_canvas.ViewPositionSet(DXVector3(0,0,0));     // dirigimos la mirada desde el centro de las coordenadas
      m_canvas.ViewTargetSet(DXVector3(0,0,6));       // orientamos la mirada hacia el centro del cubo      
      //--- redibujamos la escena
      Redraw();
      //--- succeed
      return(true);
     }

Modificamos el método OnTimer() de forma que balancee el vector del horizonte a izquierda y derecha.

   //+------------------------------------------------------------------+
   //| Timer handler                                                    |
   //+------------------------------------------------------------------+
   void              OnTimer(void)
     {
      //--- variables para calcular el ángulo de giro
      static ulong last_time=0;
      static float max_angle=(float)M_PI/30;
      static float time=0;
      //--- obtenemos la hora actual
      ulong current_time=GetMicrosecondCount();
      //--- calculamos delta
      float deltatime=(current_time-last_time)/1000000.0f;
      if(deltatime>0.1f)
         deltatime=0.1f;
      //--- aumentamos el ángulo de giro del cubo alrededor del eje Z
      time+=deltatime;
      //--- guardamos la hora
      last_time=current_time;
      //--- establecemos el ángulo de giro alrededor del eje Z
      DXVector3 direction=DXVector3(0,1,0);     // dirección inicial de la parte superior
      DXMatrix rotation;                        // vector de giro      
      //--- calculamos la matriz de giro 
      DXMatrixRotationZ(rotation,float(MathSin(time)*max_angle));
      DXVec3TransformCoord(direction,direction,rotation);
      m_canvas.ViewUpDirectionSet(direction);   // establecemos la nueva dirección de la parte superior
      //--- recalculamos la escena 3-D y la dibujamos en el lienzo
      Redraw();
     }

Guardamos el ejemplo con el nombre "Step3 ViewUpDirectionSet.mq5" e iniciamos. Obtenemos la imagen de un cubo balanceándose, aunque en realidad permanece inmóvil. Este efecto se consigue balanceando a izquierda y derecha la propia cámara en la que se filma el vídeo.

La dirección de la parte superior se balancea a izquierda y derecha

La dirección de la parte superior se balancea a izquierda y derecha

Debemos recordar que existe una relación entre las coordenadas del objetivo, la cámara y la dirección de la parte superior: para controlar la posición de la cámara, necesitamos establecer también la dirección de la parte superior y las coordenadas del objetivo al que se dirige la mirada.


Control del color del objeto

Vamos a modificar ligeramente nuestro código, ubicando el cubo en el centro de las coordenadas y desplazando la cámara.

   //+------------------------------------------------------------------+
   //| Create                                                           |
   //+------------------------------------------------------------------+
   virtual bool      Create(const int width,const int height)
     {
  ...
      //--- creamos un cubo que transmitimos al gestor de recursos, los parámetros de la escena  y las coordenadas de los dos ángulos opuestos del cubo
      if(!m_box.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),DXVector3(-1.0,-1.0,-1.0),DXVector3(1.0,1.0,1.0)))
        {
         m_canvas.Destroy();
         return(false);
        }
      //--- establecemos el color 
      m_box.DiffuseColorSet(DXColor(0.0,0.5,1.0,1.0));        
      //--- añadimos el cubo a la escena
      m_canvas.ObjectAdd(&m_box);
      //--- establecemos las posiciones de la cámara, el objetivo y la dirección de la parte superior
      m_canvas.ViewUpDirectionSet(DXVector3(0.0,1.0,0.0));  // establecemos el vector de dirección hacia arriba a lo largo del eje Y
      m_canvas.ViewPositionSet(DXVector3(3.0,2.0,-5.0));    // establecemos la cámara a la derecha, encima y delante del cubo
      m_canvas.ViewTargetSet(DXVector3(0,0,0));             // orientamos la mirada hacia el centro del cubo
      //--- redibujamos la escena
      Redraw();
      //--- succeed
      return(true);
     }

Asimismo, coloreamos el cubo de azul celeste: el color se establece en el formato RGB con el canal alfa (el canal alfa se indica en último lugar), pero, en este caso, los valores se normalizan hasta la unidad. De esta manera, el valor 1 indica 255, y el 0.5 indica 127.

Añadimos la rotación alrededor del eje X y guardamos los cambios en "Step4 Box Color.mq5".

Vista sobre el cubo en rotación desde arriba y a la derecha.

Vista sobre el cubo en rotación desde arriba y a la derecha.


Rotación y desplazamiento

Podemos desplazar y rotar objetos en tres direcciones al mismo al mismo tiempo. Todos los cambios de los objetos se realizan con la ayuda de matrices, cada una de las cuales se puede calcular por separado: la rotación, el desplazamiento y la transformación de las dimensiones. Vamos a modificar el ejemplo: ahora, la cámara mira hacia el cubo desde arriba y de frente.

   //+------------------------------------------------------------------+
   //| Create                                                           |
   //+------------------------------------------------------------------+
   virtual bool      Create(const int width,const int height)
     {
  ...
      m_canvas.ProjectionMatrixSet(m_matrix_view_angle,(float)m_width/m_height,0.1f,100.0f);
      //--- ubicamos la cámara por encima y delante del centro de las coordenadas
      m_canvas.ViewPositionSet(DXVector3(0.0,2.0,-5.0));
      m_canvas.ViewTargetSet(DXVector3(0.0,0.0,0.0));
      m_canvas.ViewUpDirectionSet(DXVector3(0.0,1.0,0.0));      
      //--- creamos un cubo que transmitimos al gestor de recursos, los parámetros de la escena  y las coordenadas de los dos ángulos opuestos del cubo
      if(!m_box.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),DXVector3(-1.0,-1.0,-1.0),DXVector3(1.0,1.0,1.0)))
        {
         m_canvas.Destroy();
         return(false);
        }
      //--- establecemos el color del cubo
      m_box.DiffuseColorSet(DXColor(0.0,0.5,1.0,1.0));        
      //--- calculamos la posición del cubo y la matriz de traslado
      DXMatrix rotation,translation;
      //--- giramos el cubo sucesivamente alrededor de los ejes X, Y y Z
      DXMatrixRotationYawPitchRoll(rotation,(float)M_PI/4,(float)M_PI/3,(float)M_PI/6);
      //-- desplazamos el cubo a la derecha-hacia abajo-hacia el fondo
      DXMatrixTranslation(translation,1.0,-2.0,5.0);
      //--- obtenemos la matriz de transformación como producto del giro y el traslado
      DXMatrix transform;
      DXMatrixMultiply(transform,rotation,translation);
      //--- establecemos la matriz de transformación 
      m_box.TransformMatrixSet(transform);      
      //--- añadimos el cubo a la escena
      m_canvas.ObjectAdd(&m_box);    
      //--- redibujamos la escena
      Redraw();
      //--- succeed
      return(true);
     }

Creamos secuencialmente las matrices de giro y traslado, aplicamos la matriz de transformación obtenida y dibujamos el cubo. Guardamos los cambios en "Step5 Translation.mq5" e iniciamos.

Rotación y desplazamiento del cubo

Rotación y desplazamiento del cubo

Recordemos que la cámara mira inmóvil hacia el centro de las coordenas, un poco desde arriba. El cubo ha sido girado en tres direcciones y desplazado hacia la derecha, hacia abajo y hacia el fondo de la escena.


Trabajo con la iluminación

Para obtener una imagen realista en tres dimensiones, necesitamos calcular la iluminación de cada punto en la superficie del objeto. Para ello, se usa el modelo de Phong, que calcula la intensidad del color de los tres componentes de la iluminación: el del fondo (ambient), el difuso (diffuse) y el especular (specular). En este caso, se utilizan los siguientes parámetros:

  • DirectionLight — la dirección de la fuente de iluminación directa se establece en CCanvas3D
  • AmbientLight — el color y la intensidad de la iluminación indirecta se establecen en CCanvas3D
  • DiffuseColor — el cálculo del componente de luz difusa se establece en CDXMesh y sus clases herederas
  • EmissionColor — el componente de iluminación de fondo se establece en CDXMesh y sus clases herederas
  • SpecularColor — el compoenente de reflejo especular se establece en CDXMesh y sus clases herederas

Modelo de iluminación de Phong
Modelo de iluminación de Phong


El modelo de iluminación se ha implementado en sombreadores estándar; los propios parámetros del modelo se indican en CCanvas3D, y los de los objetos, en CDXMesh y sus clases herederas. Vamos a introducir los cambios en nuestro ejemplo:

  1. Devolvemos el cubo al centro de las coordenadas.
  2. Le asignamos el color blanco.
  3. Añadimos una fuente directa de luz amarilla que ilumina la escena de arriba hacia abajo.
  4. Establecemos el color azul para la iluminación indirecta.
      //--- establecemos el color amarillo para la fuente y lo dirigimos de arriba hacia abajo
      m_canvas.LightColorSet(DXColor(1.0,1.0,0.0,0.8f));
      m_canvas.LightDirectionSet(DXVector3(0.0,-1.0,0.0));
      //--- establecemos el color azul para la iluminación ambiental 
      m_canvas.AmbientColorSet(DXColor(0.0,0.0,1.0,0.4f));          
      //--- creamos un cubo que transmitimos al gestor de recursos, los parámetros de la escena y las coordenadas de los dos ángulos opuestos del cubo
      if(!m_box.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),DXVector3(-1.0,-1.0,-1.0),DXVector3(1.0,1.0,1.0)))
        {
         m_canvas.Destroy();
         return(false);
        }
      //--- establecemos el color blanco para el cubo
      m_box.DiffuseColorSet(DXColor(1.0,1.0,1.0,1.0)); 
      // añadimos verde al cubo
      m_box.EmissionColorSet(DXColor(0.0,1.0,0.0,0.2f)); 

Notemos que en el modelo Canvas3D no se establece la posición de la fuente directa de iluminación, sino solo la dirección de difusión de la luz. Se supone que la fuente de luz directa se encuentra a una distancia infinita, y que sobre la escena incide un flujo de luz estrictamente paralelo.

m_canvas.LightDirectionSet(DXVector3(0.0,-1.0,0.0));

En este caso, el vector de difusión de la luz está dirigido a lo largo del eje Y en dirección negativa, es decir, de arriba hacia abajo. Además, si usted establece los parámetros de la fuente de luz directa (LightColorSet y LightDirectionSet), será necesario establecer también el color de la iluminación ambiental difusa (AmbientColorSet), ya que, por defecto, el color de la iluminación ambiental se establece como blanco y a la máxima intensidad, y todas las sombras serán de color blanco. Esto significa que los objetos en la escena estarán saturados con el color blanco de la iluminación indirecta, y el color de la iluminación ambiental será interrumpido por la luz blanca.

      //--- establecemos el color amarillo para la fuente y lo dirigimos de arriba hacia abajo
      m_canvas.LightColorSet(DXColor(1.0,1.0,0.0,0.8f));
      m_canvas.LightDirectionSet(DXVector3(0.0,-1.0,0.0));
      //--- establecemos el color azul para la iluminación ambiental 
      m_canvas.AmbientColorSet(DXColor(0.0,0.0,1.0,0.4f));  // indicar necesariamente

En la figura, se muestra con la ayuda de una animación GIF el cambio secuencial de la imagen al añadir iluminación. El código fuente del ejemplo se encuentra en el archivo "Step6 Add Light.mq5".

Cubo blanco con iluminación verde bajo una fuente de luz amarilla dentro de una iluminación ambiental azul

Cubo blanco con iluminación verde bajo una fuente de luz amarilla dentro de una iluminación ambiental azul.

Usted podrá desactivar en el código adjunto los métodos de trabajo con el color, para ver cómo funciona esto.


Animación

Una animación consiste en el cambio de los parámetros de una escena y unos objetos durante un determinado tiempo. Se puede cambiar cualquier propiedad, dependiendo del tiempo o el evento. Como evento que controla la actualización de la escena, crearemos un temporizador de 10 milisegundos:

int OnInit()
  {
...
//--- create canvas
   ExtAppWindow=new CCanvas3DWindow();
   if(!ExtAppWindow.Create(width,height))
      return(INIT_FAILED);
//--- set timer
   EventSetMillisecondTimer(10);
//---
   return(INIT_SUCCEEDED);
  }

Añadimos a la clase CCanvas3DWindow el procesador de este evento, en el que cambiaremos los parámetros del objeto (rotación, desplazamiento y escalado) y la dirección de la iluminación:

   //+------------------------------------------------------------------+
   //| Timer handler                                                    |
   //+------------------------------------------------------------------+
   void              OnTimer(void)
     {    
      static ulong last_time=0;
      static float time=0;       
      //--- obtenemos la hora actual
      ulong current_time=GetMicrosecondCount();
      //--- calculamos delta
      float deltatime=(current_time-last_time)/1000000.0f;
      if(deltatime>0.1f)
         deltatime=0.1f;
      //--- aumentamos el valor del tiempo transcurrido
      time+=deltatime;
      //--- guardamos la hora
      last_time=current_time;
      //--- calculamos la posición del cubo y la matriz de giro
      DXMatrix rotation,translation,scale;
      DXMatrixRotationYawPitchRoll(rotation,time/11.0f,time/7.0f,time/5.0f);
      DXMatrixTranslation(translation,(float)sin(time/3),0.0,0.0);
      //--- calculamos la compresión/expansión del cubo a lo largo del eje
      DXMatrixScaling(scale,1.0f+0.5f*(float)sin(time/1.3f),1.0f+0.5f*(float)sin(time/1.7f),1.0f+0.5f*(float)sin(time/1.9f));
      //--- multiplicamos las matrices para obtener la transformación final
      DXMatrix transform;
      DXMatrixMultiply(transform,scale,rotation);
      DXMatrixMultiply(transform,transform,translation);
      //--- establecemos la matriz de transformación
      m_box.TransformMatrixSet(transform);
      //--- calculamos el giro de la fuente de luz alrededor del eje Z
      DXMatrixRotationZ(rotation,deltatime);
      DXVector3 light_direction;
      //--- obtenemos la dirección actual de la fuente de luz
      m_canvas.LightDirectionGet(light_direction);
      //--- calculamos la nueva dirección de la fuente de luz y la establecemos
      DXVec3TransformCoord(light_direction,light_direction,rotation);
      m_canvas.LightDirectionSet(light_direction);
      //--- recalculamos la escena 3D y dibujamos en el lienzo
      Redraw();
     }

Notemos que los cambios del objeto se superponen sobre los valores iniciales, como si tomaramos cada vez un cubo desde el punto en que se creó y realizásemos todas las operaciones de rotación/desplazamiento/expansión desde cero, es decir, no se guarda memoria alguna sobre el estado actual. Al mismo tiempo, cambiamos la dirección de la fuente de luz según los incrementos de deltatime desde el valor actual hacia el nuevo.

Cubo giratorio con iluminación dinámica

Cubo giratorio con cambio dinámico de dirección de la fuente de luz.

Como resultado, hemos obtenido una animación en 3D muy compleja. El código del ejemplo se encuentra en el archivo "Step7 Animation.mq5".


Control de la cámara con ayuda del ratón

No queda el último elemento de animación en el gráfico 3D: la reacción a las acciones del usuario. Vamos a añadir a nuestro ejemplo el control de la cámara con la ayuda del ratón. Para ello, nos suscribimos a los eventos del ratón y creamos los manejadores correspondientes:

int OnInit()
  {
...
//--- establecemos el temporizador
   EventSetMillisecondTimer(10);
//--- activamos la obtención de eventos del ratón: el desplazamiento y la pulsación de botones
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,1);
   ChartSetInteger(0,CHART_EVENT_MOUSE_WHEEL,1)
//---
   return(INIT_SUCCEEDED);
  }
void OnDeinit(const int reason)
  {
//--- eliminamos el temporizador
   EventKillTimer();
//--- desactivamos la obtención de eventos del ratón
   ChartSetInteger(0,CHART_EVENT_MOUSE_MOVE,0);
   ChartSetInteger(0,CHART_EVENT_MOUSE_WHEEL,0);
//--- eliminamos el objeto
   delete ExtAppWindow;
//--- devolvemos el gráfico al modo de muestra habitual de los gráficos de precio
   ChartSetInteger(0,CHART_SHOW,true);
  }
void OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
...
//--- evento de cambio del gráfico
   if(id==CHARTEVENT_CHART_CHANGE)
      ExtAppWindow.OnChartChange();
//--- evento de desplazamiento del ratón
   if(id==CHARTEVENT_MOUSE_MOVE)
      ExtAppWindow.OnMouseMove((int)lparam,(int)dparam,(uint)sparam);
//--- evento de giro de la ruleta del ratón
   if(id==CHARTEVENT_MOUSE_WHEEL)
      ExtAppWindow.OnMouseWheel(dparam);

Creamos en CCanvas3DWindow el manejador del movimiento del ratón, que cambia los ángulos de dirección de la cámara al mover el ratón con el botón izquierdo pulsado:

   //+------------------------------------------------------------------+
   //| Procesando el movimiento del ratón                               |
   //+------------------------------------------------------------------+
   void              OnMouseMove(int x,int y,uint flags)
     {
      //--- botón izquierdo del ratón
      if((flags&1)==1)
        {
         //--- no hay información sobre la anterior posición del ratón
         if(m_mouse_x!=-1)
           {
            //--- actualizar el ángulo de la cámara según el cambio de posición
            m_camera_angles.y+=(x-m_mouse_x)/300.0f;
            m_camera_angles.x+=(y-m_mouse_y)/300.0f;
            //--- establecer el ángulo vertical en el intervalo (-Pi/2,Pi2)
            if(m_camera_angles.x<-DX_PI*0.49f)
               m_camera_angles.x=-DX_PI*0.49f;
            if(m_camera_angles.x>DX_PI*0.49f)
               m_camera_angles.x=DX_PI*0.49f;
            //--- actualizar la posición de la cámara
            UpdateCameraPosition();
           }
         //--- guardamos la posición del ratón
         m_mouse_x=x;
         m_mouse_y=y;
        }
      else
        {
         //--- reseteamos la posición guardada si el botón izquierdo del ratón no está pulsado
         m_mouse_x=-1;
         m_mouse_y=-1;
        }
     }

Y el manejador del giro de la ruleta, que cambia la distancia de la cámara hasta el centro de la escena:

   //+------------------------------------------------------------------+
   //| Procesamiento de los eventos de la ruleta del ratón              |
   //+------------------------------------------------------------------+
   void              OnMouseWheel(double delta)
     {
      //--- actualizando el distanciamiento de la cámara según el giro de la ruleta del ratón
      m_camera_distance*=1.0-delta*0.001;
      //--- estableciendo la distancia en el intervalo [3,50]
      if(m_camera_distance>50.0)
         m_camera_distance=50.0;
      if(m_camera_distance<3.0)
         m_camera_distance=3.0;
      //--- actualizar la posición de la cámara
      UpdateCameraPosition();
     }

Los dos manejadores llaman al método UpdateCameraPosition() para actualizar la posición de la cámara de acuerdo con los parámetros modificados:

   //+------------------------------------------------------------------+
   //| Actualiza la posición de la cámara                               |
   //+------------------------------------------------------------------+
   void              UpdateCameraPosition(void)
     {
      //--- posición de la cámara sin giro, teniendo en cuenta la distancia hasta el centro de las coordenadas
      DXVector4 camera=DXVector4(0.0f,0.0f,-(float)m_camera_distance,1.0f);
      //--- rotación de la cámara alrededor del eje X
      DXMatrix rotation;
      DXMatrixRotationX(rotation,m_camera_angles.x);
      DXVec4Transform(camera,camera,rotation);
      //--- rotación de la cámara alrededor del eje Y
      DXMatrixRotationY(rotation,m_camera_angles.y);
      DXVec4Transform(camera,camera,rotation);
      //--- ponemos la cámara en posición
      m_canvas.ViewPositionSet(DXVector3(camera));
     }

El código modificado se encuentra en el archivo "Step8 Mouse Control.mq5".

Control de la cámara con ayuda del ratón

Control de la cámara con la ayuda del ratón.


Aplicando texturas

Una textura es una imagen de mapa de bits superpuesta sobre la superficie de un polígono para representar colores, patrones o transmitir la sensación de relieve. El uso de texturas permite reproducir en una superficie objetos pequeños cuya representación requeriría muchos recursos si usáramos polígonos. Por ejemplo, un dibujo que imite una piedra, un árbol, una pared o tierra.

La clase CDXMesh y sus clases herederas permiten establecer para un objeto una textura que, en un sombreador de píxeles estándar, se usa de forma conjunta con su color de difusión (DiffuseColor). Vamos a quitar la animación del objeto y a superponer sobre el mismo la textura de una piedra que deberá encontrarse en la carpeta MQL5\Files del directorio de trabajo del terminal:

   virtual bool      Create(const int width,const int height)
     {
  ...
      //--- establecemos el color blanco para la iluminación indirecta
      m_box.DiffuseColorSet(DXColor(1.0,1.0,1.0,1.0));

      //--- añadimos la textura para dibujar las caras del cubo
      m_box.TextureSet(m_canvas.DXDispatcher(),"stone.bmp");
      //--- añadimos el cubo a la escena
      m_canvas.ObjectAdd(&m_box);
      //--- redibujamos la escena
      Redraw();
      //--- succeed
      return(true);
     }

Cubo con una textura de piedra colocada

Cubo con una textura de piedra colocada.


Creación de objetos personalizados

Todos los objetos constan de vértices (DXVector3), y estos vértices se conectan con ayuda de índices en ciertas primitivas. La primitiva más extendida es el triángulo. Para crear un objeto en 3D, debemos crear una lista de vértices que contengan como mínimo sus coordenadas (pero pueden contener multitud de datos adicionales, como el vector normal, el color, etcétera), el tipo de primitivas en las que se unen y una lista con los índices de los vértices, según la cual se unirán en primitivas.


En la Biblioteca Estándar se ha definido el tipo de vértice DXVertex, que contiene su coordenada, el vértice normal para el cálculo, las coordenadas de textura y el color. Con este tipo de vértices trabaja el sombreador de vértices estándar.

struct DXVertex
  {
   DXVector4         position;  // coordenadas de los vértices
   DXVector4         normal;    // vector normal
   DXVector2         tcoord;    // coordenadas de la cara para colocar la textura
   DXColor           vcolor;    // color
  };

El archivo auxiliar MQL5\Include\Canvas\DXDXUtils.mqh contiene un conjunto de métodos para generar la geometría (vértices e índices) de las primitivas básicas, así como para cargar la geometría 3D desde archivos .OBJ.

Vamos a añadir la creación de la esfera y el toro, superponiendo sobre ellos la misma textura de piedra:

   virtual bool      Create(const int width,const int height)
     {
 ...     
      //--- vértices e índices para los objetos creados manualmente
      DXVertex vertices[];
      uint indices[];
      //--- preparamos los vértices e índices para la esfera
      if(!DXComputeSphere(0.3f,50,vertices,indices))
         return(false);
      //--- establecemos el color blanco para los vértices
      DXColor white=DXColor(1.0f,1.0f,1.0f,1.0f);
      for(int i=0; i<ArraySize(vertices); i++)
         vertices[i].vcolor=white;
      //--- creamos el objeto de esfera
      if(!m_sphere.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),vertices,indices))
        {
         m_canvas.Destroy();
         return(false);
        }
      //--- establecemos el color de iluminación dispersa para la esfera
      m_sphere.DiffuseColorSet(DXColor(0.0,1.0,0.0,1.0));
      //--- establecemos el color blanco para el reflejo de la esfera
      m_sphere.SpecularColorSet(white);
      m_sphere.TextureSet(m_canvas.DXDispatcher(),"stone.bmp");
      //--- añadimos la esfera a la escena
      m_canvas.ObjectAdd(&m_sphere);
      //--- preparamos los vértices e índices para el toro
      if(!DXComputeTorus(0.3f,0.1f,50,vertices,indices))
         return(false);
      //--- establecemos el color blanco para los vértices
      for(int i=0; i<ArraySize(vertices); i++)
         vertices[i].vcolor=white;
      //--- creamos el objeto toro
      if(!m_torus.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),vertices,indices))
        {
         m_canvas.Destroy();
         return(false);
        }
      //--- establecemos el color de iluminación dispersa para el toro
      m_torus.DiffuseColorSet(DXColor(0.0,0.0,1.0,1.0));
      m_torus.SpecularColorSet(white);
      m_torus.TextureSet(m_canvas.DXDispatcher(),"stone.bmp");
      //--- añadimos el toro a la escena
      m_canvas.ObjectAdd(&m_torus);      
      //--- redibujamos la escena
      Redraw();
      //--- succeed
      return(true);
     }

Asimismo, añadimos la animación para los nuevos objetos:

   void              OnTimer(void)
     {
...
      m_canvas.LightDirectionSet(light_direction);
      //--- órbita de la esfera
      DXMatrix translation;
      DXMatrixTranslation(translation,1.1f,0,0);
      DXMatrixRotationY(rotation,time);
      DXMatrix transform;
      DXMatrixMultiply(transform,translation,rotation);
      m_sphere.TransformMatrixSet(transform);
      //--- órbita del toro con rotación alrededor de su eje
      DXMatrixRotationX(rotation,time*1.3f);
      DXMatrixTranslation(translation,-2,0,0);
      DXMatrixMultiply(transform,rotation,translation);
      DXMatrixRotationY(rotation,time/1.3f);
      DXMatrixMultiply(transform,transform,rotation);
      m_torus.TransformMatrixSet(transform);           
      //--- recalculamos la escena 3D y dibujamos en el lienzo
      Redraw();
     }

Guardamos los cambios en "Three Objects.mq5" e iniciamos.

Figuras rotando en la órbita del cubo.

Figuras rotando en la órbita del cubo.


Superficie 3D basada en datos

Para crear informes y analizar datos, se suelen usar gráficos diversos: gráficos lineales, histogramas, diagramas circulares, etcétera. MQL5 también ofrece para dichos objetivos usar una cómoda biblioteca gráfica, pero esta puede dibujar solo gráficos planos.

La clase CDXSurface permite visualizar en el espacio una superficie basada en los datos de usuario guardados en una matriz bidimensional. Vamos a mostrar cómo se hace esto usando una función matemática

z=sin(2.0*pi*sqrt(x*x+y*y))

Creamos un objeto para dibujar la superficie, y una matriz para guardar los datos:

   virtual bool      Create(const int width,const int height)
     {
...
      //--- preparamos la matriz para el guardado de datos
      m_data_width=m_data_height=100;
      ArrayResize(m_data,m_data_width*m_data_height);
      for(int i=0;i<m_data_width*m_data_height;i++)
         m_data[i]=0.0;
      //--- creamos el objeto de superficie
      if(!m_surface.Create(m_canvas.DXDispatcher(),m_canvas.InputScene(),m_data,m_data_width,m_data_height,2.0f,
                           DXVector3(-2.0,-0.5,-2.0),DXVector3(2.0,0.5,2.0),DXVector2(0.25,0.25),
                           CDXSurface::SF_TWO_SIDED|CDXSurface::SF_USE_NORMALS,CDXSurface::CS_COLD_TO_HOT))
        {
         m_canvas.Destroy();
         return(false);
        }
      //--- establecemos la textura y el reflejo para la superficie
      m_surface.SpecularColorSet(DXColor(1.0,1.0,1.0,1.0));
      m_surface.TextureSet(m_canvas.DXDispatcher(),"checker.bmp");
      //--- añadimos la superficie a la escena
      m_canvas.ObjectAdd(&m_surface);
      //--- succeed
      return(true);
     }

La superficie se dibujará dentro de un paralelepípedo con una base de 4x4 y una altura de 1. El tamaño de la textura será de 0.25x0.25.

  • SF_TWO_SIDED indica que la superficie se dibujará tanto por arriba como por abajo, en el caso de que la cámara se mueva por debajo de la superficie.
  • SF_USE_NORMALS indica que se usarán los cálculos de los vectores normales para calcular los reflejos sobre la superficie provocados por una fuente de luz directa.
  • CS_COLD_TO_HOT establece el mapa de color de la superficie, yendo del color azul al rojo con transiciones de verde y amarillo.

Para animar la superficie, vamos a añadir la hora bajo el signo del seno, actualizándola después según el temporizador.

   void              OnTimer(void)
     {
      static ulong last_time=0;
      static float time=0;
      //--- obtenemos la hora actual
      ulong current_time=GetMicrosecondCount();
      //--- calculamos delta
      float deltatime=(current_time-last_time)/1000000.0f;
      if(deltatime>0.1f)
         deltatime=0.1f;
      //--- aumentamos el valor del tiempo transcurrido
      time+=deltatime;
      //--- guardamos la hora
      last_time=current_time;
      //--- calculamos los valores de la superficie teniendo en cuenta los cambios temporales
      for(int i=0; i<m_data_width; i++)
        {
         double x=2.0*i/m_data_width-1;
         int offset=m_data_height*i;
         for(int j=0; j<m_data_height; j++)
           {
            double y=2.0*j/m_data_height-1;
            m_data[offset+j]=MathSin(2.0*M_PI*sqrt(x*x+y*y)-2*time);
           }
        }
      //--- actualizamos los datos para el dibujado de la superficie
      if(m_surface.Update(m_data,m_data_width,m_data_height,2.0f,
                          DXVector3(-2.0,-0.5,-2.0),DXVector3(2.0,0.5,2.0),DXVector2(0.25,0.25),
                          CDXSurface::SF_TWO_SIDED|CDXSurface::SF_USE_NORMALS,CDXSurface::CS_COLD_TO_HOT))
        {
         //--- recalculamos la escena 3D y dibujamos en el lienzo
         Redraw();
        }
     }
El archivo fuente se encuentra en "3D Surface.mq5". En el vídeo se muestra un ejemplo del funcionamiento.




En este artículo, hemos mostrado cómo las funciones DirectX permiten crear figuras geométricas sencillas y gráficos 3D animados para analizar datos visualmente. Podrá encontrar ejemplos más complejos en la carpeta de instalación del terminal MetaTrader 5: los expertos "Correlation Matrix 3D" y "Math 3D Morpher", así como el script "Remnant 3D". 

Con la ayuda de MQL5, podemos solucionar varias tareas importantes de trading algorítmico sin recurrir a paquetes externos:

  • Optimizar estrategias comerciales complejas que contienen multitud de parámetros de entrada
  • Obtener los resultados de la optimización
  • Representar de una forma más conveniente en la pantalla los datos obtenidos usando imágenes en tres dimensiones.
Use la avanzada funcionalidad para visualizar datos bursátiles y desarrollar estrategias comerciales en MetaTrader 5, ¡ahora con gráficos en 3D!


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

Archivos adjuntos |
Step4_Box_Color.mq5 (15.08 KB)
Step6_Add_Light.mq5 (14.14 KB)
Step7_Animation.mq5 (18.59 KB)
Step9_Texture.mq5 (23.06 KB)
Three_Objects.mq5 (27.84 KB)
3D_Surface.mq5 (24.52 KB)
MQL5.zip (202.07 KB)
Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXXIII): Solicitudes comerciales pendientes - Cierre de posiciones según condiciones Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXXIII): Solicitudes comerciales pendientes - Cierre de posiciones según condiciones

Continuamos trabajando con la funcionalidad de la biblioteca para implementar el comercio con la ayuda de solicitudes pendientes. Ya hemos implementado el envío de solicitudes comerciales según condiciones para la apertura de posiciones y la colocación de órdenes pendientes. Hoy, implementaremos el cierre de posiciones completo, parcial o por opuesta, según condiciones.

Pronosticación de series temporales (Parte 1): el método de descomposición modal empírica (EMD) Pronosticación de series temporales (Parte 1): el método de descomposición modal empírica (EMD)

En el artículo se analiza la teoría y el uso práctico del algoritmo de pronosticación de series temporales usando como base la descomposición modal empírica, y se propone su implementación en MQL, además de presentarse indicadores de prueba y expertos.

Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXXIV): Solicitudes comerciales pendientes - Eliminación de órdenes y posiciones según condiciones Biblioteca para el desarrollo rápido y sencillo de programas para MetaTrader (Parte XXXIV): Solicitudes comerciales pendientes - Eliminación de órdenes y posiciones según condiciones

En el presente artículo, finalizaremos la descripción del concepto de trabajo con solicitudes pendientes y crearemos la funcionalidad para eliminar órdenes pendientes y posiciones según una condición. De esta forma, dispondremos de toda una funcionalidad con la que podremos crear estrategias de usuario sencillas, para ser más exactos, una cierta lógica de comportamiento que el asesor activará al cumplirse las condiciones establecidas por el usuario.

Trabajando con las funciones de red, o MySQL sin DLL: Parte II - El programa para monitorear los cambios de las propiedades de las señales Trabajando con las funciones de red, o MySQL sin DLL: Parte II - El programa para monitorear los cambios de las propiedades de las señales

En el artículo anterior, nos familiarizamos con la implementación del conector MySQL. En esta parte, vamos a analizar su aplicación usado como ejemplo la implementación del servicio de recopilación de las propiedades de las señales y el programa. Además, el ejemplo implementado puede tener un sentido práctico en el caso de que el usuario necesite observar los cambios de las propiedades que no se representan en la página web de la señal.