English Русский Deutsch 日本語 Português
preview
Patrones de diseño en MQL5 (Parte 2): Patrones estructurales

Patrones de diseño en MQL5 (Parte 2): Patrones estructurales

MetaTrader 5Trading | 15 abril 2024, 09:59
305 0
Mohamed Abdelmaaboud
Mohamed Abdelmaaboud

Introducción

Le presentamos un nuevo artículo dedicado a un aspecto importante del desarrollo de software: los patrones de diseño. En el artículo anterior, hablamos de los patrones de creación. Si no lo ha leído, le recomiendo empezar por ahí: Patrones de diseño en MQL5 (Parte I): Patrones de creación. El artículo anterior supone una introducción al tema de los patrones de diseño y habla en general de su utilidad en el desarrollo de software.

¿Quién debería estudiar los patrones de diseño? Cualquiera que desee desarrollar sus habilidades de programación y llevarlas al siguiente nivel. Estos patrones ofrecen un plan listo para resolver problemas específicos. Con ellos no tendrá que reinventar la rueda. Si utiliza estos patrones, obtendrá soluciones muy prácticas y comprobables.

En el presente artículo analizaremos los patrones de diseño estructurales. Asimismo, veremos cómo pueden resultar útiles a la hora de escribir programas y cómo ayudan a formar estructuras más amplias. Para ello, usaremos las clases existentes. Por supuesto, el objetivo principal de este artículo es mostrar cómo utilizar dichos patrones en la programación MQL5, cómo utilizarlos para desarrollar aplicaciones comerciales eficaces y cómo usarlos en el terminal MetaTrader 5.

Analizaremos los patrones de tipo estructural en las secciones siguientes:

Esperamos que el artículo le resulte útil y le ayude a mejorar sus habilidades de desarrollo y programación. Una vez más, permítanme recordarles que el tema de los patrones de diseño está estrechamente relacionado con la programación orientada a objetos, cuyos principios resultan bastante importantes para entender esta serie de artículos. Así que si necesita una introducción a la programación orientada a objetos, le recomiendo que lea uno de mis artículos anteriores dedicado ello: "Programación orientada a objetos (POO) en MQL5".

¡Atención! El contenido de este artículo se facilita "tal cual", tiene fines exclusivamente ilustrativos y no constituye una recomendación comercial. El artículo no ofrece ninguna garantía de resultados. El lector deberá entender que cualquier cosa que ponga en práctica usando como base este artículo, se realizará por su propia cuenta y riesgo: el autor no garantiza ningún resultado.


Patrones estructurales

En esta parte, nos familiarizaremos con los patrones de diseño estructurales, sus tipos y estructuras. Los patrones estructurales se refieren a un método de estructuración de clases y objetos que servirán como componentes para crear estructuras mayores. Estos patrones se usan para componer interfaces e implementaciones, utilizando fundamentalmente el concepto de herencia. El concepto de herencia significa que tendremos una clase que posee o combina las propiedades de sus clases padre. Este patrón resultará especialmente útil cuando haya que organizar el trabajo conjunto de varias clases desarrolladas de forma independiente.

Tipos de patrones estructurales:

  • Adaptador - permite obtener la interfaz que esperan los clientes transformando la interfaz de la clase.
  • Puente - permite separar una abstracción y su implementación para que puedan cambiar independientemente.
  • Compositor - combina objetos en una estructura de árbol para representar una jerarquía de lo particular a lo global. Además, el compositor permite a los clientes tratar de la misma manera los objetos individuales y sus composiciones.
  • Decorador - se puede utilizar para adjuntar dinámicamente un comportamiento adicional a los objetos actuales, y como alternativa flexible a la práctica de crear subclases para ampliar la funcionalidad.
  • Fachada - ofrece una interfaz unificada para un conjunto de interfaces de un subsistema usando la definición de un único punto de interacción con el subsistema.
  • Peso mosca - se utiliza para reducir costes al trabajar con un gran número de objetos pequeños.
  • Sustituto (Proxy) - ofrece un objeto que controla el acceso a otro objeto interceptando todas las llamadas. 

Al analizar estos patrones, intentaremos responder a las siguientes preguntas para cada uno de ellos:

  • ¿Qué hace el patrón?
  • ¿Qué problema resuelve el patrón?
  • ¿Cómo usarlo en MQL5?


Adaptador (Adapter)

Trataremos los tipos de patrones de diseño estructurales desde el primero, el adaptador. La palabra clave para entender este modelo es adaptabilidad. En pocas palabras, si tenemos una interfaz que se puede usar en determinadas condiciones, y luego hay actualizaciones de esas condiciones, deberemos hacer actualizaciones de la interfaz para que el código pueda adaptarse y funcionar eficazmente en esas nuevas condiciones. Eso es exactamente lo que hace un patrón: convierte la interfaz de nuestra clase en otra que el cliente puede usar. Así, el patrón adaptador permite que las clases funcionen juntas si las interfaces son incompatibles. El patrón también se llama envolvente (wrapper) porque crea una clase de envoltorio con la interfaz necesaria.

¿Qué hace el patrón?

Podemos utilizar un patrón cuando la interfaz diseñada no cumple los requisitos de la interfaz de la aplicación para un tema concreto. El patrón convierte una interfaz de esta clase en otra para permitir que las clases funcionen juntas.

Los siguientes esquemas muestran la estructura del patrón Adaptador:

Adapter1

Adapter1

Como podemos ver en los esquemas, gracias al soporte de herencia múltiple, tenemos un adaptador de clase y un adaptador de objeto. También existe un objetivo que identifica una nueva interfaz específica del área temática que utiliza el cliente. Además, tenemos un cliente que participa con objetos adaptables a la interfaz de destino, un objeto adaptable que representa una interfaz heredada existente que debe hacerse adaptable, y un adaptador que hace que la interfaz del objeto adaptable sea adaptable a la interfaz de destino.

¿Qué problema resuelve el patrón?

  • El uso de una clase existente con una interfaz que no coincide con la interfaz requerida.
  • La creación de clases reutilizables que puedan funcionar con clases no relacionadas, independientemente de si esas clases tienen interfaces compatibles o incompatibles.
  • La adaptación de la interfaz de una clase padre cuando se desea utilizar muchas subclases existentes.

