English Русский 中文 Español 日本語 Português
Mini Market Emulator oder ein manueller Strategie-Tester

Mini Market Emulator oder ein manueller Strategie-Tester

MetaTrader 5Tester | 1 Dezember 2017, 09:08
849 0
Dmitriy Zabudskiy
Dmitriy Zabudskiy

Einführung

Der Forex-Handel beginnt mit dem Studium der theoretischen Grundlagen: Ertragsstrategie, Datenanalysemethoden, erfolgreiche Handelsmodelle. Alle unerfahrenen Händler werden von der gleichen Idee geleitet - jeder will Geld verdienen. Aber jeder definiert seine eigenen Prioritäten, Begriffe, Chancen, Ziele etc.

Es gibt mehrere Verhaltensweisen eines unerfahrenen Händlers.

  • Die Option "Alles auf einmal": Die meisten Anfänger wollen viel und das schnell verdienen. Sie erliegen der verlockenden Werbung einer magischen und makellosen Strategie, die für sehr wenig Geld oder sogar umsonst eingesetzt werden kann. So etwas schaut nach dem schnellen Geld aus, obwohl der Verlust der Einzahlung genauso schnell und einfach ist.
  • Die Option "Ausbildung, Ausbildung, Ausbildung": Es gibt Neulinge, die verantwortungsbewusst und ohne Glauben an Märchen trainieren. Sie studieren gründlich die Gesetze des Marktes und der Handelssysteme. Und dann beginnt man mit echten Geld zu handeln - aber der Gewinn fällt trotzdem noch geringer aus die Lehrbüchern versprechen. Wie passiert das und was ist als nächstes zu tun?

In der ersten Situation sind die meisten Neulinge schnell für immer von den Finanzmärkten enttäuscht. Anfänger aber mit der zweiten Zugang setzen das Studium der Theorie und ihrer praktischen Umsetzung fort.

Dieser Artikel richtet sich vor allem an Anfänger, die es nicht abwarten können, mit einem Demokonto zu handeln und ihre Strategien zu testen. Auch hier gibt es zwei Möglichkeiten:

  • Eine Gruppe will eine untersuchte, kurzfristige Strategie ausprobieren. Aber wenn solche Händler Vollzeit arbeiten, bleiben ihnen nur die Nachtstunden, weil der Markt an den Wochenenden geschlossen ist.
  • Die zweite Kategorie von Händlern arbeitet mit mittel- oder langfristigen Strategien. Sie wollen definitiv nicht ein ganzes Jahr damit verbringen, ihre Strategie auf einem Demokonto zu verfeinern.

Natürlich fragt man sich: Wenn es ein Chart mit historischen Priesen gibt, in dem jede Strategie schnell und effektiv getestet werden kann, warum gibt es dann solche Schwierigkeiten? In der Praxis funktioniert das aber nicht immer: Oft kommt es vor, dass eine Strategie mit hervorragenden Ergebnissen aus dem Backtesting im realen Handel aus irgendeinem Grund sehr schlecht funktioniert. Auf jeden Fall ist es besser, den Handel in Systemen zu erlernen, die mehr oder weniger realitätsnah sind. Zum Beispiel reichen Markt-Emulatoren völlig aus (solche Programme können im Internet gekauft werden).

In diesem Artikel möchte ich meine eigene Implementierung eines solchen Systems für den MetaTrader 5 besprechen. Ich habe den Indikator "Mini Market Emulator" mit einer eingeschränkten Funktionalität im Vergleich zur Vollversion des Terminals geschrieben. Es ist für die theoretische Verifikation von Strategien konzipiert.

Anwendungsmerkmale

Die Anwendung verfügt über ein eigenes Bedienfeld (control panel) sowie über bestimmte Schaltflächen des "übergeordneten Systems", d.h. für den MetaTrader 5 Terminals selbst.

Hier sind die wichtigsten Aktionen, die vom Emulator ausgeführt werden können.

  1. Es können nur zwei Aufträge in verschiedene Richtungen erteilt werden: Kaufen und Verkaufen. Er unterstützt auch die Setzen von Stop-Loss und Take-Profit vor dem Festlegen des Auftrages mit dessen Volumen. Sobald die Order platziert ist, kann sie modifiziert und ihre Stopps können verschoben werden.
  2. Es gibt nur sieben Modellierungsgeschwindigkeiten, sie lassen sich in drei Gruppen unterteilen. Die erste ist das "Juwel", es geht um die Modellierung auf der Grundlage der Generierung von Ticks aus den Daten des einminütigen Zeitrahmens, fast wie im Strategy Tester. Die zweite betrachtet Minutendaten, ein Erstellen ohne Generierung (dieser Modus ist schneller, aber weniger genau). Der dritte Modus ist der schnellste: Es wird eine Kerze pro Sekunde erstellt, unabhängig vom Zeitrahmen.
  3. Es werden die aktuellen Handelsinformationen zur Verfügung gestellt: Gewinn, Anzahl der 'points' und das Volumen. Die Daten werden für die aktuellen und vergangenen Aufträge sowie für den allgemeinen Handel ab dem Beginn der Emulation angegeben.
  4. Es stehen alle im Terminal vorhandenen Standard-Grafikobjekte zur Verfügung.
  5. Es werden alle Standardzeitrahmen unterstützt (Umschaltung über die Schaltflächen des Terminal-Panels).


Abb. 1. Die Steuerelemente und das Erscheinungsbild der Anwendung


Das System der Tickerzeugung

Das Prinzip der Tickerzeugung wurde dem Artikel "Der Algorithmus der Tick-Erzeugung im Strategietester des MetaTrader 5 Terminals" entnommen. Es wurde kreativ überarbeitet und als alternative Version präsentiert.

Für die Tickerzeugung sind zwei Funktionen zuständig: Haupt- und Hilfsfunktionen.

Die Hauptfunktion ist die Tick Generation. Es werden zwei Parameter übergeben: die Kerze selbst und ein Array für die Ergebnisdaten (Ticks). Wenn dann alle vier Preise der übergebenen Kerze gleich sind, wird das Volumen der Ticks auf einen Tick gesetzt. Dies wurde getan, um die Gefahr einer Division durch Null auszuschließen, falls fehlerhafte Daten übergeben werden.

Es folgt die Bildung einer neuen Kerze. Wenn sich 1-3 Ticks in der Kerze befinden, wird der Prozess der Tickerzeugung wie im oben genannten Artikel beschrieben fortgesetzt.

Wenn es mehr als 3 Ticks gibt, wird die Arbeit komplizierter. Die übergebene Kerze wird in drei ungleiche Teile geteilt (das Prinzip der Teilung ist im Code unten angegeben, gesondert für fallende oder steigende Kerzen). Dann, falls oben und unten keine Ticks mehr vorhanden sind, wird die Anpassung vorgenommen. Danach wird die Steuerung in Abhängigkeit von der Art der Kerze der Hilfsfunktion übergeben.

