English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Creare Expert Advisor multipli sulla base dei modelli di trading

Creare Expert Advisor multipli sulla base dei modelli di trading

MetaTrader 5Tester | 17 dicembre 2021, 14:46
677 0
Vasiliy Sokolov
Vasiliy Sokolov


Introduzione

Le capacità tecniche del terminale MetaTrader 5 e il suo tester di strategia determinano il lavoro e la verifica dei sistemi di trading multivaluta. La complessità dello sviluppo di tali sistemi per MetaTrader 4 è condizionata, prima di tutto, dall'impossibilità di effettuare test simultanei tick per tick su più strumenti di trading. Inoltre, le limitate risorse linguistiche del linguaggio MQL4 non consentivano l'organizzazione di strutture di dati complesse e la gestione efficiente dei dati.

Con il rilascio di MQL5 la situazione è cambiata. Adesso, MQL5 supporta l'approccio orientato agli oggetti, si basa su un meccanismo sviluppato di funzioni ausiliarie e dispone persino di una serie di classi base della libreria standard per facilitare le attività quotidiane degli utenti, che vanno dall'organizzazione dei dati alle interfacce di lavoro per le funzioni di sistema standard.

E sebbene le specifiche tecniche del tester di strategia e del terminale consentano l'uso di EA multivaluta, non possiedono metodi integrati per la parallelizzazione del lavoro di un singolo EA contemporaneamente su più strumenti o intervalli di tempo. Come prima, per il lavoro di un EA, nel caso più semplice, è necessario eseguirlo nella finestra del simbolo, che determina il nome dello strumento di trading e il suo intervallo di tempo. Di conseguenza, la metodologia di lavoro, accettata dai tempi di MetaTrader 4, non consente di sfruttare appieno il tester di strategia e il terminale MetaTrader 5.

La situazione è complicata dal fatto che viene consentita una sola posizione cumulativa per ogni strumento, pari all'importo totale delle operazioni su quello strumento. Certamente, il passaggio a una posizione netta è corretto e tempestivo. La posizione netta si avvicina di più alla perfetta rappresentazione dell'interesse del trader su un particolare mercato.

