Na platformě MT4 se mi líbí, že se dá spustit backtest a zároveň můžu sledovat online, jak AOS otevírá jednotlivé pozice. A přesně toto se dá využít i při ručním backtestování. Jen to chce v testeru spustit EA, kterému za běhu sdělím, kdy a jak má otevřít obchod. Zkoušel jsem vykreslit tlačítka do grafu, která by měla tuto funkci, ale během backtestu nefunguje volání metody OnChartEvent. Touto cestou to tedy nevede. Další varianta je vykreslit horizontální linku do grafu, kterou si pak EA už přebere. Výhodou tohoto backtestu bude, že výsledek bude obsahovat všechny ty vychytávky, co backtester v MT4 dělá. Kdo by hledal na internetu, tak jistě takovýto obdobný nástroj najde. Ale kdybych si ho nenapsal sám, tak tady nebudu mít o čem psát .
Tak tedy, než se pustím do samotného backtestování, tak si budu muset naprogramovat onen vytoužený nástroj, který mi backtesty usnadní. Pravděpodobně tento nástroj sám užívat stejně nebudu, protože budu backtesty dělat v cTraderu, ale třeba to někomu tady pomůže.
Jak jsem již psal, EA se bude ovládat pomocí horizontálních linek, které pojmenuju podle toho, co od ní čekám. Pro jednoduchost budu mít otevřený vždy jen jeden pokyn. Ovládání tak bude mnohem jednodušší, než kdybych otevíral více obchodů současně. Otevřít půjdou, ale abych jim nastavil SL a TP, jak bych se musel nějak třídit. A taky by se to špatně ovládalo.
Co do ovládání, tak moje představa je taková, že vložím horizontální linku do grafu. Zobrazím si vlastnosti linky a přejmenuju její název na nějaké klíčové slovo, na které bude EA reagovat a následně linku z grafu vymaže. Klíčová slova si definuji takto:
buy – nákup za market
sell – prodej za market
pbuy – (pendidng buy) čekající pokyn pro nákup (EA si vybere sám, jestli to bude STOP, nebo LIMIT)
psell – (pending sell) čekající pokyn pro prodej (EA si vybere sám, jestli to bude STOP, nebo LIMIT)
tp – na této ceně se nastaví TP
sl – na této ceně se nastaví SL
Vytvořím si tedy v MetaEditoru EA, které příhodně pojmenuji Backtester J. Při otevření pokynu potřebuji zadat velikost pozice – vytvořím si proto globální externí proměnnou s výchozí hodnotou třeba 0.01 lotu:
extern double TradeVolume = 0.01;
Nyní se můžeme vrhnout na metodu OnTick, která se volá – dle svého názvu – při každém novém ticku. Můžu použít detekci horizontální linky pomocí ObjectFind, nebo můžu použít smyčku, kde v každém ticku projdu všechny objekty a ověřím jejich název. Zvolím si tu druhou (složitější) variantu, protože pokud se rozhodu dále EA rozšiřovat pro práci s více obchody, musel bych to předělávat. Nyní si tedy snadním následnou možnou rozšiřitelnost. Začnu tedy smyčkou, která bud detekovat linky s názvem buy, sell, pbuy a psell a následně na ně reagovat.
for( int oindex = 0; oindex <= ObjectsTotal(); oindex++ )
{
string oname = ObjectName( oindex );
if( ObjectType( oname ) != OBJ_HLINE )
continue;
if( oname == "buy" )
{
if( OrderSend( Symbol(), OP_BUY, TradeVolume, Ask, 0, 0, 0 ) == -1 )
Alert( "Nepodařilo se zadat příkaz. Error: " + IntegerToString( GetLastError() ) );
ObjectDelete( oname );
}
if( oname == "sell" )
{
if( OrderSend( Symbol(), OP_SELL, TradeVolume, Bid, 0, 0, 0 ) == -1 )
Alert( "Nepodařilo se zadat příkaz. Error: " + IntegerToString( GetLastError() ) );
ObjectDelete( oname );
}
if( oname == "pbuy" )
{
double price = NormalizeDouble( ObjectGet( oname, OBJPROP_PRICE1 ), Digits() );
if( price > Ask )
{
if( OrderSend( Symbol(), OP_BUYSTOP, TradeVolume, price, 0, 0, 0 ) == -1 )
Alert( "Nepodařilo se zadat příkaz. Error: " + IntegerToString( GetLastError() ) );
}
else
{
if( OrderSend( Symbol(), OP_BUYLIMIT, TradeVolume, price, 0, 0, 0 ) == -1 )
Alert( "Nepodařilo se zadat příkaz. Error: " + IntegerToString( GetLastError() ) );
}
ObjectDelete( oname );
}
if( oname == "psell" )
{
double price = NormalizeDouble( ObjectGet( oname, OBJPROP_PRICE1 ), Digits() );
if( price < Bid )
{
if( OrderSend( Symbol(), OP_SELLSTOP, TradeVolume, NormalizeDouble( ObjectGet( oname, OBJPROP_PRICE1 ), Digits() ), 0, 0, 0 ) == -1 )
Alert( "Nepodařilo se zadat příkaz. Error: " + IntegerToString( GetLastError() ) );
}
else
{
if( OrderSend( Symbol(), OP_SELLLIMIT, TradeVolume, NormalizeDouble( ObjectGet( oname, OBJPROP_PRICE1 ), Digits() ), 0, 0, 0 ) == -1 )
Alert( "Nepodařilo se zadat příkaz. Error: " + IntegerToString( GetLastError() ) );
}
ObjectDelete( oname );
}
}
Dále potřebuji zjistit, jestli již je zadaný nějaký příkaz. Pokud ano, tak musím zjistit jeho ticket. Až budu vědět, že je otevřený obchod a budu znát jeho ticket, tak můžu ověřit jestli existuje linka, která nastaví jeho TP, nebo SL. Pro zjištění existence obchodu a jeho ticketu použiji metodu GetTicket(), kterou si vytvořím nakonec. Tato metoda mi po zavolání vrátí číslo ticketu otevřené pozice (int).
int ticket = GetTicket();
if( !OrderSelect( ticket, SELECT_BY_TICKET ) )
return;
if( ticket > 0 )
{
for( int oindex = 0; oindex <= ObjectsTotal(); oindex++ )
{
string oname = ObjectName( oindex );
if(ObjectType( oname ) != OBJ_HLINE )
continue;
if( oname == "tp" )
{
if( !OrderModify( OrderTicket(), OrderOpenPrice(), OrderStopLoss(), NormalizeDouble( ObjectGet( oname, OBJPROP_PRICE1 ), Digits() ), OrderExpiration() ) )
Alert( "Nepodařilo se upravit. Error: " + IntegerToString( GetLastError() ) );
ObjectDelete( oname );
}
if( oname == "sl" )
{
if( !OrderModify( OrderTicket(), OrderOpenPrice(), NormalizeDouble( ObjectGet( oname, OBJPROP_PRICE1 ), Digits() ), OrderTakeProfit(), OrderExpiration() ) )
Alert( "Nepodařilo se upravit. Error: " + IntegerToString( GetLastError() ) );
ObjectDelete( oname );
}
}
}
Tímto bych metodu OnTick() ukončil. Nakonec ještě musím doprogramovat GetTicket(), která mi vrátí číslo ticketu a zapíše do té proměnné ticket. Mám na to svou oblíbenou smyčku, kterou vždy jen dle potřeby trochu upravím. Například aby mi hledala pouze BUY a SELL obchody a nehledala čekačky atd… Základem je podmínka, která filtruje symbol. Zde – u EA které poběží jen v backtestech to není nutné, ale pokud bych tuto smyčku použil u AOSu, tak je nutné filtrovat, aby ignoroval obchody na ostatních instrumentech, než na kterém běží AOS. Další věc, kterou je dobré u AOSů filtrovat (v této smyčce ale není) je magic number. Hodí se to například, pokud mi na instrumentu EURUSD pojedou 2 AOSy, tak aby si rozpoznaly svoje obchody. Takže metoda GetTicket() bude vypadat nějak takto:
int GetTicket()
{
for( int pos = 0; pos < OrdersTotal(); pos++ )
{
if( OrderSelect( pos, SELECT_BY_POS ) == false )
continue;
if( Symbol() != OrderSymbol() )
continue;
return( OrderTicket() );
}
return(0);
}
Tak a teď kompilovat a máme hotovo! Hurá do backtestu!
Pokud budu již mít zadaný pokyn a u něj nastavený SL a budu jej chtít třeba jen posunout, tak prostě vložím novou linku, kterou pojmenuju „sl“ a pozice se upraví.
Celý soubor je ke stažení zde.