MQL5 Nesne Yönelimli Programlama Yaklaşımını Kullanarak Expert Advisor Yazma
Giriş
İlk makalede, MQL5'te bir Expert Advisor oluşturma, hata ayıklama ve test etme temel adımlarına doğru bir yolculuğa çıktık.
Yaptığımız her şey çok basit ve ilginçti, ancak, yeni MQL5 dilinin sunacağı çok daha fazla şey var. Bu makalede, ilk makalede yaptığımız şeyi yapmak için Nesne Yönelimli Yaklaşımı ele alacağız. Birçok kişi bunun zor olduğunu düşünüyor, ancak bu makaleyi okumayı bitirdiğinizde, nesne yönelimi temelinde kendi Expert Advisor'ınızı yazabileceğinizi garanti ediyorum.
İlk makalede öğrendiğimiz konulardan bazılarını tekrarlamayacağız; bu nedenle öncelikli olarak okumadıysanız makaleyi okumanızı öneririm.
1. Nesne Yönelimli Paradigma
Yeni MQL5'i MQL4'ten çok daha güçlü ve sağlam hale getiren şeylerden biri OOP (Nesne Yönelimli Programlama) yaklaşımıdır.
OOP'de, bir nesnenin uygulama ayrıntılarının hiçbirini göstermemesi önerilir. Bu şekilde, nesneyi kullanan kodu değiştirmeden uygulaması değiştirilebilir. Bu, bir sınıfın, bir programcının yazdığı sınıfın nasıl uygulandığını gizlemesine (ve ayrıca değişiklikleri engellemesine) izin verdiği anlamına gelir.
Konuyu daha da netleştirmek için, az önce sözü edilen "sınıf" ve "nesne" terimleri üzerinde biraz duralım.
- SINIF. Bir sınıf daha çok bir veri yapısının genişletilmiş kavramına benzer, ancak yalnızca verileri içermek yerine hem verileri hem de işlevleri içerir. Sınıf, sınıfın üyeleri olarak adlandırılan birkaç değişken ve işlev içerebilir. Verileri değiştiren veri üyelerinin ve işlevlerin bir kapsüllemesidir. Sınıf, çok daha güçlüdür; zira tüm Expert Advisor işlevlerinizi bir sınıfta toplayabilirsiniz. EA kodunuzda işlevlere yalnızca ihtiyaç duyduğunuzda başvuracaksınız. Bu arada, bu makalenin konusu bu.
- NESNE. Nesne, bir sınıfın bir örneğidir. Bir sınıf oluşturulduktan sonra, sınıfı kullanmak için bir sınıf örneği bildirmeliyiz. Bu, nesne olarak adlandırılır. Başka bir deyişle, bir nesne oluşturmak için bir sınıfa ihtiyacınız vardır.
1.1. SINIFI BİLDİRME
Sınıf, temel olarak, sınıftan oluşturmak istediğiniz bir nesnenin üyelerinin (özellikler ve işlevler/yöntemler) açıklamasını içerir. Şimdi bir örneğe göz atalım…
Kapıları, koltukları, lastikleri, ağırlığı vb. olan ve ayrıca hareket edebilen, vites değiştirebilen, durabilen ve korna çalabilen bir nesne oluşturmak istiyorsak o zaman bunun için bir sınıf yazmamız gerekir. Kapılar, koltuklar, lastikler, ağırlık, hareket etme, vites değiştirme, durma ve korna çalma sınıfın üyeleri olacaktır.
Kuşkusuz ki, bu üyelerin kategorilere ayrıldığını göreceksiniz; bazıları nesnemizin içereceği şeylerdir (özellikler), diğerleri ise nesnemizin gerçekleştireceği şeylerdir (eylemler – işlevler/yöntemler). Sınıfımızı bildirmek için onun için çok iyi ve açıklayıcı bir ad düşünmemiz gerekir. Bu durumda, sınıfımızı ARABA olarak adlandıracağız. ARABA sınıfımız, üyeleri olarak yukarıda belirtilen özellikleri ve işlevleri içerecektir.
Bir sınıf bildirmek için, sınıf anahtar sözcüğünü, ardından sınıfın adını ve daha sonra sınıfın üyelerini içeren bir çift kaşlı ayraç yazmayla başlarız.
Dolayısıyla, bir sınıfın temel biçimi aşağıda gösterildiği gibidir:class class_name
{
access_keyword_1:
members1;
access_keyword_2:
members2;
...
};
Burada, class_name, yazmak istediğimiz sınıf için geçerli bir tanımlayıcıdır, members1 ve members2 sınıfın veri üyeleridir.
access_keyword, sınıfımızın üyelerinin erişim hakkını belirtir. access_keyword, özel, korumalı veya genel olabilir. Gerçekte uygulama ayrıntılarını göstermeden kendimiz ve başkaları tarafından kullanılabilecek bir sınıf yazmaya çalıştığımızı unutmayın. Bu nedenle erişim hakları gereklidir.
Sınıfımızın dışından erişmek istemediğimiz bazı üyeler olabilir. Bunlar, özel erişim bölümünde özel veya korumalı anahtar sözcüğü kullanılarak bildirilir. Sınıfımızın dışından erişmek istediğimiz diğer üyeler daha sonra genel erişim bölümünde public anahtar sözcüğü kullanılarak bildirilecektir. Yeni ARABA sınıfımız şimdi aşağıdaki gibi görünecek:
class CAR { private: int doors; int sits; int tyres; double weight; public: bool start(); void changegear(); void stop(); bool horn(); ... };
ARABA sınıfımız, sınıf anahtar sözcüğü kullanılarak bildirilir. Bu sınıf dört üyesi özel erişime ve dört üyesi genel erişime sahip sekiz üye içerir. Özel bölümdeki dört üye veri üyeleridir. Üçü tamsayı (int) veri türünde ve biri çift veri türündedir. Bu üyelere, bu sınıfın dışında bildirilen başka bir işlev tarafından erişilemez.
Genel bölümdeki dört üye, işlev üyeleridir. İki dönüş bool veri türü ve iki dönüş void türüdür. Bunlar, sınıfımızı kullanan herhangi biri tarafından her oluşturulduğunda, bu sınıfın herhangi bir nesnesine erişilebilen üyelerdir. Sınıfımızın bir nesnesi oluşturulduğunda, bu üyeler kolaylıkla kullanılabilecektir.
Doğru şekilde gözlemleyeceğiniz üzere, erişim anahtar sözcüklerinin (özel, genel, korumalı) ardından her zaman iki nokta üst üste işareti gelir. Sınıf bildirimi de noktalı virgülle sona erer. Üyeler doğru veri türleri kullanılarak bildirilir.
Bir sınıfı bildirdiğinizde, yukarıda yaptığımız gibi açıkça belirtilmediği sürece sınıfın tüm üyelerine özel erişim hakları verildiği unutulmamalıdır. Örneğin, aşağıdaki sınıf bildiriminde olduğu gibi:
class CAR { int doors; int sits; int tyres; double weight; public: bool start(); void changegear(); void stop(); bool horn(); ... };
Herkese açık erişim anahtar sözcüğünün üzerinde bildirilen dört üyenin tamamı otomatik olarak herkese açık erişime sahiptir.
Sınıfımızın kullanılabilmesi için öncelikle sınıfın bir nesnesi oluşturulmalıdır. Şimdi, sınıfımızın bir türü olan bir nesne oluşturalım. Bunu yapmak için sınıf adımızı, ardından nesneye vermek istediğimiz adı kullanacağız.
ARABA Honda;
Veya başka bir nesne oluşturabiliriz
ARABA Toyota;
Honda veya Toyota artık bir ARABA türüdür ve üye işlevlerin herkese açık erişim bölümünde bildirilmesi koşuluyla, artık ARABA sınıfımızın tüm üye işlevlerine erişebilir. Bu konuya daha sonra döneceğiz.
Bu sınıftan istediğimiz kadar nesne oluşturabileceğimizi görebilirsiniz. Bu, Nesne Yönelimli programlamanın faydalarından biridir.
Bu noktada, MQL5'te bir sınıfın biçimini ayrıntılı olarak ele alalım.
class class_name { private: members1; members2; members3; public: class_name() //Constructor; ~class_name() //Destructor; Members4(); Members5(); protected: members6; members7; };
Bu, class_name adının sınıfın adı olduğu bir sınıfın bildirimidir. Bu sınıfın dokuz üyesi var, ancak bu dokuz üyeden ikisi özel üye.
Oluşturucu:
Oluşturucu (class_name() olarak temsil edilir), sınıf türünden yeni bir nesne oluşturulduğunda otomatik olarak çağrılan özel bir işlevdir. Bu durumda, bu sınıf türünde bir nesne oluşturduğunuzda
class_name nesne;
oluşturucu, class_name() otomatik olarak çağrılır. Oluşturucunun adı sınıfın adıyla eşleşmelidir; bu nedenle oluşturucuyu class_name() olarak adlandırdık. MQL5'te, bir oluşturucu herhangi bir giriş parametresi almaz ve dönüş türüne sahip değildir. Sınıf üyelerinin bellek ayırma ve başlatma işlemleri normalde oluşturucu çağrıldığında yapılır. Oluşturucular, normal üye işlevleriymiş gibi açıkça çağrılamaz. Yalnızca o sınıfın yeni bir nesnesi oluşturulduğunda yürütülürler. MQL5'teki bir sınıf yalnızca bir oluşturucu içerebilir.
Yıkıcı:
İkinci özel üye ~class_name() olarak temsil edilir. Bu, sınıf adından önce bir gelgit (~) ile yazılmış sınıf yıkıcıdır. Bir sınıf nesnesi yok edildiğinde otomatik olarak çağrılır. Bu aşamada, sınıfın başlatılmasının geri alınması gereken tüm üyeleri sıfırlanır ve yıkıcıyı açıkça bildirip bildirmemeniz gerçekten önemli değildir.
Veri Üyeleri:
Bir sınıfın üyeleri, herhangi bir geçerli veri türü, sınıf türü veya yapı türü olabilir. Başka bir deyişle, bir sınıfın üye değişkenlerini bildirirken, herhangi bir geçerli veri türünü (int, double, string, vb.), başka bir sınıfın nesnesini veya bir yapı türünü (örneğin, MQL5 MqlTradeRequest, vb.) kullanabilirsiniz.
İşlev Üyeleri:
Bunlar, veri üyelerini değiştirmek ve sınıfın ana işlevlerini/ yöntemlerini yürütmek için kullanılan sınıfın üyeleridir. İşlev üyeleri için dönüş türü, herhangi bir geçerli dönüş türünde (bool, void, double, string, vb.) olabilir.
Özel:
Bu bölümde bildirilen üyelere yalnızca sınıfın işlev üyeleri tarafından erişilebilir. Bunlara, sınıf dışında başka bir işlev tarafından erişilemez.
Korumalı:
Bu bölümde bildirilen üyelere, sınıfın işlev üyeleri tarafından erişilebilir ve ayrıca bu sınıftan türetilen diğer sınıfların üye işlevleri tarafından erişilebilir. Bu, bu sınıftan yeni bir sınıf da oluşturabileceğimiz anlamına gelir. Bu durumda, bu sınıftan türetilen yeni sınıf (artık temel sınıf olacak), temel sınıfın korumalı üyelerine erişebilecektir. Bu, OOP'deki devralma kavramıdır. Bunu az sonra ele alacağız, rahatlayın…
Genel:
Bu bölümde bildirilen üyeler, sınıfın bir nesnesi tarafından sınıfın dışında kullanılabilir. Sınıfı diğer programlarda kullanmak için gerekli olacak bazı işlevlerin bildirileceği yer burasıdır.
Bir sınıfın temel biçimini gözden geçirdik, umarım henüz sıkılmamışsınızdır; zira sonunda Expert Advisor'ımız için bir sınıf paketi oluşturmaya geçmeden önce, sınıfların hala göz atmamız gereken başka ilginç yönleri var.
1.2. DEVRALMA
Diyelim ki ilk sınıfımız olan base_class sınıfından başka bir sınıf oluşturmak istiyoruz. Üyeler ilk sınıftan yeni bir sınıf türetme biçimi şu şekildedir:
Temel Sınıf:
class base_class { private: members1; members2; members3; public: class_name() //Constructor; ~class_name() //Destructor; Members4(); Members5(); protected: members6; members7; };
Türetilmiş Sınıf:
class new_class : access_keyword base_class { private: members8; public: new_class() //Constructor; ~new_class() //Destructor; Members9(); };
Ayrıntıları açıklamaya devam etmeden önce burada birkaç açıklamada bulunulacaktır. Üyeler gösterildiği gibi iki nokta üst üste işareti ve bir access_keyword kullanılarak new_class sınıfı base_class sınıfından türetilir. Artık, base_class sınıfından türetilen/yapılan new_class, base_class sınıfının hem genel hem de korumalı üyelerine erişebilir (veya devralabilir) fakat base_class sınıfının özel üyelerine erişemez (veya devralamaz). new_class, base_class sınıfından farklı yeni üye yöntemleri/işlevleri de uygulayabilir. Başka bir deyişle, new_class sınıfı base_class sınıfından devraldıkları dışında kendi veri ve işlev üyelerini de içerebilir.
Türetilmiş sınıfın oluşturulmasında genel anahtar sözcüğü kullanılırsa bu, temel sınıfın genel ve korumalı üyelerinin, türetilmiş sınıfın genel ve korumalı üyeleri olarak devralınacağı anlamına gelir. Korumalı anahtar sözcüğü kullanılırsa temel sınıfın genel ve korumalı üyeleri, türetilmiş sınıfın korumalı üyeleri olarak devralınacaktır. Özel anahtar sözcüğü kullanılırsa temel sınıfın genel ve korumalı üyeleri, türetilmiş sınıfın özel üyeleri olarak devralınacaktır.
new_class sınıfının (türetilmiş sınıf) yeni bir nesnesi oluşturulduğunda, base_class sınıfının oluşturucusunun new_class sınıfının oluşturucusundan önce çağrıldığına; nesne yok edildiğinde, new_class sınıfının (türetilmiş sınıf) yıkıcısının base_class sınıfının yıkıcısından önce çağrıldığına dikkat etmek önemlidir.
Bu devralma kavramını daha iyi anlamak için başlangıç sınıfımız olan ARABA'ya geri dönelim.
class CAR { protected: int doors; int sits; double weight; public: bool start(); void changegear(); void stop(); bool horn(); private: int tyres; };
Bu sınıftan başka bir SALON sınıfını türetebiliriz. ARABA sınıfının veri üyelerinden üçünü korumalı olarak bildirdiğime dikkat edin. Bu, yeni sınıf SALON'un bu üyeleri devralmasını sağlamak içindir.
Ayrıca, erişim anahtar sözcüklerini yerleştirdiğiniz sıranın önemli olmadığını bilmenizi istiyorum. Önemli olan, bir erişim anahtar sözcüğü altında bildirilen tüm üyelerin o anahtar sözcüğe ait olmasıdır.
class SALOON : public CAR { private: int maxspeed; public: void runathighspeed(); };
Türetilmiş SALON sınıfımız iki üye içerir ve aynı zamanda ARABA temel sınıfından yedi üyeyi (korumalı ve genel üyeler) devralır. Bu, SALON sınıfına ait bir nesne oluşturulduğunda, ARABA'nın genel üye işlevlerine erişebileceği anlamına gelir; bunlar kendi genel üye işlevi olan yüksekhızdaçalış() ile birlikte hareket et(), vites değiştir(), dur() ve korna çal()'dır. Bu, devralma kavramıdır.
Tıpkı babamızın/ebeveynlerimizin (temel sınıf) bazı karakteristik özelliklerinin/davranışlarının (yöntemler) bizde, onların çocuklarında (türetilmiş sınıf) ortaya çıkması gibi; zira bu davranışları (yöntemler/işlevler) onlardan genetik ya da başka bir şekilde devralıyoruz. Üzgünüm, sağlık personeli değilim ama çizmeye çalıştığım tabloyu çok iyi kavradığınıza inanıyorum. Bu arada, MQL5 çoklu devralmayı desteklemediği için bunun hakkında konuşmaya gerek olmadığını düşünüyorum.
Hımm!!! Umarım OOP veya SINIF olarak adlandırılan gizemli şeyin üzerini örten siyah perde yavaş yavaş aralanıyordur… Kendinizi yormayın; bu noktada hala neleri ele aldığımız konusunda net olmadığınızı hissediyorsanız rahatlamanız gerekebilir; bir fincan kahve alın ve sonra geri gelin ve baştan başlayın. Bu, sandığınız kadar gizemli değil…
Bu noktaya geri döndüyseniz, açıklamamı takip ettiğinizi varsayıyorum. Temel sınıfımız olan ARABA'dan daha kaç sınıf türetebileceğinizi söylemenizi istiyorum. Lütfen.. Yanıtınıza ihtiyacım var. Ciddiyim. Türettiğiniz sınıfları adlandırın ve bildirimlerini yazıp bana e-posta ile gönderin. Hepsini adlandırabilirseniz, sizi yemeğe çıkaracağım… (şaka yapmıyorum)
Artık daha fazlası için hazır olduğunuza göre, devam edelim…
Yazı yazma tarzımın babamınkine benzediği doğrudur. Babamın el yazısı da benimki gibi çok düzgün ve şıktır. Sanırım bu, ondan bana miras kalan bir şey ama bir küçük farkla. Babam yazarken sol elini kullanırken ben sağ elimi kullanıyorum ve yazım tarzımız birbirine o kadar çok benziyor ki yazdıklarımızı görseniz güçlükle ayırt edersiniz. Peki buradaki sorun ne? Güzel el yazım babamdan miras kaldı ama babam gibi sol elimle yazamıyorum. Bu, bana miras kalan şey olmasına ve benzer görünmesine rağmen, eyleme dönüştürme tarzımın babamınkinden farklı olduğu anlamına gelir. Bu sizin için bir anlam ifade ediyor mu? Bu, OOP'de çok biçimlilik olarak adlandırılan şeyin bir fikridir.
Türetilmiş bir sınıf (yukarıdaki örnekte olduğu gibi kendim) temel bir sınıftan (babam) bir üye işlevi (writefine() – el yazım için) devralır, ancak söz konusu sınıf (Ben), işlevi (writefine() ) temel sınıftan (Babam) farklı bir şekilde uygular.
ARABA sınıfımıza ve türetilmiş SALON sınıfına geri dönelim;
class CAR { protected: int doors; int sits; double weight; public: bool start(); virtual void changegear(){return(0);} void stop(); bool horn(); private: int tyres; };
class SALOON : public CAR { private: int maxspeed; public: void runathighspeed(); virtual void changegear(){gear1=reverse; gear2=low; gear3=high;} }; class WAGON : public CAR { private: bool hasliftback; public: virtual void changegear(){gear1=low; gear2=high; gear3=reverse;} };
Burada yaptığımız birkaç değişikliğe göz atalım. İlk olarak, ARABA sınıfından iki üyeli VAGON adında yeni bir türetilmiş sınıf bildirdik. Ayrıca vites değiştirme() üye işlevini de temel sınıfta sanal işlev olacak şekilde değiştirdik. Peki neden vites değiştirme() üye işlevini sanal işlev yaptık? Bunun nedeni, işlevi temel sınıftan devralan herhangi bir sınıfın onu kendine göre uygulayabilmesidir.
Başka bir deyişle, bir sınıfın sanal üye işlevleri, bildirildikleri sınıftan türetilen herhangi bir sınıfta farklı şekilde geçersiz kılınabilen veya uygulanabilen üye işlevleridir. Üye işlevi gövdesi daha sonra türetilmiş sınıfta yeni bir uygulama kümesi ile değiştirilebilir. Sanal kelimesini türetilmiş sınıflarda tekrar kullanmasak da bunu türetilmiş sınıflarda her zaman kullanmak iyi bir programlama uygulamasıdır.
Yukarıdaki örneklerden, SALON ve VAGON sınıfları, vites değiştir() işlevini kendilerine göre uygular.
1.3. SINIF YÖNTEMLERİNİ TANIMLAMA (ÜYE İŞLEVLERİ)
Bir dereceye kadar sınıfları nasıl bildireceğimizi bildiğimiz için bir sınıfın üye işlevlerinin nasıl tanımlanacağını ele alarak daha fazla ilerleme kaydedelim. Sınıfı bildirdikten sonra sıra sınıfımızın üye işlevlerini tanımlamaktır. ARABA sınıfımıza tekrar bakalım
class CAR { protected: int doors; int sits; double weight; public: void CAR() // Constructor bool start(); void changegear(); void stop(); bool horn(){press horn;} private: int tyres; }; void CAR::CAR() { // initialize member variables here } bool CAR::start() { // car start procedure here } void CAR::changegear() { // car changegear procedure here } void CAR::stop() { // car stop procedure here }
Üye işlevlerini tanımlarken, kapsam işleci adında bir çift iki nokta üst üste (::) işleci kullandık. Bu, normal işlevler gibi yazılır; tek farkı sınıf adı ve eklenen kapsam işlecidir. Ayrıca işlevlerden birinin zaten sınıf içinde tanımlanmış olduğunu göreceksiniz. (üye işlevi korna çal()). Burada gördüğünüz gibi bir üye işlevi sınıf bildiriminin içinde veya sınıf bildiriminin dışında tanımlanabilir.
İlerlemeden önce işlevler kavramını biraz gözden geçirmenin önemli olacağını düşünüyorum.
1.4. İŞLEVLER
Bu arada, işlev nedir?
Bazen üç çocuğun olduğu bir evde, evdeki tüm işleri bir çocuğun yapması yerine; birine her gün akşam yemeğinden sonra tabakları yıkama, diğerine döşemeleri süpürme, üçüncüsüne de her sabah yatakları düzeltme görevi verilebilir.
Evde yapılması gereken birtakım işler vardı ve tüm işleri bir çocuğa yüklemek yerine bunları üç çocuk arasında bölüştürmüş olduk. Bu, yalnızca birine yük olmaktan ziyade, her biri için görevi çok kolay ve hafif hale getirecektir. Ayrıca çocuklardan biri görevini yapmazsa hangisini cezalandırmamız gerektiğini kolaylıkla biliriz. İşlevlerin arkasındaki fikir budur.
Çoğu zaman birçok görevi yerine getirecek bir kod yazmak isteriz. İşlevlerin devreye girdiği yer burasıdır. Görevi daha küçük görevlere ayırmaya karar verebilir ve ardından daha küçük görevlerin her birini gerçekleştirmek için bir işlev yazabiliriz. İşlev, bir dizi işlemi gerçekleştiren veya uygulayan bir kod bloğudur. Bu, bir programın herhangi bir noktasından her çağrıldığında yürütülen bir ifade grubudur.
Bir işlev aşağıdaki gibi tanımlanabilir:
Return_type function_name (parameters1,parameters2,…)
{
Expressions; //(actions to carry out by the function)
}
- Return_type: İşlev tarafından döndürülen veri türü (geçerli bir veri türü olmalıdır veya hiçbir şey döndürmezse void olmalıdır)
- Function_name: İşlevi çağırmak için kullanılacak işlevin adı (geçerli bir ad olmalıdır)
- Parametreler: Parametreler, işlev içinde yerel bir değişken olarak hareket edecek geçerli veri türü değişkenleridir. Bir işlevin birden fazla parametresi varsa bunlar virgülle ayrılır.
- Açıklamalar: İfadeler bloğunu içeren işlevin gövdesi
Bir işlev örneği:
int doaddition (int x, int y) { return (x+y); }
İşlev dönüş türü tamsayı (int), doaddition işlevin adıdır ve int x ve int y parametrelerdir. İşlevin yaptığı, kendisine sağlanan herhangi iki giriş parametresini eklemek ve sonucu döndürmektir. Bu nedenle, işleve 2 ve 3 tamsayı değişkeni sağlarsak işlev, ekleme işlemini yapacak ve sonuç olarak 5 döndürecektir.
int doaddition(2,3) // returns 5
İşlevler hakkında daha fazla bilgi için lütfen MQL5 Referans kılavuzuna bakın.
Artık işe koyulmamıza yetecek kadar teori mevcut.
Bu makalenin özü, MQL5'te sunulan Nesne Yönelimli yaklaşımı kullanarak Expert Advisor'ınız için nasıl bir sınıf yazabileceğinizi öğretmektir.
Şimdi harekete geçme zamanı…
2. Expert Advisor Yazma
Bu noktada, ilk makalede oluşturduğumuz Expert Advisor'a atıfta bulunacağız. Makaleyi okumadıysanız lütfen yeterli bilgi sahibi olmak için okuyun; bu noktadan sonra tartışacağımız şeylerin çoğunun size tuhaf gelmesini istemeyiz. Ancak, yine de gerekli olabilecek birkaç şeyi gözden geçirebilirim.
Sınıfınızı yazabilmeniz için önce oturup alım satım stratejinizi geliştirmeniz gerekir. Bunu ilk makalede zaten yaptık. Sıradaki husus, sınıfımıza devretmek istediğimiz işlevleri seçmektir. Bu işlevler sınıfımızın üye değişkenlerini belirleyecektir. İlk makaleden alım satım stratejimizin bir özeti.
EA'mız ne yapacak?
- Belirli bir göstergeyi izleyecek ve belirli bir koşul karşılandığında (veya belirli koşullar karşılandığında), karşılanan mevcut koşula bağlı olarak bir alım satım işlemi (Kısa Pozisyon/Sat veya Uzun Pozisyon/Al) yapacaktır.
Yukarıdakiler alım satım stratejisi olarak adlandırılır. Bir EA yazmadan önce, EA'da otomatikleştirmek istediğiniz stratejiyi geliştirmelisiniz. Bu durumda, yukarıdaki ifadeyi bir EA'ya dönüştürmek istediğimiz stratejiyi yansıtacak şekilde değiştirelim.
- Dönemi 8 olan Hareketli Ortalama adlı bir gösterge kullanacağız (Herhangi bir dönemi seçebilirsiniz, ancak stratejimizin amacı için biz 8'i kullanacağız)
- EA'mızın Hareketli Ortalama-8 (tartışmamızı kolaylaştırması için, ben HO-8 olarak adlandıracağım) yukarı doğru çıktığında ve fiyat onun üzerine yakın olduğunda Uzun (Alış) bir alım satım işlemi yapmasını, HO-8 aşağı doğru düştüğünde ve fiyat bunun altına yakın olduğunda Kısa (Satış) bir alım satım işlemi yapmasını istiyoruz.
- Ayrıca, piyasanın trend olup olmadığını belirlememize yardımcı olması için 8. döneme sahip Ortalama Yönlü Hareket (ADX) adlı başka bir gösterge kullanacağız. Bunu, alım satım işlemine yalnızca piyasa trendde girdiğinde girmek ve piyasa değişkenken (yani trendde değilken) rahatlamak istediğimiz için yapıyoruz. Bunu başarmak için, alım satım işlemlerimizi (Al veya Sat) yalnızca yukarıdaki koşullar karşılandığında ve ADX değeri 22'den büyük olduğunda gerçekleştireceğiz. ADX 22'den büyükse fakat düşüyorsa veya ADX 22'den küçükse, B koşulu karşılansa dahi alım satım yapmayacağız.
- Ayrıca 30 piplik bir Zararı Durdur ayarlayarak kendimizi korumak istiyoruz; Kar hedefimiz için 100 piplik bir kar hedefleyeceğiz.
- Ayrıca EA'mızın yalnızca yeni bir çubuk oluştuğunda Alış/Satış fırsatlarını aramasını istiyoruz ve aynı zamanda Alış koşulları karşılanıyorsa ve halihazırda açılmış bir pozisyonumuz yoksa bir Alış pozisyonu açtığımızdan ve Satış koşulları karşılanıyorsa ve halihazırda açılmış bir pozisyonumuz yoksa bir Satış pozisyonu açtığımızdan emin olacağız.
Bunun yanı sıra, bir alım satım işlemi yaparken kullanılabilecek Serbest Marjımızın yüzdesini kontrol edebildiğimizden ve ayrıca herhangi bir alım satım işlemi yapmadan önce mevcut serbest marjı kontrol ettiğimizden emin olmak istiyoruz. EA'mız, yalnızca alım satım işlemi için mevcut marj yeterliyse alım satım yapacaktır.
Artık ne yapmak istediğimizi anlıyorsunuz. Sınıfımıza devretmek istediğimiz işlevler şunlardır:
- Alış ve Satış koşullarını kontrol edin
- Kontrol edilen koşulların sonucuna göre Alım/Satım yapın
Temel olarak, EA'mızın yapmasını istediğimiz tek şey bu. Bu iki işlev ana işlevlerdir, ancak daha fazlası da vardır. Örneğin Alış/Satış pozisyonları kontrol edilirken göstergeler kullanılmalıdır. Bu, göstergelerin değerlerini elde etmenin de sınıfımızda olması gerektiği anlamına gelir. Bu nedenle şunları ekleriz:
- Tüm gösterge tanıtıcılarını almak (EA OnInit bölümünde)
- Tüm gösterge arabelleklerini almak (EA OnTick bölümünde)
- Tüm gösterge tanıtıcılarını serbest bırakmak (EA OnDeinit bölümünde)
Gösterge değerlerini alırken, sınıfımızın MA ve ADX dönemlerini, grafik dönemini ve sembolünü (birlikte çalıştığımız para birimi çifti) bilmesi gerekecek; bu nedenle şunları da eklemeliyiz:
- ADX ve MA dönemlerini ve grafik dönemi ve sembolü gibi diğer önemli parametreleri almak.
Ayrıca, bir alım satım işlemi yapmadan önce serbest marjın kontrolü için,
- alım satım işlemi için kullanılacak Serbest Marjı/hesap yüzdesini Kontrol Etmeyi dahil edeceğiz.
Bununla zaten sınıfımızda hangi değişkenlerin ve işlevlerin olması gerektiğine ilişkin bir fikrimiz var.
Peki, sizin için düşündüm; şimdi biraz kod yazma zamanı.
2.1. Sınıf yazma
MetaEditor'u başlatarak başlayalım (bunu zaten bildiğinizi düşünüyorum). MetaEditor açıldıktan sonra Yeni araç çubuğuna veya Ctrl+N'ye tıklayarak yeni bir MQL belgesi başlatalım. Sihirbaz penceresinde "Ekle" öğesini seçin ve İLERİ düğmesine tıklayın.
Şekil 1. Yeni bir MQL5 belgesi başlatma
Aşağıda gösterildiği gibi dosyanın adını yazın ve Bitir'e tıklayın:
Şekil 2. Yeni bir belgeyi adlandırma
Ekleme seçeneğini seçtik; zira sınıfımız, kullanmaya hazır olduğumuzda EA kodumuza dahil edilecek bir içerme dosyası olacak. Bu nedenle giriş parametrelerini girmek için yeriniz yok.
Her zamanki gibi, düzenleyici size yapmak istediğinizi düşündüğü şeyin bir taslağını sağlar.
Başlamak için lütfen "#property link ..." kod satırının altındaki her şeyi silin. Artık buna benzer bir şeye sahip olmalısınız.
//+------------------------------------------------------------------+ //| my_expert_class.mqh | //| Copyright 2010, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "http://www.mql5.com"
Artık sınıfımızın bildirimini yazalım, sınıfımızı MyExpert olarak adlandıralım.
//+------------------------------------------------------------------+ //| CLASS DECLARATION | //+------------------------------------------------------------------+ class MyExpert {
Sınıf bildirimini analiz edelim. Bildirim, sınıfın adıyla başlar. Daha sonra sınıfın özel üyelerini bildirdik.
Bu Üyeler:
//+------------------------------------------------------------------+ //| CLASS DECLARATION | //+------------------------------------------------------------------+ class MyExpert { //--- private members private: int Magic_No; // Expert Magic Number int Chk_Margin; // Margin Check before placing trade? (1 or 0) double LOTS; // Lots or volume to Trade double TradePct; // Percentage of Account Free Margin to trade double ADX_min; // ADX Minimum value int ADX_handle; // ADX Handle int MA_handle; // Moving Average Handle double plus_DI[]; // array to hold ADX +DI values for each bars double minus_DI[]; // array to hold ADX -DI values for each bars double MA_val[]; // array to hold Moving Average values for each bars double ADX_val[]; // array to hold ADX values for each bars double Closeprice; // variable to hold the previous bar closed price MqlTradeRequest trequest; // MQL5 trade request structure to be used for sending our trade requests MqlTradeResult tresult; // MQL5 trade result structure to be used to get our trade results string symbol; // variable to hold the current symbol name ENUM_TIMEFRAMES period; // variable to hold the current timeframe value string Errormsg; // variable to hold our error messages int Errcode; // variable to hold our error codes
Daha önce açıklandığı gibi, bu özel üye değişkenlerine sınıf dışındaki herhangi bir işlev tarafından erişilemez. Değişkenlerin çoğu bildirimlerinde çok net olduğu için söz konusu değişkenler hakkında konuşarak zaman kaybetmeyeceğim.
Ancak, tartışmamızda üye değişkenlerin herhangi bir geçerli veri türü, yapısı veya sınıfı olabileceğini belirttiğimizi hatırlayacaksınız.
Bunun işleyiş biçimini burada, MqlTradeRequest ve MqlTradeResults türlerinin bildirimi ile görebileceğinize inanıyorum.
Oluşturucu
//--- Public member/functions public: void MyExpert(); //Class Constructor
Oluşturucu herhangi bir giriş parametresi almaz; lütfen kendi sınıfınızı yazarken bunu göz önünde bulundurun.
Üye işlevleri
//--- Public member/functions public: void MyExpert(); //Class Constructor void setSymbol(string syb){symbol = syb;} //function to set current symbol void setPeriod(ENUM_TIMEFRAMES prd){period = prd;} //function to set current symbol timeframe/period void setCloseprice(double prc){Closeprice=prc;} //function to set prev bar closed price void setchkMAG(int mag){Chk_Margin=mag;} //function to set Margin Check value void setLOTS(double lot){LOTS=lot;} //function to set The Lot size to trade void setTRpct(double trpct){TradePct=trpct/100;} //function to set Percentage of Free margin to use for trading void setMagic(int magic){Magic_No=magic;} //function to set Expert Magic number void setadxmin(double adx){ADX_min=adx;} //function to set ADX Minimum values
Bu üye işlevlerini, sınıfımızın işlevini yerine getirmesi için ihtiyaç duyacağı önemli değişkenleri belirlememize izin verecek şekilde tanımladık. Bu işlevler kullanılmadan bu değişkenler sınıfımız tarafından kullanılamaz. Sizin de fark edeceğiniz gibi, bu işlevler tarafından ayarlandıktan sonra bu değerleri tutacak karşılık gelen bir değişkeni sınıfımızda zaten bildirmiştik.
Dikkat edilmesi gereken başka bir nokta da, bu üye işlevlerini sınıf bildiriminde tanımlamış olmamızdır. Daha önce açıkladığım gibi, buna izin verilir. Bu, çok yakında göreceğiniz üzere, diğer üye işlevlerini tanımlarken bunları yeniden tanımlamamıza gerek olmayacağı anlamına gelir.
Normal işlevler gibi, bunlar, her işlevin döndürülen değerlerine bağlı olarak doğru veri türünde parametrelere sahiptir. Bunun size tuhaf gelmemesi gerektiğine inanıyorum.
void doInit(int adx_period,int ma_period); //function to be used at our EA intialization void doUninit(); //function to be used at EA de-initializatio bool checkBuy(); //function to check for Buy conditions bool checkSell(); //function to check for Sell conditions void openBuy(ENUM_ORDER_TYPE otype,double askprice,double SL, double TP,int dev,string comment=""); //function to open Buy positions void openSell(ENUM_ORDER_TYPE otype,double bidprice,double SL, double TP,int dev,string comment=""); //function to open Sell positions
Yalnızca bu üye işlevleri bildiririz, ancak bunları tanımlamadık. Çünkü bunu daha sonra yapacağız. Bunlar, sınıfımızın üye değişkenlerinde saklanan değerlerin çoğunu değiştirecek işlevlerdir ve aynı zamanda sınıfımızın ana rolü için işlevleri oluştururlar. Bunları daha sonra tartışacağız.
Korumalı üyeler
Bu üyeler, sınıfımızdan türetilen herhangi bir sınıf tarafından devralınır. Bu sınıftan başka bir sınıf türetmeyi düşünmüyorsanız gerçekten gerekli değildir. Onları özel üyeler olarak da yerleştirebilirsiniz. Bunu yalnızca daha önce sınıflar hakkında tartıştığımız çeşitli konuları anlamanızı sağlamak için yapıyorum.
//--- Protected members protected: void showError(string msg, int ercode); //function for use to display error messages void getBuffers(); //function for getting Indicator buffers bool MarginOK(); //function to check if margin required for lots is OK
Bu üç işlev, sınıfımıza dahil olsa da çok önemlidir. showError hatalarımızı gösterecek ve gösterge arabelleklerini almak için getBuffers kullanılacaktır. MarginOK bir pozisyon açmak için yeterli serbest marj olup olmadığını kontrol eder.
Sınıf bildirimini tamamladıktan sonra noktalı virgülü unutmayın. Bu, çok önemlidir.
}; // end of class declaration
Bu bildirdikten hemen sonra yapılacak şey, bildirim bölümünde tanımlanmamış üye işlevleri tanımlamaktır.
//+------------------------------------------------------------------+ // Definition of our Class/member functions //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| This CLASS CONSTRUCTOR //| *Does not have any input parameters //| *Initilizes all the necessary variables //+------------------------------------------------------------------+ void MyExpert::MyExpert() { //initialize all necessary variables ZeroMemory(trequest); ZeroMemory(tresult); ZeroMemory(ADX_val); ZeroMemory(MA_val); ZeroMemory(plus_DI); ZeroMemory(minus_DI); Errormsg=""; Errcode=0; }
Bu, bizim sınıf oluşturucumuzdur. Burada sınıf adı ile üye işlev adı arasında çift iki nokta üst üste (::) (kapsam işleci) kullandık. Söylemeye çalıştığımız şey şudur:
Bu üye işlevini sınıf bildiriminin dışında tanımlamamıza rağmen, yine de sınıfın kapsamındadır. Adı iki adet iki nokta üst üste işaretinden (kapsam işleci) önce gelen sınıfın bir üyesidir.
Herhangi bir giriş parametresi yoktur. Bu noktada gerekli üye değişkenlerin çoğunu başlatırız ve bunu gerçekleştirmek için ZeroMemory işlevini kullanırız.
void ZeroMemory(
void & değişkeni // reset değişkeni
);
Bu işlev, kendisine iletilen değişkenlerin değerlerini sıfırlar. Bu durumda, bunu, yapı türlerimizin (MqlTradeRequest ve MqlTradeResult) ve dizilerimizin değerlerini sıfırlamak için kullanırız.
showError işlevi:
//+------------------------------------------------------------------+ //| SHOWERROR FUNCTION //| *Input Parameters - Error Message, Error Code //+------------------------------------------------------------------+ void MyExpert::showError(string msg,int ercode) { Alert(msg,"-error:",ercode,"!!"); // display error }
Bu, sınıfımızın herhangi bir nesnesinin işlemleri sırasında karşılaşılan tüm hataları görüntülemek için kullanılan korumalı bir üye işlevidir. İki bağımsız değişken/parametre alır – Hata açıklaması ve hata kodu.
getBuffers işlevi:
//+------------------------------------------------------------------+ //| GETBUFFERS FUNCTION //| *No input parameters //| *Uses the class data members to get indicator's buffers //+------------------------------------------------------------------+ void MyExpert::getBuffers() { if(CopyBuffer(ADX_handle,0,0,3,ADX_val)<0 || CopyBuffer(ADX_handle,1,0,3,plus_DI)<0 || CopyBuffer(ADX_handle,2,0,3,minus_DI)<0 || CopyBuffer(MA_handle,0,0,3,MA_val)<0) { Errormsg="Error copying indicator Buffers"; Errcode = GetLastError(); showError(Errormsg,Errcode); } }
Bu işlev, ilgili gösterge tanıtıcısını kullanarak tüm gösterge arabelleklerimizi üye değişkenlerde belirttiğimiz dizilere kopyalamak için kullanılır.
CopyBuffer işlevi, ilk makalede zaten açıklandı. Sınıfın üye değişkenlerinden gelen değerleri kullandığımız için getBuffers işlevinde herhangi bir giriş parametresi yoktur.
Arabellekleri kopyalama sürecinde oluşabilecek herhangi bir hatayı görüntülemek için burada dahili hata işlevimizi kullandık.
MarginOK işlevi:
//+------------------------------------------------------------------+ //| MARGINOK FUNCTION //| *No input parameters //| *Uses the Class data members to check margin required to place a trade //| with the lot size is ok //| *Returns TRUE on success and FALSE on failure //+------------------------------------------------------------------+ bool MyExpert::MarginOK() { double one_lot_price; //Margin required for one lot double act_f_mag = AccountInfoDouble(ACCOUNT_FREEMARGIN); //Account free margin long levrage = AccountInfoInteger(ACCOUNT_LEVERAGE); //Leverage for this account double contract_size = SymbolInfoDouble(symbol,SYMBOL_TRADE_CONTRACT_SIZE); //Total units for one lot string base_currency = SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE); //Base currency for currency pair // if(base_currency=="USD") { one_lot_price=contract_size/levrage; } else { double bprice= SymbolInfoDouble(symbol,SYMBOL_BID); one_lot_price=bprice*contract_size/levrage; } // Check if margin required is okay based on setting if(MathFloor(LOTS*one_lot_price)>MathFloor(act_f_mag*TradePct)) { return(false); } else { return(true); } }
Bu işlev aslında iki görev görür. Alım satım işlemi yapmak için yeterli serbest marjımız olup olmadığını kontrol eder ve ayrıca alım satım işlemi yapmak için mevcut serbest marjın belirli bir yüzdesinden fazlasını kullanıp kullanmadığımızı kontrol eder. Bu şekilde, her alım satım işlemi için ne kadar para kullandığımızı kontrol edebiliriz.
Hesap için Serbest Marj almak için AccountInfoDouble() işlevini ENUM_ACCOUNT_INFO_DOUBLE tanımlayıcısı ile birlikte kullanırız. Ayrıca, hesap için Kaldıraç almak için AccountInfoInteger() işlevini ENUM_ACCOUNT_INFO_INTEGER tanımlayıcısı ile birlikte kullanırız. AccountInfoInteger() ve AccountInfoDouble(), EA kullanarak cari hesabın ayrıntılarını almak için kullanılan hesap işlevleridir.
double AccountInfoDouble( int property_id // identifier of the property );
Ayrıca, geçerli sembol (para birimi çifti) için sözleşme boyutunu ve temel para birimini almak için SymbolInfoDouble() ve SymbolInfoString() sembol özellikleri işlevlerini kullandık. SymbolInfoDouble() işlevi, parametreler olarak sembol adını ve ENUM_SYMBOL_INFO_DOUBLE tanımlayıcısını alırken SymbolInfoString() işlevi, parametreler olarak sembol adını ve ENUM_SYMBOL_INFO_STRING tanımlayıcısını alır. Bu işlevlerin sonuçları, her veri türü için bildirilen değişkenlerde saklanır.
double SymbolInfoDouble( string name, // symbol int prop_id // identifier of the property );
Bu yaptığımız hesaplama çok basittir.
Alım satım yapmak için gerekli marjı elde etmek için iki durumu göz önünde bulundururuz:
- Temel para birimi USD'dir (USD/CAD, USD/CHF, USD/JPY vb.)
Gerekli marj = Lot /Kaldıraç başına sözleşme boyutu
2. Temel para birimi USD değildir (EUR/USD vb.)
Gerekli marj = Sembolün mevcut fiyatı * lot/Kaldıraç başına sözleşme boyutu.
Artık, belirtilen lot boyutu veya hacmiyle alım satım yapmak için gereken marjın, bir alım satım işlemi için kullanmak istediğiniz serbest marj yüzdesinden büyük olup olmadığını kontrol etmeye karar verme zamanı. Gerekli marj daha azsa işlev TRUE değerini döndürür ve alım satım işlemi yapılır; aksi takdirde FALSE değerini döndürür ve alım satım işlemi yapılmaz.
doInit işlevi:
//+-----------------------------------------------------------------------+ // OUR PUBLIC FUNCTIONS | //+-----------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| DOINIT FUNCTION //| *Takes the ADX indicator's Period and Moving Average indicator's //| period as input parameters //| *To be used in the OnInit() function of our EA //+------------------------------------------------------------------+ void MyExpert::doInit(int adx_period,int ma_period) { //--- Get handle for ADX indicator ADX_handle=iADX(symbol,period,adx_period); //--- Get the handle for Moving Average indicator MA_handle=iMA(symbol,period,ma_period,0,MODE_EMA,PRICE_CLOSE); //--- What if handle returns Invalid Handle if(ADX_handle<0 || MA_handle<0) { Errormsg="Error Creating Handles for indicators"; Errcode=GetLastError(); showError(Errormsg,Errcode); } // Set Arrays as series // the ADX values arrays ArraySetAsSeries(ADX_val,true); // the +DI value arrays ArraySetAsSeries(plus_DI,true); // the -DI value arrays ArraySetAsSeries(minus_DI,true); // the MA values arrays ArraySetAsSeries(MA_val,true); }
Bu, yakında yazacağımız EA'mızın OnInit() işlevinde kullanmayı planladığımız genel bir işlevdir ve iki işlev görecektir.
İlk olarak, göstergelerimiz için tanıtıcıları belirleyecek ve ayrıca dizi değişkenleri üzerinde array-set-as-series eylemini gerçekleştirecektir. EA kodumuzdan sağlanacak iki giriş parametresi içerir.
doUninit işlevi:
//+------------------------------------------------------------------+ //| DOUNINIT FUNCTION //| *No input parameters //| *Used to release ADX and MA indicators handleS | //+------------------------------------------------------------------+ void MyExpert::doUninit() { //--- Release our indicator handles IndicatorRelease(ADX_handle); IndicatorRelease(MA_handle); }
Bu, işlev aynı zamanda, kullandığımız göstergeler için tüm tanıtıcıları serbest bırakmak için EA'mızın UnDeInit işlevinde kullanılacak bir genel üye işlevidir. Herhangi bir giriş parametresi yoktur.
checkBuy işlevi:
//+------------------------------------------------------------------+ //| CHECKBUY FUNCTION //| *No input parameters //| *Uses the class data members to check for Buy setup based on the //| the defined trade strategy //| *Returns TRUE if Buy conditions are met or FALSE if not met //+------------------------------------------------------------------+ bool MyExpert::checkBuy() { /* Check for a Long/Buy Setup : MA increasing upwards, previous price close above MA, ADX > ADX min, +DI > -DI */ getBuffers(); //--- Declare bool type variables to hold our Buy Conditions bool Buy_Condition_1=(MA_val[0]>MA_val[1]) && (MA_val[1]>MA_val[2]); // MA Increasing upwards bool Buy_Condition_2=(Closeprice>MA_val[1]); // previous price closed above MA bool Buy_Condition_3=(ADX_val[0]>ADX_min); // Current ADX value greater than minimum ADX value bool Buy_Condition_4=(plus_DI[0]>minus_DI[0]); // +DI greater than -DI //--- Putting all together if(Buy_Condition_1 && Buy_Condition_2 && Buy_Condition_3 && Buy_Condition_4) { return(true); } else { return(false); } }
Bu işlev, bir satın alma koşulunun belirlenip belirlenmediğini kontrol etmek için kullanılacaktır. Bu nedenle dönüş türü bool'dur. Bu, TRUE veya FALSE döndüreceği anlamına gelir. Alış işlemi stratejimizi burada tanımladık. Tanımladığımız stratejiye göre bir satın alma koşulu karşılanırsa TRUE değerini döndürecek; ancak Satın alma koşulu karşılanmazsa FALSE değerini döndürecektir. Kodumuzda bu işlevi kullanırken, TRUE değerini döndürürse bir satın alma işlemi yapacağız.
Burada yaptığımız ilk şey, checkBuy işlevinin ihtiyaç duyduğu tüm dizi değerlerini karşılık gelen dizi değişkenlerine kopyalayacak olan getBuffers() dahili üye işlevini çağırmaktır.
Buraya kodlanan koşullar birinci makalede halihazırda açıklandı.
checkSell işlevi:
//+------------------------------------------------------------------+ //| CHECKSELL FUNCTION //| *No input parameters //| *Uses the class data members to check for Sell setup based on the //| the defined trade strategy //| *Returns TRUE if Sell conditions are met or FALSE if not met //+------------------------------------------------------------------+ bool MyExpert::checkSell() { /* Check for a Short/Sell Setup : MA decreasing downwards, previous price close below MA, ADX > ADX min, -DI > +DI */ getBuffers(); //--- Declare bool type variables to hold our Sell Conditions bool Sell_Condition_1=(MA_val[0]<MA_val[1]) && (MA_val[1]<MA_val[2]); // MA decreasing downwards bool Sell_Condition_2=(Closeprice <MA_val[1]); // Previous price closed below MA bool Sell_Condition_3=(ADX_val[0]>ADX_min); // Current ADX value greater than minimum ADX bool Sell_Condition_4=(plus_DI[0]<minus_DI[0]); // -DI greater than +DI //--- Putting all together if(Sell_Condition_1 && Sell_Condition_2 && Sell_Condition_3 && Sell_Condition_4) { return(true); } else { return(false); } }
Normal checkBuy gibi, bu işlev bir Satış koşulunun belirlenip belirlenmediğini kontrol etmek için kullanılacaktır. Bu nedenle dönüş türü de bool'dur. Bu, bir TRUE veya FALSE değerini döndüreceği anlamına gelir. Satış işlemi stratejimizi burada tanımladık. Tanımladığımız stratejiye göre bir satış koşulu karşılanırsa TRUE değerini döndürecek; ancak satış koşulu karşılanmazsa FALSE değerini döndürecektir.
Kodumuzda bu işlevi kullanırken, TRUE değerini döndürürse satış yapacağız. Tıpkı checkBuy işlevindeki gibi, ilk olarak getBuffers() dahili işlevini çağırdık. Üyeler kodlanan koşullar aynı zamanda birinci makalede de açıklanmıştır.
openBuy işlevi:
//+------------------------------------------------------------------+ //| OPENBUY FUNCTION //| *Has Input parameters - order type, Current ASK price, Stop Loss, //| Take Profit, deviation, comment //| *Checks account free margin before pacing trade if trader chooses //| *Alerts of a success if position is opened or shows error //+------------------------------------------------------------------+ void MyExpert::openBuy(ENUM_ORDER_TYPE otype,double askprice,double SL,double TP,int dev,string comment="") { //--- do check Margin if enabled if(Chk_Margin==1) { if(MarginOK()==false) { Errormsg= "You do not have enough money to open this Position!!!"; Errcode =GetLastError(); showError(Errormsg,Errcode); } else { trequest.action=TRADE_ACTION_DEAL; trequest.type=otype; trequest.volume=LOTS; trequest.price=askprice; trequest.sl=SL; trequest.tp=TP; trequest.deviation=dev; trequest.magic=Magic_No; trequest.symbol=symbol; trequest.type_filling=ORDER_FILLING_FOK; // send OrderSend(trequest,tresult); // check result if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed { Alert("A Buy order has been successfully placed with Ticket#:",tresult.order,"!!"); } else { Errormsg= "The Buy order request could not be completed"; Errcode =GetLastError(); showError(Errormsg,Errcode); } } } else { trequest.action=TRADE_ACTION_DEAL; trequest.type=otype; trequest.volume=LOTS; trequest.price=askprice; trequest.sl=SL; trequest.tp=TP; trequest.deviation=dev; trequest.magic=Magic_No; trequest.symbol=symbol; trequest.type_filling=ORDER_FILLING_FOK; //--- send OrderSend(trequest,tresult); //--- check result if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed { Alert("A Buy order has been successfully placed with Ticket#:",tresult.order,"!!"); } else { Errormsg= "The Buy order request could not be completed"; Errcode =GetLastError(); showError(Errormsg,Errcode); } } }
Bu, EA'mızda her çağrıldığında bir alış pozisyonu açan işlevdir. Giriş parametreleri olarak, alım satım yapmak için ihtiyaç duyulacak değişkenlerin çoğunu içerir ve bazı değişkenler EA kodumuz tarafından sağlanacaktır. İlk makalede açıklandığı gibi, burada MqlTraderequest türü değişkenleri kullandığımızı fark edeceksiniz.
Onları EA kodumuzda kullanmamız gerekmeyecek. Bir alım satım işlemi yapılmadan önce, kullanıcının marjı kontrol etmek isteyip istemediğini doğrulamak isteriz; Chk_Margin (EA'dan alınacak olan) değeri 1 ise, MarginOK() işlevi bu işlemi bizim için yapar. Bu işlevin sonucu, atılacak bir sonraki adımı belirler. Ancak, kullanıcı marjı kontrol etmek istemiyorsa devam edip alım satım yaparız.
openSell işlevi:
//+------------------------------------------------------------------+ //| OPENSELL FUNCTION //| *Has Input parameters - order type, Current BID price, Stop Loss, //| Take Profit, deviation, comment //| *Checks account free margin before pacing trade if trader chooses //| *Alerts of a success if position is opened or shows error //+------------------------------------------------------------------+ void MyExpert::openSell(ENUM_ORDER_TYPE otype,double bidprice,double SL,double TP,int dev,string comment="") { //--- do check Margin if enabled if(Chk_Margin==1) { if(MarginOK()==false) { Errormsg= "You do not have enough money to open this Position!!!"; Errcode =GetLastError(); showError(Errormsg,Errcode); } else { trequest.action=TRADE_ACTION_DEAL; trequest.type=otype; trequest.volume=LOTS; trequest.price=bidprice; trequest.sl=SL; trequest.tp=TP; trequest.deviation=dev; trequest.magic=Magic_No; trequest.symbol=symbol; trequest.type_filling=ORDER_FILLING_FOK; // send OrderSend(trequest,tresult); // check result if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed { Alert("A Sell order has been successfully placed with Ticket#:",tresult.order,"!!"); } else { Errormsg= "The Sell order request could not be completed"; Errcode =GetLastError(); showError(Errormsg,Errcode); } } } else { trequest.action=TRADE_ACTION_DEAL; trequest.type=otype; trequest.volume=LOTS; trequest.price=bidprice; trequest.sl=SL; trequest.tp=TP; trequest.deviation=dev; trequest.magic=Magic_No; trequest.symbol=symbol; trequest.type_filling=ORDER_FILLING_FOK; //--- send OrderSend(trequest,tresult); //--- check result if(tresult.retcode==10009 || tresult.retcode==10008) //Request successfully completed { Alert("A Sell order has been successfully placed with Ticket#:",tresult.order,"!!"); } else { Errormsg= "The Sell order request could not be completed"; Errcode =GetLastError(); showError(Errormsg,Errcode); } } }
openBuy işlevi gibi, bu işlev de EA'mızda her çağrıldığında bir satış pozisyonu açar. Giriş parametreleri olarak, alım satım yapmak için ihtiyaç duyulacak değişkenlerin çoğunu içerir ve bazı değişkenler EA kodumuz tarafından sağlanacaktır.
Bir Alış pozisyonu açarken yaptığımız gibi, bir alım satım işlemi yapılmadan önce, kullanıcının marjı kontrol etmek isteyip istemediğini, Chk_Margin değerinin (EA'dan alınacak) 1 olup olmadığını doğrulamak istiyoruz, bu durumda da bunu bizim için yapması için MarginOK() işlevini çağırırız.
Bu işlevin sonucu, atılacak bir sonraki adımı belirler. Ancak, kullanıcı marjı kontrol etmek istemiyorsa devam edip alım satım işlemini yaparız.
Artık sınıfımızın ve üye işlevlerin bildirimini ve tanımını bitirdik, ancak EA kodumuzda ele almayı düşündüğümüz diğer bazı görevlerin üzerinde durmadık. Bunlar, mevcut çubukların, yeni çubukların ve mevcut açık pozisyonların kontrol edilmesini içerir. Bunlar, EA kodumuzda ele alınacaktır.
Sınıfımızın tüm işlevlerinin ve yöntemlerinin bir listesini görmek için aşağıda gösterildiği gibi MetaEditor'daki işlevler komutuna/menüsüne tıklayın. İşlev, kodumuzda açıkça bildirmediğimiz Yıkıcı dahil tüm üye işlevlerini görüntüler.
Korumalı üyeler yeşil oklarla, Oluşturucu ve yıkıcı ise mavi oklarla gösterilir.
Şekil 3. Sınıf yıkıcıyı gösteren sınıf üyesi işlevlerimiz
Peki sırada ne var?
Hata ayıklama dediğinizi duyar gibiyim. Haklı olabilirsiniz. Kodunuzda hata olup olmadığını test etmek ve görmek her zaman iyidir; aksi takdirde kodu herkese açık olarak serbest bıraktığınızda hayal kırıklığına uğrayacaksınız. Buradaki sorun, bunun yalnızca bir içerme dosyası olması, grafiğe eklenebilecek bir expert adviser kodu veya script dosyası veya gösterge kodu olmamasıdır. Bu noktada iki seçeneğiniz var (deneyimlerime göre);,
- düzenleyicinizdeki hata ayıklama düğmesine basma olasılığınız vardır; böylece hata ayıklayıcı, bir .mqh dosyası bir .ex5 dosyasına derlenemediği için görüntülenecek olan 'yürütülebilir dosya oluşturulmadı' hatası hariç olmak üzere kodunuzdaki herhangi bir hatayı bildirecektir. VEYA
- Devam edin ve sınıfınızı kullanacak EA için kod yazın. EA'da hata ayıklamaya başladığınızda, içerilen dosya onunla birlikte kontrol edilecektir. Aslında, bunu yapmanın en iyi ve en kabul edilebilir yolu budur.
Şekil 4. .mqh dosyaları derlenemez
2.2. EXPERT ADVISOR'I YAZMA
Düzenleyicinizin hala açık olduğunu tahmin ediyorum. Yeniden yeni bir belge başlatın, ancak bu kez Expert Advisor'ı seçin. (Ayrıntılar için lütfen ilk makaleye bakın). Ancak bu kez EA'nıza 'my_oop_ea' adını verin.
Bu, anda olmanız gereken yer burası:
Artık OOP tabanlı EA'mızı yazmaya hazırız.
Bu yapacağımız ilk şey, az önce yazdığımız sınıfı #include ön işlemci komutunu kullanarak eklemektir. Son önişlemci özellik komutundan hemen sonra sınıfı ekleyin
//+------------------------------------------------------------------+ //| my_oop_ea.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "http://www.mql5.com" #property version "1.00" // Include our class #include <my_expert_class.mqh>
Bir dosya eklemenin iki yolu vardır;
// Include using angle brackets #include <my_expert_class.mqh> // Include using quotations #include "my_expert_class.mqh"
Açılı ayracı (< ... >) kullandığımızda, bu, eklenecek dosyanın standart içerme dizininden (yani MQL5 dizini içindeki içerme klasörü) alınacağı anlamına gelir. Geçerli dizin (MQL5 dizini içindeki Experts klasörü, dosyayı aramak için olası bir yer olarak kabul edilmeyecektir). Ancak, dosya tırnak işaretleri (" ... ") içine alınırsa dosyanın geçerli dizinde (Experts klasörü olan) olduğu kabul edilecek ve standart dizin (İçerme klasörü) kontrol edilmeyecektir.
Sınıfınız Include klasöründe (standart dizin) saklanmışsa ve açılı ayraçların yerine tırnak işaretlerini kullanırsanız veya tırnak işaretlerinin yerine açılı ayraçları kullanırsanız kodu derlerken bir hata alırsınız.
Şekil 5. Include dosyası bulunamadığında görüntülenen bir hata mesajı
EA GİRİŞ PARAMETRELERİ
//--- input parameters input int StopLoss=30; // Stop Loss input int TakeProfit=100; // Take Profit input int ADX_Period=14; // ADX Period input int MA_Period=10; // Moving Average Period input int EA_Magic=12345; // EA Magic Number input double Adx_Min=22.0; // Minimum ADX Value input double Lot=0.2; // Lots to Trade input int Margin_Chk=0; // Check Margin before placing trade(0=No, 1=Yes) input double Trd_percent=15.0; // Percentage of Free Margin To use for Trading
Buradaki giriş parametrelerinin çoğu yeni değil. O halde yenilerini tartışalım.
Marj kontrolü kullanmak isteyip istemediğimizden bağımsız olarak 0, 1 değerini tutmak için bir tamsayı değişkeni ekledik. Ayrıca, bir pozisyon açarken kullanılacak maksimum Serbest marj yüzdesini tutmak için başka bir değişken bildirdik. Bu değerler oluşturulduğunda daha sonra sınıf nesnemizde kullanılacaktır.
Giriş parametrelerinden hemen sonra, giriş değişkenlerinin değerlerini değiştiremediğimiz için manipüle etmek (5 ve 3 basamaklı fiyatları karşılamak için) istediğimiz diğer iki parametreyi (STP ve TKP) tanımlarız. Ardından, EA kodumuzda kullanılmak üzere sınıfımızın bir nesnesini oluştururuz.
//--- Other parameters int STP,TKP; // To be used for Stop Loss & Take Profit values // Create an object of our class MyExpert Cexpert;
Daha önce açıklandığı gibi, bir sınıfın nesnesini oluşturmak için sınıf adını ve ardından oluşturmak istediğiniz nesnenin adını kullanırsınız. Burada MyExpert sınıfının bir türü olan Cexpert nesnesini oluşturduk. Artık Cexpert, MyExpert sınıfının tüm genel üye işlevlerine erişmek için kullanılabilir.
EA'YI BAŞLATMA BÖLÜMÜ
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Run Initialize function Cexpert.doInit(ADX_Period,MA_Period); //--- Set all other necessary variables for our class object Cexpert.setPeriod(_Period); // sets the chart period/timeframe Cexpert.setSymbol(_Symbol); // sets the chart symbol/currency-pair Cexpert.setMagic(EA_Magic); // sets the Magic Number Cexpert.setadxmin(Adx_Min); // sets the ADX miniumm value Cexpert.setLOTS(Lot); // set the Lots value Cexpert.setchkMAG(Margin_Chk); // set the margin check variable Cexpert.setTRpct(Trd_percent); // set the percentage of Free Margin for trade //--- Let us handle brokers that offers 5 digit prices instead of 4 STP = StopLoss; TKP = TakeProfit; if(_Digits==5 || _Digits==3) { STP = STP*10; TKP = TKP*10; } //--- return(0); }
Bu noktada, sınıfımızın doInit işlevini çağırdık ve ona ADX ve MA dönem değişkenlerini ilettik. Daha sonra, yeni oluşturduğumuz nesnenin gerektireceği diğer tüm değişkenleri, sınıfımızı yazarken daha önce tanımladığımız işlevleri kullanarak nesnenin üye değişkenlerinde saklanacak şekilde ayarlıyoruz.
Bir sonraki kod satırı garip olmamalı; yalnızca üç ve beş basamaklı fiyatlar için Zararı Durdur ve Kar Al değerlerimizi ayarlamaya karar veriyoruz.
EA'YI YENİDEN BAŞLATMA BÖLÜMÜ
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Run UnIntilialize function Cexpert.doUninit(); }
Yalnızca başlatma işlevinde oluşturulmuş olması gereken tüm gösterge tanıtıcılarını serbest bırakmak için sınıfın doUninit işlevini çağırdık.
EA'YI ONTICK KESİTİ
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Do we have enough bars to work with int Mybars=Bars(_Symbol,_Period); if(Mybars<60) // if total bars is less than 60 bars { Alert("We have less than 60 bars, EA will now exit!!"); return; } //--- Define some MQL5 Structures we will use for our trade MqlTick latest_price; // To be used for getting recent/latest price quotes MqlRates mrate[]; // To be used to store the prices, volumes and spread of each bar /* Let's make sure our arrays values for the Rates is store serially similar to the timeseries array */ // the rates arrays ArraySetAsSeries(mrate,true);
Burada yaptığımız ilk şey, toplam kullanılabilir çubukları kontrol etmektir. Çubuklar EA'mızın alım satım yapması için yeterliyse alım satım yapacaktır; aksi takdirde yeterli çubuğumuz (yani 60 çubuk) olana kadar alım satım yapmayacaktır. Bunun ardından MQL5 yapısının (MqlTick ve MqlRates) iki değişkenini bildirdik. Ve son olarak, oranlar dizisinde ArraySetAsSeries işlevini kullanıyoruz.
//--- Get the last price quote using the MQL5 MqlTick Structure if(!SymbolInfoTick(_Symbol,latest_price)) { Alert("Error getting the latest price quote - error:",GetLastError(),"!!"); return; } //--- Get the details of the latest 3 bars if(CopyRates(_Symbol,_Period,0,3,mrate)<0) { Alert("Error copying rates/history data - error:",GetLastError(),"!!"); return; } //--- EA should only check for new trade if we have a new bar // lets declare a static datetime variable static datetime Prev_time; // lest get the start time for the current bar (Bar 0) datetime Bar_time[1]; // copy time Bar_time[0] = mrate[0].time; // We don't have a new bar when both times are the same if(Prev_time==Bar_time[0]) { return; } //copy time to static value, save Prev_time = Bar_time[0];
Burada, en son fiyat teklifini almak için SymbolInfoTick işlevini kullandık ve son üç çubuk (mevcut çubuk dahil) için son oranları almak için CopyRates kullandık. Sonraki kod satırları, yeni bir çubuğumuz olup olmadığını kontrol eder. Biri statik değişken (Prev_Time) ve diğeri Bar_Time olmak üzere iki tarih saat değişkeni bildirdik.
Yeni bir çubuğumuz varsa çubuk zamanı, bir sonraki tick'te değerini Bar_Time değeriyle karşılaştırabilmemiz için Prev_Time statik değişkeninde saklanır. Bir sonraki tick'te, Prev_Time, Bar_Time değerine eşitse bu durumda, bu, hala zamanı saklanan aynı çubuktur. Yani EA'mız rahatlayacak.
Ancak Bar_Time, Prev_Time değerine eşit değilse o zaman yeni bir çubuğumuz olur. Yeni çubuk başlangıç zamanını statik tarih saat değişkeninde, Prev_Time saklamaya karar verdik; EA'mız artık yeni ALIŞ veya SATIŞ fırsatlarını kontrol etmeye devam edebilir.
//--- we have no errors, so continue //--- Do we have positions opened already? bool Buy_opened = false, Sell_opened=false; // variables to hold the result of the opened position if (PositionSelect(_Symbol) ==true) // we have an opened position { if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { Buy_opened = true; //It is a Buy } else if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { Sell_opened = true; // It is a Sell } }
Zaten açık bir pozisyonumuz olup olmadığını kontrol etmeye karar verdik. Açılmış bir Alış olmadığında bir alış işlemi ve açılmış bir satış olmadığında bir satış işlemi açtığımızdan emin olmak istiyoruz.
// Copy the bar close price for the previous bar prior to the current bar, that is Bar 1 Cexpert.setCloseprice(mrate[1].close); // bar 1 close price //--- Check for Buy position if(Cexpert.checkBuy()==true) { // Do we already have an opened buy position if(Buy_opened) { Alert("We already have a Buy Position!!!"); return; // Don't open a new Buy Position } double aprice = NormalizeDouble(latest_price.ask,_Digits); // current Ask price double stl = NormalizeDouble(latest_price.ask - STP*_Point,_Digits); // Stop Loss double tkp = NormalizeDouble(latest_price.ask + TKP*_Point,_Digits); // Take profit int mdev = 100; // Maximum deviation // place order Cexpert.openBuy(ORDER_TYPE_BUY,aprice,stl,tkp,mdev); }
Şimdi oluşturduğumuz nesneye geri döndük; peki neden? Çünkü nesnemizin işini yapması için gerekli tüm kontrolleri yapabildik.
Yaptığımız ilk şey, nesneler üye işlevimizi setCloseprice kullanarak önceki çubuğun kapanış fiyatını elde etmektir.
Ardından, satın alma için bir koşulun belirlenip belirlenmediğini öğrenmek için checkBuy işlevini çağırırız; TRUE değerini döndürürse halihazırda açık bir satın alma pozisyonumuz olmadığından emin olmak isteriz. Halihazırda açık bir satın alma pozisyonumuz yoksa talimatımız için kullanılacak gerekli değişkenleri, (talimat türü, güncel SATIŞ fiyatı, zararı durdur, kar al ve maksimum sapma) hazırlarız ve openBuy işlevini çağırırız. Yazdığımız sınıfı kullanmanın ne kadar kolay olduğuna bakın.
//--- Check for any Sell position if(Cexpert.checkSell()==true) { // Do we already have an opened Sell position if(Sell_opened) { Alert("We already have a Sell position!!!"); return; // Don't open a new Sell Position } double bprice=NormalizeDouble(latest_price.bid,_Digits); // Current Bid price double bstl = NormalizeDouble(latest_price.bid + STP*_Point,_Digits); // Stop Loss double btkp = NormalizeDouble(latest_price.bid - TKP*_Point,_Digits); // Take Profit int bdev=100; // Maximum deviation // place order Cexpert.openSell(ORDER_TYPE_SELL,bprice,bstl,btkp,bdev); }
Bu, yukarıda yaptığımız şeyle aynı. Satışı kontrol ettiğimiz için checkSell işlevini çağırdık; bu, TRUE değerini döndürürse ve halihazırda açılmış bir satış pozisyonumuz yoksa, talimatımızı vermek için gerekli değişkenleri hazırlarız ( talimat türü, geçerli SATIŞ fiyatı, zararı durdur, kar al ve maksimum sapma) ve ardından openSell işlevini çağırırız.
Oldukça kolay, değil mi? Kod yazma işlemini tamamladık. Şimdi kodumuzda hata ayıklama zamanı. Hata ayıklayıcıyı nasıl kullanacağınızı bilmiyorsanız daha iyi anlamak için lütfen ilk makaleyi okuyun.
F5 tuşuna veya hata ayıklama düğmesine bastığınızda, içerilen dosya (sınıfımız) eklenecek ve kontrol edilecek ve herhangi bir hata varsa bunu bildirecektir. Hatayı gördüğünüzde koda geri dönmeniz ve hatayı düzeltmeniz gerekir.
Şekil 6. İçerme dosyamız, ana EA kodunda hata ayıklarken eklenir
Her şey yolundaysa, adımları doğru şekilde uyguladınız demektir. Şimdi strateji test cihazını kullanarak EA'mızı test etme zamanı. Strateji Test Cihazı ile test etmeden önce EA'mızı derlememiz gerekiyor. Bunu yapmak için, Derle düğmesine tıklayın veya bilgisayar klavyenizde F7 tuşuna basın.
Şekil 7. Kodumuzu derlemek için Derle menü düğmesine tıklayın
Alım satım terminali menü çubuğundan, Görünüm Strateji Test Cihazı seçeneğine gidin veya strateji test cihazını başlatmak için CONTROL+R tuşlarına basın. (Test cihazının nasıl kullanılacağına ilişkin ayrıntılar için lütfen ilk makaleyi okuyun).
EA'yı strateji test cihazı ile test edebilmeniz için öncelikle onu derlemeniz gerekir. Derleme yapmazsanız strateji test cihazının ayarlar çubuğunda Expert Advisor seçtiğinizde bir hata alırsınız. (Bunu yalnızca terminalin yeni sürümünde keşfettim.)
Şekil 8. EA kodu, Strateji Test Cihazında kullanılmadan önce derlenmelidir
OOP tabanlı Expert Advisor'ımız için Strateji test cihazının sonuçlarını aşağıda bulabilirsiniz.
Şekil 9. Nesne Yönelimli Expert Advisor'ımız için alım satım işlemi sonuçları
Grafik:
Şekil 10. Nesne Yönelimli Expert Advisor'ımızın grafik sonuçları
Alım satım etkinliği raporu/günlüğü:
Şekil 11. Nesne Yönelimli Expert Advisor'ımız için alım satım etkinliği sonuçları
Test grafiği:
Şekil 12. Nesne Yönelimli Expert Advisor'ımız için alım satım grafiği sonuçları
Sonuç
Bu makalede, bir sınıfın temellerini ve bu sınıfın basit bir Expert Advisor yazarken nasıl kullanılacağını bir dereceye kadar ele aldık. Sınıfların ileri düzey alanlarına çok fazla girmedik ancak bu makalede tartıştıklarımız, kendinizi kendi nesne yönelimli Expert Advisor kodunuzu yazabilecek seviyeye getirmenize yardımcı olmak için yeterli olacaktır.
Ayrıca, açmak istediğimiz pozisyon için mevcut serbest marj yeterli olmadığında EA'mızın alım satım işlemi yapmaması için serbest marjı nasıl kontrol edebileceğimizi de tartıştık.
Artık yeni MQL5 dilinin sunabileceği çok şey olduğu ve bu yeni dilin avantajlarından yararlanmak için bir programlama gurusu olmanız gerekmediği konusunda benimle hemfikirsinizdir. Adım adım kılavuzları yazmanın ana nedeni budur.
MetaQuotes Ltd tarafından İngilizceden çevrilmiştir.
Orijinal makale: https://www.mql5.com/en/articles/116
- Ücretsiz ticaret uygulamaları
- İşlem kopyalama için 8.000'den fazla sinyal
- Finansal piyasaları keşfetmek için ekonomik haberler
Gizlilik ve Veri Koruma Politikasını ve MQL5.com Kullanım Şartlarını kabul edersiniz