¿Cómo usarlo en MQL5?

Veamos ahora como podemos usar este patrón (AdapterClass y ObjectClass) en MQL5. Vamos a desglosar el código paso a paso:

Utilizaremos una función de espacio de nombres y declararemos un área (AdapterClass) en la que definimos las funciones, variables y clases.

namespace AdapterClass

Utilizaremos la función de interfaz para declarar Target. Haciéndolo, definiremos una funcionalidad específica que puede ser implementada posteriormente por la clase.

interface Target
  {
   void Request();
  };

Utilizaremos la función class para definir una interfaz de la clase Adaptee, que definirá una interfaz existente que se adaptará utilizando un único miembro público (SpecificRequest()).

class Adaptee
  {
public:
   void              SpecificRequest();
  };

 Luego mostraremos un mensaje cuando la solicitud sea ejecutada por la interfaz de la clase Adaptee

void Adaptee::SpecificRequest(void)
  {
   Print("A specific request is executing by the Adaptee");
  }

Después declararemos una clase Adapter que adaptará la interfaz Adaptee del objeto Adaptee a las interfaces de objetivo Target que se heredarán del objeto Target y Adaptee como herencia múltiple.

class Adapter;
class AdapterAsTarget:public Target
  {
public:
   Adapter*          asAdaptee;
   void              Request();
  };
void AdapterAsTarget::Request()
  {
   printf("The Adapter requested Operation");
   asAdaptee.SpecificRequest();
  }
class Adapter:public Adaptee
  {
public:
   AdapterAsTarget*  asTarget;
                     Adapter();
                    ~Adapter();
  };
void Adapter::Adapter(void)
  {
   asTarget=new AdapterAsTarget;
   asTarget.asAdaptee=&this;
  }
void Adapter::~Adapter(void)
  {
   delete asTarget;
  }

Declaración de la clase de cliente Client

class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output()
  {
   return __FUNCTION__;
  }

Inicio del cliente

void Client::Run()
  {
   Adapter adapter;
   Target* target=adapter.asTarget;
   target.Request();
  }

A continuación le mostramos el código MQL5 completo de la clase Adapter en un solo bloque.

namespace AdapterClass
{
interface Target
  {
   void Request();
  };
class Adaptee
  {
public:
   void              SpecificRequest();
  };
void Adaptee::SpecificRequest(void)
  {
   Print("A specific request is executing by the Adaptee");
  }
class Adapter;
class AdapterAsTarget:public Target
  {
public:
   Adapter*          asAdaptee;
   void              Request();
  };
void AdapterAsTarget::Request()
  {
   printf("The Adapter requested Operation");
   asAdaptee.SpecificRequest();
  }
class Adapter:public Adaptee
  {
public:
   AdapterAsTarget*  asTarget;
                     Adapter();
                    ~Adapter();
  };
void Adapter::Adapter(void)
  {
   asTarget=new AdapterAsTarget;
   asTarget.asAdaptee=&this;
  }
void Adapter::~Adapter(void)
  {
   delete asTarget;
  }
class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output()
  {
   return __FUNCTION__;
  }
void Client::Run()
  {
   Adapter adapter;
   Target* target=adapter.asTarget;
   target.Request();
  }
}

Más abajo podrá ver cómo utilizar el adaptador de objetos en MQL5:

Utilizaremos la función namespace, y declararemos el área en la que definiremos las funciones, variables y clases de AdapterObject.

namespace AdapterObject

Luego usaremos la interfaz para definir Target (el objeto objetivo de la interfaz).

interface Target
  {
   void Request();
  };

Ahora crearemos una clase Adaptee para definir la interfaz existente que se va a adaptar.

class Adaptee
  {
public:
   void              SpecificRequest();
  };
void Adaptee::SpecificRequest(void)
  {
   Print("The specific Request");
  }
class Adapter:public Target
  {
public:
   void              Request();
protected:
   Adaptee           adaptee;
  };
void Adapter::Request(void)
  {
   Print("The request of Operation requested");
   adaptee.SpecificRequest();
  }

Declaración de la clase de cliente Client

class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output()
  {
   return __FUNCTION__;
  }

Inicio del cliente cuando los clientes inician operaciones en un ejemplar de adaptador

void Client::Run()
  {
   Target* target=new Adapter;
   target.Request();
   delete target;
  }

El código completo será el siguiente:

namespace AdapterObject
{
interface Target
  {
   void Request();
  };
class Adaptee
  {
public:
   void              SpecificRequest();
  };
void Adaptee::SpecificRequest(void)
  {
   Print("The specific Request");
  }
class Adapter:public Target
  {
public:
   void              Request();
protected:
   Adaptee           adaptee;
  };
void Adapter::Request(void)
  {
   Print("The request of Operation requested");
   adaptee.SpecificRequest();
  }
class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output()
  {
   return __FUNCTION__;
  }
void Client::Run()
  {
   Target* target=new Adapter;
   target.Request();
   delete target;
  }
}


Puente (Bridge)

En esta parte, analizaremos otro patrón de diseño estructural, el Puente (Bridge). La idea principal de uso de este patrón consiste en separar la abstracción de sus implementaciones para evitar futuros conflictos que puedan surgir si se dan actualizaciones o cambios en alguna de ellas. También se denomina Handle o Body.

¿Qué hace el patrón?

El patrón Puente se usa cuando hay una abstracción que tiene muchas implementaciones posibles. En lugar de utilizar la herencia normal, que siempre vincula una implementación a una abstracción, podemos utilizar este patrón y separar la abstracción de sus implementaciones para evitar el problema en caso de cambios o actualizaciones. Esta separación puede ayudar a crear un código limpio que pueda reutilizarse, ampliarse y probarse cómodamente.

La estructura del patrón Puente se muestra en el siguiente esquema:

Puente (Bridge)

El esquema Puente mostrado anteriormente tiene los siguientes elementos:

  • Abstraction - abstracción, define una interfaz de abstracción y mantiene una referencia al objeto implementador.
  • RefinedAbstraction - extiende la interfaz de abstracción.
  • Implementor - implementación, identifica la interfaz de las clases de implementación.
  • ConcreteImplementor - implementa una interfaz de desarrollador e identifica una implementación específica de esta.

