Einen interaktiven, halbautomatischen Drag-and-Drop Expert Advisor auf Grundlage vorab festgelegter Risiken und dem R/R-Verhältnis (relatives Risiko) bauen

investeo | 14 März, 2016

Einleitung

Einige Händler führen all ihre Handel automatisch aus und einige arbeiten sowohl mit automatischen als auch manuellem Handeln auf Grundlage der Ergebnisse verschiedener Indikatoren. Da ich zur zweiten Gruppe gehöre, wollte ich ein interaktives Tool, mit dem ich Risiko- und Prämien-Levels direkt vom Chart aus dynamisch abschätzen kann.

Nach der Deklarierung des maximalen Risikos für mein Eigenkapital, wollte ich Echtzeit-Parameter auf Grundlage des Stop-Loss Levels, das ich ins Chart eingetragen habe, berechnen und meinen Handel, auf Grundlage der berechneten SL- und TP-Levels direkt aus dem EA ausführen können.

In diesem Beitrag wird erläutert, wie man einen interaktiven, halb-automatischen Expert Advisor mit vorab festgelegten Eigenkapitalrisiko und einem R/R-Verhältnis implementiert. Das Expert Advisor Risiko sowie die Parameter für relativer Risiko und die Postengrößen können während der EA-Laufzeit in seinem Bedienfeld verändert werden. 


1. Anforderungen

Folgende Anforderungen bestanden für den EA:

2. Design

Angesichts der Anforderungen an den EA, Parameter während seiner Laufzeit anzeigen und verändern zu können, habe ich mich für den Einsatz der CChartObject  Klassen und ihrer Nachkommen zur Anzeige der grafischen Benutzeroberfläche (GUI) im Chartfenster entschieden sowie zur Verarbeitung ankommender Chart-Ereignisse für Benutzerinteraktion. Daher brauchte der EA eine Benutzerschnittstelle mit Kennzeichnungen sowie Schalt- und Bearbeitungsflächen.

Ich wollte zuerst das CChartObjectPanel Objekt zur Gruppierung anderer Objekte im Bedienfeld des EA verwenden, entschloss mich jedoch für einen anderen Weg: ich entwarf eine Klasse, die Kennzeichnungen und Bearbeitungsfelder und Schaltflächen enthält und sie im Bildhintergrund anzeigt. Das Hintergrundbild der Schnittstelle wurde mit Hilfe der GIMP Software angelegt. Die von MQL5 generierten Objekte sind das Bearbeitungsfeld, rote, in Echtzeit aktualisierte Kennzeichnungen und Schaltflächen.

Ich habe einfach Kennzeichnungsobjekte ins Chart gesetzt, ihre Position aufgezeichnet und eine CRRDialog Klasse gebaut, die alle Funktionen der Anzeige errechneten Ergebnisses und empfangener Parameter der CChartObjectEdit Felder sowie die Aufzeichnung der Schaltflächen-Stati verarbeitet. Die Farbe des Risikos und die Prämienrechtecke sind Objekte der CChartObjectRectangle Klasse; der 'dragfähige' Stop-Loss Zeiger ist ein Bitmap-Objekt der CChartObjectBitmap Klasse.

 

Abb. 1 Screenshot des EA

Abb. 1 Screenshot des EA

 


3. Implementierung der EA Dialogklasse

Die CRRDialog Klasse verarbeitet die gesamte Benutzerschnittstelle des EA. Sie enthält einige Variablen, die angezeigt werden, Objekte, die zur Anzeige der Variablen notwendig sind und Methoden, um die Variablenwerte zu bekommen/einzurichten und den Dialog zu aktualisieren.

Für den Hintergrund verwende ich das CChartObjectBmpLabel Objekt, für die Bearbeitungsfelder die CChartObjectEdit Objekte, zur Anzeige der Kennzeichnungen die CChartObjectLabel Objekte und die CChartObjectButton Objekte für die Schaltflächen: 