//+------------------------------------------------------------------+
//| Func Tick Generation                                             |
//+------------------------------------------------------------------+
void func_tick_generation(
MqlRates &rates,      // Daten der Kerze
double &tick[]        // dynamischer Array der Ticks
)
{
 if(rates.open==rates.close && rates.high==rates.low && rates.open==rates.high){rates.tick_volume=1;}
 if(rates.tick_volume<4)// less than four ticks
 {
ArrayResize(tick,int(rates.tick_volume));         // Größenänderung des Arrays auf die Anzahl der Ticks
if(rates.tick_volume==1)tick[0]=rates.close;      // ein Tick
if(rates.tick_volume==2)                          // zwei Ticks
{
 tick[0]=rates.open;
 tick[1]=rates.close;
}
if(rates.tick_volume==3)                          // drei Ticks
{
 tick[0]=rates.open;
 tick[2]=rates.close;
 if(rates.open==rates.close)                      // bewegte sich in eine Richtung und dann zurück zum Eröffnungspreis
 {
if(rates.high==rates.open)tick[1]=rates.low;
if(rates.low==rates.open)tick[1]=rates.high;
 }
 if(rates.close==rates.low && rates.open!=rates.high)tick[1]=rates.high;           // bewegte sich in eine Richtung und dann zurück und über den Eröffnungspreis hinaus
 if(rates.close==rates.high && rates.open!=rates.low)tick[1]=rates.low;
 if(rates.open==rates.high && rates.close!=rates.low)tick[1]=rates.low;            // bewegte sich in eine Richtung und dann zurück aber nicht über den Eröffnungspreis hinaus
 if(rates.open==rates.low && rates.close!=rates.high)tick[1]=rates.high;
 if((rates.open==rates.low && rates.close==rates.high) || (rates.open==rates.high && rates.close==rates.low))
 {
tick[1]=NormalizeDouble((((rates.high-rates.low)/2)+rates.low),_Digits);       // mehrere 'points' in eine Richtung
 }
}
 }
 if(rates.tick_volume>3)      // mehr als drei Ticks
 {

 // calculate the candle size by points
int candle_up=0;
int candle_down=0;
int candle_centre=0;
if(rates.open>rates.close)
{
 candle_up=int(MathRound((rates.high-rates.open)/_Point));
 candle_down=int(MathRound((rates.close-rates.low)/_Point));
}
if(rates.open<=rates.close)
{
 candle_up=int(MathRound((rates.high-rates.close)/_Point));
 candle_down=int(MathRound((rates.open-rates.low)/_Point));
}
candle_centre=int(MathRound((rates.high-rates.low)/_Point));
int candle_all=candle_up+candle_down+candle_centre;      // Gesamtlänge der Bewegung
int point_max=int(MathRound(double(candle_all)/2));      // die maximal mögliche Anzahl von Ticks
double share_up=double(candle_up)/double(candle_all);
double share_down=double(candle_down)/double(candle_all);
double share_centre=double(candle_centre)/double(candle_all);

// Berechnen der Referenzpunkte von jeder Sektion
char point=0;
if(rates.tick_volume<10)point=char(rates.tick_volume);
else point=10;
if(point>point_max)point=char(point_max);
char point_up=char(MathRound(point*share_up));
char point_down=char(MathRound(point*share_down));
char point_centre=char(MathRound(point*share_centre));

// Prüfen der Referenzpunkte des gewählten Bereiches
if(candle_up>0 && point_up==0)
{point_up=1;point_centre=point_centre-1;}
if(candle_down>0 && point_down==0)
{point_down=1;point_centre=point_centre-1;}

// Größenänderung der Ausgabearrays
ArrayResize(tick,11);
char p=0;                     // Index des Arrays der Ticks (tick[])
tick[p]=rates.open;           // der erste Tick ist gleich dem Eröffnungspreis
if(rates.open>rates.close)    // abwärts
{
 func_tick_small(rates.high,1,candle_up,point_up,tick,p);
 func_tick_small(rates.low,-1,candle_centre,point_centre,tick,p);
 func_tick_small(rates.close,1,candle_down,point_down,tick,p);
 ArrayResize(tick,p+1);
}
if(rates.open<=rates.close)   // aufwärts oder Doji
{
 func_tick_small(rates.low,-1,candle_down,point_down,tick,p);
 func_tick_small(rates.high,1,candle_centre,point_centre,tick,p);
 func_tick_small(rates.close,-1,candle_up,point_up,tick,p);
 ArrayResize(tick,p+1);
}
 }
}


Wie der Name schon sagt, führt die Funktion Tick Small eine kleine Generation von Ticks aus. Sie erhält Informationen über den zuletzt bearbeiteten Tick, die Richtung (aufwärts oder abwärts), die erforderliche Anzahl von Schritten, den letzten Preis und übergibt die berechneten Schritte an das obige Array der Ticks. Das resultierende Array enthält nicht mehr als 11 Ticks.

//+------------------------------------------------------------------+
//| Func Tick Small                                                  |
//+------------------------------------------------------------------+
void func_tick_small(
 double end,        // Ende der Bewegung
 char route,        // Richtung der Bewegung
 int candle,        // Abstand der Bewegung
 char point,        // die Anzahl der Punkte
 double &tick[],    // Array der Ticks
 char&i             // der aktuelle Index des Arrays
 )
{
 if(point==1)
 {
i++;
if(i>10)i=10;       // Anpassung
tick[i]=end;
 }
 if(point>1)
 {
double wave_v=(point+1)/2;
double step_v=(candle-1)/MathFloor(wave_v)+1;
step_v=MathFloor(step_v);
for(char p_v=i+1,i_v=i; p_v<i_v+point;)
{
 i++;
 if(route==1)tick[i]=tick[i-1]+(step_v*_Point);
 if(route==-1)tick[i]=tick[i-1]-(step_v*_Point);
 p_v++;
 if(p_v<i_v+point)
 {
i++;
if(route==1)tick[i]=tick[i-1]-_Point;
if(route==-1) tick[i]=tick[i-1]+_Point;
 }
 p_v++;
}
if(NormalizeDouble(tick[i],_Digits)!=NormalizeDouble(end,_Digits))
{
 i++;
 if(i>10)i=10;    // Anpassung
 tick[i]=end;
}
 }
}


Dies ist sozusagen das Herzstück der gesamten Modellierung des "Juwels" (in der Schlussbemerkung wird erklärt, warum es "Juwel" heißt). Kommen wir nun zur Essenz der Systeminteraktion.

Interaktion und Datenaustausch