¿Qué problema resuelve el patrón?

El patrón puente puede utilizarse siempre que lo necesitemos:

  • Alejarnos del vínculo constante entre la abstracción y su implementación (separarlas utilizando un patrón).
  • Combinar diferentes abstracciones e implementaciones y ampliar cada una de ellas de manera independiente y sin conflictos.
  • Evitar el impacto en los clientes al cambiar la implementación de la abstracción.
  • Ocultar por completo la implementación de la abstracción a los clientes.

¿Cómo usarlo en MQL5?

En esta parte, veremos cómo utilizar este patrón en MQL5 y crear aplicaciones más eficientes con su ayuda en términos de escritura de código. A continuación podemos ver como implementar la estructura del patrón Bridge en el código MQL5:

En primer lugar, crearemos un área de declaración para definir las variables, funciones y clases del patrón.

namespace Bridge

Después, crearemos la interfaz Implementor; aquí utilizaremos la palabra clave interface para definir la funcionalidad implementada por la clase.

interface Implementor
  {
   void OperationImp();
  };

En el siguiente fragmento, crearemos una clase de abstracción con miembros abiertos y protegidos y almacenaremos una referencia (reference) al objeto desarrollador.

class Abstraction
  {
public:
   virtual void      Operation();
                     Abstraction(Implementor*);
                     Abstraction();
                    ~Abstraction();
protected:
   Implementor*      implementor;
  };
void Abstraction::Abstraction(void) {}
void Abstraction::Abstraction(Implementor*i):implementor(i) {}
void Abstraction::~Abstraction()
  {
   delete implementor;
  }
void Abstraction::Operation()
  {
   implementor.OperationImp();
  }

A continuación crearemos la clase RefinedAbstraction; en este ejemplo será un participante

class RefinedAbstraction:public Abstraction
  {
public:
                     RefinedAbstraction(Implementor*);
   void              Operation();
  };
void RefinedAbstraction::RefinedAbstraction(Implementor*i):Abstraction(i) {}
void RefinedAbstraction::Operation()
  {
   Abstraction::Operation();
  }

Crearemos las clases ConcreteImplementorA y ConcreteImplementorB

class ConcreteImplementorA:public Implementor
  {
public:
   void              OperationImp();
  };
void ConcreteImplementorA::OperationImp(void)
  {
   Print("The implementor A");
  }
class ConcreteImplementorB:public Implementor
  {
public:
   void              OperationImp();
  };
void ConcreteImplementorB::OperationImp(void)
  {
   Print("The implementor B");
  }

Luego crearemos la clase de cliente Client

class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }

Inicio del cliente

void Client::Run(void)
  {
   Abstraction* abstraction;
   abstraction=new RefinedAbstraction(new ConcreteImplementorA);
   abstraction.Operation();
   delete abstraction;
   abstraction=new RefinedAbstraction(new ConcreteImplementorB);
   abstraction.Operation();
   delete abstraction;
  }

A continuación le mostramos el código de la estructura del patrón Bridge en su totalidad en un solo bloque.

namespace Bridge
{
interface Implementor
  {
   void OperationImp();
  };
class Abstraction
  {
public:
   virtual void      Operation();
                     Abstraction(Implementor*);
                     Abstraction();
                    ~Abstraction();
protected:
   Implementor*      implementor;
  };
void Abstraction::Abstraction(void) {}
void Abstraction::Abstraction(Implementor*i):implementor(i) {}
void Abstraction::~Abstraction()
  {
   delete implementor;
  }
void Abstraction::Operation()
  {
   implementor.OperationImp();
  }
class RefinedAbstraction:public Abstraction
  {
public:
                     RefinedAbstraction(Implementor*);
   void              Operation();
  };
void RefinedAbstraction::RefinedAbstraction(Implementor*i):Abstraction(i) {}
void RefinedAbstraction::Operation()
  {
   Abstraction::Operation();
  }
class ConcreteImplementorA:public Implementor
  {
public:
   void              OperationImp();
  };
void ConcreteImplementorA::OperationImp(void)
  {
   Print("The implementor A");
  }
class ConcreteImplementorB:public Implementor
  {
public:
   void              OperationImp();
  };
void ConcreteImplementorB::OperationImp(void)
  {
   Print("The implementor B");
  }
class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }
void Client::Run(void)
  {
   Abstraction* abstraction;
   abstraction=new RefinedAbstraction(new ConcreteImplementorA);
   abstraction.Operation();
   delete abstraction;
   abstraction=new RefinedAbstraction(new ConcreteImplementorB);
   abstraction.Operation();
   delete abstraction;
  }
}


Compositor (Composite)

El Compositor (Composite) es otro tipo de patrón de diseño estructural. Este patrón permite combinar objetos en una estructura similar a la de un árbol y, de esta forma, proporcionar un procesamiento uniforme por parte del cliente de objetos y composiciones individuales.

¿Qué hace el patrón?

El patrón Compositor/Composite se utiliza cuando necesitamos componer objetos en estructuras de árbol. Por ello, el árbol será el elemento principal de este patrón. Dicho esto, si tenemos un componente que se representa como una estructura de árbol, tendremos dos elementos de ese componente en el patrón: la hoja Leaf, que en este caso solo realizará operaciones, y el componente del propio compositor Composite, que realizará un conjunto de operaciones como añadir, eliminar y llamar a un elemento hijo.

A continuación le mostramos un esquema de la estructura del patrón de diseño "Compositor":

Compositor

Vamos a considerar los elementos presentes en el esquema anterior:

  • Component - componente; declara una interfaz de objeto e implementa el comportamiento de interfaz por defecto para las clases, y posibilita el acceso y la gestión de los componentes de interfaz declarados para él.
  • Leaf - hoja; representa los objetos de hoja en la composición, y esta hoja no tiene hijos, define el comportamiento de los objetos que pueden considerarse primitivos en la composición.
  • Compositor - el compositor identifica el comportamiento de los componentes con los elementos hijos, almacena estos elementos hijos de los componentes e implementa las operaciones de los elementos hijos en la interfaz de los componentes.
  • Cliente - a través de la interfaz del componente, el cliente manipula los objetos.

¿Qué problema resuelve el patrón?