Tuttavia, una tale organizzazione delle operazioni non rende il processo di trading semplice e facilmente visualizzabile. In precedenza, era sufficiente per un EA selezionare il suo ordine aperto (ad esempio, l'ordine poteva essere identificato utilizzando il magic number) e implementare l'azione richiesta. Ora, anche l'assenza di una posizione netta su uno strumento non significa che una particolare istanza di un EA su di esso non sia al momento sul mercato!

Gli sviluppatori di terze parti offrono vari modi per risolvere il problema con la posizione netta, che vanno dalla scrittura di uno speciale gestore di ordini virtuali (vedi l'articolo Un gestore di ordini virtuale per tracciare gli ordini all'interno dell'ambiente MT5 incentrato sulla posizione) all'integrazione degli input in un posizione aggregata, utilizzando il magic number (vedi Il metodo ottimale per il calcolo del volume totale della posizione in base al numero magico specificato o L'uso di ORDER_MAGIC per il trading con diversi Expert Advisor su un singolo strumento).

Tuttavia, oltre ai problemi con la posizione aggregata, c'è un problema con la cosiddetta multivaluta, quando lo stesso EA è tenuto a fare trading su più strumenti. La soluzione a questo problema può essere trovata nell'articolo Creazione di un Expert Advisor che esegue operazioni su diversi strumenti.

Tutti i metodi proposti funzionano e hanno i loro vantaggi. Tuttavia, il loro difetto fatale è che ognuno di questi metodi sta cercando di affrontare il problema dalla propria prospettiva, offrendo soluzioni che sono, ad esempio, adatte per il trading simultaneo di più EA su un singolo strumento, ma non sono adatte per soluzioni multivaluta.

Questo articolo mira a risolvere tutti i problemi con un'unica soluzione. L'utilizzo di questa soluzione può risolvere il problema dei test multivaluta e persino multisistema dell'interazione tra diversi EA su un singolo strumento. Sembra difficile, o addirittura impossibile da raggiungere, ma in realtà è tutto molto semplice.

Immagina che il tuo singolo EA esegua operazioni contemporaneamente su diverse dozzine di strategie di trading, su tutti gli strumenti disponibili e su tutti i possibili intervalli di tempo!  Inoltre, l'EA è facilmente testabile nel tester e per tutte le strategie, incluse nella sua composizione, possiede uno o più sistemi di gestione del denaro funzionanti.

Quindi, ecco le attività principali che dovremo risolvere:

  1. L'EA deve fare trading contemporaneamente sulla base di diversi sistemi di trading. Inoltre, deve fare trading con la stessa facilità sia su un solo sia su più sistemi di trading;
  2. Tutto i sistemi di trading, implementati nell'EA, non devono entrare in conflitto tra loro. Ogni sistema di trading deve gestire solo il proprio contributo alla posizione netta totale e solo i propri ordini;
  3. Per ogni sistema deve essere ugualmente facile fare trading su un singolo intervallo di tempo dello strumento, nonché su tutti gli intervalli di tempo contemporaneamente.
  4. Ogni sistema deve ugualmente e facilmente fare trading su un singolo strumento di trading, così come su tutti gli strumenti disponibili contemporaneamente.

Se esaminiamo attentamente l'elenco delle attività che dobbiamo gestire, arriveremo a un array tridimensionale. La prima dimensione dell'array, cioè il numero di sistemi di trading, la seconda dimensione, ovvero il numero di intervalli di tempo, su cui lo specifico TS deve operare, e la terza, ossia il numero di strumenti di trading per il TS. Un semplice calcolo mostra che anche un EA così semplice come il MACD Sample, quando si lavora contemporaneamente su 8 principali coppie di valute, avrà 152 soluzioni indipendenti: 1 EA * 8 coppie * 19 intervalli di tempo (non sono inclusi gli intervalli settimanali e mensili).

Se il sistema di trading sarà molto più grande e il portafoglio di trading degli EA considerevolmente più ampio, il numero di soluzioni potrebbe facilmente superare le 500 e, in alcuni casi, le 1000! È chiaro che è impossibile configurare manualmente e quindi caricare ciascuna combinazione separatamente. Pertanto è necessario costruire un sistema in modo tale che aggiusti automaticamente ogni combinazione e la carichi nella memoria dell'EA, il quale farà trading in base alle regole di una specifica istanza di questa combinazione.


Termini e concetti

Qui e anche in futuro la nozione di "strategia di trading" sarà sostituita da un termine più specifico, ovvero modello di trading o semplicemente modello. Un modello di trading è una classe speciale, costruita secondo regole specifiche e che descrive completamente la strategia di trading: indicatori utilizzati nel trading, le condizioni di trading in entrata e uscita, i metodi di gestione del denaro, ecc. Ogni modello di trading è astratto e non definisce parametri specifici per il suo funzionamento.

Un esempio semplice è la tattica di trading basata sull'incrocio di due medie mobili. Se la media mobile veloce incrocia quella lenta verso l'alto, si aprirà un’operazione di acquisto. Se, al contrario, incrocia quella verso il basso, si aprirà un’operazione di vendita. Questa formulazione è sufficiente per scrivere un modello di trading che opera sulle sue basi.

Tuttavia, una volta che tale modello viene descritto, sarà necessario determinare le modalità delle medie mobili, con il loro periodo di media, il periodo della finestra dati, e lo strumento, su cui questo modello farà trading. In generale, questo modello astratto conterrà dei parametri che dovranno essere compilati una volta che sarà necessario creare un'istanza di modello specifica. È ovvio che con questo approccio un modello astratto può essere genitore di più istanze di modelli che differiscono nei loro parametri.


Rifiuto completo della contabilizzazione della posizione netta 

Molti sviluppatori stanno cercando di tenere traccia della posizione aggregata. Tuttavia, possiamo vedere da quanto detto sopra che né la dimensione della posizione aggregata, né la sua dinamica sono rilevanti per una particolare istanza del modello. Il modello può essere breve, mentre la posizione aggregata può non esistere affatto (posizione aggregata neutra). Al contrario, la posizione aggregata può essere corta, mentre il modello avrà una posizione long.

Consideriamo infatti questi casi in modo più dettagliato. Supponiamo che uno strumento esegua operazioni con tre diverse tattiche di trading, ognuna delle quali ha il proprio sistema indipendente di gestione del denaro. Supponiamo inoltre che il primo dei tre sistemi abbia deciso di vendere tre contratti senza copertura, o detta in parole semplici, di adoperare una posizione short con un volume di tre contratti. Dopo il completamento delle operazioni, la posizione netta sarà costituita esclusivamente dalle operazioni del primo sistema di trading, il suo volume avrà tre contratti in meno o semplicemente tre contratti brevi senza copertura. Dopo qualche tempo, il secondo sistema di trading prenderà la decisione di acquistare4 contratti dello stesso asset.

Di conseguenza, la posizione netta cambierà e sarà composta da 1 contratto long. Questa volta includerà i contributi dei due sistemi di trading. Inoltre, il terzo sistema di trading entrerà in scena e creerà una posizione short con lo stesso asset, con il volume di un contratto standard. La posizione netta diventerà neutrale perché -3 short+ 4 long - 1 short = 0.

L'assenza della posizione netta significa che tutti e tre i sistemi di trading non sono sul mercato? Niente affatto. Due di loro possiedono quattro contratti senza copertura, il che significa che la copertura verrà fatta nel corso del tempo. Il terzo sistema, invece, possiede 4 contratti long che devono ancora essere rivenduti. Solo quando il rimborso integrale dei quattro contratti short sarà completo e verrà effettuata una vendita coperta di quattro contratti long, per posizione neutrale si intenderà una reale mancanza di posizioni in tutti e tre i sistemi.

Possiamo, naturalmente, ricostruire ogni volta l'intera sequenza di azioni per ciascuno dei modelli e quindi determinare il suo contributospecifico nella dimensione della posizione corrente, ma esiste un metodo molto più semplice. Questo metodo è semplice: è necessario abbandonare completamente la contabilizzazione della posizione aggregata, che può essere di qualsiasi dimensione e che può dipendere sia da fattori esterni (es., trading manuale) sia da fattori interni (il lavoro di altri modelli dell’EA su un unico strumento). Poiché non si può fare affidamento sulla posizione aggregata corrente, come si considerano allora le azioni di una specifica istanza del modello?

Il modo più semplice ed efficace sarebbe quello di dotare ogni istanza di un modello di una propria tabella degli ordini, che considererebbe tutti gli ordini, sia quelli in sospeso, sia quelli avviati da un’operazione o eliminati. Le informazioni dettagliate sugli ordini vengono memorizzate sul server di trading. Conoscendo il ticket dell’ordine, possiamo ottenere quasi tutte le informazioni a riguardo, che vanno dal momento della sua apertura al suo volume.

L'unica cosa che dobbiamo fare è collegare il ticket dell'ordine con un'istanza specifica del modello. Ogni istanza del modello dovrà contenere la sua istanza individuale di una classe speciale, ovvero la tabella degli ordini, che conterrebbe un elenco degli ordini correnti stabiliti dall'istanza del modello.


Proiettare un modello di trading astratto

Proviamo adesso a descrivere la classe astratta comune del modello, sulla quale si baseranno specifiche tattiche di trading. Poiché l'EA deve utilizzare più modelli (o un numero illimitato di modelli), è ovvio che questa classe dovrà avere un'interfaccia uniforme attraverso la quale un power expert esterno fornirà i segnali.

Ad esempio, questa interfaccia potrebbe essere la funzione Processing(). In poche parole, ogni classe CModel avrà la sua funzione Processing(). Questa funzione verrà chiamata a ogni tick o ogni minuto, oppure al verificarsi di un nuovo evento di Trading.

Ecco un semplice esempio di risoluzione di questa attività:

class CModel
{
protected:
   string            m_name;
public:
   void             CModel(){m_name="Model base";}
   bool virtual      Processing(void){return(true);}
};

class cmodel_macd : public CModel
{
public:
   void              cmodel_macd(){m_name="MACD Model";}
   bool              Processing(){Print("Model name is ", m_name);return(true);}
};

class cmodel_moving : public CModel
{
public:
   void              cmodel_moving(){m_name="Moving Average";}
   bool              Processing(){Print("Model name is ", m_name);return(true);}
};

cmodel_macd     *macd;
cmodel_moving   *moving;

Scopriamo come funziona questo codice. La classe base CModel contiene una variabile protetta di tipo stringa chiamata m_name. La parola chiave "protected" consente l'utilizzo di questa variabile da parte degli eredi della classe, quindi i suoi discendenti conterranno già questa variabile. Inoltre, la classe base definisce la funzione virtuale Processing(). In questo caso la parola 'virtuale' indica che si tratta di un wrapper o dell'interfaccia tra l'Expert Advisor e l'istanza specifica del modello.

Qualsiasi classe ereditata dal CModel avrà la garanzia di avere l'interfaccia Processing() per l'interazione. L'implementazione del codice di questa funzione è delegata ai suoi discendenti. Questa delega è ovvia, dal momento che il funzionamento interno dei modelli può differire in modo significativo l'uno dall'altro, quindi non esistono generalizzazioni comuni che potrebbero essere localizzate su un CModel di livello generale.

Più avanti descriveremo due classi, cmodel_macd e cmodel_moving. Entrambe sono generate dalla classe CModel, quindi entrambe possiedono le proprie istanze della funzione Processing() e della variabile m_name. Da notare che l'implementazione interna della funzione Processing() di entrambi i modelli è diversa. Nel primo modello, è costituita da Print ("It is cmodel_macd. Il nome del modello è ", m_name), nel secondo è Print ("It is cmodel_moving. Il nome del modello è ", m_name). Successivamente, vengono creati due puntatori, ognuno dei quali può puntare a un'istanza specifica del modello, uno alla classe di tipo cmodel_macd e l'altro al tipo cmodel_moving.

Nella funzione OnInit, questi puntatori ereditano i modelli/le classi creati dinamicamente. Dopodiché, all'interno della funzione OnEvent() viene chiamata una funzione Processing() , contenuta in ogni classe. Entrambi i puntatori vengono annunciati a livello globale, quindi, anche dopo essere usciti dalla funzione OnInit(), le classi create in essa non vengono eliminate, ma continuano a esistere a livello globale. Ora, ogni cinque secondi, la funzione OnTimer() campiona a turno entrambi i modelli, chiamando in essi la funzione Processing() appropriata.

Questo sistema primitivo di campionamento dei modelli che abbiamo appena creato manca di flessibilità e scalabilità. Cosa facciamo se vogliamo lavorare con diverse dozzine di modelli come questi? Lavorare con ognuno di loro separatamente è scomodo. Sarebbe molto più semplice raccogliere tutti i modelli in una singola comunità, ad esempio un array, e quindi eseguire l'iterazione su tutti gli elementi di questo array chiamando la funzione Processing() per ciascuno di questi elementi.

Ma il problema è che l'organizzazione degli array richiede che i dati in essi memorizzati siano dello stesso tipo. Nel nostro caso, sebbene i modelli cmodel_macd e cmodel_moving siano molto simili tra loro, non sono identici, il che rende automaticamente impossibile utilizzarli negli array.

Fortunatamente, gli array non sono l'unico modo per riassumere i dati, ma esistono altre generalizzazioni più flessibili e scalabili. Uno di questi è la tecnica delle liste collegate. Lo schema di funzionamento è semplice. Ogni elemento incluso nell'elenco generale deve contenere due puntatori. Un puntatore punta all'elemento dell'elenco precedente, il secondo a quello successivo. 

Inoltre, conoscendo il numero di indice dell'articolo, puoi sempre fare riferimento ad esso. Quando desideri aggiungere o eliminare un elemento, è sufficiente ricostruire i suoi puntatori e i puntatori degli elementi vicini, in modo che si riferiscano coerentemente l'uno all'altro. Non è necessario conoscere l'organizzazione interna di tali comunità, ma è sufficiente comprendere il loro dispositivo comune.

L'installazione standard di MetaTrader 5 include una speciale classe ausiliaria CList che offre l'opportunità di lavorare con elenchi collegati. Tuttavia, l'elemento di questa lista può essere solo un oggetto di tipo CObject, poiché solo questi possiedono i puntatori speciali per lavorare con le liste collegate. Di per sé, la classe CObject è piuttosto primitiva, essendo semplicemente un'interfaccia per interagire con la classe CList.

Puoi notarlo dando un'occhiata alla sua implementazione:

//+------------------------------------------------------------------+
//|                                                       Object.mqh |
//|                      Copyright © 2010, MetaQuotes Software Corp. |
//|                                       https://www.metaquotes.net/ |
//|                                              Revision 2010.02.22 |
//+------------------------------------------------------------------+
#include "StdLibErr.mqh"
//+------------------------------------------------------------------+
//| Class CObject.                                                   |
//| Purpose: Base class element storage.                             |
//+------------------------------------------------------------------+
class CObject
  {
protected:
   CObject          *m_prev;               // previous list item
   CObject          *m_next;               // next list item

public:
                     CObject();
   //--- methods of access to protected data
   CObject          *Prev()                { return(m_prev); }
   void              Prev(CObject *node)   { m_prev=node;    }
   CObject          *Next()                { return(m_next); }
   void              Next(CObject *node)   { m_next=node;    }
   //--- methods for working with files
   virtual bool      Save(int file_handle) { return(true);   }
   virtual bool      Load(int file_handle) { return(true);   }
   //--- method of identifying the object
   virtual int       Type() const          { return(0);      }

protected:
   virtual int       Compare(const CObject *node,int mode=0) const { return(0); }
  };
//+------------------------------------------------------------------+
//| Constructor CObject.                                             |
//| INPUT:  no.                                                      |
//| OUTPUT: no.                                                      |
//| REMARK: no.                                                      |
//+------------------------------------------------------------------+
void CObject::CObject()
  {
//--- initialize protected data
   m_prev=NULL;
   m_next=NULL;
  }
//+------------------------------------------------------------------+

Come si può vedere, la base di questa classe sono due puntatori per i quali vengono implementate le caratteristiche tipiche.

Ora la parte più importante. Grazie al meccanismo dell'ereditarietà, è possibile includere questa classe nel modello di trading, il che significa che la classe del modello di trading può essere inclusa in una lista di tipo CList! Proviamo a farlo.

E così renderemo la nostra classe CModel astratta come un discendente della classe CObject :

class CModel : public CObject

Poiché le nostre classi cmodel_moving e cmodel_average sono ereditate dalla classe CModel, includono i dati e i metodi della classe CObject, quindi possono essere incluse nell'elenco di tipo CList. Il codice sorgente, che crea i due modelli di trading condizionali e li inserisce nell'elenco e campiona in sequenza ogni tick, è mostrato di seguito:

//+------------------------------------------------------------------+
//|                                            ch01_simple_model.mq5 |
//|                            Copyright 2010, Vasily Sokolov (C-4). |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Vasily Sokolov (C-4)."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Arrays\List.mqh>

// Base model
class CModel:CObject
{
protected:
   string            m_name;
public:
        void              CModel(){m_name="Model base";}
        bool virtual      Processing(void){return(true);}
};

class cmodel_macd : public CModel
{
public:
   void              cmodel_macd(){m_name="MACD Model";}
   bool              Processing(){Print("Processing ", m_name, "...");return(true);}
};

class cmodel_moving : public CModel
{
public:
   void              cmodel_moving(){m_name="Moving Average";}
   bool              Processing(){Print("Processing ", m_name, "...");return(true);}
};

//Create list of models
CList *list_models;

void OnInit()
{
   int rezult;
   // Great two pointer
   cmodel_macd          *m_macd;
   cmodel_moving        *m_moving;
   list_models =        new CList();
   m_macd   =           new cmodel_macd();
   m_moving =           new cmodel_moving();
   //Check valid pointer
   if(CheckPointer(m_macd)==POINTER_DYNAMIC){
      rezult=list_models.Add(m_macd);
      if(rezult!=-1)Print("Model MACD successfully created");
      else          Print("Creation of Model MACD has failed");
   }
   //Check valid pointer
   if(CheckPointer(m_moving)==POINTER_DYNAMIC){
      rezult=list_models.Add(m_moving);
      if(rezult!=-1)Print("Model MOVING AVERAGE successfully created");
      else          Print("Creation of Model MOVING AVERAGE has failed");
   }
}

void OnTick()
{
   CModel               *current_model;
   for(int i=0;i<list_models.Total();i++){
      current_model=list_models.GetNodeAtIndex(i);
      current_model.Processing();
   }
}

void OnDeinit(const int reason)
{
   delete list_models;
}

Una volta che questo programma è stato compilato ed eseguito, righe simili che indicano il normale funzionamento dell'EA appariranno nel journal "Experts".

2010.10.10 14:18:31     ch01_simple_model (EURUSD,D1)   Prosessing Moving Average...
2010.10.10 14:18:31     ch01_simple_model (EURUSD,D1)   Processing MACD Model...
2010.10.10 14:18:21     ch01_simple_model (EURUSD,D1)   Model MOVING AVERAGE was created successfully
2010.10.10 14:18:21     ch01_simple_model (EURUSD,D1)   Model MACD was created successfully  

Analizziamo nel dettaglio come funziona questo codice. Quindi, come accennato in precedenza, il nostro modello di trading base CModel è derivato dalla classe CObject, la quale ci dà il diritto di includere i discendenti del modello base nell'elenco di tipo CList:

rezult=list_models.Add(m_macd);
rezult=list_models.Add(m_moving);

L'organizzazione dei dati richiede di lavorare con i puntatori. Una volta creati i puntatori di modelli specifici a livello locale della funzione OnInit() e inseriti nella lista globale list_models, la loro necessità scompare e possono essere distrutti in sicurezza, insieme ad altre variabili di questa funzione.

In generale, una caratteristica distintiva del modello proposto è che l'unica variabile globale (oltre alle stesse classi del modello) è una lista dinamicamente collegata di questi modelli. Quindi, fin dall'inizio, c'è il supporto di un alto grado di incapsulamento del progetto.

Se la creazione del modello non è riuscita per qualche motivo (ad esempio, i valori dei parametri richiesti sono stati elencati in modo errato), questo modello non verrà aggiunto all'elenco. Ciò non influirà sul lavoro complessivo dell'EA, poiché gestirà solo quei modelli che sono stati aggiunti con successo all'elenco.

Il campionamento dei modelli creati viene effettuato nella funzione OnTick() . Si compone di un ciclo for. In questo ciclo si determina il numero di elementi, dopodiché si ha un passaggio seriale dal primo elemento del ciclo (i = 0) all'ultimo (i <lista_modelli.Totale();i++):

CModel               *current_model;
for(int i=0;i<list_models.Total();i++){
   current_model=list_models.GetNodeAtIndex(i);
   current_model.Processing();
}

Il puntatore alla classe base CModel viene utilizzato come adattatore universale. Ciò garantisce che qualsiasi funzione supportata da questo indicatore sarà disponibile per i modelli derivati. In questo caso, abbiamo bisogno dell'unica funzione Processing(). Ogni modello ha la propria versione di Processing(), la cui implementazione interna può differire da funzioni simili di altri modelli. Non è necessario sovraccaricare questa funzione che può esistere solo in una forma: non avere parametri di input e restituire il valore di tipo bool.

Le attività che ricadono sulle "spalle" di questa funzione sono estesi:

  1. La funzione deve determinare in modo indipendente l'attuale situazione del mercato sulla base dei propri modelli di trading.
  2. Dopo che è stata presa la decisione di entrare nel mercato, la funzione deve calcolare in modo indipendente l'importo richiesto della garanzia coinvolta nell'operazione (il margine), il volume dell'operazione, il valore della massima perdita possibile o il livello di profitto.
  3. Il comportamento del modello deve essere correlato con le sue azioni precedenti. Ad esempio, se esiste una posizione short avviata dal modello, allora potrebbe essere impossibile costruirla ulteriormente in futuro. Tutte queste verifiche devono essere eseguite all'interno della funzione Processing().
  4. Ognuna di queste funzioni deve avere accesso ai parametri comuni, come lo stato dell'account. Sulla base di questi dati, questa funzione deve svolgere la propria gestione del denaro utilizzando i parametri incorporati nel suo modello. Ad esempio, se la gestione del denaro in uno dei modelli avviene tramite la formula ottima f, il suo valore deve essere diverso per ciascuno dei suoi modelli.

Ovviamente, la funzione Processing() , a causa dell'entità delle attività ad essa affidate, farà affidamento sull'apparato sviluppato delle classi ausiliarie, quelle incluse su MetaTrader 5, nonché quelle progettate specificamente per questa soluzione.

Come si può osservare, la maggior parte del lavoro è delegata a istanze specifiche del modello. Il livello esterno dell'EA fornisce, a turno, il controllo a ciascun modello e il suo lavoro è completato. Ciò che verrà fatto dal modello specifico dipenderà dalla sua logica interna.

In generale, il sistema di interazione che abbiamo costruito può essere descritto dal seguente schema:

Da notare che, sebbene la suddivisione dei modelli, come presentato nel codice precedente, avvenga all'interno della funzione OnTick() , non deve essere necessariamente così. Il ciclo di suddivisione può essere facilmente inserito in qualsiasi altra funzione desiderata, come OnTrade() o OnTimer()

 

La tabella degli ordini virtuali: la base del modello

Una volta che abbiamo combinato tutti i modelli di trading in un unico elenco, è il momento di descrivere il processo di trading. Torniamo alla classe CModel e proviamo ad integrarla con dati e funzioni aggiuntivi su cui si potrebbe basare nel processo di trading.

Come accennato in precedenza, il nuovo paradigma della posizione netta definisce le diverse regole per lavorare con ordini e operazioni. Su MetaTrader 4, ogni operazione è accompagnata dal suo ordine, il quale esiste nella scheda "Trade" dal momento della sua emissione e fino alla conclusione dell'ordine o alla chiusura dell'operazione da esso avviata.

Su MetaTrader 5 gli ordini in sospeso esistono solo fino al momento dell'effettivo completamento dell'operazione. Dopo che l'operazione, o l'ingresso del mercato, è stata implementata, questi ordini passano allo storico degli ordini memorizzato sul server di trading. Questa situazione crea incertezza. Supponiamo che l'EA emetta un ordine che è stato eseguito. La posizione aggregata è cambiata. Dopo qualche tempo, l'EA deve chiudere la sua posizione.

La chiusura dell'ordine specifico, come si potrebbe fare su MetaTrader 4, non può essere eseguita in quanto manca il concetto stesso di chiusura degli ordini. Possiamo chiudere la posizione o parte di essa. La domanda è quale parte della posizione deve essere chiusa. In alternativa, possiamo esaminare tutti gli ordini dello storico, selezionare quelli che sono stati emessi dall'EA e quindi correlare questi ordini con l'attuale situazione di mercato e, se necessario, bloccare i loro ordini contrapposti. Questo metodo presenta molte difficoltà.

Ad esempio, come possiamo stabilire che gli ordini non siano già stati bloccati in passato? Possiamo prendere un'altra strada, supponendo che la posizione attuale appartenga esclusivamente all'EA attuale. Questa opzione può essere utilizzata solo se intendi fare trading con un EA, facendo trading su una strategia. Tutti questi metodi non sono in grado di risolvere elegantemente le sfide che dobbiamo affrontare.

La soluzione più ovvia e più semplice sarebbe quella di memorizzare tutte le informazioni necessarie sugli ordini del modello corrente (ovvero gli ordini che non sono bloccati da operazioni opposte) all'interno del modello stesso.

Ad esempio, se il modello emette un ordine, il suo ticket viene registrato in un'area speciale della memoria di questo modello. Ad esempio, può essere organizzato con l'aiuto di un sistema di elenchi concatenati a noi già familiare.

Conoscendo il ticket dell'ordine, puoi trovare quasi tutte le informazioni su di esso, quindi tutto ciò di cui abbiamo bisogno è collegare il ticket dell'ordine con il modello che lo ha emesso. Lascia che il ticket dell'ordine venga archiviato nella classe speciale CTableOrder. Oltre al ticket, può contenere le informazioni più essenziali, come ad esempio il volume degli ordini, l'ora della sua installazione, il magic number, ecc.

Vediamo com’è strutturata questa classe:

#property copyright "Copyright 2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"

#include <Trade\_OrderInfo.mqh>
#include <Trade\_HistoryOrderInfo.mqh>
#include <Arrays\List.mqh>
class CTableOrders : CObject
{
private:
   ulong             m_magic;       // Magic number of the EA that put out the order
   ulong             m_ticket;      // Ticket of the basic order
   ulong             m_ticket_sl;    // Ticket of the simulated-Stop-Loss order, assigned with the basic order
   ulong             m_ticket_tp;    // Ticket of the simulated-Take-Profit, assigned with the basic order
   ENUM_ORDER_TYPE   m_type;         // Order type
   datetime          m_time_setup;  // Order setup time
   double            m_price;       // Order price
   double            m_sl;          // Stop Loss price
   double            m_tp;          // Take Profit price
   double            m_volume_initial;  // Order Volume
public:
                     CTableOrders();
   bool              Add(COrderInfo &order_info, double stop_loss, double take_profit);
   bool              Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit);
   double            StopLoss(void){return(m_sl);}
   double            TakeProfit(void){return(m_tp);}
   ulong             Magic(){return(m_magic);}
   ulong             Ticket(){return(m_ticket);}
   int               Type() const;
   datetime          TimeSetup(){return(m_time_setup);}
   double            Price(){return(m_price);}
   double            VolumeInitial(){return(m_volume_initial);}
};

CTableOrders::CTableOrders(void)
{
   m_magic=0;
   m_ticket=0;
   m_type=0;
   m_time_setup=0;
   m_price=0.0;
   m_volume_initial=0.0;
}

bool CTableOrders::Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit)
{
   if(HistoryOrderSelect(history_order_info.Ticket())){
      m_magic=history_order_info.Magic();
      m_ticket=history_order_info.Ticket();
      m_type=history_order_info.Type();
      m_time_setup=history_order_info.TimeSetup();
      m_volume_initial=history_order_info.VolumeInitial();
      m_price=history_order_info.PriceOpen();
      m_sl=stop_loss;
      m_tp=take_profit;
      return(true);
   }
   else return(false);
}

bool CTableOrders::Add(COrderInfo &order_info, double stop_loss, double take_profit)
{
   if(OrderSelect(order_info.Ticket())){
      m_magic=order_info.Magic();
      m_ticket=order_info.Ticket();
      m_type=order_info.Type();
      m_time_setup=order_info.TimeSetup();
      m_volume_initial=order_info.VolumeInitial();
      m_price=order_info.PriceOpen();
      m_sl=stop_loss;
      m_tp=take_profit;
      return(true);
   }
   else return(false);
}

int   CTableOrders::Type() const
{
   return((ENUM_ORDER_TYPE)m_type);
}

Simile alla classe CModel , la classe CTableOrders è ereditata da CObject. Proprio come le classi dei modelli, inseriremo istanze di CTableOrders nell'elenco ListTableOrders di tipo CList.

Oltre al proprio ticket dell’ordine (m_ticket), la classe contiene informazioni sul magic number (ORDER_MAGIC) dell'EA che lo ha emesso, il suo tipo, il prezzo di apertura, il volume e il livello di sovrapposizione stimata degli ordini: stoploss (m_sl) e takeprofit (m_tp). Sugli ultimi due valori occorre fare un discorso separato. È ovvio che qualsiasi affare prima o poi debba essere chiuso da un’operazione opposta. L'operazione opposta può essere avviata sulla base dell'attuale situazione di mercato o della parziale chiusura della posizione ad un prezzo predeterminato al momento della sua conclusione.

Su MetaTrader4, tali "uscite incondizionate dalla posizione" sono tipi speciali di uscite: StopLoss e TakeProfit. La caratteristica distintiva di MetaTrader 4 è il fatto che questi livelli si applicano a ordini specifici. Ad esempio, se si verifica un’interruzione in uno degli ordini attivi, non influirà sugli altri ordini aperti su questo strumento.

Su MetaTrader 5, ciò è diverso. Sebbene per ciascuno degli ordini impostati, tra l'altro, sia possibile specificare un prezzo dello StopLoss e del TakeProfit, questi livelli non agiranno contro un ordine specifico in cui sono stati fissati questi prezzi, ma nel rispetto dell'intera posizione su questo strumento.

Supponiamo che ci sia una posizione BUY aperta per EURUSD di un lotto standard senza i livelli di StopLoss e TakeProfit. Qualche tempo dopo, viene emesso un altro ordine per EURUSD per l'acquisto di 0,1 lotti con i livelli impostati di StopLoss e TakeProfit, ciascuno a una distanza di 100 punti dal prezzo corrente. Dopo qualche tempo, il prezzo raggiunge il livello di StopLoss o il livello di TakeProfit. Quando ciò si verifica, l'intera posizione con una dimensione di 1,1 lotti su EURUSD verrà chiusa.

In altre parole, lo StopLoss e il TakeProfit possono essere impostati solo in relazione alla posizione aggregata e non a un ordine particolare. Su questa base, diventa impossibile utilizzare questi ordini in EA multisistema. Questo è ovvio, perché se un sistema emetterà i propri StopLoss e TakeProfit, allora si applicherà a tutti gli altri sistemi i cui interessi sono già inclusi nella posizione aggregata dello strumento!

Di conseguenza, ciascuno dei sottosistemi dell'EA di trading deve utilizzare solo i propri StopLoss e TakeProfit interni per ciascun ordine individualmente. Inoltre, questo concetto può essere derivato dal fatto che, anche all'interno dello stesso sistema di trading, ordini diversi possono avere diversi livelli di StopLoss e TakeProfit e, come già accennato in precedenza, su MetaTrader 5 questi output non possono essere designati a singoli ordini.

Se posizioniamo, all'interno degli ordini virtuali, livelli sintetici di StopLoss e TakeProfit, l'EA sarà in grado di bloccare autonomamente gli ordini esistenti una volta che il prezzo raggiunge o supera questi livelli. Dopo aver bloccato questi ordini, potranno essere rimossi in sicurezza dall'elenco degli ordini attivi. Di seguito verrà descritto come fare.

La classe CTableOrders, oltre ai propri dati, contiene una funzione Add() molto importante. Questa funzione riceve il ticket dell'ordine, il quale deve essere registrato nella tabella. Oltre al ticket dell'ordine, questa funzione riceve i livelli di StopLoss e TakeProfit. Innanzitutto, la funzione Add() cerca di allocare l'ordine nello storico degli ordini che sono memorizzati sul server. Se ci riesce, inserirà le informazioni sul ticket nell'istanza della classe history_order_info, quindi inizierà a inserire le informazioni attraverso di essa nel nuovo elemento TableOrders. Inoltre, questo elemento verrà aggiunto all'elenco degli ordini. Se non è stato possibile completare la selezione dell'ordine, allora, forse, abbiamo a che fare con un ordine in sospeso, quindi dobbiamo provare ad allocare questo ordine dagli ordini attuali tramite la funzione OrderSelect() . In caso di esito positivo della selezione di questo ordine, vengono eseguite le stesse azioni dell'ordine storico.

Al momento, prima dell'introduzione della struttura, che descrive l'evento Trade, è difficile lavorare con ordini pendenti per EA multisistema. Certamente, dopo l'introduzione di questa struttura, sarà possibile progettare degli EA sulla base degli ordini pendenti. Inoltre, se è presente una tabella degli ordini, praticamente qualsiasi strategia di trading con ordini in sospeso può essere spostata nella performance sul mercato. Per questi motivi, tutti i modelli di trading presentati nell'articolo avranno l'esecuzione di mercato (ORDER_TYPE_BUY or ORDER_TYPE_SELL).


CModel: la classe base del modello di trading

E così, quando la tabella degli ordini è completamente progettata, arriva il momento di descrivere la versione completa del modello base CModel:

class CModel : public CObject
{
protected:
   long              m_magic;
   string            m_symbol;
   ENUM_TIMEFRAMES   m_timeframe;
   string            m_model_name;
   double            m_delta;
   CTableOrders      *table;
   CList             *ListTableOrders;
   CAccountInfo      m_account_info;
   CTrade            m_trade;
   CSymbolInfo       m_symbol_info;
   COrderInfo        m_order_info;
   CHistoryOrderInfo m_history_order_info;
   CPositionInfo     m_position_info;
   CDealInfo         m_deal_info;
   t_period          m_timing;
public:
                     CModel()  { Init();   }
                     ~CModel() { Deinit(); }
   string            Name(){return(m_model_name);}
   void              Name(string name){m_model_name=name;}
   ENUM_TIMEFRAMES    Timeframe(void){return(m_timeframe);}
   string            Symbol(void){return(m_symbol);}
   void              Symbol(string set_symbol){m_symbol=set_symbol;}
   bool virtual      Init();
   void virtual      Deinit(){delete ListTableOrders;}
   bool virtual      Processing(){return (true);}
   double            GetMyPosition();
   bool              Delete(ENUM_TYPE_DELETED_ORDER);
   bool              Delete(ulong Ticket);
   void              CloseAllPosition();
   //bool virtual      Trade();
protected:
   bool              Add(COrderInfo &order_info, double stop_loss, double take_profit);
   bool              Add(CHistoryOrderInfo &history_order_info, double stop_loss, double take_profit);

   void              GetNumberOrders(n_orders &orders);
   bool              SendOrder(string symbol, ENUM_ORDER_TYPE op_type, ENUM_ORDER_MODE op_mode, ulong ticket, double lot,
                              double price, double stop_loss, double take_profit, string comment);
};

I dati di questa classe contengono le costanti fondamentali di qualsiasi modello di trading.

Questo è il magic number (m_magic), il simbolo su cui verrà lanciato il modello (m_simbolo), l’intervallo di tempo (m_timeframe) e il nome del modello più utilizzato per il trading (m_name).

Inoltre, il modello include la già nota classe della tabella degli ordini, (CTableOrders * table) e l'elenco in cui verranno conservate le istanze di questa tabella, una copia per ogni ordine (CList*ListTableOrders). Poiché tutti i dati verranno creati dinamicamente, in caso di necessità, il lavoro con questi dati verrà eseguito tramite i puntatori.

Questo è seguito dalla variabile m_delta. Questa variabile deve contenere un coefficiente speciale per il calcolo del lotto corrente nelle formule per la gestione del denaro. Ad esempio, per la formula di capitalizzazione a frazione fissa, questa variabile può memorizzare una quota del conto, che può essere rischiata. Ad esempio, per il rischio del 2% del conto, questa variabile deve essere pari a 0,02. Per i metodi più aggressivi, ad esempio, e per un metodo ottimale, questa variabile f potrebbe essere maggiore.

La cosa importante di questa variabile è che consente la selezione individuale del rischio per ciascun modello, il quale fa parte di un unico EA. Se la formula delle maiuscole non viene utilizzata, non è necessario compilarla. Per impostazione predefinita sarà uguale a 0,0.

Segue poi l'inclusione di tutte le classi di trading ausiliarie, progettate per facilitare la ricezione e l'elaborazione di tutte le informazioni richieste, che vanno dalle informazioni sul conto alle informazioni sulla posizione. Resta inteso che i derivati di modelli di trading specifici devono utilizzare attivamente queste classi ausiliarie e non le caratteristiche regolari di tipo OrderSelect o OrderSend.

La variabile m_timing deve essere descritta separatamente. Durante il processo di lavoro dell'EA, è necessario chiamare determinati eventi a determinati intervalli di tempo. La funzione OnTimer() non è adatta a questo, poiché i diversi modelli possono esistere in intervalli di tempo diversi.

Ad esempio, alcuni eventi devono essere chiamati a ogni nuova barra. Per il modello in cui si fa trading su un grafico orario, tali eventi devono essere chiamati ogni ora, mentre per un modello in cui si fa trading su un grafico giornaliero, occorre ogni giorno nuova barra. È chiaro che questi modelli presentano impostazioni temporali diverse e ciascuno deve essere memorizzato, rispettivamente, nel proprio modello. La struttura t_period inclusa nella classe CModel permette di memorizzare queste impostazioni separatamente, ognuna nel suo modello.

Ecco come si presenta la struttura:

struct t_period
{
   datetime m1;
   datetime m2;
   datetime m3;
   datetime m4;
   datetime m5;
   datetime m6;
   datetime m10;
   datetime m12;
   datetime m15;
   datetime m20;
   datetime m30;
   datetime h1;
   datetime h2;
   datetime h3;
   datetime h4;
   datetime h6;
   datetime h8;
   datetime h12;
   datetime d1;
   datetime w1;
   datetime mn1;  
   datetime current; 
};

Come si può osservare, include il solito elenco di intervalli di tempo. Per vedere se si è verificata una nuova barra, è necessario confrontare l'ora dell'ultima barra con l'ora che è stata registrata nella struttura t_period. Se i tempi non corrispondono, allora si è verificata una nuova barra e l'ora nella struttura deve essere aggiornata all'ora della barra corrente e restituire un risultato positivo (true). Se l'ora dell'ultima barra e la struttura sono identiche, significa che la nuova barra non si è ancora verificata e si deve restituire un risultato negativo (false).

Ecco una funzione funzionante e basata sull'algoritmo descritto:

bool timing(string symbol, ENUM_TIMEFRAMES tf, t_period &timeframes)
{
   int rez;
   MqlRates raters[1];
   rez=CopyRates(symbol, tf, 0, 1, raters);
   if(rez==0)
   {
      Print("Error timing");
      return(false);
   }
   switch(tf){
      case PERIOD_M1:
         if(raters[0].time==timeframes.m1)return(false);
         else{timeframes.m1=raters[0].time; return(true);}
      case PERIOD_M2:
         if(raters[0].time==timeframes.m2)return(false);
         else{timeframes.m2=raters[0].time; return(true);}
      case PERIOD_M3:
         if(raters[0].time==timeframes.m3)return(false);
         else{timeframes.m3=raters[0].time; return(true);}
      case PERIOD_M4:
         if(raters[0].time==timeframes.m4)return(false);
         else{timeframes.m4=raters[0].time; return(true);}
     case PERIOD_M5:
         if(raters[0].time==timeframes.m5)return(false);
         else{timeframes.m5=raters[0].time; return(true);}
     case PERIOD_M6:
         if(raters[0].time==timeframes.m6)return(false);
         else{timeframes.m6=raters[0].time; return(true);}
     case PERIOD_M10:
         if(raters[0].time==timeframes.m10)return(false);
         else{timeframes.m10=raters[0].time; return(true);}
     case PERIOD_M12:
         if(raters[0].time==timeframes.m12)return(false);
         else{timeframes.m12=raters[0].time; return(true);}
     case PERIOD_M15:
         if(raters[0].time==timeframes.m15)return(false);
         else{timeframes.m15=raters[0].time; return(true);}
     case PERIOD_M20:
         if(raters[0].time==timeframes.m20)return(false);
         else{timeframes.m20=raters[0].time; return(true);}
     case PERIOD_M30:
         if(raters[0].time==timeframes.m30)return(false);
         else{timeframes.m30=raters[0].time; return(true);}
     case PERIOD_H1:
         if(raters[0].time==timeframes.h1)return(false);
         else{timeframes.h1=raters[0].time; return(true);}
     case PERIOD_H2:
         if(raters[0].time==timeframes.h2)return(false);
         else{timeframes.h2=raters[0].time; return(true);}
     case PERIOD_H3:
         if(raters[0].time==timeframes.h3)return(false);
         else{timeframes.h3=raters[0].time; return(true);}
     case PERIOD_H4:
         if(raters[0].time==timeframes.h4)return(false);
         else{timeframes.h4=raters[0].time; return(true);}
     case PERIOD_H6:
         if(raters[0].time==timeframes.h6)return(false);
         else{timeframes.h6=raters[0].time; return(true);}
     case PERIOD_H8:
         if(raters[0].time==timeframes.h8)return(false);
         else{timeframes.h8=raters[0].time; return(true);}
     case PERIOD_H12:
         if(raters[0].time==timeframes.h12)return(false);
         else{timeframes.h12=raters[0].time; return(true);}
     case PERIOD_D1:
         if(raters[0].time==timeframes.d1)return(false);
         else{timeframes.d1=raters[0].time; return(true);}
     case PERIOD_W1:
         if(raters[0].time==timeframes.w1)return(false);
         else{timeframes.w1=raters[0].time; return(true);}
     case PERIOD_MN1:
         if(raters[0].time==timeframes.mn1)return(false);
         else{timeframes.mn1=raters[0].time; return(true);}
     case PERIOD_CURRENT:
         if(raters[0].time==timeframes.current)return(false);
         else{timeframes.current=raters[0].time; return(true);}
     default:
         return(false);
   }
}

Al momento non è prevista la possibilità di una suddivisione sequenziale delle strutture. Tale suddivisione può essere richiesta quando è necessario creare più istanze in un ciclo dello stesso modello di trading, facendo trading su intervalli di tempo diversi. Quindi ho dovuto scrivere uno speciale selezionatore di funzioni per quanto riguarda la struttura t_period.

Ecco il codice sorgente di questa funzione:

int GetPeriodEnumerator(uchar n_period)
{
   switch(n_period)
   {
      case 0: return(PERIOD_CURRENT);
      case 1: return(PERIOD_M1);
      case 2: return(PERIOD_M2);
      case 3: return(PERIOD_M3);
      case 4: return(PERIOD_M4);
      case 5: return(PERIOD_M5);
      case 6: return(PERIOD_M6);
      case 7: return(PERIOD_M10);
      case 8: return(PERIOD_M12);
      case 9: return(PERIOD_M15);
      case 10: return(PERIOD_M20);
      case 11: return(PERIOD_M30);
      case 12: return(PERIOD_H1);
      case 13: return(PERIOD_H2);
      case 14: return(PERIOD_H3);
      case 15: return(PERIOD_H4);
      case 16: return(PERIOD_H6);
      case 17: return(PERIOD_H8);
      case 18: return(PERIOD_H12);
      case 19: return(PERIOD_D1);
      case 20: return(PERIOD_W1);
      case 21: return(PERIOD_MN1);
      default:
         Print("Enumerator period must be smallest 22");
         return(-1);
   }
}

Tutte queste funzioni sono convenientemente combinate in un unico file nella cartella \\Include. Chiamiamolo Time.mqh.

Questo è ciò che sarà incluso nella nostra classe base CModel:

#incude <Time.mqh>

Oltre alle semplici funzioni get/set di tipo Name(), Timeframe() e Symbol(), la classe CModel contiene funzioni complesse di tipo Init(),GetMyPosition(), Delete(), CloseAllPosition() и Processing(). La designazione dell'ultima funzione dovrebbe già esserti familiare. Parleremo più dettagliatamente della sua struttura interna in seguito, ma per ora iniziamo con una descrizione delle funzioni principali della classe base CModel.

La funzione CModel::Add() crea dinamicamente un’istanza della classe CTableOrders e la compila usando la funzione CTabeOrders::Add() appropriata. Il suo principio di funzionamento è stato descritto sopra. Dopo essere stato compilato, questo elemento viene incluso nell'elenco generale di tutti gli ordini del modello corrente (ListTableOrders.Add (t)).

La funzione CModel::Delete() , invece, ha eliminato l'elemento di tipo CTableOrders dall'elenco degli ordini attivi. Per fare ciò è necessario specificare il ticket degli ordini, che deve essere cancellato. I principi delle suoi lavori è semplice. La funzione riordina in sequenza l'intera tabella degli ordini alla ricerca dell'ordine con il ticket corretto. Se trova questo ordine, lo elimina.

La funzione CModel::GetNumberOrders() conta il numero di ordini attivi. Riempie la struttura speciale n_orders:

struct n_orders
{
   int all_orders;
   int long_orders;
   int short_orders;
   int buy_sell_orders;
   int delayed_orders;
   int buy_orders;
   int sell_orders;
   int buy_stop_orders;
   int sell_stop_orders;
   int buy_limit_orders;
   int sell_limit_orders;
   int buy_stop_limit_orders;
   int sell_stop_limit_orders;
};

Come si può vedere, dopo che è stato chiamato possiamo scoprire quanti tipi specifici di ordini sono stati impostati. Ad esempio, per ottenere il numero di tutti gli ordini short, è necessario leggere tutti i valori di short_orders dell’istanza n_orders.

La funzione CModel::SendOrder() è l’unica funzione di base per l'invio effettivo di ordini al server di trading. Anziché avere ciascun modello particolare con il proprio algoritmo per l'invio degli ordini al server, la funzione SendOrder() definisce la procedura generale per questi invii. Indipendentemente dal modello, il processo di evasione degli ordini è associato alle stesse verifiche che vengono svolte efficacemente in una postazione centralizzata.

Familiarizziamo con il codice sorgente di questa funzione:

bool CModel::SendOrder(string symbol, ENUM_ORDER_TYPE op_type, ENUM_ORDER_MODE op_mode, ulong ticket, 
                          double lot, double price, double stop_loss, double take_profit, string comment)
{
   ulong code_return=0;
   CSymbolInfo symbol_info;
   CTrade      trade;
   symbol_info.Name(symbol);
   symbol_info.RefreshRates();
   mm send_order_mm;
   
   double lot_current;
   double lot_send=lot;
   double lot_max=m_symbol_info.LotsMax();
   //double lot_max=5.0;
   bool rez=false;
   int floor_lot=(int)MathFloor(lot/lot_max);
   if(MathMod(lot,lot_max)==0)floor_lot=floor_lot-1;
   int itteration=(int)MathCeil(lot/lot_max);
   if(itteration>1)
      Print("The order volume exceeds the maximum allowed volume. It will be divided into ", itteration, " deals");
   for(int i=1;i<=itteration;i++)
   {
      if(i==itteration)lot_send=lot-(floor_lot*lot_max);
      else lot_send=lot_max;
      for(int i=0;i<3;i++)
      {
         //Print("Send Order: TRADE_RETCODE_DONE");
         symbol_info.RefreshRates();
         if(op_type==ORDER_TYPE_BUY)price=symbol_info.Ask();
         if(op_type==ORDER_TYPE_SELL)price=symbol_info.Bid();
         m_trade.SetDeviationInPoints(ulong(0.0003/(double)symbol_info.Point()));
         m_trade.SetExpertMagicNumber(m_magic);
         rez=m_trade.PositionOpen(m_symbol, op_type, lot_send, price, 0.0, 0.0, comment); 
         // Sleeping is not to be deleted or moved! Otherwise the order will not have time to get recorded in m_history_order_info!!!
         Sleep(3000);
         if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||
            m_trade.ResultRetcode()==TRADE_RETCODE_DONE_PARTIAL||
            m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
         {
               //Print(m_trade.ResultComment());
               //rez=m_history_order_info.Ticket(m_trade.ResultOrder());
               if(op_mode==ORDER_ADD){
                  rez=Add(m_trade.ResultOrder(), stop_loss, take_profit);
               }
               if(op_mode==ORDER_DELETE){
                  rez=Delete(ticket);
               }
               code_return=m_trade.ResultRetcode();
               break;
         }
         else
         {
            Print(m_trade.ResultComment());
         }
         if(m_trade.ResultRetcode()==TRADE_RETCODE_TRADE_DISABLED||
            m_trade.ResultRetcode()==TRADE_RETCODE_MARKET_CLOSED||
            m_trade.ResultRetcode()==TRADE_RETCODE_NO_MONEY||
            m_trade.ResultRetcode()==TRADE_RETCODE_TOO_MANY_REQUESTS||
            m_trade.ResultRetcode()==TRADE_RETCODE_SERVER_DISABLES_AT||
            m_trade.ResultRetcode()==TRADE_RETCODE_CLIENT_DISABLES_AT||
            m_trade.ResultRetcode()==TRADE_RETCODE_LIMIT_ORDERS||
            m_trade.ResultRetcode()==TRADE_RETCODE_LIMIT_VOLUME)
         {
            break;
         }
      }
   }
   return(rez);
}

Come prima cosa, questa funzione verifica la possibilità di eseguire il volume dichiarato del server di trading. E lo fa usando la funzione CheckLot() . Potrebbero esserci alcune restrizioni di trading sulla dimensione della posizione. Occorre tenerlo considerazione.

Considera il seguente caso: c'è un limite sulla dimensione di una posizione di trading di 15 lotti standard in entrambe le direzioni. Quella attuale è una long position ed è pari a 3 lotti. Il modello di trading, basato sul suo sistema di gestione del denaro, vuole aprire una posizione long con un volume di 18,6 lotti. La funzione CheckLot() restituirà il volume corretto dell'operazione. In questo caso, sarà pari a 12 lotti (dato che 3 lotti su 15 sono già occupati da altre operazioni). Se quella corrente fosse una short position anziché long, la funzione restituirebbe 15 lotti invece di 18,6. Questo è il volume massimo possibile di posizioni.

Dopo aver messo in vendita 15 lotti di acquisto, la posizione netta, in questo caso, sarà di 12 lotti (3 - vendita, 15 - acquisto). Quando un altro modello supera la sua posizione short iniziale di 3 lotti, l’acquisto della posizione aggregata raggiungerà il suo massimo, cioè 15 lotti. Gli altri segnali di acquisto non verranno elaborati fino a quando il modello non supererà alcuni o tutti i suoi 15 lotti di acquisto. È stato superato un possibile volume per l'operazione richiesta, la funzione restituisce un EMPTY_VALUE costante e tale segnale deve essere passato.

Se la verifica della possibilità del volume impostato ha avuto esito positivo, vengono allora effettuati i calcoli sul valore del margine richiesto. I fondi sul conto potrebbero non essere sufficienti per il volume indicato. Per questi scopi, esiste una funzione CheckMargin() . Se il margine non è sufficiente, cercherà di correggere il volume dell'ordine in modo che l'attuale margine libero ne permetta l'apertura. Se il margine non è sufficiente anche per aprire l'importo minimo, allora siamo in uno stato di Margin-Call.

Se al momento non ci sono posizioni e il margine non viene utilizzato, significa solo una cosa: technical margin-call, uno stato in cui è impossibile aprire un'operazione. Senza aggiungere denaro al conto, non siamo in grado di continuare. Se un margine è ancora in uso, non ci resta altro che attendere fino alla chiusura della transazione che utilizza questo margine. In ogni caso, la mancanza di margine restituirà un EMPTY_VALUE costante.

Una caratteristica distintiva di questa funzione è la capacità di suddividere l'ordine corrente in più operazioni indipendenti. Se i modelli di trading utilizzano un sistema di capitalizzazione del conto, l'importo richiesto può facilmente superare tutti i limiti immaginabili (ad esempio, il sistema di capitalizzazione può richiedere l'apertura di un affare con un volume di diverse centinaia, e talvolta migliaia, lotti standard). Chiaramente è impossibile garantire un tale importo per una singola operazione. In genere, le condizioni di trading determinano la dimensione di un'operazione massima di cento lotti, ma alcuni server di trading hanno altre restrizioni. Ad esempio, su MetaQuotes Championship 2010 server questa limitazione era di 5 lotti. È chiaro che tali restrizioni devono essere prese in considerazione e, sulla base di ciò, calcolare correttamente il volume effettivo dell'operazione.

Per prima cosa, viene conteggiato il numero di ordini necessari per implementare il volume impostato. Se l'importo impostato non supera l'importo massimo della transazione, è necessario un solo passaggio per l'emissione di questo ordine. Se il volume desiderato delle transazioni supera il volume massimo possibile, questo volume viene diviso in più parti. Ad esempio, se vuoi acquistare 11,3 lotti di EURUSD. La dimensione massima della transazione su questo strumento è di 5,0 lotti. Quindi la funzioneOrderSendsuddivide questo volume in tre ordini: un ordine di 5,0 lotti, un secondo ordine di 5,0 lotti, un terzo ordine di 1,3 lotti.

Quindi, invece di un ordine, ce ne saranno tre. Ciascuno di essi sarà elencato nella tabella degli ordini e avrà le proprie impostazioni indipendenti, come i valori virtuali di Stop Loss e Take Profit, il magic number e altri parametri. Non dovrebbero esserci difficoltà nell'elaborazione di tali ordini, poiché i modelli di trading sono progettati in modo tale da poter gestire un numero qualsiasi di ordini nei loro elenchi.

Infatti, tutti gli ordini avranno gli stessi valori di TakeProfit e StopLoss. Ciascuno di essi verrà ordinato in sequenza dalle funzioni LongClose e ShortClose . Una volta verificatesi le giuste condizioni per la loro chiusura o raggiunte le loro soglie SL e TP, verranno tutti chiusi.

Ogni ordine viene inviato al server utilizzando la funzione OrderSend della classe CTrade. Il dettaglio più interessante del lavoro è nascosto di seguito.

Il fatto è che l'assegnazione di un ordine può essere duplice. L'ordine di acquisto o vendita può essere inviato al verificarsi del segnale, oppure può essere un ordine di blocco di quello già esistente. La funzione OrderSend deve conoscere il tipo di ordine inviato, poiché questa è la funzione che inserisce effettivamente tutti gli ordini nella tabella degli ordini o li rimuove da essa al verificarsi di determinati eventi.

Quando il tipo di ordine che vuoi aggiungere è ADD_ORDER. Cioè un ordine indipendente che deve essere inserito nella tabella degli ordini, quindi la funzione aggiunge informazioni su questo ordine nella tabella. Se l'ordine viene emesso per riscrivere l'ordine precedentemente inserito (ad esempio, nel caso di uno stop-loss virtuale), allora deve avere un tipo DELETE_ORDER. Dopo che è stato inserito, la funzione OrderSend rimuove manualmente le informazioni sull'ordine a cui è collegato dall'elenco degli ordini. Per questo motivo la funzione, oltre al tipo di ordine, eredita un ticket dell’ordine al quale è collegata. Nel caso di ADD_ORDER, il ticket può essere riempito semplicemente con uno zero.


Il primo modello di trading, basato sul crossover delle medie mobili

Abbiamo parlato di tutti gli elementi principali della classe baseCModel. È il momento di prendere in considerazione una classe di trading specifica.

Per questi scopi, creeremo prima un semplice modello di trading basato su un semplice indicatore MACD.

Questo modello avrà sempre una posizione long o short. Non appena la linea veloce incrocia la linea lenta verso il basso, apriremo una posizione short, mentre la posizione long, se presente, verrà chiusa. Nel caso del crossover al rialzo, apriremo una posizione long, mentre la posizione short, se presente, verrà chiusa. In questo modello, non utilizziamo livelli protettivi di stop e profit.

#include <Models\Model.mqh>
#include <mm.mqh>
//+----------------------------------------------------------------------+
//| This model uses MACD indicator.                                      |
//| Buy when it crosses the zero line downward                           |
//| Sell when it crosses the zero line upward                            |
//+----------------------------------------------------------------------+  
struct cmodel_macd_param
{
   string            symbol;
   ENUM_TIMEFRAMES   timeframe;
   int               fast_ema;
   int               slow_ema;
   int               signal_ema;
};
   
class cmodel_macd : public CModel
{
private:
   int               m_slow_ema;
   int               m_fast_ema;
   int               m_signal_ema;
   int               m_handle_macd;
   double            m_macd_buff_main[];
   double            m_macd_current;
   double            m_macd_previous;
public:
                     cmodel_macd();
   bool              Init();
   bool              Init(cmodel_macd_param &m_param);
   bool              Init(string symbol, ENUM_TIMEFRAMES timeframes, int slow_ma, int fast_ma, int smothed_ma);
   bool              Processing();
protected:
   bool              InitIndicators();
   bool              CheckParam(cmodel_macd_param &m_param);
   bool              LongOpened();
   bool              ShortOpened();
   bool              LongClosed();
   bool              ShortClosed();
};

cmodel_macd::cmodel_macd()
{
   m_handle_macd=INVALID_HANDLE;
   ArraySetAsSeries(m_macd_buff_main,true);
   m_macd_current=0.0;
   m_macd_previous=0.0;
}
//this default loader
bool cmodel_macd::Init()
{
   m_magic      = 148394;
   m_model_name =  "MACD MODEL";
   m_symbol     = _Symbol;
   m_timeframe  = _Period;
   m_slow_ema   = 26;
   m_fast_ema   = 12;
   m_signal_ema = 9;
   m_delta      = 50;
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_macd::Init(cmodel_macd_param &m_param)
{
   m_magic      = 148394;
   m_model_name = "MACD MODEL";
   m_symbol     = m_param.symbol;
   m_timeframe  = (ENUM_TIMEFRAMES)m_param.timeframe;
   m_fast_ema   = m_param.fast_ema;
   m_slow_ema   = m_param.slow_ema;
   m_signal_ema = m_param.signal_ema;
   if(!CheckParam(m_param))return(false);
   if(!InitIndicators())return(false);
   return(true);
}


bool cmodel_macd::CheckParam(cmodel_macd_param &m_param)
{
   if(!SymbolInfoInteger(m_symbol, SYMBOL_SELECT))
   {
      Print("Symbol ", m_symbol, " selection has failed. Check symbol name");
      return(false);
   }
   if(m_fast_ema == 0)
   {
      Print("Fast EMA must be greater than 0");
      return(false);
   }
   if(m_slow_ema == 0)
   {
      Print("Slow EMA must be greater than 0");
      return(false);
   }
   if(m_signal_ema == 0)
   {
      Print("Signal EMA must be greater than 0");
      return(false);
   }
   return(true);
}

bool cmodel_macd::InitIndicators()
{
   if(m_handle_macd==INVALID_HANDLE)
   {
      Print("Load indicators...");
      if((m_handle_macd=iMACD(m_symbol,m_timeframe,m_fast_ema,m_slow_ema,m_signal_ema,PRICE_CLOSE))==INVALID_HANDLE)
      {
         printf("Error creating MACD indicator");
         return(false);
      }
   }
   return(true);
}

bool cmodel_macd::Processing()
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_account_info.TradeAllowed()==false)return(false);
   //if(m_account_info.TradeExpert()==false)return(false);
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   if(m_orders.buy_orders>0)   LongClosed();
   else                        LongOpened();
   if(m_orders.sell_orders!=0) ShortClosed();
   else                        ShortOpened();
   return(true);
}

bool cmodel_macd::LongOpened(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, ticket_bool;
   double lot=0.1;
   mm open_mm;
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   
   //Print("LongOpened");
   if(m_macd_current>0&&m_macd_previous<=0&&m_orders.buy_orders==0)
   {
      //lot=open_mm.optimal_f(m_symbol, ORDER_TYPE_BUY, m_symbol_info.Ask(), 0.0, m_delta);
      lot=open_mm.jons_fp(m_symbol, ORDER_TYPE_BUY, m_symbol_info.Ask(), 0.1, 10000, m_delta);
      rezult=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_ADD, 0, lot, m_symbol_info.Ask(), 0, 0, "MACD Buy");
      return(rezult);
   }
   return(false);
}