Der Code des Systems wirkt auf den ersten Blick verwirrend. Funktionen sind nicht ganz konsistent, ihre Aufrufe aus verschiedenen Teilen des Programms sind möglich. Es stellte sich heraus, dass das System nicht nur mit dem Benutzer, sondern auch mit dem Terminal interagieren muss. Hier ist ein ungefähres Schema dieser Wechselwirkungen (Abb. 2):


Abb. 2. Schema der Interaktionen in der Anwendung

Um die Anzahl der Kontrollobjekte im Anzeigefenster zu reduzieren, wurde der Mechanismus zum Umschalten der Periode vom Terminal entlehnt. Da die Anwendung jedoch beim Umschalten der Periode neu initialisiert wird und alle Variablen des lokalen und globalen Bereichs überschrieben werden, wird das Datenfeld bei jedem Umschalten kopiert. Insbesondere werden die Daten von zwei Perioden kopiert - M1 und die ausgewählte Periode. Die Parameter für die Weiterverarbeitung dieser Daten werden auf dem Panel ausgewählt: Geschwindigkeit und Qualität der Simulation (das "Juwel" oder die einfache schnelle Methode). Sobald alles fertig ist, beginnt die Modellierung des Charts.

Das Bedienfeld ist praktisch, um Aufträge zu erteilen und zu löschen. Dazu verweist das Programm auf die Klasse "COrder". Diese Klasse wird auch für die Verwaltung von Aufträgen verwendet, während das Chart aufgebaut wird.

Wie bereits erwähnt, wird der Indikator neu gestartet, wenn sich die Chartperiode ändert. Dementsprechend werden globale Variablen des Client-Terminals verwendet, um die Kommunikation über die gesamte Struktur der Anwendung hinweg zu gewährleisten. Im Gegensatz zu den herkömmlichen globalen Variablen werden sie über einen längeren Zeitraum (4 Wochen) gespeichert und sind tolerant gegenüber einem Neustart. Der einzige Nachteil ist ihr Datentyp, ausschließlich "double". Aber im Allgemeinen ist das viel bequemer, als eine neue Datei zu erstellen, zu schreiben und zu lesen.

Kommen wir nun direkt zum Code der Interaktionselemente.

Die Umsetzung des Programms

Der Anfang des Code

Zuerst kommen die Standardprozeduren für die Deklaration der Variablen. Dann werden in der Funktion OnInit() die Puffer initialisiert, das Bedienfeld gezeichnet und der Abstand vom Anfang der Emulation berechnet. Der Abstand wird benötigt, um sicherzustellen, dass die Simulation nicht mit einem leeren Chart beginnt, sondern mit einer bestimmten Historie, um sofort mit der Überprüfung der Strategie zu beginnen.

Auch hier werden die Daten-Arrays kopiert und die wichtigste Verbindungsvariable gelesen (time_end). Sie gibt den Zeitpunkt an, zu dem die Simulation gestoppt wurde:

//--- setzen der Zeit auf den Zeitpunkt, zu dem der Indikator gezeichnet wurde
 if(GlobalVariableCheck(time_end))end_time_indicator=datetime(GlobalVariableGet(time_end));


Auf diese Weise "weiß" der Indikator immer, wo er stehen geblieben ist. Die OnInit() Funktion endet mit einem Timeraufruf, der in der Tat den Befehl gibt, einen neuen Tick auszugeben oder eine ganze Kerze zu bilden (abhängig von der Geschwindigkeit).

Die Timer-Funktion

Der Zustand der "Play"-Taste auf dem Bedienfeld wird zu Beginn der Funktion überprüft. Wird sie gedrückt, wird der weitere Code ausgeführt.

Zuerst wird die Bar bestimmt, an der die Simulation gestoppt wurde (relativ zur aktuellen Zeit). Als Endpunkte werden die letzte Simulationszeit 'end_time_indicator' und die aktuelle Zeit genommen. Die Daten werden jede Sekunde neu berechnet, da sich das Chart ständig bewegt (außer Samstag und Sonntag) und nicht zeitsynchronisiert ist. So wird das Chart dynamisch verfolgt und durch den Befehl ChartNavigate() bewegt.

Danach werden die Variablen 'number_now_rates','bars_now_rates','all_bars_indicator' berechnet. Die Uhrzeit wird anschließend überprüft. Wenn es nicht gemäß den Eingabeparametern abgelaufen ist, wird die Modellierung mit der Funktion func_merger() durchgeführt. Anschließend werden die aktuellen Positionen und deren Rentabilität überprüft, wobei die Werte in den globalen Variablen erfasst und im Informationsblock des Indikators ausgegeben werden.

Auch hier wird die Klasse "COrder" aufgerufen, d.h. sie ist für das automatische Löschen von Aufträgen infolge von Benutzeraktionen (position.Delete) oder das Aktivieren der Stopps (position.Check) verantwortlich.