class CRRDialog
  {
private:

   int               m_baseX;
   int               m_baseY;
   int               m_fontSize;
   
   string            m_font;
   string            m_dialogName;
   string            m_bgFileName;

   double            m_RRRatio;
   double            m_riskPercent;
   double            m_orderLots;
   double            m_SL;
   double            m_TP;
   double            m_maxAllowedLots;
   double            m_maxTicksLoss;
   double            m_orderEquityRisk;
   double            m_orderEquityReward;
   ENUM_ORDER_TYPE   m_orderType;

   CChartObjectBmpLabel m_bgDialog;

   CChartObjectEdit  m_riskRatioEdit;
   CChartObjectEdit  m_riskValueEdit;
   CChartObjectEdit  m_orderLotsEdit;

   CChartObjectLabel m_symbolNameLabel;
   CChartObjectLabel m_tickSizeLabel;
   CChartObjectLabel m_maxEquityLossLabel;
   CChartObjectLabel m_equityLabel;
   CChartObjectLabel m_profitValueLabel;
   CChartObjectLabel m_askLabel;
   CChartObjectLabel m_bidLabel;
   CChartObjectLabel m_tpLabel;
   CChartObjectLabel m_slLabel;
   CChartObjectLabel m_maxAllowedLotsLabel;
   CChartObjectLabel m_maxTicksLossLabel;
   CChartObjectLabel m_orderEquityRiskLabel;
   CChartObjectLabel m_orderEquityRewardLabel;
   CChartObjectLabel m_orderTypeLabel;

   CChartObjectButton m_switchOrderTypeButton;
   CChartObjectButton m_placeOrderButton;
   CChartObjectButton m_quitEAButton;

public:

   void              CRRDialog(); // CRRDialog constructor
   void             ~CRRDialog(); // CRRDialog destructor

   bool              CreateCRRDialog(int topX,int leftY);
   int               DeleteCRRDialog();
   void              Refresh();
   void              SetRRRatio(double RRRatio);
   void              SetRiskPercent(double riskPercent);
   double            GetRiskPercent();
   double            GetRRRRatio();
   void              SetSL(double sl);
   void              SetTP(double tp);
   double            GetSL();
   double            GetTP();
   void              SetMaxAllowedLots(double lots);
   void              SetMaxTicksLoss(double ticks);
   void              SetOrderType(ENUM_ORDER_TYPE);
   void              SwitchOrderType();
   void              ResetButtons();
   ENUM_ORDER_TYPE   GetOrderType();
   void              SetOrderLots(double orderLots);
   double            GetOrderLots();
   void              SetOrderEquityRisk(double equityRisk);
   void              SetOrderEquityReward(double equityReward);
  };

Da die Methoden zum Erhalten/Einrichten der Variablen eindeutig sind, beschäftige ich mich hier nur mit den CreateCRRDialog() und Refresh() Methoden. Die CreateCRRDialog() Methode initialisiert das Hintergrundbild, die Kennzeichnungen, die Schaltflächen und die Bearbeitungsfelder.

Zur Initialisierung der Kennzeichnungen und Bearbeitungsfelder verwende ich: Die Create() Methode mit Koordinaten-Parameter, um das Objekt auf dem Chart zu finden, die Font() und FontSize() Methode zur Einrichtung der Schriftart und die Description() Methode, um die Kennzeichnung mit Text zu versehen.

Für die Schaltflächen: Die zusätzlichen Parameter der Create() Methode legt die Größe der Schaltfläche fest und die BackColor() Methode legt die Hintergrundfarbe der Schaltfläche fest. 