Este patrón Composite puede utilizarse siempre que lo necesitemos:

  • Representación de los objetos en una jerarquía de lo particular a lo global.
  • Garantizar el mismo procesamiento del cliente para todos los objetos de la composición.

¿Cómo usarlo en MQL5?

Ahora veremos cómo se puede utilizar el patrón de compositor en MQL5. Vamos a desglosar el código paso a paso:

Luego crearemos un área Composite donde declararemos todas las funciones, variables y clases. Crearemos el área utilizando la palabra clave namespace.

namespace Composite

Crearemos la clase Component con miembros públicos y protegidos y acceso al elemento padre del componente.

class Component
  {
public:
   virtual void      Operation(void)=0;
   virtual void      Add(Component*)=0;
   virtual void      Remove(Component*)=0;
   virtual Component*   GetChild(int)=0;
                     Component(void);
                     Component(string);
protected:
   string            name;
  };
Component::Component(void) {}
Component::Component(string a_name):name(a_name) {}

A continuación en el fragmento, hemos decidido mostrar un error de usuario que se produce al añadir a una hoja y eliminar de la misma, o al crear una clase Leaf

#define ERR_INVALID_OPERATION_EXCEPTION   1
class Leaf:public Component
  {
public:
   void              Operation(void);
   void              Add(Component*);
   void              Remove(Component*);
   Component*        GetChild(int);
                     Leaf(string);
  };
void Leaf::Leaf(string a_name):Component(a_name) {}
void Leaf::Operation(void)
  {
   Print(name);
  }
void Leaf::Add(Component*)
  {
   SetUserError(ERR_INVALID_OPERATION_EXCEPTION);
  }
void Leaf::Remove(Component*)
  {
   SetUserError(ERR_INVALID_OPERATION_EXCEPTION);
  }
Component* Leaf::GetChild(int)
  {
   SetUserError(ERR_INVALID_OPERATION_EXCEPTION);
   return NULL;
  }

Creación de una clase Composite como participante. Trabajo, adición, eliminación de componentes y GetChild(int)

class Composite:public Component
  {
public:
   void              Operation(void);
   void              Add(Component*);
   void              Remove(Component*);
   Component*        GetChild(int);
                     Composite(string);
                    ~Composite(void);
protected:
   Component*        nodes[];
  };
Composite::Composite(string a_name):Component(a_name) {}
Composite::~Composite(void)
  {
   int total=ArraySize(nodes);
   for(int i=0; i if (CheckPointer(i_node)==1)
        {
         delete i_node;
        }
     }
  }
void Composite::Operation(void)
  {
   Print(name);
   int total=ArraySize(nodes);
   for(int i=0; i void  Composite::Add(Component *src)
  {
   int size=ArraySize(nodes);
   ArrayResize(nodes,size+1);
   nodes[size]=src;
  }
void Composite::Remove(Component *src)
  {
   int find=-1;
   int total=ArraySize(nodes);
   for(int i=0; i if (nodes[i]==src)
        {
         find=i;
         break;
        }
     }
   if(find>-1)
     {
      ArrayRemove(nodes,find,1);
     }
  }
Component* Composite::GetChild(int i)
  {
   return nodes[i];
  }

Creación de la clase Client como participante

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}

Inicio del cliente

void Client::Run(void)
  {
   Component* root=new Composite("root");
   Component* branch1=new Composite("The branch 1");
   Component* branch2=new Composite("The branch 2");
   Component* leaf1=new Leaf("The leaf 1");
   Component* leaf2=new Leaf("The leaf 2");
   root.Add(branch1);
   root.Add(branch2);
   branch1.Add(leaf1);
   branch1.Add(leaf2);
   branch2.Add(leaf2);
   branch2.Add(new Leaf("The leaf 3"));
   Print("The tree");
   root.Operation();
   root.Remove(branch1); 
   Print("Removing one branch");
   root.Operation();
   delete root;
   delete branch1;
  }


Decorador (Decorator)

El patrón de diseño estructural Decorator permite generar estructuras más grandes para objetos creados o existentes. Este patrón puede usarse para añadir características, comportamientos o capacidades adicionales a un objeto en un método dinámico en el tiempo de ejecución. Así, el patrón ofrece una alternativa flexible a la creación de subclases. La clase de este patrón también se conoce como Wrapper.

¿Qué hace el patrón?

Por ello, el patrón permite añadir funciones a cualquier objeto individual sin hacerlo en toda la clase. Esto se hace usando un envoltorio en lugar de utilizar el método de creación de subclases.

A continuación le mostramos un esquema de la estructura del patrón de diseño "Decorador":

Decorador

El esquema muestra que en este patrón intervienen los siguientes elementos:

  • Component - define la interfaz de los objetos, y también que poseen funciones adicionales de forma dinámica.
  • ConcreteComponent - determina qué objeto puede imponer responsabilidades adicionales.
  • Decorator - permite almacenar una referencia a un objeto de componente y definir una interfaz que se corresponda con la interfaz del componente.
  • ConcreteDecorator - es responsable de añadir funciones al componente.

¿Qué problema resuelve el patrón?

El patrón Decorador puede utilizarse siempre que necesitemos:

  • Añadir de forma dinámica y transparente responsabilidades adicionales a objetos individuales sin que ello afecte a otros objetos.
  • Quitar responsabilidades a los objetos.
  • Cuando utilizar el método de creación de subclases resulta poco práctico para la extensión.

¿Cómo usarlo en MQL5?

Para implementar el patrón Decorator en código MQL5 y luego utilizarlo para escribir programas, realizaremos los siguientes pasos:

Una vez más, empezaremos creando el área de declaración de nuestro Decorador, dentro de la cual declararemos todo lo que necesitemos.

namespace Decorator

Crearemos una clase Component con un miembro público para definir la interfaz de los objetos.

class Component
  {
public:
   virtual void      Operation(void)=0;
  };

Luego crearemos la clase Decorator como participante

class Decorator:public Component
  {
public:
   Component*        component;
   void              Operation(void);
  };
void Decorator::Operation(void)
  {
   if(CheckPointer(component)>0)
     {
      component.Operation();
     }
  }

Y crearemos la clase ConcreteComponent como participante

class ConcreteComponent:public Component
  {
public:
   void              Operation(void);
  };
void ConcreteComponent::Operation(void)
  {
   Print("The concrete operation");
  }

Creación de ConcreteDecoratorA y ConcreteDecoratorB

class ConcreteDecoratorA:public Decorator
  {
protected:
   string            added_state;
public:
                     ConcreteDecoratorA(void);
   void              Operation(void);
  };
ConcreteDecoratorA::ConcreteDecoratorA(void):
   added_state("The added state()")
  {
  }
void ConcreteDecoratorA::Operation(void)
  {
   Decorator::Operation();
   Print(added_state);
  }
class ConcreteDecoratorB:public Decorator
  {
public:
   void              AddedBehavior(void);
   void              Operation(void);
  };
void ConcreteDecoratorB::AddedBehavior(void)
  {
   Print("The added behavior()");
  }
void ConcreteDecoratorB::Operation(void)
  {
   Decorator::Operation();
   AddedBehavior();
  }

Luego crearemos la clase de cliente Client

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }

Inicio del cliente

void Client::Run(void)
  {
   Component* component=new ConcreteComponent();
   Decorator* decorator_a=new ConcreteDecoratorA();
   Decorator* decorator_b=new ConcreteDecoratorB();
   decorator_a.component=component;
   decorator_b.component=decorator_a;
   decorator_b.Operation();
   delete component;
   delete decorator_a;
   delete decorator_b;
  }

A continuación hemos reunido todo el código del patrón en su totalidad en un solo bloque

namespace Decorator
{
class Component
  {
public:
   virtual void      Operation(void)=0;
  };
class Decorator:public Component
  {
public:
   Component*        component;
   void              Operation(void);
  };
void Decorator::Operation(void)
  {
   if(CheckPointer(component)>0)
     {
      component.Operation();
     }
  }
class ConcreteComponent:public Component
  {
public:
   void              Operation(void);
  };
void ConcreteComponent::Operation(void)
  {
   Print("The concrete operation");
  }
class ConcreteDecoratorA:public Decorator
  {
protected:
   string            added_state;
public:
                     ConcreteDecoratorA(void);
   void              Operation(void);
  };
ConcreteDecoratorA::ConcreteDecoratorA(void):
   added_state("The added state()")
  {
  }
void ConcreteDecoratorA::Operation(void)
  {
   Decorator::Operation();
   Print(added_state);
  }
class ConcreteDecoratorB:public Decorator
  {
public:
   void              AddedBehavior(void);
   void              Operation(void);
  };
void ConcreteDecoratorB::AddedBehavior(void)
  {
   Print("The added behavior()");
  }
void ConcreteDecoratorB::Operation(void)
  {
   Decorator::Operation();
   AddedBehavior();
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }
void Client::Run(void)
  {
   Component* component=new ConcreteComponent();
   Decorator* decorator_a=new ConcreteDecoratorA();
   Decorator* decorator_b=new ConcreteDecoratorB();
   decorator_a.component=component;
   decorator_b.component=decorator_a;
   decorator_b.Operation();
   delete component;
   delete decorator_a;
   delete decorator_b;
  }
}


Fachada (Facade)

Facade es otro patrón estructural que puede utilizarse en el desarrollo de software para crear otras estructuras mayores. Identifica una interfaz de nivel superior que facilita y agiliza el uso de los subsistemas.

¿Qué hace el patrón?

El uso del patrón Facade puede considerarse una forma de ocultar al Cliente la complejidad del subsistema, ya que proporciona una interfaz unificada para un conjunto de interfaces del subsistema. Así, el cliente interactuará con esta interfaz unificada para obtener lo que solicita, y sin embargo esta misma interfaz interactuará con el subsistema para retornar lo que el cliente ha solicitado.

Echemos un vistazo al esquema que muestra la estructura del funcionamiento del patrón de diseño Facade:

Fachada (Facade)

Como podemos ver en el esquema, la estructura del patrón consta de los siguientes elementos:

  • Facade - sabe qué subsistema puede solicitar, y delega las solicitudes de los clientes a los objetos del subsistema correspondientes.
  • Clases de subsistema - realizan las funciones de un subsistema; cuando reciben una petición de Fachada la procesan, no tienen referencias a Fachada.

¿Qué problema resuelve el patrón?

El patrón de Fachada puede utilizarse cuando necesitamos:

  • Simplificar la complejidad del subsistema ofreciendo una interfaz sencilla.
  • Separar el subsistema de los clientes y de otros subsistemas para cambiar las dependencias existentes entre los clientes y las implementaciones de las clases de abstracción, al tiempo que el subsistema resulta independiente y portátil.
  • Identificar los puntos de entrada a cada nivel del subsistema representándolos jerárquicamente.

¿Cómo usarlo en MQL5?

Para implementar el patrón de Fachada en código MQL5 y luego utilizarlo para escribir programas, ejecutaremos los siguientes pasos:

Crearemos el espacio Facade para declarar todo lo que necesitamos.

namespace Facade

Declaramos las clases SubSystemA, SubSystemB y SubSystemC

class SubSystemA
  {
public:
   void              Operation(void);
  };
void SubSystemA::Operation(void)
  {
   Print("The operation of the subsystem A");
  }
class SubSystemB
  {
public:
   void              Operation(void);
  };
void SubSystemB::Operation(void)
  {
   Print("The operation of the subsystem B");
  }
class SubSystemC
  {
public:
   void              Operation(void);
  };
void SubSystemC::Operation(void)
  {
   Print("The operation of the subsystem C");
  }

Declaración de la clase de fachada Facade

class Facade
  {
public:
   void              Operation_A_B(void);
   void              Operation_B_C(void);
protected:
   SubSystemA        subsystem_a;
   SubSystemB        subsystem_b;
   SubSystemC        subsystem_c;
  };
void Facade::Operation_A_B(void)
  {
   Print("The facade of the operation of A & B");
   Print("The request of the facade of the subsystem A operation");
   subsystem_a.Operation();
   Print("The request of the facade of the subsystem B operation");
   subsystem_b.Operation();
  }
void Facade::Operation_B_C(void)
  {
   Print("The facade of the operation of B & C");
   Print("The request of the facade of the subsystem B operation");
   subsystem_b.Operation();
   Print("The request of the facade of the subsystem C operation");
   subsystem_c.Operation();
  }