//+------------------------------------------------------------------+
//| Timer function                                                   |
//+------------------------------------------------------------------+
void OnTimer()
{
//---
 if(button_play)
 {
end_bar_indicator=Bars(_Symbol,_Period,end_time_indicator,TimeCurrent());      // the number of bars from the earliest to the current
ChartNavigate(0,CHART_END,-end_bar_indicator);                                 // Bewegen des Chart (Indikator) zur aktuellen, bearbeiteten Bar
number_now_rates=(Bars(_Symbol,_Period,real_start,end_time_indicator)-1);      // die aktuelle bar, die bearbeitet wird
bars_now_rates=(Bars(_Symbol,_Period,real_start,stop)-1);                      // die Anzahl der Bars der Historie der aktuellen Periode
all_bars_indicator=(Bars(_Symbol,_Period,real_start,TimeCurrent()))-1;         // die Anzahl der Bars seit Beginn der Simulation bis zur aktuellen Zeit

if(end_time_indicator<stop)                                                    // Prüfen der Simulationszeit
{
 func_merger();
 ObjectSetDouble(0,line_bid,OBJPROP_PRICE,price_bid_now);
 if(ObjectFind(0,line_ask)>=0)
 {ObjectSetDouble(0,line_ask,OBJPROP_PRICE,price_ask_now);}

 //--- die aktuellen Werte des Auftrages
 int point_now=0;
 double vol_now=0;
 double money_now=0;
 if(ObjectFind(0,order_buy)>=0 && GlobalVariableGet(order_buy)>0)             // es gibt einen Kaufauftrag
 {
int p_now=int((price_bid_now-GlobalVariableGet(order_buy))*dig_pow);
double v_now=GlobalVariableGet(vol_buy);
double m_now=p_now*v_now*10;
point_now+=p_now;
vol_now+=v_now;
money_now+=m_now;
 }
 if(ObjectFind(0,order_sell)>=0 && GlobalVariableGet(order_sell)>0)           // es gibt einen Verkaufsauftrag
 {
int p_now=int((GlobalVariableGet(order_sell)-price_ask_now)*dig_pow);
double v_now=GlobalVariableGet(vol_sell);
double m_now=p_now*v_now*10;
point_now+=p_now;
vol_now+=v_now;
money_now+=m_now;
 }
 GlobalVariableSet(info_point_now,point_now);
 GlobalVariableSet(info_vol_now,vol_now);
 GlobalVariableSet(info_money_now,money_now);
}

COrder position;    //Objekt der Klasse "COrder"
position.Delete(price_bid_now,price_ask_now,(-1));
position.Check(end_time_indicator,GlobalVariableGet(order_buy),GlobalVariableGet(tp_buy),GlobalVariableGet(sl_buy),
 GlobalVariableGet(order_sell),GlobalVariableGet(tp_sell),GlobalVariableGet(sl_sell));

func_info_print("Money All: ",info_money_all,2);
func_info_print("Money Last: ",info_money_last,2);
func_info_print("Money Now: ",info_money_now,2);
func_info_print("Volume All: ",info_vol_all,2);
func_info_print("Volume Last: ",info_vol_last,2);
func_info_print("Volume Now: ",info_vol_now,2);
func_info_print("Point All: ",info_point_all,0);
func_info_print("Point Last: ",info_point_last,0);
func_info_print("Point Now: ",info_point_now,0);

position.Modify();
 }
//--- Handhabung des Knopfes zum Ausblenden
 char x=char(GlobalVariableGet("hide"));
 if(x==1)
 {
ObjectSetInteger(0,"20",OBJPROP_STATE,false);
ObjectSetInteger(0,"14",OBJPROP_YDISTANCE,24);
ObjectSetInteger(0,"15",OBJPROP_YDISTANCE,24);
ObjectSetInteger(0,"16",OBJPROP_YDISTANCE,24);
ObjectSetInteger(0,"17",OBJPROP_YDISTANCE,24);
ObjectSetInteger(0,"18",OBJPROP_YDISTANCE,24);
ObjectSetInteger(0,"19",OBJPROP_YDISTANCE,24);
 }
 if(x==2)
 {
ObjectSetInteger(0,"20",OBJPROP_STATE,true);
ObjectSetInteger(0,"14",OBJPROP_YDISTANCE,-24);
ObjectSetInteger(0,"15",OBJPROP_YDISTANCE,-24);
ObjectSetInteger(0,"16",OBJPROP_YDISTANCE,-24);
ObjectSetInteger(0,"17",OBJPROP_YDISTANCE,-24);
ObjectSetInteger(0,"18",OBJPROP_YDISTANCE,-24);
ObjectSetInteger(0,"19",OBJPROP_YDISTANCE,-24);
 }

Die Klasse COrder

Diese Klasse enthält die Funktionen zum Eröffnen und Schließen von Positionen, zum Modifizieren und Überprüfen des aktuellen Status der Aufträge (Verwaltung ihrer Take-Profit und Stop-Loss).

Beginnen wir mit der Auftragserteilung über die Methode Placed(). Die Auswahl der Auftragsart (Kauf oder Verkauf) erfolgt über den Switch-Operator, die Daten werden in einer globalen Variable (order_buy oder order_sell) gespeichert. Wenn m_take_profit und m_stop_loss zuvor definiert wurden, speichern Sie sie in den entsprechenden globalen Variablen und zeichnen Sie ihre Linien auf dem Diagramm. Die Linien werden durch die Methode Line() dieser Klasse gezeichnet.

//+------------------------------------------------------------------+
//| Class COrder                                                     |
//+------------------------------------------------------------------+
class COrder
{
public:
 void Placed(
 char m_type,// Auftragstyp (1-Kaufen, 2-Verkaufen)
 double m_price_bid, // Bid-Preis
 double m_price_ask, // Ask-Preis
 int m_take_profit,// 'points' bis Take-Profit
 int m_stop_loss // 'points' bis zu Stop-Loss
 )
 {
switch(m_type)
{
 case 1:
 {
GlobalVariableSet(order_buy,m_price_ask);
Line(GlobalVariableGet(order_buy),order_buy,col_buy,STYLE_SOLID,1,true);
if(m_take_profit>0)
{
 GlobalVariableSet(tp_buy,(m_price_ask+(_Point*m_take_profit)));
 Line(GlobalVariableGet(tp_buy),tp_buy,col_tp,STYLE_DASH,1,true);
}
if(m_stop_loss>0)
{
 GlobalVariableSet(sl_buy,(m_price_ask-(_Point*m_stop_loss)));
 Line(GlobalVariableGet(sl_buy),sl_buy,col_sl,STYLE_DASH,1,true);
}
 }
 break;
 case 2:
 {
GlobalVariableSet(order_sell,m_price_bid);
Line(GlobalVariableGet(order_sell),order_sell,col_sell,STYLE_SOLID,1,true);
if(m_take_profit>0)
{
 GlobalVariableSet(tp_sell,(m_price_bid-(_Point*m_take_profit)));
 Line(GlobalVariableGet(tp_sell),tp_sell,col_tp,STYLE_DASH,1,true);
}
if(m_stop_loss>0)
{
 GlobalVariableSet(sl_sell,(m_price_bid+(_Point*m_stop_loss)));
 Line(GlobalVariableGet(sl_sell),sl_sell,col_sl,STYLE_DASH,1,true);
}
 }
 break;
}

Als nächstes folgt die Methode Delete() zum Löschen von Aufträgen. Auch hier wählt der Switch-Operator eine der drei Optionen - automatisches Löschen, Kaufen oder Verkaufen. In diesem Fall ist das automatische Löschen eine Situation, in der ein Auftrag dadurch gelöscht wird, dass die Linie vom Chart entfernt wurde.

Dies geschieht durch die Hilfsfunktionen Small_del_buy und Small_del_sell der Klasse

 void Delete(
 double m_price_bid,      // Bid-Preis
 double m_price_ask,      // Ask-Preis
 char m_del_manual        // Lösche-Typen: (-1 - auto, 1 - Kaufen, 2 - Verkaufen)
 )
 {
switch(m_del_manual)
{
 case(-1):
if(ObjectFind(0,order_buy)<0 && GlobalVariableGet(order_buy)>0)
{Small_del_buy(m_price_bid);}
if(ObjectFind(0,order_sell)<0 && GlobalVariableGet(order_sell)>0)
{Small_del_sell(m_price_ask);}
break;
 case 1:
if(ObjectFind(0,order_buy)>=0)
{
 ObjectDelete(0,order_buy);
 Small_del_buy(m_price_bid);
}
break;
 case 2:
if(ObjectFind(0,order_sell)>=0)
{
 ObjectDelete(0,order_sell);
 Small_del_sell(m_price_ask);
}
break;
}

Betrachten wir ein von ihnen - Small_del_sell.

Überprüfen der Linien von Take-Profit und Stop-Loss. Wenn sie vorhanden sind, werden sie gelöscht. Dann wird die globale Variable order_sell auf Null gesetzt. Dies wird später benötigt, falls die globalen Variablen für die Überprüfung der Existenz von Positionen verwendet wird.

Die Informationen über den Gewinn von Positionen werden ebenfalls in den globalen Variablen (info_point_last, info_vol_last, info_money_last, info_money_last) gespeichert. Dies geschieht durch small_concatenation (ähnlich dem Operator +=, jedoch mit globalen Variablen). Summieren Sie den Gewinn (Volumen) und speichern Sie ihn auch in globalen Variablen (info_point_all, info_vol_all, info_money_all).

void Small_del_sell(double m_price_ask)
 {
if(ObjectFind(0,tp_sell)>=0)ObjectDelete(0,tp_sell);       // Löschen der Linie von Take-Profit
 if(ObjectFind(0,sl_sell)>=0)ObjectDelete(0,sl_sell);      // Löschen der Linie von Stop-Loss
 int point_plus=int(MathRound((GlobalVariableGet(order_sell)-m_price_ask)/_Point));      // Berechnen des Gewinns einer Position
GlobalVariableSet(order_sell,0);                           // Null setzen der Variablen des Preises des platzierten Auftrages
GlobalVariableSet(info_vol_last,GlobalVariableGet(vol_sell));
GlobalVariableSet(vol_sell,0);
GlobalVariableSet(info_point_last,point_plus);
GlobalVariableSet(info_money_last,(GlobalVariableGet(info_point_last)*GlobalVariableGet(info_vol_last)*10));
Small_concatenation(info_point_all,info_point_last);
Small_concatenation(info_vol_all,info_vol_last);
Small_concatenation(info_money_all,info_money_last);
 }

Die Änderung eines Position erfolgt durch die Änderung der Position auf dem Chart mit der Maus. Es gibt zwei Möglichkeiten, dies zu tun. Erstens kann man versuchen, die Eröffnungslinie des Auftrages zu verschieben. In diesem Fall werden je nach Bewegungsrichtung und Auftragsart neue Linien für Take-Profit und Stop-Loss erstellt. Die Funktion Small_mod ist auch in der Klasse COrder implementiert. Deren Eingabeparameter sind der Objektname, die Berechtigung zum Verschieben des Objekts und der Auftragstyp.

Zu Beginn der Funktion Small_mod wird das Vorhandensein eines Objekts geprüft. Dann, wenn das Verschieben von Take-Profit/Stop-Loss erlaubt ist, wird die Kursänderung in einer globalen Variable gespeichert. Wenn das Verschieben (der Linien für Kaufen und Verkaufen) verboten ist, erscheint je nach Ordertyp eine neue Linie für Take-Profit oder Stop-Loss am neuen Ort der Linie und die Linie des Auftrages kehrt an ihren Platz zurück.

 void Small_mod(string m_name,      // Name des Objektes und globalen Variablen
bool m_mode,                        // Erlaubnis, die Position zu ändern
char m_type                         // 1 — Kaufen, 2 — Verkaufen
)
 {
if(ObjectFind(0,m_name)>=0)
{
 double price_obj_double=ObjectGetDouble(0,m_name,OBJPROP_PRICE);
 int price_obj=int(price_obj_double*dig_pow);
 double price_glo_double=GlobalVariableGet(m_name);
 int price_glo=int(price_glo_double*dig_pow);
 if(price_obj!=price_glo && m_mode==true)
 {
GlobalVariableSet(m_name,(double(price_obj)/double(dig_pow)));
 }
 if(price_obj!=price_glo && m_mode==false)
 {
switch(m_type)
{
 case 1:                         // Kaufauftrag
if(price_obj>price_glo)          // TP
{
 GlobalVariableSet(tp_buy,(double(price_obj)/double(dig_pow)));
 Line(GlobalVariableGet(tp_buy),tp_buy,col_tp,STYLE_DASH,1,true);
}
if(price_obj<price_glo)          // SL
{
 GlobalVariableSet(sl_buy,(double(price_obj)/double(dig_pow)));
 Line(GlobalVariableGet(sl_buy),sl_buy,col_sl,STYLE_DASH,1,true);
}
break;
 case 2:                        // Verkaufsauftrag
if(price_obj>price_glo)         // SL
{
 GlobalVariableSet(sl_sell,(double(price_obj)/double(dig_pow)));
 Line(GlobalVariableGet(sl_sell),sl_sell,col_sl,STYLE_DASH,1,true);
}
if(price_obj<price_glo)         // TP
{
 GlobalVariableSet(tp_sell,(double(price_obj)/double(dig_pow)));
 Line(GlobalVariableGet(tp_sell),tp_sell,col_tp,STYLE_DASH,1,true);
}
break;
}
ObjectSetDouble(0,m_name,OBJPROP_PRICE,(double(price_glo)/double(dig_pow)));
 }
}

Während der Modellierung des Charts werden die Aufträge ständig durch die Methode Check() der Klasse COrder überprüft. Alle globalen Variablen, die Informationen über Aufträge speichern, werden dieser Funktion übergeben. Es gibt auch eine separate globale Variable, die die Information über den Zeitpunkt des letzten Aufrufs enthält. Dadurch kann bei jedem Anruf die gesamte Preisspanne (einminütiger Zeitrahmen) im Intervall zwischen dem letzten Aufruf der Funktion und der aktuellen Zeichnungszeit auf dem Chart überprüft werden.

Erreicht der Kurs in dieser Zeit eine der Stopplinien oder durchbricht sie, wird die Steuerung an die Funktion zum Löschen von Positionen (Funktion Delete in der Klasse COrder) übergeben.

 void Check(
datetime m_time,
double m_price_buy,
double m_price_tp_buy,
double m_price_sl_buy,
double m_price_sell,
double m_price_tp_sell,
double m_price_sl_sell
)
 {
int start_of_z=0;
int end_of_z=0;
datetime time_end_check=datetime(GlobalVariableGet(time_end_order_check));
if(time_end_check<=0){time_end_check=m_time;}
GlobalVariableSet(time_end_order_check,m_time);
start_of_z=Bars(_Symbol,PERIOD_M1,real_start,time_end_check);
end_of_z=Bars(_Symbol,PERIOD_M1,real_start,m_time);
for(int z=start_of_z; z<end_of_z; z++)
{
 COrder del;
 double p_bid_high=period_m1[z].high;
 double p_bid_low=period_m1[z].low;
 double p_ask_high=p_bid_high+(spread*_Point);
 double p_ask_low=p_bid_low+(spread*_Point);
 if(m_price_buy>0)                                              // es gibt einen Kaufauftrag
 {
if(ObjectFind(0,tp_buy)>=0)
{
 if(m_price_tp_buy<=p_bid_high && m_price_tp_buy>=p_bid_low)    // TP ausgelöst
 {del.Delete(m_price_tp_buy,0,1);}                              // Schließen zum TP-Preis
} 
if(ObjectFind(0,sl_buy)>=0)
{
 if(m_price_sl_buy>=p_bid_low && m_price_sl_buy<=p_bid_high)    // SL ausgelöst
 {del.Delete(m_price_sl_buy,0,1);}                              // Schlie0en zum SL-Preis
}
 }
 if(m_price_sell>0)                                             // es gibt eine Verkaufsauftrag
 {
if(ObjectFind(0,tp_sell)>=0)
{
 if(m_price_sl_sell<=p_ask_high && m_price_sl_sell>=p_ask_low)  // SL ausgelöst
 {del.Delete(0,m_price_sl_sell,2);}                             // Schließen zum SL-Preis
}
if(ObjectFind(0,sl_sell)>=0)
{
 if(m_price_tp_sell>=p_ask_low && m_price_tp_sell<=p_ask_high)  // TP ausgelöst
 {del.Delete(0,m_price_tp_sell,2);}                             // Schließen zum SL-Preis
}

Damit sind die Hauptfunktionen der Klasse abgeschlossen. Betrachten wir die Funktionen, die direkt für das Zeichnen der Kerzen auf dem Chart verantwortlich sind.

Die Funktion func_filling()

Da das Umschalten der Periode den Indikator neu initialisiert, ist es notwendig, den Chart neu zu füllen und die vergangenen Kerzen bis zur aktuellen (sog. "Tail") zu platzieren. Diese Funktion wird auch verwendet, bevor eine neue Kerze erzeugt wird, was eine Normalisierung des "tails" des Charts und eine Erhöhung der Anzeigegenauigkeit ermöglicht.

Der Funktion wird ein Array mit Daten der aktuellen Periode, der aktuellen Anzeigezeit, der Anzahl aller Kerzen und der aktuell gezeichneten Kerze übergeben. Nach der Ausführung gibt die Funktion die Öffnungszeit der zuletzt angezeigten Kerze und die Öffnungszeit der darauf folgenden Kerze zurück. Das Indikator-Array wird ebenfalls gefüllt, und es wird der Merker 'work_status' zurückgegeben, der den Abschluss der Ausführung kennzeichnet.

In der Funktion füllt die for-Schleife den gesamten zuvor angezeigten Indikatorpuffer mit der aktuell gezeichneten Kerze und setzt die Preise der aktuellen Kerze (normalerweise gleich dem Eröffnungspreis).

//+------------------------------------------------------------------+
//| Func Filling |
//+------------------------------------------------------------------+
void func_filling(MqlRates &input_rates[],                // Auszufüllende Eingabedaten (der aktuellen Periode)
datetime input_end_time_indicator,      // die aktuelle Zeit des Indikators
int input_all_bars_indicator,           // die Anzahl aller Bars des Indikators
datetime &output_time_end_filling,      // Eröffnungszeit der letzten bar
datetime &output_time_next_filling,     // Eröffnungszeit der nächsten Bar
int input_end_bar_indicator,            // die aktuelle (gezeichnete) Bar des Indikators
double &output_o[],
double &output_h[],
double &output_l[],
double &output_c[],
double &output_col[],
char &work_status)                      // Status
{
 if(work_status==1)
 {
int stopped_rates_bar;
for(int x=input_all_bars_indicator,y=0;x>0;x--,y++)
{
 if(input_rates[y].time<input_end_time_indicator)
 {
output_o[x]=input_rates[y].open;
output_h[x]=input_rates[y].high;
output_l[x]=input_rates[y].low;
output_c[x]=input_rates[y].close;
if(output_o[x]>output_c[x])output_col[x]=0;
else output_col[x]=1;
output_time_end_filling=input_rates[y].time;
output_time_next_filling=input_rates[y+1].time;
input_end_bar_indicator=x;
stopped_rates_bar=y;
 }
 else break;
}
output_o[input_end_bar_indicator]=input_rates[stopped_rates_bar].open;
output_h[input_end_bar_indicator]=output_o[input_end_bar_indicator];
output_l[input_end_bar_indicator]=output_o[input_end_bar_indicator];
output_c[input_end_bar_indicator]=output_o[input_end_bar_indicator];
work_status=-1;
 }

Nach der Ausführung wird das Steuerelement auf eine der drei Funktionen zum Zeichnen der aktuellen Kerze übertragen. Betrachten wir sie der Reihe nach, beginnend mit der schnellsten.

Die Funktion func_candle_per_seconds() zum Zeichnen der Kerze im Sekundentakt

Im Gegensatz zu den beiden anderen Funktionen wird hier die Steuerung erst dann auf andere Funktionen übertragen, wenn der Indikator neu geladen oder die Frequenz, den Chart zu zeichnen, geändert wird. Jeder neue Aufruf erfolgt jede Sekunde durch den Timer, und während dieser Zeit wird die aktuelle Kerze gezeichnet (mit Daten gefüllt). Zuerst werden die Daten aus dem übergebenen Array in die aktuelle Kerze kopiert, dann werden die Anfangsdaten an die nächste Kerze übergeben. Am Ende überträgt die Funktion die Zeit, zu der die letzte Kerze gebildet wurde.

Die oben beschriebene Funktion ist verantwortlich für die "siebte Geschwindigkeit" der Kerzenerzeugung (siehe Bedienfeld).

//+------------------------------------------------------------------+
//| Func Candle Per Seconds                                          |
//+------------------------------------------------------------------+
void func_candle_per_seconds(MqlRates &input_rates[],
 datetime &input_end_time_indicator,
 int input_bars_now_rates,
 int input_number_now_rates,
 int &input_end_bar_indicator,
 double &output_o[],
 double &output_h[],
 double &output_l[],
 double &output_c[],
 double &output_col[],
 char &work_status)
{
 if(work_status==-1)
 {
if(input_number_now_rates<input_bars_now_rates)
{
 if(input_number_now_rates!=0)
 {
output_o[input_end_bar_indicator]=input_rates[input_number_now_rates-1].open;
output_h[input_end_bar_indicator]=input_rates[input_number_now_rates-1].high;
output_l[input_end_bar_indicator]=input_rates[input_number_now_rates-1].low;
output_c[input_end_bar_indicator]=input_rates[input_number_now_rates-1].close;
if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0;
else output_col[input_end_bar_indicator]=1;
 }
 input_end_bar_indicator--;
 output_o[input_end_bar_indicator]=input_rates[input_number_now_rates].open;
 output_h[input_end_bar_indicator]=input_rates[input_number_now_rates].high;
 output_l[input_end_bar_indicator]=input_rates[input_number_now_rates].low;
 output_c[input_end_bar_indicator]=input_rates[input_number_now_rates].close;
 if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0;
 else output_col[input_end_bar_indicator]=1;
 input_end_time_indicator=input_rates[input_number_now_rates+1].time;
}

Die beiden folgenden Funktionen sind einander sehr ähnlich. Eine von ihnen erstellt die Kerzen zeitgesteuert, trotz der Ticks. Die Zweite (das "Juwel") verwendet den am Anfang des Artikels beschriebenen Tick-Generator für eine vollständigere Emulation des Marktes.

Die Funktion func_of_form_candle(), die die Kerzen erstellt

Die Eingabeparameter sind die gleichen wie zuvor (OHLC). Was die Funktionalität betrifft, ist alles ganz einfach. Die Preise werden in einer Schleife aus den M1 Zeitrahmen in die aktuelle Kerze kopiert, beginnend mit der Zeit, die von der Funktion func_filling() empfangen wurde. Daraus ergibt sich, dass eine Kerze allmählich entsteht. Auf diese Weise werden die Geschwindigkeiten von der zweiten bis zur sechsten Stufe konstruiert (siehe Bedienfeld). Wenn der Zeitpunkt für das Ende der Kerzen im aktuellen Zeitrahmen erreicht ist, wird der Merker "work_status" geändert, so dass bei der nächsten Ausführung des Timers die Funktion func_filling() erneut aufgerufen wird.

//+------------------------------------------------------------------+
//| Func Of Form Candle                                              |
//+------------------------------------------------------------------+
void func_of_form_candle(MqlRates &input_rates[],
 int input_bars,
 datetime &input_time_end_filling,
 datetime &input_end_time_indicator,
 datetime &input_time_next_filling,
 int input_end_bar_indicator,
 double &output_o[],
 double &output_h[],
 double &output_l[],
 double &output_c[],
 double &output_col[],
 char &work_status)
{
 if(work_status==-1)
 {
int start_of_z=0;
int end_of_z=0;
start_of_z=Bars(_Symbol,PERIOD_M1,real_start,input_time_end_filling);
end_of_z=Bars(_Symbol,PERIOD_M1,real_start,input_end_time_indicator);
for(int z=start_of_z; z<end_of_z; z++)
{
 output_c[input_end_bar_indicator]=input_rates[z].close;
 if(output_h[input_end_bar_indicator]<input_rates[z].high)output_h[input_end_bar_indicator]=input_rates[z].high;
 if(output_l[input_end_bar_indicator]>input_rates[z].low)output_l[input_end_bar_indicator]=input_rates[z].low;
 if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0;
 else output_col[input_end_bar_indicator]=1;
}
if(input_end_time_indicator>=input_time_next_filling)work_status=1;
 }

Kommen wir nun zur Funktion, die in der Lage ist, eine Kerze zu formen, die so nah wie möglich am Markt ist.

Die Funktion func_of_form_jeweler_candle() für die Simulation von Kerzen nach dem "Juwel".

Zu Beginn der Funktion geschieht alles wie in der Vorgängerversion. Die Daten des Zeitrahmens von einer Minute füllen die aktuelle Kerze bis auf die letzte Minute vollständig aus. Seine Daten werden an die Funktion func_tick_generation() zur Erzeugung von Ticks übergeben, die am Anfang des Artikels beschrieben wird. Bei jedem Aufruf der Funktion wird das empfangene Tick-Array schrittweise als aktueller ´Schlusskurs der Kerze übergeben, wobei auch die "Schatten" angepasst werden. Wenn alle die "Ticks" des Arrays verarbeitet wurden, wird der Vorgang wiederholt.

//+------------------------------------------------------------------+
//| Func Of Form Jeweler Candle                                      |
//+------------------------------------------------------------------+
void func_of_form_jeweler_candle(MqlRates &input_rates[],                    // Information zur Tickerzeugung
 int input_bars,                             // Größe des Informationsarrays
 datetime &input_time_end_filling,           // Endzeit von "quick-fill"
 datetime &input_end_time_indicator,         // die letzte Simulationszeit des Indikators
 datetime &input_time_next_filling,          // verbleibende Zeit bis der aktuelle Zeitrahmen komplett ist
 int input_end_bar_indicator,                // die aktuell gezeichnete Bar des Indikators
 double &output_o[],
 double &output_h[],
 double &output_l[],
 double &output_c[],
 double &output_col[],
 char &work_status                           // Typus des Arbeitsendes (Befehl der Funktion "quick fill")
 )
{
 if(work_status==-1)
 {
int start_of_z=0;
int current_of_z=0;
start_of_z=Bars(_Symbol,PERIOD_M1,real_start,input_time_end_filling)-1;
current_of_z=Bars(_Symbol,PERIOD_M1,real_start,input_end_time_indicator)-1;
if(start_of_z<current_of_z-1)
{
 for(int z=start_of_z; z<current_of_z-1; z++)
 {
output_c[input_end_bar_indicator]=input_rates[z].close;
if(output_h[input_end_bar_indicator]<input_rates[z].high)output_h[input_end_bar_indicator]=input_rates[z].high;
if(output_l[input_end_bar_indicator]>input_rates[z].low)output_l[input_end_bar_indicator]=input_rates[z].low;
if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0;
else output_col[input_end_bar_indicator]=1;
 }
 input_end_time_indicator=input_rates[current_of_z].time;
}
// get the ticks in the array
static int x=0;                   // Arrayzähler und "Start-Flag"
static double tick_current[];
static int tick_current_size=0;
if(x==0)
{
 func_tick_generation(input_rates[current_of_z-1],tick_current);
 tick_current_size=ArraySize(tick_current);
 if(output_h[input_end_bar_indicator]==0)
 {output_h[input_end_bar_indicator]=tick_current[x];}
 if(output_l[input_end_bar_indicator]==0)
 {output_l[input_end_bar_indicator]=tick_current[x];}
 output_c[input_end_bar_indicator]=tick_current[x];
}
if(x<tick_current_size)
{
 output_c[input_end_bar_indicator]=tick_current[x];
 if(tick_current[x]>output_h[input_end_bar_indicator])
 {output_h[input_end_bar_indicator]=tick_current[x];}
 if(tick_current[x]<output_l[input_end_bar_indicator])
 {output_l[input_end_bar_indicator]=tick_current[x];}
 if(output_o[input_end_bar_indicator]>output_c[input_end_bar_indicator])output_col[input_end_bar_indicator]=0;
 else output_col[input_end_bar_indicator]=1;
 x++;
}
else
{
 input_end_time_indicator=input_rates[current_of_z+1].time;
 x=0;
 tick_current_size=0;
 ArrayFree(tick_current);
}
if(input_end_time_indicator>input_time_next_filling)
{work_status=1;}
}

Alle drei Funktionen zur Erzeugung von Kerzen sind in der Funktion Merger zusammengefasst.

Die Funktion func_merger() für die kombinierte Simulation

Die im Emulationsprozess verwendete Funktion wird in Abhängigkeit von der vom Schalter gewählten Geschwindigkeit bestimmt. Die Funktion hat drei Typen. Jede beginnt mit der Funktion func_filling(), dann wird die Steuerung an eine der drei Funktionen zur Erstellen der Kerzen übergeben: func_of_form_jeweler_candle(), func_of_form_candle() oder func_candle_per_seconds(). Die Zeit wird bei jedem Durchgang der zweiten bis sechsten Geschwindigkeit inklusive neu berechnet. Die Funktion func_calc_time() berechnet den benötigten Teil des aktuellen Zeitrahmens und fügt ihn zur aktuellen Zeit hinzu. Der Bid-Preis wird aus dem Schlusskurs der aktuellen Kerze entnommen, und Ask wird auf der Grundlage des vom Server kommenden Spread berechnet.

//+------------------------------------------------------------------+
//| Func Merger                                                      |
//+------------------------------------------------------------------+
void func_merger()
{
 switch(button_speed)
 {
case 1:
{
 func_filling(period_array,end_time_indicator,all_bars_indicator,time_open_end_rates,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 func_of_form_jeweler_candle(period_m1,bars_m1,time_open_end_rates,end_time_indicator,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 price_bid_now=TST_C_C[end_bar_indicator];
 price_ask_now=price_bid_now+(spread*_Point);
}
break;
case 2:
{
 func_filling(period_array,end_time_indicator,all_bars_indicator,time_open_end_rates,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 func_of_form_candle(period_m1,bars_m1,time_open_end_rates,end_time_indicator,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 price_bid_now=TST_C_C[end_bar_indicator];
 price_ask_now=price_bid_now+(spread*_Point);
 end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,13);
}
break;
case 3:
{
 ...
 end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,11);
}
break;
case 4:
{
 ...
 end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,9);
}
break;
case 5:
{
 ...
 end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,7);
}
break;
case 6:
{
 ...
 end_time_indicator+=func_calc_time(time_open_end_rates,time_open_next_rates,5);
}
break;
case 7:
{
 func_filling(period_array,end_time_indicator,all_bars_indicator,time_open_end_rates,time_open_next_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 func_candle_per_seconds(period_array,end_time_indicator,bars_now_rates,number_now_rates,end_bar_indicator,TST_C_O,TST_C_H,TST_C_L,TST_C_C,TST_C_Col,status);
 price_bid_now=TST_C_C[end_bar_indicator];
 price_ask_now=price_bid_now+(spread*_Point);
}
break;
 }
}

Einsatzmöglichkeiten

Ich schlage vor, diesen Indikator zu verwenden, um neue Ideen und Handelsstrategien zu testen, das Verhalten eines unerfahrenen Händlers in einer bestimmten Situation zu modellieren und das Eröffnen und Schließen von Positionen zu üben. Dies betrifft in erster Linie technische Hilfsmittel: Dieser Indikator kann z.B. zum Plotten der Elliott-Wellen, Kanäle oder zum Testen der Arbeit von Unterstützungs-/Widerstandslinien verwendet werden.

Beispiel für die Funktionsweise des Indikators:

Schlussfolgerung

Nun will ich das Geheimnis lüften: Warum wurde einer der Generationentypen überhaupt "Juwel" genannt?

Das ist einfach. Während der Entwicklung dieser Anwendung bin ich zu dem Schluss gekommen, dass eine solch glatte und genaue Modellierung nicht notwendig ist, um die meisten Strategien zu testen. Deshalb ist es eine Art Luxus, ein Juwel eben. Diese Ticks simulieren die Fluktuationen fast vergleichbar mit dem Spread und haben wenig Einfluss auf den Fluss der Strategie, geschweige denn auf die Testgeschwindigkeit. Es ist unwahrscheinlich, dass jemand mehrere Tage vergeudet, um einen Einstiegspunkt abzupassen, wenn es möglich ist, zum nächsten, geeigneten Punkt zurückzuspulen.

Was den Code betrifft, so ist die Möglichkeit verschiedener Ausfälle nicht ausgeschlossen. Dies sollte jedoch die Analyse der Strategie als Ganzes nicht beeinträchtigen. Schließlich sind alle grundlegenden Aktionen in den globalen Variablen gespeichert, und es ist möglich, einfach den Zeitrahmen oder das Terminal neu zu laden (ohne das Fenster des Indikators zu schließen) und dann die weitere Emulation fortzusetzen.

Viele Hilfsfunktionen wurden in der Beschreibung des Codes weggelassen. Sie sind unkompliziert oder werden bereits in der Dokumentation erläutert. In jedem Fall, zögern Sie nicht, Fragen zu stellen, wenn es etwas gibt, das Sie nicht verstehen. Wie immer sind Kommentare sehr willkommen.


Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/3965

Beigefügte Dateien |
STSv1.1.ex5 (120.96 KB)
STSv1.1.mq5 (127.38 KB)
for_STS.zip (13.39 KB)
Trianguläre Arbitrage Trianguläre Arbitrage
Der Artikel beschäftigt sich mit der populären Handelsmethode - dem Trianguläre Arbitrage. Wir analysieren hier das Thema so detailliert wie möglich, betrachten die positiven und negativen Aspekte der Strategie und entwickeln den fertigen Code für einen Expert Advisor.
Vergleich verschiedener Typen gleitender Durchschnitte im Handel Vergleich verschiedener Typen gleitender Durchschnitte im Handel
Es wurden 7 Typen gleitender Durchschnitte (MA) betrachtet; es wurde eine Handelsstrategie für das Arbeiten mit ihnen entwickelt. Verschiedene gleitende Durchschnitte wurden anhand einer Handelsstrategie getestet, des Weiteren wurden diese hinsichtlich der Effektivität der Anwendung verglichen.
Testen der Muster, die beim Handel mit Körben von Währungspaaren auftreten. Teil II Testen der Muster, die beim Handel mit Körben von Währungspaaren auftreten. Teil II
Wir testen die Muster und prüfen die Methoden weiter, die in den Artikeln über den Handel mit Körben von Währugnspaaren beschrieben wurden. Betrachten wir in der Praxis, ob man die Muster verwenden kann, bei welchen die Grafik eines vereinigten WPR einen gleitenden Durchschnitt kreuzt, und wenn ja, dann wie genau.
Fuzzy-Logik in Handelsstrategien Fuzzy-Logik in Handelsstrategien
Der Artikel befasst sich mit einem Beispiel für die Anwendung der Fuzzy-Logik, um ein einfaches Handelssystem unter Verwendung der Fuzzy-Bibliothek zu erstellen. Es werden Varianten zur Verbesserung des Systems durch Kombination von Fuzzy-Logik, genetischen Algorithmen und neuronalen Netzen vorgeschlagen.