bool cmodel_macd::ShortOpened(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY)return(false);
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, ticket_bool;
   double lot=0.1;
   mm open_mm;
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
   
   m_macd_current=m_macd_buff_main[0];
   m_macd_previous=m_macd_buff_main[1];
   GetNumberOrders(m_orders);
   
   if(m_macd_current<=0&&m_macd_previous>=0&&m_orders.sell_orders==0)
   {
      //lot=open_mm.optimal_f(m_symbol, ORDER_TYPE_SELL, m_symbol_info.Bid(), 0.0, m_delta);
      lot=open_mm.jons_fp(m_symbol, ORDER_TYPE_SELL, m_symbol_info.Bid(), 0.1, 10000, m_delta);
      rezult=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_ADD, 0, lot, m_symbol_info.Bid(), 0, 0, "MACD Sell");
      return(rezult);
   }
   return(false);
}

bool cmodel_macd::LongClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_BUY)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
      if(m_symbol_info.Bid()<=t.StopLoss()&&t.StopLoss()!=0.0)
      {
         
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "MACD: buy close buy stop-loss");
      }
      if(m_macd_current<0&&m_macd_previous>=0)
      {
         //Print("Long position closed by Order Send");
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "MACD: buy close by signal");
      }
   }
   return(rez);
}