bool CRRDialog::CreateCRRDialog(int topX,int leftY)
  {
   bool isCreated=false;

   MqlTick current_tick;
   SymbolInfoTick(Symbol(),current_tick);

   m_baseX = topX;
   m_baseY = leftY;

   m_bgDialog.Create(0, m_dialogName, 0, topX, leftY);
   m_bgDialog.BmpFileOn(m_bgFileName);

   m_symbolNameLabel.Create(0, "symbolNameLabel", 0, m_baseX + 120, m_baseY + 40);
   m_symbolNameLabel.Font("Verdana");
   m_symbolNameLabel.FontSize(8);
   m_symbolNameLabel.Description(Symbol());

   m_tickSizeLabel.Create(0, "tickSizeLabel", 0, m_baseX + 120, m_baseY + 57);
   m_tickSizeLabel.Font("Verdana");
   m_tickSizeLabel.FontSize(8);
   m_tickSizeLabel.Description(DoubleToString(SymbolInfoDouble(Symbol(), SYMBOL_TRADE_TICK_SIZE), Digits()));

   m_riskRatioEdit.Create(0, "riskRatioEdit", 0, m_baseX + 120, m_baseY + 72, 35, 15);
   m_riskRatioEdit.Font("Verdana");
   m_riskRatioEdit.FontSize(8);
   m_riskRatioEdit.Description(DoubleToString(m_RRRatio, 2));

   m_riskValueEdit.Create(0, "riskValueEdit", 0, m_baseX + 120, m_baseY + 90, 35, 15);
   m_riskValueEdit.Font("Verdana");
   m_riskValueEdit.FontSize(8);
   m_riskValueEdit.Description(DoubleToString(m_riskPercent, 2));

   m_equityLabel.Create(0, "equityLabel", 0, m_baseX + 120, m_baseY + 107);
   m_equityLabel.Font("Verdana");
   m_equityLabel.FontSize(8);
   m_equityLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2));

   m_maxEquityLossLabel.Create(0, "maxEquityLossLabel", 0, m_baseX + 120, m_baseY + 122);
   m_maxEquityLossLabel.Font("Verdana");
   m_maxEquityLossLabel.FontSize(8);
   m_maxEquityLossLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY)*m_riskPercent/100.0,2));

   m_askLabel.Create(0, "askLabel", 0, m_baseX + 120, m_baseY + 145);
   m_askLabel.Font("Verdana");
   m_askLabel.FontSize(8);
   m_askLabel.Description("");

   m_bidLabel.Create(0, "bidLabel", 0, m_baseX + 120, m_baseY + 160);
   m_bidLabel.Font("Verdana");
   m_bidLabel.FontSize(8);
   m_bidLabel.Description("");

   m_slLabel.Create(0, "slLabel", 0, m_baseX + 120, m_baseY + 176);
   m_slLabel.Font("Verdana");
   m_slLabel.FontSize(8);
   m_slLabel.Description("");

   m_tpLabel.Create(0, "tpLabel", 0, m_baseX + 120, m_baseY + 191);
   m_tpLabel.Font("Verdana");
   m_tpLabel.FontSize(8);
   m_tpLabel.Description("");

   m_maxAllowedLotsLabel.Create(0, "maxAllowedLotsLabel", 0, m_baseX + 120, m_baseY + 208);
   m_maxAllowedLotsLabel.Font("Verdana");
   m_maxAllowedLotsLabel.FontSize(8);
   m_maxAllowedLotsLabel.Description("");

   m_maxTicksLossLabel.Create(0, "maxTicksLossLabel", 0, m_baseX + 120, m_baseY + 223);
   m_maxTicksLossLabel.Font("Verdana");
   m_maxTicksLossLabel.FontSize(8);
   m_maxTicksLossLabel.Description("");

   m_orderLotsEdit.Create(0, "orderLotsEdit", 0, m_baseX + 120, m_baseY + 238, 35, 15);
   m_orderLotsEdit.Font("Verdana");
   m_orderLotsEdit.FontSize(8);
   m_orderLotsEdit.Description("");

   m_orderEquityRiskLabel.Create(0, "orderEquityRiskLabel", 0, m_baseX + 120, m_baseY + 255);
   m_orderEquityRiskLabel.Font("Verdana");
   m_orderEquityRiskLabel.FontSize(8);
   m_orderEquityRiskLabel.Description("");

   m_orderEquityRewardLabel.Create(0, "orderEquityRewardLabel", 0, m_baseX + 120, m_baseY + 270);
   m_orderEquityRewardLabel.Font("Verdana");
   m_orderEquityRewardLabel.FontSize(8);
   m_orderEquityRewardLabel.Description("");

   m_switchOrderTypeButton.Create(0, "switchOrderTypeButton", 0, m_baseX + 20, m_baseY + 314, 160, 20);
   m_switchOrderTypeButton.Font("Verdana");
   m_switchOrderTypeButton.FontSize(8);
   m_switchOrderTypeButton.BackColor(LightBlue);

   m_placeOrderButton.Create(0, "placeOrderButton", 0, m_baseX + 20, m_baseY + 334, 160, 20);
   m_placeOrderButton.Font("Verdana");
   m_placeOrderButton.FontSize(8);
   m_placeOrderButton.BackColor(LightBlue);
   m_placeOrderButton.Description("Place Market Order");

   m_quitEAButton.Create(0, "quitEAButton", 0, m_baseX + 20, m_baseY + 354, 160, 20);
   m_quitEAButton.Font("Verdana");
   m_quitEAButton.FontSize(8);
   m_quitEAButton.BackColor(LightBlue);
   m_quitEAButton.Description("Quit");

   return isCreated;
  }

Die Refresh() Methode aktualisiert alle Kennzeichnungen und die Beschreibungen der Schaltflächen mit den CRRDialog Variablen und den aktuellen Geld-/Brieflevels, das Eigenkapital des Accounts und die Werte des Eigenkapitalrisikos: 

void CRRDialog::Refresh()
  {
   MqlTick current_tick;
   SymbolInfoTick(Symbol(),current_tick);

   m_equityLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY),2));
   m_maxEquityLossLabel.Description(DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY)*
                                         StringToDouble(m_riskValueEdit.Description())/100.0,2));
   m_askLabel.Description(DoubleToString(current_tick.ask, Digits()));
   m_bidLabel.Description(DoubleToString(current_tick.bid, Digits()));
   m_slLabel.Description(DoubleToString(m_SL, Digits()));
   m_tpLabel.Description(DoubleToString(m_TP, Digits()));
   m_maxAllowedLotsLabel.Description(DoubleToString(m_maxAllowedLots,2));
   m_maxTicksLossLabel.Description(DoubleToString(m_maxTicksLoss,0));
   m_orderEquityRiskLabel.Description(DoubleToString(m_orderEquityRisk,2));
   m_orderEquityRewardLabel.Description(DoubleToString(m_orderEquityReward,2));

   if(m_orderType==ORDER_TYPE_BUY) m_switchOrderTypeButton.Description("Order Type: BUY");
   else if(m_orderType==ORDER_TYPE_SELL) m_switchOrderTypeButton.Description("Order Type: SELL");
  }

4. Chart-Ereignisse

Da der EA ja interaktiv sein soll, muss er Ereignisse im Chart verarbeiten können.

Dazu gehören folgende Ereignisse:

Ereignisse, die verarbeitet werden, sind CHARTEVENT_OBJECT_CLICK zur Auswahl des Zeigers und Schaltflächen, CHARTEVENT_OBJECT_DRAG zum Ziehen des S/L Zeigers und CHARTEVENT_OBJECT_ENDEDIT, nachdem die Bearbeitungsfelder vom Händler aktualisiert wurden.

Die Implementierung der OnChartEvent() Funktion hat anfänglich einige Seiten Code benötigt, doch habe ich mich entschlossen sie in mehrere Ereignis-Verarbeiter zu unterteilen, sodass die OnChartEvent() Funktion somit auch vernünftig lesbar ist: 

void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Check the event by pressing a mouse button
   if(id==CHARTEVENT_OBJECT_CLICK)
     {
      string clickedChartObject=sparam;

      if(clickedChartObject==slButtonID)
         SL_arrow.Selected(!SL_arrow.Selected());

      if(clickedChartObject==switchOrderTypeButtonID)
        {
         EA_switchOrderType();
        };

      if(clickedChartObject==placeOrderButtonID)
        {
         EA_placeOrder();
        }

      if(clickedChartObject==quitEAButtonID) ExpertRemove();

      ChartRedraw();
     }

   if(id==CHARTEVENT_OBJECT_DRAG)
     {
      // BUY 
      if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY)
        {
         EA_dragBuyHandle();
        };

      // SELL
      if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL)
        {
         EA_dragSellHandle();
        };
      ChartRedraw();
     }

   if(id==CHARTEVENT_OBJECT_ENDEDIT)
     {
      if((sparam==riskRatioEditID || sparam==riskValueEditID || sparam==orderLotsEditID) && orderPlaced==false)
        {
         EA_editParamsUpdate();
        }
     }
  }

Die Implementierung der Ereignis-Verarbeiter wird detaillierter im nächsten Abschnitt beschrieben. Besonders hinweisen möchte ich allerdings auf den Trick, den ich bei der Auswahl des SL_Pfeilobjekts angewendet habe. Um ein Objekt auf dem Chart auszuwählen, muss man normalerweise mit einem Doppelklick draufgehen, doch in meinem Fall genügt zu seiner Auswahl nur ein einmaliges Anklicken, sodass die Selected() Methode des CChartObject Objekts oder seines Nachkommens in der OnChartEvent() Funktion innerhalb des CHARTEVENT_OBJECT_CLICK Ereignis-Verarbeiters in Gang gesetzt wird:

      if(clickedChartObject==slButtonID)
         SL_arrow.Selected(!SL_arrow.Selected());

Das Objekt wird je nach seinem vorigen Status nach einem Klick ausgewählt oder eben nicht. 

5. Erweiterte Money-Management Klasse auf Grundlage der CMoneyFixedRisk

Bevor ich die ChartEvent-Verarbeiter erläutern kann, muss ich zunächst auf die Money-Management Klasse eingehen.

Für die Geldverwaltung (Money Management) habe ich erneut die CMoneyFixedRisk Klasse verwendet, die in MetaQuotes vorhanden und in der CMoneyFixedRiskExt Klasse implementiert ist.

Ursprüngliche CMoneyFixedRisk Klassenmethoden liefern mir die für einen gegebenen Preis zulässigen Mengen an Orderposten, das Stop-Loss-Level und Eigenkapitalrisiko zwischen der vom Makler angesetzten Minimal- und Maximalpostengröße. Ich habe die CheckOpenLong() und CheckOpenShort() Methoden verändert, sodass sie 0,0 Postengröße liefern, wenn die Risikovoraussetzungen nicht zutreffen und sie mit vier Methoden erweitert: GetMaxSLPossible(), CalcMaxTicksLoss(), CalcOrderEquityRisk() und CalcOrderEquityReward():

class CMoneyFixedRiskExt : public CExpertMoney
  {
public:
   //---
   virtual double    CheckOpenLong(double price,double sl);
   virtual double    CheckOpenShort(double price,double sl);
   
   double GetMaxSLPossible(double price, ENUM_ORDER_TYPE orderType);
   double CalcMaxTicksLoss();
   double CalcOrderEquityRisk(double price, double sl, double lots);
   double CalcOrderEquityReward(double price, double sl, double lots, double rrratio);
  };

Die GetMaxSLPossible() Methode berechnet den Maximalen Stop-Loss Kurswert für ein gegebenes Kapitalrisiko und die minimal zulässige Handelsgröße.

Wenn sich also der Account-Saldo auf 10.000 der Basiswährung des Accounts beläuft und das Risiko bei 2% liegt, können wir höchstens 200 der Accountwährung riskieren Wenn die Mindestgröße des Handelsposten bei 0,1 Posten liegt, liefert diese Methode ein Kurslevel für eine ORDER_TYPE_BUY oder ORDER_TYPE_SELL Order, die den Wert für Eigenkapitalrisiko für die Position von 0,1 Posten erfüllt. Damit kann das maximale Stop-Loss Level abgeschätzt werden, das wir uns für einen Handel mit der Mindestgröße des Postens erlauben können. Dieses Preislevel können wir für ein gegebenes Eigenkapitalrisiko-Level nicht überschreiten. 

