MQL5 Tarif Defteri Pozisyon Parametrelerini MetaTrader 5 Strateji Test Cihazında Analiz Etme
Anatoli Kazharski | 13 Ocak, 2022
Giriş
Bu makalede, aşağıda yer alan bir önceki makalede oluşturulan Uzman Danışmanı değiştireceğiz: "MQL5 Tarif Defteri: Özel Bilgi Panelindeki Pozisyon Özellikleri" ve aşağıdaki sorunları ele alacağız:
- Mevcut sembol üzerindeki yeni çubuk olaylarını kontrol etme;
- Çubuklardan veri alma;
- Standart Kitaplığın bir alım satım sınıfını bir dosyaya ekleme;
- Alım satım sinyallerini aramak için bir fonksiyon oluşturma;
- Alım satım işlemlerini yürütmek için bir fonksiyon oluşturma;
- OnTrade() fonksiyonundaki alım satım olaylarını belirleme.
Aslında, yukarıdaki konuların her biri kendi başına bir makaleyi hak edebilir, ancak bence böyle bir yaklaşım dilin incelenmesini yalnızca karmaşıklaştıracaktır.
Bu özelliklerin nasıl uygulanabileceğini size göstermek için çok basit örnekler kullanacağım. Diğer bir deyişle, yukarıda listelenen görevlerin her birinin uygulanması, deyim yerindeyse basit ve anlaşılır bir fonksiyona sığacaktır. Serinin gelecekteki makalelerinde belirli bir fikir geliştirirken, bu fonksiyonları, gerektiği kadar ve elimizdeki işin gerektirdiği ölçüde yavaş yavaş daha karmaşık hale getireceğiz.
İlk olarak, tüm fonksiyonlarına ihtiyaç duyacağımız için önceki makalesinden Uzman Danışmanı kopyalayalım.
Bir Uzman Danışman Geliştirme
Standart Kitaplıktan CTrade sınıfını dosyamıza ekleyerek başlayacağız. Bu sınıfta, alım satım işlemlerini yürütmek için gerekli tüm fonksiyonlar vardır. Başlangıç olarak, bunları içeriğine bile bakmadan kolayca kullanabiliriz, işte yapacağımız şey bu.
Sınıfı eklemek için aşağıdakileri yazmamız gerekiyor:
//--- Include a class of the Standard Library #include <Trade/Trade.mqh>
Daha sonra kolayca bulabilmek için bu kodu dosyanın en başına, örneğin #define direktifinden sonra yerleştirebilirsiniz. #include komutu, Trade.mqh dosyasının, <MetaTrader 5 terminal dizini>\MQL5\Include\Trade\ konumundan alınması gerektiğini belirtir. Aynı yaklaşım, fonksiyon içeren başka herhangi bir dosyayı eklemek için de kullanılabilir. Bu, özellikle proje kodunun miktarı artığında ve içinde gezinmenin zorlaştığı durumlarda kullanışlıdır.
Şimdi, tüm fonksiyonlarına erişebilmek için sınıfın bir örneğini oluşturmamız gerekiyor. Bu, sınıfın adından sonra örneğin adı yazılarak yapılabilir:
//--- Load the class
CTrade trade;
Uzman Danışmanın bu sürümünde, CTrade sınıfında bulunan tüm fonksiyonlardan yalnızca bir alım satım fonksiyonunu kullanacağız. Bu, bir pozisyon açmak için kullanılan PositionOpen() fonksiyonudur. Mevcut bir açık pozisyonun ters çevrilmesi için de kullanılabilir. Bu fonksiyonun sınıftan nasıl çağrılabileceği bu makalenin ilerleyen kısımlarında alım satım işlemlerinin yürütülmesinden sorumlu bir fonksiyon oluşturulurken gösterilecektir.
Ayrıca, global kapsamda iki dinamik dizi ekleriz. Bu diziler çubuk değerlerini alacaktır.
//--- Price data arrays double close_price[]; // Close (closing prices of the bar) double open_price[]; // Open (opening prices of the bar)
Ardından, alım satım işlemleri yalnızca tamamlanmış çubuklarda yürütüleceğinden, programın yeni çubuk olaylarını kontrol edeceği bir CheckNewBar() fonksiyonu oluşturun.
Aşağıda, detaylı yorumlar ile CheckNewBar() fonksiyon kodu yer almaktadır:
//+------------------------------------------------------------------+ //| CHECKING FOR THE NEW BAR | //+------------------------------------------------------------------+ bool CheckNewBar() { //--- Variable for storing the opening time of the current bar static datetime new_bar=NULL; //--- Array for getting the opening time of the current bar static datetime time_last_bar[1]={0}; //--- Get the opening time of the current bar // If an error occurred when getting the time, print the relevant message if(CopyTime(_Symbol,Period(),0,1,time_last_bar)==-1) { Print(__FUNCTION__,": Error copying the opening time of the bar: "+IntegerToString(GetLastError())+""); } //--- If this is a first function call if(new_bar==NULL) { // Set the time new_bar=time_last_bar[0]; Print(__FUNCTION__,": Initialization ["+_Symbol+"][TF: "+TimeframeToString(Period())+"][" +TimeToString(time_last_bar[0],TIME_DATE|TIME_MINUTES|TIME_SECONDS)+"]"); return(false); // Return false and exit } //--- If the time is different if(new_bar!=time_last_bar[0]) { new_bar=time_last_bar[0]; // Set the time and exit return(true); // Store the time and return true } //--- If we have reached this line, then the bar is not new, return false return(false); }
Yukarıdaki kodda görebileceğiniz gibi, CheckNewBar() fonksiyonu, çubuk yeniyse true veya henüz yeni bir çubuk yoksa false döndürür. Bu şekilde, alım satım/test yaparken durumu kontrol ederek sadece tamamlanmış çubuklarda alım satım işlemlerini yürütürsünüz.
Fonksiyonun en başında, bir static değişken ve datetime türünde bir statik dizi bildiririz. Statik yerel değişkenler, fonksiyondan çıkıldıktan sonra bile değerlerini korur. Bu yerel değişkenler, sonraki her fonksiyon çağrısında, fonksiyonun önceki çağrısında aldıkları değerleri içerecektir.
Ayrıca, lütfen CopyTime() fonksiyonuna dikkat edin. Bu, time_last_bar dizisindeki son çubuğun zamanını almamıza yardımcı olur. MQL5 Referansı içindeki fonksiyon sözdizimini kontrol ettiğinizden emin olun.
Ayrıca, bu makale dizisinde daha önce hiç bahsedilmeyen kullanıcı tanımlı TimeframeToString() fonksiyonu da fark edilebilir. Bu, zaman aralığı değerlerini kullanıcı için anlaşılır bir dizeye dönüştürür:
string TimeframeToString(ENUM_TIMEFRAMES timeframe) { string str=""; //--- If the passed value is incorrect, take the time frame of the current chart if(timeframe==WRONG_VALUE || timeframe == NULL) timeframe = Period(); switch(timeframe) { case PERIOD_M1 : str="M1"; break; case PERIOD_M2 : str="M2"; break; case PERIOD_M3 : str="M3"; break; case PERIOD_M4 : str="M4"; break; case PERIOD_M5 : str="M5"; break; case PERIOD_M6 : str="M6"; break; case PERIOD_M10 : str="M10"; break; case PERIOD_M12 : str="M12"; break; case PERIOD_M15 : str="M15"; break; case PERIOD_M20 : str="M20"; break; case PERIOD_M30 : str="M30"; break; case PERIOD_H1 : str="H1"; break; case PERIOD_H2 : str="H2"; break; case PERIOD_H3 : str="H3"; break; case PERIOD_H4 : str="H4"; break; case PERIOD_H6 : str="H6"; break; case PERIOD_H8 : str="H8"; break; case PERIOD_H12 : str="H12"; break; case PERIOD_D1 : str="D1"; break; case PERIOD_W1 : str="W1"; break; case PERIOD_MN1 : str="MN1"; break; } //--- return(str); }
CheckNewBar() fonksiyonunun nasıl kullanıldığı, diğer tüm gerekli fonksiyonlar hazır olduğunda makalenin ilerleyen bölümlerinde gösterilecektir. Şimdi istenen sayıda çubuğun değerlerini alan GetBarsData() fonksiyonuna bakalım.
//+------------------------------------------------------------------+ //| GETTING BAR VALUES | //+------------------------------------------------------------------+ void GetBarsData() { //--- Number of bars for getting their data in an array int amount=2; //--- Reverse the time series ... 3 2 1 0 ArraySetAsSeries(close_price,true); ArraySetAsSeries(open_price,true); //--- Get the closing price of the bar // If the number of the obtained values is less than requested, print the relevant message if(CopyClose(_Symbol,Period(),0,amount,close_price)<amount) { Print("Failed to copy the values (" +_Symbol+", "+TimeframeToString(Period())+") to the Close price array! " "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } //--- Get the opening price of the bar // If the number of the obtained values is less than requested, print the relevant message if(CopyOpen(_Symbol,Period(),0,amount,open_price)<amount) { Print("Failed to copy the values (" +_Symbol+", "+TimeframeToString(Period())+") to the Open price array! " "Error "+IntegerToString(GetLastError())+": "+ErrorDescription(GetLastError())); } }
Yukarıdaki kodu daha yakından inceleyelim. İlk olarak miktar (amount) değişkeninde verisini almamız gereken çubuk sayısını belirtiriz. Ardından, ArraySetAsSeries() fonksiyonunu kullanarak son (mevcut) çubuğun değeri dizinin sıfır indisinde olacak şekilde dizi indisleme sırasını ayarlarız. Örneğin, hesaplamalarınızda son çubuğun değerini kullanmak istiyorsanız, açılış fiyatı ile örneklendirilirse şu şekilde yazılabilir: open_price[0]. Sondan ikinci çubuğun gösterimi benzer şekilde olacaktır: open_price[1].
Kapanış ve açılış fiyatlarını alma mekanizması, son çubuğun zamanını elde etmemiz gereken CheckNewBar() fonksiyonuna benzerdir. Sadece bu durumda CopyClose() veCopyOpen() fonksiyonlarını kullanırız. Benzer şekilde, CopyHigh() ve CopyLow(), sırasıyla yüksek ve düşük çubuk fiyatlarını elde etmek için kullanılır.
Devam edelim ve bir pozisyonun açılması/ters çevrilmesi için sinyallerin nasıl belirleneceğini gösteren çok basit bir örneği ele alalım. Fiyat dizileri, iki çubuk için verileri saklar (mevcut çubuk ve önceki tamamlanmış çubuk). Tamamlanmış çubuktaki verileri kullanacağız.
- Kapanış fiyatı açılış fiyatının üzerinde olduğunda, bir Alış sinyali yukarı yönlü çubuk) meydana gelir;
- Kapanış fiyatı açılış fiyatının altında olduğunda, bir Satış sinyali (aşağı yönlü çubuk) olduğunda meydana gelir;
Bu basit koşulların uygulanması için kod aşağıda verilmektedir:
//+------------------------------------------------------------------+ //| DETERMINING TRADING SIGNALS | //+------------------------------------------------------------------+ int GetTradingSignal() { //--- A Buy signal (0) : if(close_price[1]>open_price[1]) return(0); //--- A Sell signal (1) : if(close_price[1]<open_price[1]) return(1); //--- No signal (3): return(3); }
Gördüğünüz gibi, çok basit. Daha karmaşık durumların nasıl benzer bir şekilde ele alınacağı kolaylıkla anlaşılabilir. Fonksiyon, tamamlanmış bir çubuk yukarıdaysa sıfır veya tamamlanmış bir çubuk aşağıdaysa bir döndürür. Herhangi bir sebeple sinyal yoksa, fonksiyon 3 döndürecektir.
Şimdi, alım satım faaliyetlerinin uygulanması için bir TradingBlock() fonksiyonu oluşturmamız gerekiyor. Aşağıda, detaylı yorumlar içeren fonksiyon kodu yer almaktadır:
//+------------------------------------------------------------------+ //| TRADING BLOCK | //+------------------------------------------------------------------+ void TradingBlock() { int signal=-1; // Variable for getting a signal string comment="hello :)"; // Position comment double start_lot=0.1; // Initial volume of a position double lot=0.0; // Volume for position calculation in case of reverse position double ask=0.0; // Ask price double bid=0.0; // Bid price //--- Get a signal signal=GetTradingSignal(); //--- Find out if there is a position pos_open=PositionSelect(_Symbol); //--- If it is a Buy signal if(signal==0) { //--- Get the Ask price ask=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- If there is no position if(!pos_open) { //--- Open a position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,start_lot,ask,0,0,comment)) { Print("Error opening a BUY position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } //--- If there is a position else { //--- Get the position type pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- If it is a SELL position if(pos_type==POSITION_TYPE_SELL) { //--- Get the position volume pos_volume=PositionGetDouble(POSITION_VOLUME); //--- Adjust the volume lot=NormalizeDouble(pos_volume+start_lot,2); //--- Open a position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,ORDER_TYPE_BUY,lot,ask,0,0,comment)) { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } } //--- return; } //--- If there is a Sell signal if(signal==1) { //-- Get the Bid price bid=NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- If there is no position if(!pos_open) { //--- Open a position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,start_lot,bid,0,0,comment)) { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } //--- If there is a position else { //--- Get the position type pos_type=(ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); //--- If it is a BUY position if(pos_type==POSITION_TYPE_BUY) { //--- Get the position volume pos_volume=PositionGetDouble(POSITION_VOLUME); //--- Adjust the volume lot=NormalizeDouble(pos_volume+start_lot,2); //--- Open a position. If the position failed to open, print the relevant message if(!trade.PositionOpen(_Symbol,ORDER_TYPE_SELL,lot,bid,0,0,comment)) { Print("Error opening a SELL position: ",GetLastError()," - ",ErrorDescription(GetLastError())); } } } //--- return; } }
Bir pozisyon açılana kadar her şeyin anlaşılır olduğuna inanıyorum. Yukarıdaki kodda görebileceğiniz gibi, (trade (alım satım)) işaretçisini bir nokta takip eder ve ardından PositionOpen() yöntemi gelir. Bir sınıftan belirli bir yöntemi bu şekilde çağırabilirsiniz. Bir nokta koyduktan sonra, tüm sınıf yöntemlerini içeren bir liste göreceksiniz. Tek ihtiyacınız olan listeden gerekli yöntemi seçmek:
Şekil 1. Bir sınıf yöntemini çağırma.
TradingBlock() fonksiyonunda iki ana blok vardır - alış yapmak ve satış yapmak. Sinyalin yönünü belirledikten hemen sonra, Alış sinyali olması durumunda satış (ask) fiyatını, Satış sinyali olması durumunda ise alış (bid) fiyatını elde ederiz.
Alım satım emirlerinde kullanılan tüm fiyatlar/seviyeler, NormalizeDouble() fonksiyonu kullanılarak normalleştirilmelidir, aksi takdirde pozisyonu açma veya değiştirme girişimi hataya yol açar. Lot hesaplanırken de bu fonksiyonun kullanılması tavsiye edilir. Ayrıca, Zarar Durdur ve Kâr Al parametrelerinin sıfır değerinde olduklarını lütfen unutmayın. Alım satım seviyelerinin ayarlanmasına dair daha fazla bilgi, serinin bir sonraki makalesinde sağlanacaktır.
Artık tüm kullanıcı tanımlı fonksiyonlar hazır olduğuna göre, bunları doğru sırada düzenleyebiliriz:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Initialize the new bar CheckNewBar(); //--- Get position properties and update the values on the panel GetPositionProperties(); //--- return(0); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Print the deinitialization reason to the journal Print(GetDeinitReasonText(reason)); //--- When deleting from the chart if(reason==REASON_REMOVE) //--- Delete all objects relating to the info panel from the chart DeleteInfoPanel(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- If the bar is not new, exit if(!CheckNewBar()) return; //--- If there is a new bar else { GetBarsData(); // Get bar data TradingBlock(); // Check the conditions and trade } //--- Get the properties and update the values on the panel GetPositionProperties(); }
Değerlendirilecek tek bir şey kaldı: OnTrade() fonksiyonunu kullanarak alım satım olaylarını belirleme. Size genel bir fikir vermesi açısından burada bu konuya kısaca değineceğiz. Bizim durumumuzda aşağıdaki senaryoyu uygulamamız gerekiyor: bir pozisyonu manuel olarak açarken/kapatırken/değiştirirken, bilgi panelindeki pozisyon özellikleri listesindeki değerlerin yeni bir tik alındıktan sonra değil, işlem tamamlanır tamamlanmaz güncellenmesi gerekiyor. Bunun için sadece aşağıdaki kodu eklememiz gerekiyor:
//+------------------------------------------------------------------+ //| TRADE EVENT | //+------------------------------------------------------------------+ void OnTrade() { //--- Get position properties and update the values on the panel GetPositionProperties(); }
Temel olarak, her şey hazır ve teste geçebiliriz. Strateji Test Cihazı, görselleştirme modunda bir test yapmanızı ve varsa hataları bulmanızı sağlar. Strateji Test Cihazının kullanımı, piyasaların kapalı olduğu hafta sonlarında bile programınızı geliştirmeye devam edebilmeniz nedeniyle faydalı görülebilir.
Strateji Test Cihazını kurun, görselleştirme modunu etkinleştirin ve Başlat seçeneğine tıklayın. Uzman Danışman, Strateji Test Cihazında alım satım yapmaya başlayacak ve aşağıda gösterilene benzer bir resim ortaya çıkacaktır:
Şekil 2. MetaTrader 5 Strateji Test Cihazındaki görselleştirme modu.
Görselleştirme modunda testi herhangi bir zamanda askıya alabilir ve F12 tuşuna tıklayarak teste adım adım devam edebilirsiniz. Strateji Test Cihazını Yalnızca açılış fiyatları moduna ayarlarsanız adım bir çubuğa veya Her tik modunu seçtiyseniz bir tike eşit olacaktır. Test hızını da kontrol edebilirsiniz.
Uzman Danışman, manuel olarak bir pozisyon açtıktan/kapattıktan veya Zarar Durdur/Kâr Al seviyeleri ekledikten/değiştirdikten sonra bilgi panelindeki değerlerin güncellenmesini sağlamak için gerçek zamanlı modda test edilmelidir. Çok fazla beklememek adına, Uzman Danışmanı 1 dakikalık bir zaman aralığında çalıştırmanız yeterlidir, böylece alım satım işlemleri her dakika yürütülür.
Bunun yanı sıra, bilgi panelinde pozisyon özelliklerinin adları için başka bir dizi ekledim:
// Array of position property names string pos_prop_texts[INFOPANEL_SIZE]= { "Symbol :", "Magic Number :", "Comment :", "Swap :", "Commission :", "Open Price :", "Current Price :", "Profit :", "Volume :", "Stop Loss :", "Take Profit :", "Time :", "Identifier :", "Type :" };
Bir önceki makalede, SetInfoPanel() fonksiyon kodunu indirgemek için bu diziye ihtiyacımız olacağından bahsetmiştim. Henüz uygulamadıysanız veya kendi başınıza çözemediyseniz, şimdi bunun nasıl yapılabileceğini görebilirsiniz. Pozisyon özellikleriyle ilgili nesneleri oluşturma listesinin yeni uygulaması aşağıdaki gibidir:
//--- List of the names of position properties and their values for(int i=0; i<INFOPANEL_SIZE; i++) { //--- Property name CreateLabel(0,0,pos_prop_names[i],pos_prop_texts[i],anchor,corner,font_name,font_size,font_color,x_first_column,y_prop_array[i],2); //--- Property value CreateLabel(0,0,pos_prop_values[i],GetPropertyValue(i),anchor,corner,font_name,font_size,font_color,x_second_column,y_prop_array[i],2); }
SetInfoPanel() fonksiyonunun başında aşağıdaki satırı görebilirsiniz:
//--- Testing in the visualization mode if(MQL5InfoInteger(MQL5_VISUAL_MODE)) { y_bg=2; y_property=16; }
Bu, program o anda görselleştirme modunda test ediliyorsa, programa bilgi panelindeki nesnelerin Y koordinatlarının ayarlanması gerektiğini iletir. Bunun nedeni, Strateji Test Cihazının görselleştirme modunda test yapılırken, Uzman Danışmanın adının grafiğin sağ üst köşesinde gerçek zamanlı olarak görüntülenmemesidir. Dolayısıyla gereksiz girintiler bu şekilde silinebilir.
Sonuç
Şimdilik işimiz bitti. Bir sonraki makalede, alım satım seviyelerini belirlemeye ve değiştirmeye odaklanacağız. Uzman danışman kaynak kodunu buradan indirebilirsiniz: PositionPropertiesTesterEN.mq5.