bool cmodel_macd::ShortClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_SELL)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyBuffer(this.m_handle_macd,0,1,2,m_macd_buff_main);
      if(m_symbol_info.Ask()>=t.StopLoss()&&t.StopLoss()!=0.0)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                                 m_symbol_info.Ask(), 0.0, 0.0, "MACD: sell close buy stop-loss");
      }
      if(m_macd_current>0&&m_macd_previous<=0)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                                 m_symbol_info.Ask(), 0.0, 0.0, "MACD: sell close by signal");
      }
   }
   return(rez);
}

La classe base CModel non impone alcuna restrizione sul contenuto interno dei suoi discendenti. L'unica cosa che rende obbligatorio è l'uso della funzione di interfaccia Processing(). Tutti i problemi dell'organizzazione interna di tale funzione sono delegati a una specifica classe di modelli. Non esiste alcun algoritmo universale che possa essere inserito all'interno di una funzione Processing(), quindi non c'è motivo di imporre ai discendenti il suo metodo su come organizzare un particolare modello. Tuttavia, la struttura interna di qualsiasi modello può essere standardizzata. Tale standardizzazione faciliterà notevolmente la comprensione di un codice esterno e persino del tuo codice e renderà il modello più "formulaico".

Ogni modello deve avere il proprio inizializzatore. L'inizializzatore del modello è responsabile del caricamento dei parametri corretti e necessari per il suo funzionamento. Ad esempio, affinché il nostro modello funzioni, dobbiamo selezionare i valori dell'indicatore MACD, ottenere un handle del buffer corrispondente e, inoltre, determinare ovviamente lo strumento di negoziazione e l’intervallo di tempo per il modello. Tutto questo deve essere fatto dall'inizializzatore.