double CMoneyFixedRiskExt::GetMaxSLPossible(double price, ENUM_ORDER_TYPE orderType)
{
   double maxEquityLoss, tickValLoss, maxTicksLoss;
   double minvol=m_symbol.LotsMin();
   double orderTypeMultiplier;
   
   if(m_symbol==NULL) return(0.0);
   
   switch (orderType)
   {
   case ORDER_TYPE_SELL: orderTypeMultiplier = -1.0; break;
   case ORDER_TYPE_BUY: orderTypeMultiplier = 1.0; break;
   default: orderTypeMultiplier = 0.0;
   }
   
   maxEquityLoss = m_account.Balance()*m_percent/100.0; // max loss 
   tickValLoss = minvol*m_symbol.TickValueLoss(); // tick val loss
   maxTicksLoss = MathFloor(maxEquityLoss/tickValLoss);
 
   return (price - maxTicksLoss*m_symbol.TickSize()*orderTypeMultiplier);
}

Die CalcMaxTickLoss() Methode liefert uns die Maximalanzahl an Kursschwankungen, die wir uns für ein gegebenes Risiko und die minimal zulässige Postengröße erlauben können.

Zuerst wird der maximale Eigenkapitalverlust als Prozentwert des aktuellen Saldos berechnet, dann der Wertverlust der Kursschwankung bei Veränderung durch eine Schwankung für die minimal zulässige Postengröße für ein gegebenes Symbol. Danach wird der maximale Eigenkapitalverlust durch den Wertverlust der Kursschwankung geteilt und das Ergebnis  dann auf einen ganzzahligen Wert mit Hilfe der MathFloor() Funktion aufgerundet:

double CMoneyFixedRiskExt::CalcMaxTicksLoss()
{
   double maxEquityLoss, tickValLoss, maxTicksLoss;
   double minvol=m_symbol.LotsMin();
   
   if(m_symbol==NULL) return(0.0);
   
   maxEquityLoss = m_account.Balance()*m_percent/100.0; // max loss 
   tickValLoss = minvol*m_symbol.TickValueLoss(); // tick val loss
   maxTicksLoss = MathFloor(maxEquityLoss/tickValLoss);
   
   return (maxTicksLoss);
}

Die CalcOrderEquityRisk() Methode liefert das Eigenkapitalrisiko für einen gegebenen Preis sowie das Stop-Loss Level und die Zahl der Posten Sie wird Multiplikation des Wertverlusts mit der Anzahl der Posten und des Kurses berechnet und weiter durch Multiplikation mit der Differenz zwischen dem aktuellen Preis und dem Stop-Loss Level:

double CMoneyFixedRiskExt::CalcOrderEquityRisk(double price,double sl, double lots)
{
   double equityRisk;
   
   equityRisk = lots*m_symbol.TickValueLoss()*(MathAbs(price-sl)/m_symbol.TickSize()); 
   
   if (dbg) Print("calcEquityRisk: lots = " + DoubleToString(lots) +
                 " TickValueLoss = " + DoubleToString(m_symbol.TickValueLoss()) +
                 " risk = " + DoubleToString(equityRisk));
   
   return equityRisk;
}

 Die CalcOrderEquityReward() Methode funktioniert analog zur CalcOrderEquityRisk() Methode, arbeite jedoch mit der TickValueProfit() anstatt der TickValueLoss() Methode. Ihr Ergebnis wird mit dem gegebenen Risiko-zu-Prämien Verhältnis multipliziert.  

double CMoneyFixedRiskExt::CalcOrderEquityReward(double price,double sl, double lots, double rrratio)
{
   double equityReward; 
   equityReward = lots*m_symbol.TickValueProfit()*(MathAbs(price-sl)/m_symbol.TickSize())*rrratio; 
   
   if (dbg) Print("calcEquityReward: lots = " + DoubleToString(lots) + 
                   " TickValueProfit = " + DoubleToString(m_symbol.TickValueProfit()) +
                 " reward = " + DoubleToString(equityReward));
   return equityReward;
}

Diese Methoden reichen zur Berechnung der maximalen Stop-Loss Levels und zur Lieferung von Echtzeit-Eigenkapitalrisiko und Prämie aus. Die CalcMaxTickLoss() Methode dient zur korrekten Zeichnung des Risikorechtecks - wenn ein Händler einen Handel platzieren möchte, der die Grenze der Anzahl an Kursschwankungen, die er sich zu verlieren erlauben kann, überschreitet. Das Rechteck wird nur auf der Maximalanzahl der Kursschwankungen gezeichnet, die er verlieren kann.

Es direkt auf dem Chart zu sehen, erleichtert vieles. Sehen Sie sich dazu das Demo am Ende dieses Beitrags an.