Declaración de la clase de cliente Client

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }

Inicio del cliente

void Client::Run(void)
  {
   Facade facade;
   Print("The request of client of the facade operation A & B");
   facade.Operation_A_B();
   Print("The request of client of the facade operation B & C");
   facade.Operation_B_C();
  }

A continuación le mostramos todo el código en un solo bloque:

namespace Facade
{
class SubSystemA
  {
public:
   void              Operation(void);
  };
void SubSystemA::Operation(void)
  {
   Print("The operation of the subsystem A");
  }
class SubSystemB
  {
public:
   void              Operation(void);
  };
void SubSystemB::Operation(void)
  {
   Print("The operation of the subsystem B");
  }
class SubSystemC
  {
public:
   void              Operation(void);
  };
void SubSystemC::Operation(void)
  {
   Print("The operation of the subsystem C");
  }
class Facade
  {
public:
   void              Operation_A_B(void);
   void              Operation_B_C(void);
protected:
   SubSystemA        subsystem_a;
   SubSystemB        subsystem_b;
   SubSystemC        subsystem_c;
  };
void Facade::Operation_A_B(void)
  {
   Print("The facade of the operation of A & B");
   Print("The request of the facade of the subsystem A operation");
   subsystem_a.Operation();
   Print("The request of the facade of the subsystem B operation");
   subsystem_b.Operation();
  }
void Facade::Operation_B_C(void)
  {
   Print("The facade of the operation of B & C");
   Print("The request of the facade of the subsystem B operation");
   subsystem_b.Operation();
   Print("The request of the facade of the subsystem C operation");
   subsystem_c.Operation();
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }
void Client::Run(void)
  {
   Facade facade;
   Print("The request of client of the facade operation A & B");
   facade.Operation_A_B();
   Print("The request of client of the facade operation B & C");
   facade.Operation_B_C();
  }
}


Peso mosca (Flyweight)

El patrón estructural Peso Mosca (Flyweight) se utiliza para reducir costes cuando tratamos con un gran número de objetos pequeños, ya que para soportarlo luego se utiliza el uso conjunto.

¿Qué hace el patrón?

El uso conjunto para el soporte puede resultar útil desde el punto de vista de la memoria, por eso en el original se llama Flyweight (Peso Mosca).

La estructura del patrón Flyweight se muestra en el siguiente esquema:

Flyweight1

Flyweight2

Vamos a considerar los elementos presentes en el esquema anterior:

  • Flyweight.
  • ConcreteFlyweight.
  • UnsharedConcreteFlyweight.
  • FlyweightFactory.
  • Client.

¿Qué problema resuelve el patrón?

Este patrón puede utilizarse cuando:

  • En la aplicación se usa un gran número de objetos.
  • Necesitaremos reducir los altos costes de almacenamiento.
  • Gran parte del estado de un objeto puede implementarse como externo.
  • Si eliminamos el estado externo, muchos grupos de objetos podrán sustituirse por menos objetos comunes.
  • La identidad del objeto no resulta tan importante para la aplicación desde el punto de vista de la dependencia.

¿Cómo usarlo en MQL5?

Ahora vamos a escribir el código MQL5 de este patrón:

Comenzaremos creando el área de declaración de nuestro patrón Flyweight, dentro del cual declararemos todo lo que necesitemos.

namespace Flyweight

Para ello, utilizaremos la palabra clave interface y declararemos Flyweight

interface Flyweight;

Crearemos la clase Pair con miembros protegidos y públicos como participantes

class Pair
  {
protected:
   string            key;
   Flyweight*        value;
public:
                     Pair(void);
                     Pair(string,Flyweight*);
                    ~Pair(void);
   Flyweight*        Value(void);
   string            Key(void);
  };
Pair::Pair(void){}
Pair::Pair(string a_key,Flyweight *a_value):
   key(a_key),
   value(a_value){}
Pair::~Pair(void)
  {
   delete value;
  }
string Pair::Key(void)
  {
   return key;
  }
Flyweight* Pair::Value(void)
  {
   return value;
  }

Luego crearemos la clase Reference y definiremos su constructor y destructor.

class Reference
  {
protected:
   Pair*             pairs[];
public:
                     Reference(void);
                    ~Reference(void);
   void              Add(string,Flyweight*);
   bool              Has(string);
   Flyweight*        operator[](string);
protected:
   int               Find(string);
  };
Reference::Reference(void){}
Reference::~Reference(void)
  {
   int total=ArraySize(pairs);
   for(int i=0; i if (CheckPointer(ipair))
        {
         delete ipair;
        }
     }
  }
int Reference::Find(string key)
  {
   int total=ArraySize(pairs);
   for(int i=0; i if (ipair.Key()==key)
        {
         return i;
        }
     }
   return -1;
  }
bool Reference::Has(string key)
  {
   return (Find(key)>-1)?true:false;
  }
void Reference::Add(string key,Flyweight *value)
  {
   int size=ArraySize(pairs);
   ArrayResize(pairs,size+1);
   pairs[size]=new Pair(key,value);
  }
Flyweight* Reference::operator[](string key)
  {
   int find=Find(key);
   return (find>-1)?pairs[find].Value():NULL;
  }

Después declararemos una interfaz Flyweight para influir en el estado externo

interface Flyweight
  {
   void Operation(int extrinsic_state);
  };

A continuación, declararemos la clase ConcreteFlyweight

class ConcreteFlyweight:public Flyweight
  {
public:
   void              Operation(int extrinsic_state);
protected:
   int               intrinsic_state;
  };
void ConcreteFlyweight::Operation(int extrinsic_state)
  {
   intrinsic_state=extrinsic_state;
   printf("The intrinsic state - %d",intrinsic_state);
  }

Luego declararemos la clase UnsharedConcreteFlyweight

class UnsharedConcreteFlyweight:public Flyweight
  {
protected:
   int               all_state;
public:
   void              Operation(int extrinsic_state);
  };
void UnsharedConcreteFlyweight::Operation(int extrinsic_state)
  {
   all_state=extrinsic_state;
   Print("all state - %d",all_state);
  }

Y declararemos la clase FlyweightFactory