Gli inizializzatori dei modelli sono semplici metodi sovraccaricati delle loro classi. Questi metodi hanno un nome comune, Init. Il fatto è che MQL5 non supporta i costruttori sovraccaricati. Quindi, non è possibile creare l'inizializzatore del modello nel relativo costruttore poiché l'overload sarà necessario per i parametri di input. Anche se nessuno ci impedisce di indicare nel costruttore del modello i suoi parametri di base.

Ogni modello deve avere tre inizializzatori. Il primo è l'inizializzatore predefinito. Deve configurare e caricare il modello di default, senza richiedere i parametri. Questo può essere molto comodo per i test in modalità "as is". Ad esempio, per il nostro modello l'inizializzatore predefinito come strumento e intervallo di tempo del modello selezionerà il grafico corrente e l'intervallo attuale.

Anche le impostazioni dell'indicatore MACD saranno standard: fast EMA = 12, slow EMA = 26, signal MA = 9; se è necessario configurare il modello in altro modo, tale inizializzatore non sarà più adatto. Avremo bisogno di inizializzatori con dei parametri. È opportuno (ma non necessario farne due tipi. Il primo riceverà i suoi parametri come una funzione classica: Init (type param1, type param2, ..., type paramN). Il secondo scoprirà i parametri del modello utilizzando una struttura speciale, la quale salva questi parametri. Questa opzione a volte è preferibile perché il numero di parametri può essere ampio e, in quel caso, sarebbe conveniente passarli attraverso le strutture.

Ogni modello ha la struttura dei suoi parametri. Questa struttura può avere qualsiasi nome, ma è preferibile chiamarla con il pattern nomemodello_param. Configurazione del modello: è un passaggio molto importante nell'utilizzo delle opportunità di trading multitimeframe/multisistema/multivaluta È in questa fase che si determinerà come e su quale strumento questo modello farà trading.

Il nostro modello ha solo quattro funzioni di trading. La funzione per aprire una posizione long: LongOpen, la funzione per aprire una short cortaposition: ShortOpen, la funzione per chiudere una long position: LongClosed e la funzione per chiudere una short position: ShortClosed. Il lavoro delle funzioni LongOpen и ShortOpen è irrilevante. Entrambi ricevono il valore dell'indicatore della barra precedente MACD, il quale viene confrontato con il valore delle due barre precedenti. Per evitare il "ridisegnamento", la barra corrente (zero) non verrà utilizzata.

Se c'è un crossover verso il basso, allora la funzione ShortOpen calcola utilizzando le funzioni incluse nel file di intestazione mm.mqh, dopodiché il lotto necessario invia il suo comando alla funzione OrderSend. Il LongClose in questo momento, invece, chiude tutte le posizioni long del modello. Ciò si verifica perché la funzione ordina in sequenza tutti gli ordini aperti correnti nella tabella degli ordini del modello. Se viene trovato un long order, la funzione lo chiude con un contrordine. La stessa identica cosa, sebbene nella direzione opposta, viene eseguita dalla funzione ShortClose() . Puoi trovare il lavoro di queste funzioni nell'elenco fornito sopra.

Analizziamo più in dettaglio come viene calcolato il lotto corrente nel modello di trading.

Come accennato in precedenza, per questi scopi utilizziamo funzioni speciali per la capitalizzazione del conto. Tali funzioni comprendono, oltre alle formule di capitalizzazione, la verifica del calcolo del lotto basata sul livello del margine utilizzato e dal vincolo della dimensione delle posizioni di trading. Potrebbero esserci delle limitazioni di trading sulla dimensione del la posizione. Occorre prenderli in considerazione.

Considera il seguente caso: c'è un limite sulla dimensione di una posizione di trading di 15 lotti standard in entrambe le direzioni. Quella attuale è una long position ed è pari a 3 lotti. Il modello di trading basato sul suo sistema di gestione del capitale vuole aprire una posizione long con un volume di 18,6 lotti. La funzione CheckLot() restituirà l'importo corretto del volume dell'ordine. In questo caso, sarà pari a 12 lotti (dato che 3 lotti su 15 sono già occupati da altre operazioni). Se quella attuale fosse una short position, e non long, la funzione avrebbe restituito 15 lotti invece di 18,6. Questo è il volume massimo possibile delle posizioni.