6. Implementierung der Chart Ereignis-Verarbeiter

Der EA_switchOrderType() Verarbeiter wird ausgelöst, nachdem ein CHARTEVENT_OBJECT_CLICK im m_switchOrderTypeButton Objekt ein Ereignis erhalten wurde. Er wechselt die Art der Order zwischen ORDER_TYPE_BUY und ORDER_TYPE_SELL, setzt den Status der Schaltfläche und der Variablen des Dialogs zurück und löscht die Risiko- und Prämienrechteck-Objekte auf dem Chart:

void EA_switchOrderType()
  {
   symbolInfo.RefreshRates();

   visualRRDialog.SwitchOrderType();
   visualRRDialog.ResetButtons();
   visualRRDialog.SetSL(0.0);
   visualRRDialog.SetTP(0.0);
   visualRRDialog.SetMaxAllowedLots(0.0);
   visualRRDialog.SetOrderLots(0.0);
   visualRRDialog.SetMaxTicksLoss(0);
   visualRRDialog.SetOrderEquityRisk(0.0);
   visualRRDialog.SetOrderEquityReward(0.0);

   if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY) SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Ask());
   else if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL) SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Bid());
   SL_arrow.SetInteger(OBJPROP_TIME,0,TimeCurrent());

   rectReward.Delete();
   rectRisk.Delete();

   visualRRDialog.Refresh();

  }

Der EA_dragBuyHandle() Verarbeiter wird ausgelöst, nachdem ein SL_Pfeilobjekt per Drag&Drop auf das Chart platziert wird. Er liest zunächst den Zeitpunkt der Platzierung des SL_Pfeilobjekts und die Kursparameter vom Chart und legt dann das Preislevel als hypothetischen Stop-Loss für unseren Handel fest.

Dann berechnet er wie viele Posten wir bei einem gegebenen Eigenkapitalrisiko öffnen können. Wenn der Stop-Loss Wert das Risikoziel für den geringsten zulässigen Handelsposten bei diesem Symbol nicht garantieren kann, wird er automatisch auf das maximal zulässige Stop-Loss Level verschoben. Damit können wir abschätzen, wie viel Platz wir für einen Stop-Loss bei einem gegebenen Risiko haben.

Nach der Berechnung von Risiko und Prämie, werden die Rechteck-Objekte auf dem Chart aktualisiert.

void EA_dragBuyHandle()
  {
   SL_arrow.GetDouble(OBJPROP_PRICE,0,SL_price);
   SL_arrow.GetInteger(OBJPROP_TIME,0,startTime);

   symbolInfo.RefreshRates();
   currentTime=TimeCurrent();

// BUY
   double allowedLots=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);
   Print("Allowed lots = "+DoubleToString(allowedLots,2));
   double lowestSLAllowed=MM.GetMaxSLPossible(symbolInfo.Ask(),ORDER_TYPE_BUY);

   if(SL_price<lowestSLAllowed)
     {
      SL_price=lowestSLAllowed;
      ObjectSetDouble(0,slButtonID,OBJPROP_PRICE,lowestSLAllowed);
     }

   visualRRDialog.SetSL(SL_price);
   visualRRDialog.SetTP(symbolInfo.Ask()+(symbolInfo.Ask()-SL_price)*visualRRDialog.GetRRRRatio());

   if(visualRRDialog.GetTP()<SL_price)
     {
      visualRRDialog.SetSL(0.0);
      visualRRDialog.SetTP(0.0);
      SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Ask());
      rectReward.Delete();
      rectRisk.Delete();
      return;
     }

   double lotSize=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);

   visualRRDialog.SetMaxAllowedLots(lotSize);
   visualRRDialog.SetOrderLots(lotSize);
   visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
   visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Ask(), SL_price, lotSize));
   visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Ask(), 
                                       SL_price, lotSize, visualRRDialog.GetRRRRatio()));
   visualRRDialog.Refresh();

   rectUpdate(visualRRDialog.GetOrderType());

  }

Der EA_dragSellHandle() wird zur Konfiguration einer Sell-Order ausgelöst.

Seine Berechnungen beruhen auf dem symbolInfo.Bid() Preis und es werden entsprechende Rechtecke gezeichnet, wobei der grüne Bereich, der den Gewinn anzeigt, unterhalb des aktuellen Kurslevels liegt. 

