Petit exemple de bidouille d'un EA pour le passer en Programmation Orientée Objet.
Deja a quoi ca sert ?
Pour schématiser, un EA c'est un truc qui a des entrées et des sorties.
En entrées, (tout ce dont l'EA a besoin pour fonctionner) on a :
-> Les evements OnInit(), OnTick()...etc
-> Les données des cours Bid[0], des indicateursiMA()..etc
-> Les données sur les ordres ouverts & fermés (OrderSelect/OrdersTotal...etc)
En sortie, on a (tout ce que produit l'EA):
-> Des trades (OrderSend/OrderClose)
L'idée de base, est de mettre des couches de code supplémentaires au niveau des entrées&sorties pour etre un peu + libre de faire ce que l'on veut.
Exemple :
-le faire tourner sur des series style renko ou autre faites maison
-faire du position centric plutot que du trade centric
-faire croire a l'EA qu'il trade, alors qu'on envoit les trades reels que sous certaines conditions
Bref, tout plein de choses.
Ci-dessous un peu exemple simple :
Je pars de l'EA livré avec MT4, et avec un minimum de modifications dans le code (et moins de 5mins de taff), faire tourner 2 EAs conjointement en un seul backtest.(exemple le 1er sur une MM10 l'autre sur une MM20)
Mais pour l'instant, on a les pattes un peu liées, on a un fichier .mq4 qui contient un EA, et on lance le backtest sur ce fichier.
L'idée est donc de décorréler le fichier de test de la notion d'EA.
Voici le fichier de départ :
Code : Tout sélectionner
//+------------------------------------------------------------------+
//| Moving Average.mq4 |
//| Copyright 2005-2014, MetaQuotes Software Corp. |
//| http://www.mql4.com |
//+------------------------------------------------------------------+
#property copyright "2005-2014, MetaQuotes Software Corp."
#property link "http://www.mql4.com"
#property description "Moving Average sample expert advisor"
//--- Inputs
input double Lots =0.1;
input int MovingPeriod =12;
input int MovingShift =6;
input int MagicNumber=20131111;
//+------------------------------------------------------------------+
//| Calculate open positions |
//+------------------------------------------------------------------+
int CalculateCurrentOrders(string symbol)
{
int buys=0,sells=0;
//---
for(int i=0;i<OrdersTotal();i++)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
if(OrderSymbol()==Symbol() && OrderMagicNumber()==MagicNumber)
{
if(OrderType()==OP_BUY) buys++;
if(OrderType()==OP_SELL) sells++;
}
}
//--- return orders volume
if(buys>0) return(buys);
else return(-sells);
}
//+------------------------------------------------------------------+
//| Check for open order conditions |
//+------------------------------------------------------------------+
void CheckForOpen()
{
double ma;
int res;
//--- go trading only for first tiks of new bar
if(Volume[0]>1) return;
//--- get Moving Average
ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//--- sell conditions
if(Open[1]>ma && Close[1]<ma)
{
res=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,0,"",MagicNumber,0,Red);
return;
}
//--- buy conditions
if(Open[1]<ma && Close[1]>ma)
{
res=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,0,"",MagicNumber,0,Blue);
return;
}
//---
}
//+------------------------------------------------------------------+
//| Check for close order conditions |
//+------------------------------------------------------------------+
void CheckForClose()
{
double ma;
//--- go trading only for first tiks of new bar
if(Volume[0]>1) return;
//--- get Moving Average
ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//---
for(int i=0;i<OrdersTotal();i++)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
if(OrderMagicNumber()!=MagicNumber || OrderSymbol()!=Symbol()) continue;
//--- check order type
if(OrderType()==OP_BUY)
{
if(Open[1]>ma && Close[1]<ma)
{
if(!OrderClose(OrderTicket(),OrderLots(),Bid,3,White))
Print("OrderClose error ",GetLastError());
}
break;
}
if(OrderType()==OP_SELL)
{
if(Open[1]<ma && Close[1]>ma)
{
if(!OrderClose(OrderTicket(),OrderLots(),Ask,3,White))
Print("OrderClose error ",GetLastError());
}
break;
}
}
//---
}
//+------------------------------------------------------------------+
//| OnTick function |
//+------------------------------------------------------------------+
void OnTick()
{
//--- check for history and trading
if(Bars<100 || IsTradeAllowed()==false)
return;
//--- calculate open orders by current symbol
if(CalculateCurrentOrders(Symbol())==0) CheckForOpen();
else CheckForClose();
//---
}
//+------------------------------------------------------------------+
- des parametres (input double Lots/input MovingPeriod...etc)
- des fonctions ( CalculateCurrentOrders,CheckForOpen,CheckForClose,OnTick)
- des variables globales (pas de bol ici il n'y en a pas exceptées les variables "input")
ETAPE 1 :
Allons-y, je crée une classe que j'appelle cMMA
Au départ, ca ressemble a ca :
Code : Tout sélectionner
class cMMA
{
private:
public:
cMMA(){;};
~cMMA(){;};
}; // <<---- fin de l'entete
cMMA::cMMA()
{
}
cMMA::~cMMA()
{
}
Ici de base, l'assistant a créé 2 methode, l'une du nom de la classe et l'autre ~NomDeLaClasse
La 1ere est le constructeur (i.e. en qlqsorte la méthode OnInit() appelée lors de la création de notre objet)
La 2eme est le destructeur (i.e. en qlqsorte la méthode DeInit() appelée lors de la destructeur de notre objet)
Pour chaque membre/fonction on peut choisir s'ils seront publics ou private. Ca veut juste dire, est-ce qu'il seront accessible ou non depuis l'exterieur de la classe.
La 2eme partie, c'est le corps des fonctions que l'ont a definies dans l'entete (ici constructeur et destructeur qui ne font rien)
ETAPE 2 :
Mon EA pour fonctionner aura besoin des variables declarées input du fichier de depart, je fais donc un copier-coller, je vire le mot "input" et les valeurs par defaut, on obtient ca :
Code : Tout sélectionner
class cMMA
{
private:
double Lots;
int MovingPeriod;
int MovingShift;
int MagicNumber;
public:
cMMA();
~cMMA();
};
cMMA::cMMA()
{
}
cMMA::~cMMA()
{
}
L'algo de l'EA, on va declarer les signatures de ses 4 fonctions CalculateCurrentOrders/CheckForOpen/CheckForClose/OnTick
Alors les 3eres, c'est sa toutouille interne, donc on va les mettre en private, par contre OnTick() on aura besoin de la declencher donc on la met publique :
Code : Tout sélectionner
class cMMA
{
private:
double Lots;
int MovingPeriod;
int MovingShift;
int MagicNumber;
int CalculateCurrentOrders(string symbol);
void CheckForOpen();
void CheckForClose();
public:
cMMA();
~cMMA();
void OnTick();
};
cMMA::cMMA()
{
}
cMMA::~cMMA()
{
}
Code : Tout sélectionner
void cMMA::OnTick()
{
//--- check for history and trading
if(Bars<100 || IsTradeAllowed()==false)
return;
//--- calculate open orders by current symbol
if(CalculateCurrentOrders(Symbol())==0) CheckForOpen();
else CheckForClose();
//---
}
Une fois ceci fait, normalement ca compile.
Il ne reste plus qu'a pouvoir passer des valeurs a nos membres Lots/MovingPeriod/MovingShift/MagicNumber
Ceci, on va le faire dès la creation de notre objet. Donc on choisi de prendre ces valeurs en parametre de notre constructeur,
Je modifie donc la declaration du constructeur :
Code : Tout sélectionner
public:
cMMA(double lots, int period,int shift,int magicNumber);
~cMMA();
void OnTick();
Puis dans son code (qui etait vide jusqu'alors), j'affecte les valeurs passées en paramètres aux membres :
Code : Tout sélectionner
cMMA::cMMA(double lots, int period,int shift,int magicNumber)
{
Lots=lots;
MovingPeriod=period;
MovingShift=shift;
MagicNumber=magicNumber;
}
Ca y est mon EA est devenu une classe !
... je le remets pour ceux qui n'ont pas suivi :
Code : Tout sélectionner
//+------------------------------------------------------------------+
//| cMMA.mqh |
//| Copyright 2015, MetaQuotes Software Corp. |
//| http://www.mql4.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, MetaQuotes Software Corp."
#property link "http://www.mql4.com"
#property version "1.00"
#property strict
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class cMMA
{
private:
double Lots;
int MovingPeriod;
int MovingShift;
int MagicNumber;
int CalculateCurrentOrders(string symbol);
void CheckForOpen();
void CheckForClose();
public:
cMMA(double lots, int period,int shift,int magicNumber);
~cMMA();
void OnTick();
};
cMMA::cMMA(double lots, int period,int shift,int magicNumber)
{
Lots=lots;
MovingPeriod=period;
MovingShift=shift;
MagicNumber=magicNumber;
}
cMMA::~cMMA()
{
}
int cMMA::CalculateCurrentOrders(string symbol)
{
int buys=0,sells=0;
//---
for(int i=0;i<OrdersTotal();i++)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
if(OrderSymbol()==Symbol() && OrderMagicNumber()==MagicNumber)
{
if(OrderType()==OP_BUY) buys++;
if(OrderType()==OP_SELL) sells++;
}
}
//--- return orders volume
if(buys>0) return(buys);
else return(-sells);
}
//+------------------------------------------------------------------+
//| Check for open order conditions |
//+------------------------------------------------------------------+
void cMMA::CheckForOpen()
{
double ma;
int res;
//--- go trading only for first tiks of new bar
if(Volume[0]>1) return;
//--- get Moving Average
ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//--- sell conditions
if(Open[1]>ma && Close[1]<ma)
{
res=OrderSend(Symbol(),OP_SELL,Lots,Bid,3,0,0,"",MagicNumber,0,Red);
return;
}
//--- buy conditions
if(Open[1]<ma && Close[1]>ma)
{
res=OrderSend(Symbol(),OP_BUY,Lots,Ask,3,0,0,"",MagicNumber,0,Blue);
return;
}
//---
}
//+------------------------------------------------------------------+
//| Check for close order conditions |
//+------------------------------------------------------------------+
void cMMA::CheckForClose()
{
double ma;
//--- go trading only for first tiks of new bar
if(Volume[0]>1) return;
//--- get Moving Average
ma=iMA(NULL,0,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE,0);
//---
for(int i=0;i<OrdersTotal();i++)
{
if(OrderSelect(i,SELECT_BY_POS,MODE_TRADES)==false) break;
if(OrderMagicNumber()!=MagicNumber || OrderSymbol()!=Symbol()) continue;
//--- check order type
if(OrderType()==OP_BUY)
{
if(Open[1]>ma && Close[1]<ma)
{
if(!OrderClose(OrderTicket(),OrderLots(),Bid,3,White))
Print("OrderClose error ",GetLastError());
}
break;
}
if(OrderType()==OP_SELL)
{
if(Open[1]<ma && Close[1]>ma)
{
if(!OrderClose(OrderTicket(),OrderLots(),Ask,3,White))
Print("OrderClose error ",GetLastError());
}
break;
}
}
//---
}
//+------------------------------------------------------------------+
//| OnTick function |
//+------------------------------------------------------------------+
void cMMA::OnTick()
{
//--- check for history and trading
if(Bars<100 || IsTradeAllowed()==false)
return;
//--- calculate open orders by current symbol
if(CalculateCurrentOrders(Symbol())==0) CheckForOpen();
else CheckForClose();
//---
}
Reste a l'appeler...
Je crée un nouveau fichier EA, en virant tout sauf la fonction OnTick()
Dans ce fichier, je vais avoir besoin de ma classe qui est dans un autre fichier, donc j'ajoute
#include "cMMA.mqh"
OnInit() va me servir a instancier (~= creer un cMMA en lui passant les parametres), et OnTick(), on va l'utiliser pour pour appeler le OnTick() de mon cMMA.
Comme toute variable utilisée dans 2 fonctions differentes, on la déclare au niveau de l'EA :
Code : Tout sélectionner
#property strict
#include "cMMA.mqh"
cMMA mma1; // declaration de la variable
void OnTick()
{
}
Effectivement, on a ajouté 4 parametres a notre constructeur donc il faut mettre par exemple
cMMA mma1(0.1,12,6,123456);
Dès cette declaration, le code que l'on a ajouté dans le constructeur est exécuté.
enfin dans le OnTick() du fichier EA, on va appeler le OnTick() de notre classe.
Voici le fichier obtenu au final :
Code : Tout sélectionner
#include "cMMA.mqh"
cMMA mma1(0.1,10,6,123456);
void OnTick()
{
mma1.OnTick();
}
A ce stade si on le fait tourner on obtient les memes resultats que l'EA de départ.
Maintenant je veux faire tourner 2 EAs, il suffit de declarer une autre variable, et d'appeler son OnTick :
Code : Tout sélectionner
#include "cMMA.mqh"
cMMA mma1(0.1,10,6,123456);
cMMA mma2(0.1,20,6,987654); // <--- ne pas oublier de passer un MagicNumber different !!!
void OnTick()
{
mma1.OnTick();
mma2.OnTick();
}
C'est tout pour le moment, mais si ca peut donner des idées a des gens...