Dopo aver messo su 15 lotti da acquistare, la posizione netta, in questo caso, sarà di 12 lotti (3 di vendita, 15 di acquisto). Quando un altro modello supera la sua posizione short iniziale di 3 lotti, l’acquisto della posizione aggregata raggiungerà il suo massimo, cioè 15 lotti. Gli altri segnali di acquisto non verranno elaborati fino a quando il modello non avrà superato alcuni o tutti i suoi 15 lottidi acquisto. Se il volume disponibile per la transazione richiesta è esaurito, la funzione restituirà un EMPTY_VALUE costante. Questo segnale deve essere passato.

Se la verifica della possibilità del volume impostato ha avuto esito positivo, vengono allora effettuati i calcoli sul valore del margine richiesto. I fondi del conto potrebbero essere insufficienti per il volume impostato. A tal proposito, esiste la funzione CheckMargin(). Se il margine non è sufficiente, cercherà di correggere l'importo indicato della transazione in modo che l'attuale margine libero ne permetta l'apertura. Se il margine non è sufficiente nemmeno per aprire il volume minimo, allora ci troviamo in uno stato di Margin-Call.

Se al momento non ci sono posizioni e il margine non viene utilizzato, significa solo una cosa: technical margin-call, uno stato in cui è impossibile aprire un'operazione. Senza aggiungere denaro al conto, non siamo in grado di continuare. Se viene ancora utilizzato un margine, non abbiamo altra scelta che attendere la chiusura della posizione che utilizza questo margine. In ogni caso, la mancanza di margine restituirà un EMPTY_VALUE costante.

Le funzioni per il controllo della dimensione del lotto e dei margini di solito non vengono utilizzate direttamente. Sono chiamate da funzioni speciali per la gestione del capitale. Queste funzioni implementano le formule per la capitalizzazione dei conti. Il file mm.mqh comprende solo due funzioni base della gestione del capitale. Una è calcolata sulla base di una frazione fissa del conto e l'altra è basata sul metodo proposto da Ryan Jones, noto come metodo delle proporzioni fisse.

Lo scopo del primo metodo è definire una parte fissa del conto, che può essere rischiata. Ad esempio, se il rischio consentito è il 2% del conto e il conto è pari a 10.000 dollari, l'importo massimo del rischio sarà di 200 $. Per calcolare quale lotto deve essere utilizzato per uno stop di 200 dollari, è necessario conoscere esattamente la distanza massima che può essere raggiunta dal prezzo rispetto alla posizione aperta. Pertanto, per calcolare il lotto attraverso questa formula, dobbiamo determinare con precisione lo Stop Loss e il livello di prezzo per i quali verrà effettuata l'operazione.

Il metodo proposto da Ryan Jones è diverso dal precedente. La sua essenza è che la capitalizzazione viene eseguita dalla funzione, definita da un caso particolare di un'equazione quadratica.

Ecco la sua soluzione:

x=((1.0+MathSqrt(1+4.0*d))/2)*Step;

dove: x - il limite inferiore della transizione al livello successivo d = (Profitto / delta) * 2.0 Step - un passo del delta, ad esempio 0,1 lotti.

Più bassa è la dimensione del delta, più aggressivamente la funzione cercherà di aumentare il numero di posizioni. Per maggiori dettagli su come è costruita questa funzione, puoi fare riferimento al libro di Ryan Jones: The Trading Game: Playing by the Numbers to Make Millions.

Se non si prevede di utilizzare le funzioni di gestione del capitale, è necessario chiamare direttamente le funzioni di controllo del lotto e del margine.

Quindi abbiamo rivisto tutti gli elementi del nostro EA di base. È il momento di raccogliere i frutti del nostro lavoro.

Per cominciare, creiamo quattro modelli. Lascia che un modello operi in base ai parametri predefiniti di EURUSD, mentre il secondo opererà anch’esso su EURUSD, ma in un intervallo di tempo di 15 minuti. Il terzo modello verrà lanciato sul grafico GBPUSD con parametri di default. Il quarto su USDCHF su un grafico di due ore, con i seguenti parametri: SlowEMA= 6 FastEMA = 12 SignalEMA = 9. Il periodo di verifica - H1, la modalità di verifica - tutti i tick, il periodo di tempo dal 01.01.2010 al 01.09.2010.

Ma prima di eseguire questo modello in quattro diverse modalità, proveremo a testarlo separatamente per ogni strumento e intervallo di tempo.

Ecco la tabella su cui vengono realizzati i principali indicatori di test:

Sistema
Il numero di operazioni
Profitto, $
MACD(9,12,26)H1 EURUSD
123
1092
MACD (9,12,26) EURUSD M15
598
-696
MACD(9,6,12) USDCHF H2
153
-1150
MACD(9,12,26) GBPUSD H1
139
-282
Tutti i sistemi
1013
-1032

La tabella mostra che il numero totale di operazioni per tutti i modelli dovrebbe essere 1013 e il profitto totale dovrebbe essere $ -1032.

Di conseguenza, questi sono gli stessi valori che dovremmo ottenere se testiamo questi sistemi contemporaneamente. I risultati non dovrebbero differire, anche se si verificano ancora alcune piccole deviazioni.

Quindi ecco il test finale:


Come si può vedere, c'è solo un’operazione in meno e i profitti differiscono solo di10 $, che corrisponde a soli 10 punti di differenza con un lotto di 0,1. Va notato che i risultati dei test combinati, nel caso di utilizzo del sistema di gestione del denaro, saranno radicalmente diversi dalla quantità dei risultati dei test di ciascun modello individualmente. Questo perché le dinamiche del saldo influenzano ciascuno dei sistemi, quindi i valori calcolati dei lotti varieranno. 

Nonostante il fatto che da soli i risultati non presentino interesse, abbiamo creato una struttura dell'EA complessa, ma molto flessibile e gestibile. Consideriamo ancora brevemente la sua struttura.

Per questo utilizzeremo lo schema mostrato di seguito:

Classe di riferimento

Lo schema mostra la struttura di base dell'istanza del modello.

Una volta creata un'istanza di classe del modello di trading, chiamiamo la funzione Init() sovraccaricata. Inizializza i parametri necessari, prepara i dati, carica gli handle degli Indicatori, se utilizzati. Tutto questo avviene durante l'inizializzazione dell'EA, cioè all'interno della funzione OnInit(). Da notare che i dati includono istanze delle classi base, progettate per facilitare il trading. Si suppone che i modelli di trading debbano utilizzare attivamente queste classi invece delle funzioni standard di MQL5. Dopo che la classe del modello è stata creata e inizializzata correttamente, rientrerà nell'elenco dei modelli CList. Un'ulteriore comunicazione con essa viene effettuata tramite un adattatore universale CObject.

Dopo il verificarsi degli eventi OnTrade() o OnTick(), viene eseguita una suddivisione sequenziale di tutte le istanze dei modelli nell'elenco. La comunicazione con essi avviene chiamando la funzione Processing(). Inoltre, chiama le funzioni di trading del proprio modello (il gruppo di funzioni in blu). Il loro elenco e i loro nomi non sono strettamente definiti, ma è conveniente usare i nomi standard come LongOpened(), ShortClosed(), ecc. Queste funzioni, in base alla loro logica incorporata, scelgono il momento per completare l'operazione, quindi inviano una richiesta appositamente formata per l'apertura o la chiusura dell'operazione della funzione SendOrder().

Quest'ultima effettua le verifiche necessarie, quindi emette gli ordini al market. Le funzioni di trading si affidano alle funzioni ausiliarie del modello (gruppo in verde), che a loro volta utilizzano attivamente le classi ausiliarie di base (gruppo in viola). Tutte le classi ausiliarie sono rappresentate come istanze di classi nella sezione dati (gruppo in rosa). L'interazione tra i gruppi è indicata da frecce blu scuro.


Il modello di trading basato sull'indicatore delle Bande di Bollinger

Ora che la struttura generale dei dati e dei metodi è diventata più chiara, creeremo un altro modello di trading basato sugli indicatori di tendenza delle Bande di Bollinger. Come base per questo modello di trading, abbiamo utilizzato un EA semplice, un Expert Advisor basato sulle Bande di Bollinger di Andrei Kornishkin. Le bande di Bollinger sono dei livelli pari a una certa dimensione di deviazioni standard dalla media mobile semplice. Maggiori dettagli su come è costruito questo indicatore possono essere trovati nella sezione Aiuto per l'analisi tecnica, allegata al terminale MetaTrader 5.

L'essenza dell'idea di trading è semplice: il prezzo ha la proprietà di tornare, cioè se il prezzo ha raggiunto un certo livello, molto probabilmente girerà nella direzione opposta. Questa tesi è dimostrata da un test su una normale distribuzione di qualsiasi strumento di trading reale: la normale curva di distribuzione sarà leggermente allungata. Le bande di Bollinger determinano i culmini più probabili dei livelli di prezzo. Una volta raggiunte (o le bande di Bollinger superiori o quelle inferiori), è probabile che il prezzo giri nella direzione opposta.

Semplifichiamo leggermente la tattica di trading senza utilizzare l'indicatore ausiliario, la doppia media mobile esponenziale (Doppia media mobile esponenziale o DEMA). Ma useremo dei rigorosi arresti protettivi: lo Stop Loss virtuale. Renderanno il processo di trading più stabile e allo stesso tempo ci aiuteranno a capire l’esempio in cui ogni modello di trading utilizza il proprio livello indipendente di stop protettivi.

Per il livello di stop protettivi utilizziamo il prezzo corrente più o meno l'indicatore di volatilità del valore ATR. Ad esempio, se il valore attuale di ATR è pari a 68 punti e c'è un segnale di vendita a un prezzo di 1,25720, lo Stop Loss virtuale per questa operazione sarà pari a 1,25720 + 0,0068 = 1,26400. Avviene allo stesso modo, ma nella direzione opposta, per l'acquisto: 1.25720 - 0.0068 = 1.25040.

Il codice sorgente di questo modello è fornito di seguito:

#include <Models\Model.mqh>
#include <mm.mqh>
//+----------------------------------------------------------------------+
//| This model use Bollinger bands.
//| Buy when price is lower than lower band
//| Sell when price is higher than upper band
//+----------------------------------------------------------------------+  
struct cmodel_bollinger_param
{
   string            symbol;
   ENUM_TIMEFRAMES   timeframe;
   int               period_bollinger;
   double            deviation;
   int               shift_bands;
   int               period_ATR;
   double            k_ATR;
   double            delta;
};
   
class cmodel_bollinger : public CModel
{
private:
   int               m_bollinger_period;
   double            m_deviation;
   int               m_bands_shift;
   int               m_ATR_period;
   double            m_k_ATR;
   //------------Indicators Data:-------------
   int               m_bollinger_handle;
   int               m_ATR_handle;
   double            m_bollinger_buff_main[];
   double            m_ATR_buff_main[];
   //-----------------------------------------
   MqlRates          m_raters[];
   double            m_current_price;
public:
                     cmodel_bollinger();
   bool              Init();
   bool              Init(cmodel_bollinger_param &m_param);
   bool              Init(ulong magic, string name, string symbol, ENUM_TIMEFRAMES TimeFrame, double delta,
                          uint bollinger_period, double deviation, int bands_shift, uint ATR_period, double k_ATR);
   bool              Processing();
protected:
   bool              InitIndicators();
   bool              CheckParam(cmodel_bollinger_param &m_param);
   bool              LongOpened();
   bool              ShortOpened();
   bool              LongClosed();
   bool              ShortClosed();
   bool              CloseByStopSignal();
};

cmodel_bollinger::cmodel_bollinger()
{
   m_bollinger_handle   = INVALID_HANDLE;
   m_ATR_handle         = INVALID_HANDLE;
   ArraySetAsSeries(m_bollinger_buff_main,true);
   ArraySetAsSeries(m_ATR_buff_main,true);
   ArraySetAsSeries(m_raters, true);
   m_current_price=0.0;
}
//this default loader
bool cmodel_bollinger::Init()
{
   m_magic              = 322311;
   m_model_name         =  "Bollinger Bands Model";
   m_symbol             = _Symbol;
   m_timeframe          = _Period;
   m_bollinger_period   = 20;
   m_deviation          = 2.0;
   m_bands_shift        = 0;
   m_ATR_period         = 20;
   m_k_ATR              = 2.0;
   m_delta              = 0;
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_bollinger::Init(cmodel_bollinger_param &m_param)
{
   m_magic              = 322311;
   m_model_name         = "Bollinger Model";
   m_symbol             = m_param.symbol;
   m_timeframe          = (ENUM_TIMEFRAMES)m_param.timeframe;
   m_bollinger_period   = m_param.period_bollinger;
   m_deviation          = m_param.deviation;
   m_bands_shift        = m_param.shift_bands;
   m_ATR_period        = m_param.period_ATR;
   m_k_ATR              = m_param.k_ATR;
   m_delta              = m_param.delta;
   //if(!CheckParam(m_param))return(false);
   if(!InitIndicators())return(false);
   return(true);
}

bool cmodel_bollinger::Init(ulong magic, string name, string symbol, ENUM_TIMEFRAMES timeframe, double delta,
                           uint bollinger_period, double deviation, int bands_shift, uint ATR_period, double k_ATR)
{
   m_magic           = magic;
   m_model_name      = name;
   m_symbol          = symbol;
   m_timeframe       = timeframe;
   m_delta           = delta;
   m_bollinger_period= bollinger_period;
   m_deviation       = deviation;
   m_bands_shift     = bands_shift;
   m_ATR_period      = ATR_period;
   m_k_ATR           = k_ATR;
   if(!InitIndicators())return(false);
   return(true);
}


/*bool cmodel_bollinger::CheckParam(cmodel_bollinger_param &m_param)
{
   if(!SymbolInfoInteger(m_symbol, SYMBOL_SELECT)){
      Print("Symbol ", m_symbol, " select failed. Check valid name symbol");
      return(false);
   }
   if(m_ma == 0){
      Print("Fast EMA must be bigest 0. Set MA = 12 (default)");
      m_ma=12;
   }
   return(true);
}*/

bool cmodel_bollinger::InitIndicators()
{
   m_bollinger_handle=iBands(m_symbol,m_timeframe,m_bollinger_period,m_bands_shift,m_deviation,PRICE_CLOSE);
   if(m_bollinger_handle==INVALID_HANDLE){
      Print("Error in creation of Bollinger indicator. Restart the Expert Advisor.");
      return(false);
   }
   m_ATR_handle=iATR(m_symbol,m_timeframe,m_ATR_period);
   if(m_ATR_handle==INVALID_HANDLE){
      Print("Error in creation of ATR indicator. Restart the Expert Advisor.");
      return(false);
   }
   return(true);
}

bool cmodel_bollinger::Processing()
{
   //if(timing(m_symbol,m_timeframe, m_timing)==false)return(false);
   
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_account_info.TradeAllowed()==false)return(false);
   //if(m_account_info.TradeExpert()==false)return(false);
   
   //m_symbol_info.Name(m_symbol);
   //m_symbol_info.RefreshRates();
   //Copy last data of moving average
 
   GetNumberOrders(m_orders);
   
   if(m_orders.buy_orders>0)   LongClosed();
   else                        LongOpened();
   if(m_orders.sell_orders!=0) ShortClosed();
   else                        ShortOpened();
   if(m_orders.all_orders!=0)CloseByStopSignal();
   return(true);
}