void EA_dragSellHandle()
  {
   SL_arrow.GetDouble(OBJPROP_PRICE,0,SL_price);
   SL_arrow.GetInteger(OBJPROP_TIME,0,startTime);

   symbolInfo.RefreshRates();
   currentTime=TimeCurrent();

   double allowedLots=MM.CheckOpenShort(symbolInfo.Bid(),SL_price);
   Print("Allowed lots = "+DoubleToString(allowedLots,2));
   double maxSLAllowed=MM.GetMaxSLPossible(symbolInfo.Bid(),ORDER_TYPE_SELL);

   if(SL_price>maxSLAllowed)
     {
      SL_price=maxSLAllowed;
      SL_arrow.SetDouble(OBJPROP_PRICE,0,maxSLAllowed);
     }

   visualRRDialog.SetSL(SL_price);
   visualRRDialog.SetTP(symbolInfo.Bid()-(SL_price-symbolInfo.Bid())*visualRRDialog.GetRRRRatio());

   if(visualRRDialog.GetTP()>SL_price)
     {
      visualRRDialog.SetSL(0.0);
      visualRRDialog.SetTP(0.0);
      SL_arrow.SetDouble(OBJPROP_PRICE,symbolInfo.Bid());
      rectReward.Delete();
      rectRisk.Delete();
      return;
     }

   double lotSize=MM.CheckOpenShort(symbolInfo.Bid(),SL_price);

   visualRRDialog.SetMaxAllowedLots(lotSize);
   visualRRDialog.SetOrderLots(lotSize);
   visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
   visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Bid(), SL_price, lotSize));
   visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Bid(),
                                       SL_price, lotSize, visualRRDialog.GetRRRRatio()));
   visualRRDialog.Refresh();

   rectUpdate(visualRRDialog.GetOrderType());

  }

Der EA_placeOrder() wird ausgelöst, nachdem ein m_placeOrderButton Objekt gedrückt wurde. Er platziert eine Buy- oder Sell-Marktorder für die errechneten SL- und TP-Levels und die gegebene Postengröße.

Sie sehen hier ganz klar, wie einfach die Platzierung einer Marktorder mit Hilfe der CExpertTrade Klasse geht. 

bool EA_placeOrder()
  {
   symbolInfo.RefreshRates();
   visualRRDialog.ResetButtons();

   if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY)
      orderPlaced=trade.Buy(visualRRDialog.GetOrderLots(),symbolInfo.Ask(),
                            visualRRDialog.GetSL(),visualRRDialog.GetTP(),TimeToString(TimeCurrent()));
   else if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL)
      orderPlaced=trade.Sell(visualRRDialog.GetOrderLots(),symbolInfo.Bid(),
                            visualRRDialog.GetSL(),visualRRDialog.GetTP(),TimeToString(TimeCurrent()));

   return orderPlaced;
  }

Der EA_editParamsUpdate() Verarbeiter wird ausgelöst, wenn die Eingabetaste nach der Bearbeitung einer der Bearbeitungsfelder gedrückt wird: riskRatioEdit, riskValueEdit und orderLotsEdit.

Wenn dies passiert müssen die zulässige Postengröße, das TP-Level, der max. Verlust bei Kursschwankung, das Eigenkapitalrisiko und die Prämie erneut berechnet werden:

void EA_editParamsUpdate()
  {
   MM.Percent(visualRRDialog.GetRiskPercent());

   SL_arrow.GetDouble(OBJPROP_PRICE, 0, SL_price);
   SL_arrow.GetInteger(OBJPROP_TIME, 0, startTime);

   symbolInfo.RefreshRates();
   currentTime=TimeCurrent();

   double allowedLots=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);

   double lowestSLAllowed=MM.GetMaxSLPossible(symbolInfo.Ask(),ORDER_TYPE_BUY);
   if(SL_price<lowestSLAllowed)
     {
      SL_price=lowestSLAllowed;
      ObjectSetDouble(0,slButtonID,OBJPROP_PRICE,lowestSLAllowed);
     }

   visualRRDialog.SetSL(SL_price);
   visualRRDialog.SetTP(symbolInfo.Ask()+(symbolInfo.Ask()-SL_price)*visualRRDialog.GetRRRRatio());

   visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());
   visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Ask(), 
                                     SL_price, visualRRDialog.GetOrderLots()));
   visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Ask(), SL_price, 
                                       visualRRDialog.GetOrderLots(), visualRRDialog.GetRRRRatio()));
   visualRRDialog.Refresh();
   rectUpdate(visualRRDialog.GetOrderType());

   ChartRedraw();
  }

Der EA_onTick() wird bei jedem Auftauchen einer neuen Kursschwankung in Gang gesetzt. Berechnungen erfolgen nur, wenn die Order noch nicht platziert wurde und das Stop-Loss Level durch Ziehen des SL_Pfeilzeigers bereits gesetzt wurde.

Nachdem die Order platziert ist, sind Risiko und Prämie und TP-Level sowie das nochmalige Zeichnen von Risiko und Prämie nicht mehr nötig. 