class FlyweightFactory
  {
protected:
   Reference        pool;
public:
                     FlyweightFactory(void);
   Flyweight*        Flyweight(string key);
  };
FlyweightFactory::FlyweightFactory(void)
  {
   pool.Add("1",new ConcreteFlyweight);
   pool.Add("2",new ConcreteFlyweight);
   pool.Add("3",new ConcreteFlyweight);
  }
Flyweight* FlyweightFactory::Flyweight(string key)
  {
   if(!pool.Has(key))
     {
      pool.Add(key,new ConcreteFlyweight());
     }
   return pool[key];
  }

Declaración de la clase de cliente Client

class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }

Inicio del cliente

void Client::Run(void)
  {
   int extrinsic_state=7;
   Flyweight* flyweight;
   FlyweightFactory factory;
   flyweight=factory.Flyweight("1");
   flyweight.Operation(extrinsic_state);
   flyweight=factory.Flyweight("10");
   flyweight.Operation(extrinsic_state);
   flyweight=new UnsharedConcreteFlyweight();
   flyweight.Operation(extrinsic_state);
   delete flyweight;
  }

A continuación le mostramos el código completo en un bloque:

namespace Flyweight
{
interface Flyweight;
class Pair
  {
protected:
   string            key;
   Flyweight*        value;
public:
                     Pair(void);
                     Pair(string,Flyweight*);
                    ~Pair(void);
   Flyweight*        Value(void);
   string            Key(void);
  };
Pair::Pair(void){}
Pair::Pair(string a_key,Flyweight *a_value):
   key(a_key),
   value(a_value){}
Pair::~Pair(void)
  {
   delete value;
  }
string Pair::Key(void)
  {
   return key;
  }
Flyweight* Pair::Value(void)
  {
   return value;
  }
class Reference
  {
protected:
   Pair*             pairs[];
public:
                     Reference(void);
                    ~Reference(void);
   void              Add(string,Flyweight*);
   bool              Has(string);
   Flyweight*        operator[](string);
protected:
   int               Find(string);
  };
Reference::Reference(void){}
Reference::~Reference(void)
  {
   int total=ArraySize(pairs);
   for(int i=0; i if (CheckPointer(ipair))
        {
         delete ipair;
        }
     }
  }
int Reference::Find(string key)
  {
   int total=ArraySize(pairs);
   for(int i=0; i if (ipair.Key()==key)
        {
         return i;
        }
     }
   return -1;
  }
bool Reference::Has(string key)
  {
   return (Find(key)>-1)?true:false;
  }
void Reference::Add(string key,Flyweight *value)
  {
   int size=ArraySize(pairs);
   ArrayResize(pairs,size+1);
   pairs[size]=new Pair(key,value);
  }
Flyweight* Reference::operator[](string key)
  {
   int find=Find(key);
   return (find>-1)?pairs[find].Value():NULL;
  }
interface Flyweight
  {
   void Operation(int extrinsic_state);
  };
class ConcreteFlyweight:public Flyweight
  {
public:
   void              Operation(int extrinsic_state);
protected:
   int               intrinsic_state;
  };
void ConcreteFlyweight::Operation(int extrinsic_state)
  {
   intrinsic_state=extrinsic_state;
   Print("The intrinsic state - %d",intrinsic_state);
  }
class UnsharedConcreteFlyweight:public Flyweight
  {
protected:
   int               all_state;
public:
   void              Operation(int extrinsic_state);
  };
void UnsharedConcreteFlyweight::Operation(int extrinsic_state)
  {
   all_state=extrinsic_state;
   Print("all state - %d",all_state);
  }
class FlyweightFactory
  {
protected:
   Reference        pool;
public:
                     FlyweightFactory(void);
   Flyweight*        Flyweight(string key);
  };
FlyweightFactory::FlyweightFactory(void)
  {
   pool.Add("1",new ConcreteFlyweight);
   pool.Add("2",new ConcreteFlyweight);
   pool.Add("3",new ConcreteFlyweight);
  }
Flyweight* FlyweightFactory::Flyweight(string key)
  {
   if(!pool.Has(key))
     {
      pool.Add(key,new ConcreteFlyweight());
     }
   return pool[key];
  }
class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }
void Client::Run(void)
  {
   int extrinsic_state=7;
   Flyweight* flyweight;
   FlyweightFactory factory;
   flyweight=factory.Flyweight("1");
   flyweight.Operation(extrinsic_state);
   flyweight=factory.Flyweight("10");
   flyweight.Operation(extrinsic_state);
   flyweight=new UnsharedConcreteFlyweight();
   flyweight.Operation(extrinsic_state);
   delete flyweight;
  }
}


Proxy

Hemos llegado al último patrón de los tipos de patrones de diseño estructural, el patrón Proxy. Este patrón tiene muchos tipos, desde el punto de vista de la representatividad. En general, podemos decir que Proxy puede ser utilizado para presentar una alternativa o sustituto de otro objeto para el control completo en cuanto al acceso a ese objeto. La clase de este patrón también se conoce como Surrogate.

¿Qué hace el patrón?

Este patrón proporciona un sustituto para controlar el acceso a un objeto.

A continuación le mostramos un esquema de la estructura del patrón de diseño "Proxy":

Proxy

Vamos a considerar los elementos presentes en el esquema anterior:

  • Proxy.
  • Subject.
  • Real subject.

¿Qué problema resuelve el patrón?

El patrón Proxy es adecuado para las siguientes situaciones:
  • Si necesitamos un representante local de un objeto en otro espacio de direcciones, podemos utilizar un Proxy remoto que lo represente.
  • Si necesitamos un objeto muy exigente y costoso, podemos utilizar un proxy virtual que cree estos objetos.
  • Si necesitamos controlar el acceso al objeto principal o fuente, podemos utilizar un proxy de seguridad.
  • Si necesitamos sustituir un puntero simple, podemos utilizar una referencia inteligente.

¿Cómo usarlo en MQL5?

Para implementar el patrón Proxy en código MQL5 y luego utilizarlo para escribir programas, realizaremos los siguientes pasos:

Primero declararemos el espacio Proxy para declarar todo lo que necesitemos en él en cuanto a variables, funciones, clases, etc.

namespace Proxy

Luego declararemos la clase de sujeto como participante