bool cmodel_bollinger::LongOpened(void)
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   //Print("Model Bollinger: ", m_orders.buy_orders);
   bool rezult, time_buy=true;
   double lot=0.1;
   double sl=0.0;
   double tp=0.0;
   mm open_mm;
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   //lot=open_mm.optimal_f(m_symbol,OP_BUY,m_symbol_info.Ask(),sl,delta);
   CopyBuffer(m_bollinger_handle,2,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close>m_bollinger_buff_main[1]&&m_raters[1].open<m_bollinger_buff_main[1])
   {
      sl=NormalizeDouble(m_symbol_info.Ask()-m_ATR_buff_main[0]*m_k_ATR,_Digits);
      SendOrder(m_symbol,ORDER_TYPE_BUY,ORDER_ADD,0,lot,m_symbol_info.Ask(),sl,tp,"Add buy");
   }
   return(false);
}

bool cmodel_bollinger::ShortOpened(void)
{
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY)return(false);
   //if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)return(false);
   
   bool rezult, time_sell=true;
   double lot=0.1;
   double sl=0.0;
   double tp;
   mm open_mm;
   
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(m_bollinger_handle,1,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close<m_bollinger_buff_main[1]&&m_raters[1].open>m_bollinger_buff_main[1])
   {   
      sl=NormalizeDouble(m_symbol_info.Bid()+m_ATR_buff_main[0]*m_k_ATR,_Digits);
      SendOrder(m_symbol,ORDER_TYPE_SELL,ORDER_ADD,0,lot,m_symbol_info.Ask(),sl,tp,"Add buy");
   }
   return(false);
}

bool cmodel_bollinger::LongClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   m_symbol_info.Name(m_symbol);
   m_symbol_info.RefreshRates();
   CopyBuffer(m_bollinger_handle,1,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close<m_bollinger_buff_main[1]&&m_raters[1].open>m_bollinger_buff_main[1])
   {
      for(int i=total_elements-1;i>=0;i--)
      {
         if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
         t=ListTableOrders.GetNodeAtIndex(i);
         if(CheckPointer(t)==POINTER_INVALID)continue;
         if(t.Type()!=ORDER_TYPE_BUY)continue;
         m_symbol_info.Refresh();
         m_symbol_info.RefreshRates();
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), 
                       m_symbol_info.Bid(), 0.0, 0.0, "BUY: close by signal");
      }
   }
   return(rez);
}

bool cmodel_bollinger::ShortClosed(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   CopyBuffer(m_bollinger_handle,2,0,3,m_bollinger_buff_main);
   CopyBuffer(m_ATR_handle,0,0,3,m_ATR_buff_main);
   CopyRates(m_symbol,m_timeframe,0,3,m_raters);
   if(m_raters[1].close>m_bollinger_buff_main[1]&&m_raters[1].open<m_bollinger_buff_main[1])
   {
      for(int i=total_elements-1;i>=0;i--)
      {
         if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
         t=ListTableOrders.GetNodeAtIndex(i);
         if(CheckPointer(t)==POINTER_INVALID)continue;
         if(t.Type()!=ORDER_TYPE_SELL)continue;
         m_symbol_info.Refresh();
         m_symbol_info.RefreshRates();
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Ask(), 0.0, 0.0, "SELL: close by signal");
      }
   }
   return(rez);
}

bool cmodel_bollinger::CloseByStopSignal(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   bool rez=false;
   total_elements=ListTableOrders.Total();
   if(total_elements==0)return(false);
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      if(t.Type()!=ORDER_TYPE_SELL&&t.Type()!=ORDER_TYPE_BUY)continue;
      m_symbol_info.Refresh();
      m_symbol_info.RefreshRates();
      CopyRates(m_symbol,m_timeframe,0,3,m_raters);
      if(m_symbol_info.Bid()<=t.StopLoss()&&t.Type()==ORDER_TYPE_BUY)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Bid(), 0.0, 0.0, "BUY: close by stop");
         continue;
      }
      if(m_symbol_info.Ask()>=t.StopLoss()&&t.Type()==ORDER_TYPE_SELL)
      {
         rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(),
                       m_symbol_info.Ask(), 0.0, 0.0, "SELL: close by stop");
         continue;
      }
   }
   return(rez);
}

Come si può osservare, il codice del modello di trading è molto simile al codice sorgente delle precedenti tattiche di trading. La variazione principale qui è l'emergere di ordini di stop virtuali e la funzione cmodel_bollinger:: CloseByStopSignal(), servendo questi arresti protettivi.

Infatti, in caso di utilizzo di stop protettivi, i loro valori devono essere semplicemente passati alla funzione SendOrder(). E questa funzione inserirà questi livelli nella tabella degli ordini. Quando il prezzo attraversa o tocca questi livelli, la funzione CloseByStopSignal() chiuderà l'operazione con un contrordine e lo rimuoverà dall'elenco degli ordini attivi.


La combinazione di modelli di trading, strumenti e tempistiche in un'unica entità

Ora che abbiamo due modelli di trading, è il momento di testarli contemporaneamente. Prima di fare un layout dei modelli, sarebbe utile determinarne i parametri più efficaci. Per fare questo, dobbiamo eseguire l'ottimizzazione di ogni modello individualmente.

L'ottimizzazione dimostrerà su quale mercato e intervallo di tempo il modello è più efficace. Per ogni modello verranno selezionati solo i due intervallo di tempo e i tre strumenti migliori. Di conseguenza, otteniamo dodici soluzioni indipendenti di (2 modelli * 3 strumenti * 2 tempi) che verranno testati tutti insieme. Naturalmente, il metodo di ottimizzazione selezionato soffre del cosiddetto "aggiustamento" dei risultati, ma per i nostri scopi questo non è rilevante.

I grafici seguenti mostrano i migliori risultati del campione:

1.1 MACD EURUSD M30

MACD EURUSD M30

1.2 . MACD EURUSD H3


MACD EURUSD H3

1.3 MACD AUDUSD H4

MACD AUDUSD H4

1.4 . MACD AUDUSD H1


MACD AUDUSD H1

1.5 MACD GBPUSD H12


MACD GBPUSD H12

1.6 MACD GBPUSD H6


MACD GBPUSD H6

2.1 Bollinger GBPUSD M15


Bollinger GBPUSD M15

2.2 Bollinger GBPUSD H1


Bollinger GBPUSD H1

2.3 Bollinger EURUSD M30

Bollinger EURUSD M30

 

2.4  Bollinger EURUSD H4


Bollinger EURUSD H4

 

2.5 Bollinger USDCAD M15


Bollinger USDCAD M15

 

2.6 Bollinger USDCAD H2

Bollinger USDCAD H2

Ora che i risultati ottimali sono noti, ci resta poco da fare: raccogliere i risultati in un'unica entità.

Di seguito è riportato il codice sorgente del caricatore di funzioni, il quale crea i dodici modelli di trading, mostrati sopra, dopo che l'EA inizia costantemente a fare trading su di essi:

bool macd_default=true;
bool macd_best=false;
bool bollinger_default=false;
bool bollinger_best=false;

void InitModels()
{
   list_model = new CList;             // Initialized pointer of the model list
   cmodel_macd *model_macd;            // Create the pointer to a model MACD
   cmodel_bollinger *model_bollinger;  // Create the pointer to a model Bollinger
   
//----------------------------------------MACD DEFAULT----------------------------------------
   if(macd_default==true&&macd_best==false)
    {
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      // Loading of the parameters was completed successfully
      if(model_macd.Init(129475, "Model macd M15", _Symbol, _Period, 0.0, Fast_MA,Slow_MA,Signal_MA))
      { 
      
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Загружаем модель в список моделей
      }
      else
      {
                                 // The loading of parameters was completed successfully
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
   }
//-------------------------------------------------------------------------------------------
//----------------------------------------MACD BEST------------------------------------------
   if(macd_best==true&&macd_default==false)
   {
      // 1.1 EURUSD H30; FMA=20; SMA=24; 
      model_macd = new cmodel_macd; // Initialize the pointer to the model MACD
      if(model_macd.Init(129475, "Model macd H30", "EURUSD", PERIOD_M30, 0.0, 20,24,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " created successfully");
         list_model.Add(model_macd);// load the model into the list of models
      }
      else
      {// Loading parameters was completed unsuccessfully
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.2 EURUSD H3; FMA=8; SMA=12; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H3", "EURUSD", PERIOD_H3, 0.0, 8,12,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
       {// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.3 AUDUSD H1; FMA=10; SMA=18; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd M15", "AUDUSD", PERIOD_H1, 0.0, 10,18,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// The loading of parameters was unsuccessful                       
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.4 AUDUSD H4; FMA=14; SMA=15; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H4", "AUDUSD", PERIOD_H4, 0.0, 14,15,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
         " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else{// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.5 GBPUSD H6; FMA=20; SMA=33; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H6", "GBPUSD", PERIOD_H6, 0.0, 20,33,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// Loading of parameters was  unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
               " on symbol ", model_macd.Symbol(), " creation has failed");
      }
      // 1.6 GBPUSD H12; FMA=12; SMA=30; 
      model_macd = new cmodel_macd; // Initialize the pointer by the model MACD
      if(model_macd.Init(129475, "Model macd H6", "GBPUSD", PERIOD_H12, 0.0, 12,30,9))
      { 
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " successfully created");
         list_model.Add(model_macd);// Load the model into the list of models
      }
      else
      {// Loading of parameters was unsuccessful
         Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), 
              " on symbol ", model_macd.Symbol(), " creation has failed");
      }
   }
//----------------------------------------------------------------------------------------------
//-------------------------------------BOLLINGER DEFAULT----------------------------------------
   if(bollinger_default==true&&bollinger_best==false)
   {
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger",_Symbol,PERIOD_CURRENT,0,
                             period_bollinger,dev_bollinger,0,14,k_ATR))
      {
         Print("Model ", model_bollinger.Name(), " successfully created");
         list_model.Add(model_bollinger);
      }
   }
//----------------------------------------------------------------------------------------------
//--------------------------------------BOLLLINGER BEST-----------------------------------------
   if(bollinger_best==true&&bollinger_default==false)
   {
      //2.1 Symbol: EURUSD M30; period: 15; deviation: 2,75; k_ATR=2,75;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","EURUSD",PERIOD_M30,0,15,2.75,0,14,2.75))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
              ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.2 Symbol: EURUSD H4; period: 30; deviation: 2.0; k_ATR=2.25;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","EURUSD",PERIOD_H4,0,30,2.00,0,14,2.25))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.3 Symbol: GBPUSD M15; period: 18; deviation: 2.25; k_ATR=3.0;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","GBPUSD",PERIOD_M15,0,18,2.25,0,14,3.00))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.4 Symbol: GBPUSD H1; period: 27; deviation: 2.25; k_ATR=3.75;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","GBPUSD",PERIOD_H1,0,27,2.25,0,14,3.75))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.5 Symbol: USDCAD M15; period: 18; deviation: 2.5; k_ATR=2.00;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","USDCAD",PERIOD_M15,0,18,2.50,0,14,2.00))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
      //2.6 Symbol: USDCAD M15; period: 21; deviation: 2.5; k_ATR=3.25;
      model_bollinger = new cmodel_bollinger;
      if(model_bollinger.Init(1829374,"Bollinger","USDCAD",PERIOD_H2,0,21,2.50,0,14,3.25))
      {
         Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(),
         ". Symbol: ", model_bollinger.Symbol(), " successfully created");
         list_model.Add(model_bollinger);
      }
   }
//----------------------------------------------------------------------------------------------
}

Ora testiamo tutti e dodici i modelli contemporaneamente:


Capitalizzazione dei risultati

Il grafico che ne risulta è impressionante. Tuttavia, ciò che è importante non è il risultato, ma il fatto che tutti i modelli facciano trading contemporaneamente e utilizzando tutti i propri stop protettivi individuali, indipendenti l'uno dall'altro. Ora proviamo a capitalizzare il grafico che ne deriva. Per questo utilizzeremo le funzioni standard di capitalizzazione: il metodo proporzionale fisso e il metodo proposto da Ryan Jones.

La cosiddetta f ottimale, un caso speciale del metodo proporzionale fisso. L'essenza di questo metodo è che a ogni operazione viene assegnato un limite di perdite, pari a una percentuale del conto. Le strategie di capitalizzazione moderate di solito applicano un limite di perdita del 2%. Vale a dire che a 10.000 $ la dimensione della posizione è calcolata in modo che la perdita, dopo che è stato superato lo Stop Loss, non superi i 200 $. Esiste, tuttavia, una certa funzione della crescita del saldo finale dall'aumento del rischio. Questa funzione ha una forma a campana. Ovvero, in un primo momento, con l'aumento del rischio c'è un aumento del profitto totale. Tuttavia, esiste una soglia di rischio per ogni operazione, dopo la quale il saldo del profitto totale inizia a diminuire. Questa soglia è la cosiddetta f ottimale.