void EA_onTick()
  {
   if(SL_price!=0.0 && orderPlaced==false)
     {
      double lotSize=0.0;
      SL_price=visualRRDialog.GetSL();
      symbolInfo.RefreshRates();

      if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY)
         lotSize=MM.CheckOpenLong(symbolInfo.Ask(),SL_price);
      else if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL)
         lotSize=MM.CheckOpenShort(symbolInfo.Ask(),SL_price);

      visualRRDialog.SetMaxAllowedLots(lotSize);
      if(visualRRDialog.GetOrderLots()>lotSize) visualRRDialog.SetOrderLots(lotSize);

      visualRRDialog.SetMaxTicksLoss(MM.CalcMaxTicksLoss());

      if(visualRRDialog.GetOrderType()==ORDER_TYPE_BUY)
        {
         visualRRDialog.SetTP(symbolInfo.Ask()+(symbolInfo.Ask()-SL_price)*visualRRDialog.GetRRRRatio());
         visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(symbolInfo.Ask(), 
                                           SL_price, visualRRDialog.GetOrderLots()));
         visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Ask(), SL_price, 
                                             visualRRDialog.GetOrderLots(), visualRRDialog.GetRRRRatio()));
        }
      else if(visualRRDialog.GetOrderType()==ORDER_TYPE_SELL)
        {
         visualRRDialog.SetTP(symbolInfo.Bid()-(SL_price-symbolInfo.Bid())*visualRRDialog.GetRRRRatio());
         visualRRDialog.SetOrderEquityRisk(MM.CalcOrderEquityRisk(
                                           symbolInfo.Bid(), SL_price, visualRRDialog.GetOrderLots()));
         visualRRDialog.SetOrderEquityReward(MM.CalcOrderEquityReward(symbolInfo.Bid(), SL_price, 
                                             visualRRDialog.GetOrderLots(), visualRRDialog.GetRRRRatio()));
        }
      visualRRDialog.Refresh();
      rectUpdate(visualRRDialog.GetOrderType());
     }

   ChartRedraw(0);
  }

Die rectUpdate() Funktion ist für das nochmalige Zeichnen der farbigen Risiko- und Prämienrechtecke zuständig Ihre Kontrollpunkte sind die Startzeit desSL_Pfeilobjekts, der aktuelle Wert des Brief- oder Geldkurses, je nach Art der Order, und die SL- und TP-Levels. Das hellrosa Rechteck zeigt den Kursbereich zwischen aktuellen Kurs und SL-Level; das hellgrüne Rechteck zeigt den Kursbereich zwischen aktuellen Kurs und TP-Level.

Beide Rechtecke sind ein prima Werkzeug um die Auswirkung des Risiko-zu-Prämien Verhältnisses auf die SL- und TP-Preislevels zu beobachten und helfen bei der Anpassung des Risikos vor dem tatsächlichen Beginn des Handels.

void rectUpdate(ENUM_ORDER_TYPE orderType)
  {
   symbolInfo.RefreshRates();
   currentTime=TimeCurrent();
   SL_arrow.GetInteger(OBJPROP_TIME,0,startTime);

   if(orderType==ORDER_TYPE_BUY)
     {
      rectReward.Create(0,rewardRectID,0,startTime,symbolInfo.Ask(),currentTime,symbolInfo.Ask()+
                       (symbolInfo.Ask()-visualRRDialog.GetSL())*visualRRDialog.GetRRRRatio());
      rectReward.Color(LightGreen);
      rectReward.Background(true);

      rectRisk.Create(0,riskRectID,0,startTime,visualRRDialog.GetSL(),currentTime,symbolInfo.Ask());
      rectRisk.Color(LightPink);
      rectRisk.Background(true);
     }
   else if(orderType==ORDER_TYPE_SELL)
     {
      rectReward.Create(0,rewardRectID,0,startTime,symbolInfo.Bid(),currentTime,symbolInfo.Bid()-
                        (visualRRDialog.GetSL()-symbolInfo.Bid())*visualRRDialog.GetRRRRatio());
      rectReward.Color(LightGreen);
      rectReward.Background(true);

      rectRisk.Create(0,riskRectID,0,startTime,visualRRDialog.GetSL(),currentTime,symbolInfo.Bid());
      rectRisk.Color(LightPink);
      rectRisk.Background(true);
     }
  }

 

7. Demo

Sehen Sie sich bitte das folgende Demo eines voll arbeitenden Expert Advisors an. Ich platziere eine Sell-Order nach einem großen Abprall kurz nach Markteröffnung am Montag, den 11.01.2010.

Um dieses Demo auch optimal sehen zu können, stellen Sie auf Vollbild und eine Aufllösung von 480p um. Das Video ist kommentiert:

 

Fazit

Ich habe in diesem Beitrag einen Weg gezeigt, einen interaktiven Expert Advisor für manuelles Handeln auf Grundlage vorab festgelegter Risiken und einem Risiko-zu-Prämien Verhältnis erläutert.

Des Weiteren habe ich gezeigt, wie man Standardklassen zur Anzeige von Inhalt auf dem Chart einsetzt und wie man mit Chart-Ereignissen zur Eingabe neuer Daten und der Verarbeitung von Drag&Drop-Objekten umgeht. Ich hoffe, dass meine hier dargelegten Ideen als Basis für den Bau weiterer konfigurierbarer visueller Tools in MQL5 dienen werden.

Alle Quelldateien und Bitmaps sind an diesen Beitrag angehängt.