class Subject
  {
public:
   virtual void      Request(void)=0;
  };

Creación de la clase RealSubject

class RealSubject:public Subject
  {
public:
   void              Request(void);
  };
void RealSubject::Request(void)
  {
   Print("The real subject");
  }

Creación de una clase Proxy como participante

class Proxy:public Subject
  {
protected:
   RealSubject*      real_subject;
public:
                    ~Proxy(void);
   void              Request(void);
  };
Proxy::~Proxy(void)
  {
   delete real_subject;
  }
void Proxy::Request(void)
  {
   if(!CheckPointer(real_subject))
     {
      real_subject=new RealSubject;
     }
   real_subject.Request();
  }

Declaración de la clase de cliente Client

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }

Inicio del cliente

void Client::Run(void)
  {
   Subject* subject=new Proxy;
   subject.Request();
   delete subject;
  }

A continuación le mostramos el código completo en un bloque:

namespace Proxy
{
class Subject
  {
public:
   virtual void      Request(void)=0;
  };
class RealSubject:public Subject
  {
public:
   void              Request(void);
  };
void RealSubject::Request(void)
  {
   Print("The real subject");
  }
class Proxy:public Subject
  {
protected:
   RealSubject*      real_subject;
public:
                    ~Proxy(void);
   void              Request(void);
  };
Proxy::~Proxy(void)
  {
   delete real_subject;
  }
void Proxy::Request(void)
  {
   if(!CheckPointer(real_subject))
     {
      real_subject=new RealSubject;
     }
   real_subject.Request();
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }
void Client::Run(void)
  {
   Subject* subject=new Proxy;
   subject.Request();
   delete subject;
  }
}


Conclusión

En este artículo, hemos expuesto una sencilla introducción al tema de los patrones de diseño estructurales. Asimismo, hemos definido los tipos de patrones estructurales y hemos aprendido a escribir código limpio que pueda reutilizarse, ampliarse y probarse repetidamente. Para cada patrón, hemos entendido los puntos principales: la esencia del patrón, el propósito, la estructura y qué problemas de diseño resuelve.

Además, hemos considerado los siguientes patrones de diseño estructurales:

  • Adaptador (Adapter)
  • Puente (Bridge)
  • Compositor (Composite)
  • Decorador (Decorator)
  • Fachada (Facade)
  • Peso mosca (Flyweight)
  • Proxy

Como ya hemos comentado en la Parte 1, los desarrolladores se beneficiarán de los conocimientos sobre los patrones de diseño, ya que ahorran mucho tiempo y eliminan la necesidad de reinventar la rueda. Para ello, estos usan soluciones predefinidas, probadas y prácticas para problemas concretos. Con un conocimiento suficiente de la programación orientada a objetos, podemos empezar a trabajar eficazmente con patrones de diseño.

Para aprender aún más sobre el tema de los patrones, le recomiendo leer los siguientes materiales:

  • Design Patterns - Elements of Reusable Object-Oriented Software by Eric Gamma, Richard Helm, Ralph Johnson, and John Vlissides
  • Design Patterns for Dummies by Steve Holzner
  • Head First Design Patterns by Eric Freeman, Elisabeth Robson, Bert Bates, and Kathy Sierra

Espero que este artículo le haya resultado útil y haya encontrado algo nuevo para usted en el campo de la escritura de software. Quizá estos conocimientos le ayuden a crear programas más eficaces en MQL5. Si le ha gustado este artículo, lea también el resto de contenidos. Los enlaces siguientes le llevarán a mis otros artículos. Concretamente, encontrará una serie de artículos sobre la creación de sistemas comerciales basados en los indicadores técnicos más populares como RSI, MACD, Bollinger Bands, Moving averages, Stochastics y otros. Espero que le resulten útiles y le ayuden a llevar su desarrollo de software y su comercio al siguiente nivel.

Traducción del inglés realizada por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/en/articles/13724

Archivos adjuntos |
Adapter_Class.mqh (1.07 KB)
Adapter_Object.mqh (0.72 KB)
Bridge.mqh (3.38 KB)
Composite.mqh (3.09 KB)
Decorator.mqh (3.53 KB)
Facade.mqh (3.43 KB)
Flyweight.mqh (3.47 KB)
Proxy.mqh (1.79 KB)
Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 4): Media móvil triangular - Señales del indicador Creamos un asesor multidivisa sencillo utilizando MQL5 (Parte 4): Media móvil triangular - Señales del indicador
Por asesor multidivisa en este artículo entendemos un asesor, o un robot comercial que puede operar (abrir/cerrar órdenes, gestionar órdenes como Trailing Stop Loss y Trailing Profit) con más de un par de símbolos desde un gráfico. Esta vez usaremos un solo indicador, a saber, la media móvil triangular en uno o varios marcos temporales.
Patrones de diseño en MQL5 (Parte I): Patrones de creación (Creational Patterns) Patrones de diseño en MQL5 (Parte I): Patrones de creación (Creational Patterns)
Existen métodos que pueden usarse para resolver problemas típicos. Una vez entendemos cómo utilizar estas técnicas una vez, podemos escribir programas de forma eficaz y aplicar el concepto DRY (No te repitas, en inglés, don't repeat yourself). En este contexto, resultan muy útiles los patrones de diseño que pueden aportar soluciones a problemas bien descritos y recurrentes.
Algoritmos de optimización de la población: Algoritmo de optimización de la dinámica espiral (Spiral Dynamics Optimization, SDO) Algoritmos de optimización de la población: Algoritmo de optimización de la dinámica espiral (Spiral Dynamics Optimization, SDO)
Este artículo presenta un algoritmo de optimización basado en los patrones de las trayectorias en espiral en la naturaleza, como las conchas de los moluscos: el algoritmo de optimización de la dinámica espiral o SDO. El algoritmo propuesto ha sido repensado y modificado a fondo por el autor: en el artículo analizaremos por qué estos cambios han sido necesarios.
Validación cruzada simétrica combinatoria en MQL5 Validación cruzada simétrica combinatoria en MQL5
El artículo muestra la implementación de la validación cruzada simétrica combinatoria en MQL5 puro para medir el grado de ajuste tras optimizar la estrategia usando el algoritmo completo lento del simulador de estrategias.