Questo articolo non è dedicato alla questione della gestione del capitale. Tutto ciò che dobbiamo sapere per utilizzare il metodo proporzionale fisso riguarda il livello di stop protettivi e la percentuale del conto che possiamo rischiare. La formula di Ryan Jones è costruita in modo diverso. Per il suo lavoro, non è richiesto l'uso di arresti protettivi fissi. Poiché il primo dei modelli proposti (modello MACD) è piuttosto primitivo e non ha stop protettivi, utilizzeremo questo metodo per la capitalizzazione di questo modello. Per il modello, basato sulle bande di Bollinger, utilizzeremo il metodo proporzionale fisso delle proporzioni.

Per iniziare a usare la formula della capitalizzazione, dobbiamo riempire la variabile m_delta, inclusa nel modello base.

Quando si utilizza la formula delle proporzioni fisse, deve essere uguale alla percentuale di rischio per ogni operazione. Quando si utilizza il metodo di Ryan Jones, è uguale al cosiddetto incremento delta, cioè alla quantità di denaro che deve essere guadagnata per arrivare a un livello più alto di volume di posizione.

Di seguito è riportato il grafico della capitalizzazione:


Come si può osservare, tutti i modelli possiedono le proprie formule di capitalizzazione (il metodo proporzionale fisso o il metodo di Ryan Jones).

Nell'esempio fornito, abbiamo utilizzato gli stessi valori di rischio massimo e delta per tutti i modelli. Tuttavia, per ciascuno di essi, possiamo selezionare singoli parametri per la capitalizzazione. La messa a punto di ciascuno dei modelli non è inclusa nella gamma di questioni trattate da questo articolo.


Lavorare con gli ordini in sospeso

I modelli di trading presentati non utilizzano i cosiddetti ordini in sospeso. Si tratta di ordini che vengono eseguiti solo quando sono presenti determinati termini e condizioni.

In realtà, qualsiasi strategia di trading che utilizza ordini in sospeso può essere adattata all'uso di ordini nel mercato. Piuttosto, gli ordini in sospeso sono necessari per rendere il funzionamento del sistema più affidabile perché, in caso di guasto del robot o dell'attrezzatura su cui opera, gli ordini in sospeso eseguirebbero comunque arresti protettivi o, viceversa, entrerebbero nel mercato basato sui prezzi precedentemente stabiliti.

Il modello di trading proposto ti consente di lavorare con ordini di trading in sospeso, sebbene il processo di controllo della loro presentazione, in questo caso, sia molto più complicato. Per lavorare con questi ordini, utilizziamo il metodo sovraccaricato Add (COrderInfo & order_info, double stop_loss, double take_profit) della classe CTableOrders. In questo caso, la variabile m_type di questa classe conterrà il tipo appropriato dell'ordine in sospeso, ad esempio ORDER_TYPE_BUY_STOP o ORDER_TYPE_SELL_LIMIT.

Successivamente, quando l'ordine in sospeso verrà emesso, è necessario "catturare" il momento in cui è stato attivato per funzionare o il suo termine di pertinenza scadrà. Non è così semplice, dal momento che non è sufficiente controllare semplicemente l'evento Trade, ma è anche necessario sapere cosa l'ha attivato.

Il linguaggio di MQL5 si sta evolvendo e, attualmente, si sta risolvendo il problema dell'inclusione di una struttura di servizio speciale al suo interno che spiegherebbe l'evento Trade. Ma, per adesso, dobbiamo visualizzare l'elenco degli ordini nella cronologia. Se un ordine con lo stesso ticket dell'ordine in sospeso nella tabella degli ordini viene trovato nella cronologia, si sono verificati degli eventi che devono essere riflessi nella tabella. 

Il codice del modello base include un metodo speciale CModel:: ReplaceDelayedOrders. Questo metodo funziona sul seguente algoritmo. Innanzitutto, vengono controllati tutti gli ordini attivi nella tabella degli ordini. Il ticket di questi ordini viene confrontato con il ticket degli ordini nello storico (HistoryOrderGetTicket()). Se il ticket dell'ordine nella cronologia coincide con l'ordine in sospeso nella tabella degli ordini, ma lo stato dell'ordine viene eseguito (ORDER_STATE_PARTIAL o ORDER_STATE_FILLED), allora anche lo stato degli ordini in sospeso nella tabella degli ordini viene modificato come eseguito.

Inoltre, se questo ordine non è collegato ad alcun ordine in sospeso, imitando il lavoro di Stop Loss e Take Profit, tale ordine viene emesso e i relativi ticket vengono inseriti nelle tabelle valori appropriate (TicketSL, TicketTP). E gli ordini in sospeso, imitando lo stop protettivo e i livelli di profitto, vengono emessi al prezzo specificato in anticipo con l'aiuto delle variabili m_sl e m_tp. Ovvero, questi prezzi devono essere conosciuti nel momento in cui si chiama il metodo ReplaceDelayedOrders.

È interessante notare che il metodo è adattato per gestire la situazione in cui un ordine viene immesso sul mercato, in cui gli ordini pendenti richiesti sono del tipo Stop Loss e Take Profit.

In generale, il lavoro del metodo proposto non è banale e richiede una certa comprensione per il suo utilizzo:

bool CModel::ReplaceDelayedOrders(void)
{
   if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)return(false);
   CTableOrders *t;
   int total_elements;
   int history_orders=HistoryOrdersTotal();
   ulong ticket;
   bool rez=false;
   long request;
   total_elements=ListTableOrders.Total();
   int try=0;
   if(total_elements==0)return(false);
   // View every order in the table
   for(int i=total_elements-1;i>=0;i--)
   {
      if(CheckPointer(ListTableOrders)==POINTER_INVALID)continue;
      t=ListTableOrders.GetNodeAtIndex(i);
      if(CheckPointer(t)==POINTER_INVALID)continue;
      switch(t.Type())
      {
         case ORDER_TYPE_BUY:
         case ORDER_TYPE_SELL:
            for(int b=0;i<history_orders;b++)
            {
               ticket=HistoryOrderGetTicket(b);
               // If the ticket of the historical order is equal to one of the tickets 
               // Stop Loss or Take Profit, then the order was blocked, and it needs to be
               // deleted from the table of orders
               if(ticket==t.TicketSL()||ticket==t.TicketTP())
               {
                  ListTableOrders.DeleteCurrent();
               }
            }
            // If the orders, imitating the Stop Loss and Take Profit are not found in the history,
            // then perhaps they are not yet set. Therefore, they need to be inputted,
            // using the process for pending orders below:
            // the cycle  keeps going, the exit 'break' does not exist!!!
         case ORDER_TYPE_BUY_LIMIT:
         case ORDER_TYPE_BUY_STOP:
         case ORDER_TYPE_BUY_STOP_LIMIT:
         case ORDER_TYPE_SELL_LIMIT:
         case ORDER_TYPE_SELL_STOP:
         case ORDER_TYPE_SELL_STOP_LIMIT:
            for(int b=0;i<history_orders;b++)
            {
               ticket=HistoryOrderGetTicket(b);
               // If the ticket of the historical order is equal to the ticket of the pending order 
               // then the pending order has worked and needs to be put out
               // the pending orders, imitating the work of Stop Loss and Take Profit.
               // It is also necessary to change the pending status of the order in the table
               // of orders for the executed (ORDER_TYPE_BUY или ORDER_TYPE_SELL)
               m_order_info.InfoInteger(ORDER_STATE,request);
               if(t.Ticket()==ticket&&
                  (request==ORDER_STATE_PARTIAL||request==ORDER_STATE_FILLED))
                  {
                  // Change the status order in the table of orders:
                  m_order_info.InfoInteger(ORDER_TYPE,request);
                  if(t.Type()!=request)t.Type(request);
                  //------------------------------------------------------------------
                  // Put out the pending orders, imitating Stop Loss an Take Profit:
                  // The level of pending orders, imitating Stop Loss and Take Profit
                  // should be determined earlier. It is also necessary to make sure that
                  // the current order is not already linked with the pending order, imitating Stop Loss
                  // and Take Profit:
                  if(t.StopLoss()!=0.0&&t.TicketSL()==0)
                    {
                     // Try to put out the pending order:
                     switch(t.Type())
                     {
                        case ORDER_TYPE_BUY:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.SellStop(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                        case ORDER_TYPE_SELL:
                           // Make three attempts to put up a pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.BuyStop(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                     }
                  }
                  if(t.TakeProfit()!=0.0&&t.TicketTP()==0){
                     // Attempt to put out the pending order, imitating Take Profit:
                     switch(t.Type())
                     {
                        case ORDER_TYPE_BUY:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.SellLimit(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                           break;
                        case ORDER_TYPE_SELL:
                           // Make three attempts to put out the pending order
                           for(try=0;try<3;try++)
                           {
                              m_trade.BuyLimit(t.VolumeInitial(),t.StopLoss(),m_symbol,0.0,0.0,0,0,"take-profit for buy");
                              if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE)
                              {
                                 t.TicketTP(m_trade.ResultDeal());
                                 break;
                              }
                           }
                     }
                  }
               }
            }
            break;
         
      }
   }
   return(true);
}

Utilizzando questo metodo, puoi creare facilmente un modello basato sugli ordini in sospeso.


Conclusione

Sfortunatamente, è impossibile parlare in un unico articolo di tutte le sfumature, le sfide e i vantaggi dell'approccio proposto. Non abbiamo considerato la serializzazione dei dati, un metodo che consente di archiviare, registrare e recuperare tutte le informazioni necessarie sullo stato attuale dei modelli dai file di dati. Non abbiamo considerato i modelli di trading basati sullo scambio di spread sintetici. Questi argomenti sono molto interessanti e, certamente, offrono delle soluzioni efficaci per i concetti proposti.

La cosa principale che siamo riusciti a fare è sviluppare una struttura dati completamente dinamica e gestibile. Il concetto di elenchi collegati le gestisce efficacemente, rende le tattiche di trading indipendenti e personalizzabili individualmente. Un altro vantaggio molto importante di questo approccio è che è completamente universale.

Ad esempio, sulla sua base, è sufficiente creare due EA di trading e posizionarli sullo stesso strumento. Entrambi lavoreranno automaticamente solo con i propri ordini e solo con il proprio sistema di gestione del capitale. Quindi, c'è un supporto di una compatibilità verso il basso. Tutto ciò che può essere fatto contemporaneamente in un EA, può essere distribuito tra più robot. Questa proprietà è estremamente importante quando si lavora in posizioni nette.

Il modello descritto non è solo una teoria. Comprende un apparato avanzato di funzioni ausiliarie, le funzioni per la gestione del capitale e il controllo dei fabbisogni marginali. Il sistema di invio degli ordini è resistente alle cosiddette riquotazioni e slippage, i cui effetti si vedono spesso nel trading reale.

Il motore di trading determina la dimensione massima della posizione e il volume massimo delle operazioni. Un algoritmo speciale separa le richieste di trading in diverse operazioni indipendenti, ognuna delle quali viene elaborata separatamente. Inoltre, il modello presentato si è dimostrato efficace nella Automated Trading Championship del 2010: tutte le offerte vengono effettuate accuratamente e in accordo con il piano di trading e i modelli di trading presentati nella Championship sono gestiti con diversi gradi di rischio e su diversi sistemi di gestione del denaro.

L'approccio presentato è una soluzione quasi completa per la partecipazione alle Championship, nonché per l'operazione parallela su più strumenti e intervalli di tempo e su diverse tattiche di trading. L'unica difficoltà per familiarizzare con questo approccio risiede nella sua complessità. 

Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/217

File allegati |
files-en.zip (18.6 KB)
Gli indicatori dei trend micro, medie e principali Gli indicatori dei trend micro, medie e principali
Lo scopo di questo articolo è indagare le possibilità del trading e dell'analisi sulla base di alcune idee tratte dal libro di James Hyerczyk "Pattern, Price & Time: Using Gann Theory in Trading Systems" sotto forma di indicatori ed Expert Advisor. Senza pretendere di essere esaustivi, qui indagheremo solo il Modello, la prima parte della teoria di Gann.
Disegnare i canali - Vista interna ed esterna Disegnare i canali - Vista interna ed esterna
Suppongo che non risulterà esagerato dire che i canali sono lo strumento più popolare per l'analisi del mercato e per prendere decisioni di trading dopo le medie mobili. Senza andare a fondo nella moltitudine delle strategie di trading che utilizzano i canali e i loro componenti, discuteremo le basi matematiche e l'implementazione pratica di un indicatore, il quale disegna un canale determinato da tre estremi sullo schermo del client terminal.
Wizard MQL5: Come creare un modulo di trailing delle posizioni aperte Wizard MQL5: Come creare un modulo di trailing delle posizioni aperte
Il generatore di strategie di trading del Wizard MQL5 semplifica enormemente la verifica delle idee di trading. L'articolo parla di come scrivere e connettere al generatore di strategie di trading Wizard MQL5 la tua classe di gestione delle posizioni aperte spostando il livello di Stop Loss in una zona senza perdite quando il prezzo va nella direzione della posizione, consentendo di proteggere i tuoi drawdown di diminuzione del profitto quando fai trading. Descrive anche la struttura e il formato della descrizione della classe creata per il Wizard MQL5.
Calcoli paralleli su MetaTrader 5 Calcoli paralleli su MetaTrader 5
Il tempo ha sempre avuto un grande valore in tutta la storia dell'umanità e noi ci sforziamo di non sprecarlo inutilmente. Questo articolo ti dirà come accelerare il lavoro del tuo Expert Advisor se il tuo computer ha un processore multi-core. Inoltre, l'implementazione del metodo proposto non richiede la conoscenza di altri linguaggi oltre a MQL5.