English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Italiano
preview
Sıfırdan bir ticaret Uzman Danışmanı geliştirme (Bölüm 24): Sistemin sağlamlığını artırma (I)

Sıfırdan bir ticaret Uzman Danışmanı geliştirme (Bölüm 24): Sistemin sağlamlığını artırma (I)

MetaTrader 5Ticaret | 15 Ocak 2024, 11:36
137 0
Daniel Jose
Daniel Jose

Giriş

Birçok insanın düşündüğünün aksine, bazı şeyler o kadar da basit değildir. Emir sistemi de bunlardan biridir. Birçok kişi için yararlı olabilecek temel bir sistem oluşturduğumuz Sıfırdan bir ticaret Uzman Danışmanı geliştirme makalesinde yaptığımız gibi, siz de kendinize mükemmel şekilde hizmet eden daha mütevazı bir sistem oluşturabilirsiniz. Ancak bu herkes için yeterli olmayabilir. Dolayısıyla, bir şeylerin değişmeye başladığı zaman geldi - yeni emir sistemi hakkındaki bu serinin ilk bölümü işte o zaman doğdu. Bunu Yeni emir sistemi (I) makalesinde görebilirsiniz. Orada MetaTrader 5 tarafından desteklenen ve Uzman Danışman tarafından yönetilebilen bir sistem geliştirmeye başladık. Sistemin fikri, grafikteki emirlerde sınır olmamasıydı. İlk başta sistem oldukça cesur görünüyordu ve itiraf etmeliyim ki nesnelerin Uzman Danışman tarafından değil MetaTrader 5 tarafından sürdürüleceği bir sistem oluşturma gerçeği bana oldukça anlamsız ve verimsiz geldi.

Bununla birlikte, sistemi ilerlettik ve Yeni emir sistemi (VI) makalesinde, emirlerin, pozisyonların veya durma seviyelerinin (Kârı Al ve Zararı Durdur) yönetimini kolaylaştırmak için hayalet bir sistem geliştirdik. Geliştirme süreci oldukça ilginçti, ama bir sorun vardı. MetaTrader 5 tarafından desteklenen nesne sayısına kıyasla kullanılan ve görünen nesne sayısına bakarsanız, kesinlikle şaşıracaksınız çünkü desteklenen nesne sayısı her zaman daha yüksek olacaktır.

Çoğu durumda, sorun o kadar ciddi değildir, hatta mümkün olduğunca onunla yaşayabilirsiniz. Ancak, yüksek piyasa volatilitesi dönemlerinde sistemin çok istikrarlı olmamasına neden olan iki sorun bulunmaktadır. Bazı durumlarda, kullanıcıyı yanlış hareket etmeye zorlarlar. Bunun nedeni, yatırımcı bir bekleyen emir eklediğinde, sistemin bunu sunucuya göndermesi ve sunucunun yanıt vermek için bazen normalden daha fazla zamana ihtiyaç duymasıdır. Ve sistem bazı anlarda bir emir olduğunu, bazı anlarda ise bir emir olmadığını belirtiyordu. Pozisyonlarda yapıldığında ise (emirler ve pozisyonlar arasındaki fark için dokümantasyona bakın), bu daha da hantal hale geliyordu çünkü sunucunun komutu beklendiği gibi yürütüp yürütmediği bilinmiyordu.

Bu sorunu çözmenin birkaç yolu vardır. Bazıları daha basit, bazıları daha karmaşıktır. Ancak her ne olursa olsun, Uzman Danışmana güvenmeliyiz, aksi takdirde onu hiçbir koşulda kullanmamalıyız.


1.0. Planlama

Buradaki en büyük problem, şu iki niteliğe sahip bir sistem tasarlamaktır: hız ve güvenilirlik. Bazı sistem türlerinde her ikisini de başarmak oldukça zordur, hatta imkansızdır. Bu yüzden, çoğu durumda, bazı şeyleri dengelemeye çalışıyoruz. Ancak söz konusu olan para, yani BİZİM paramız olduğu için, bu niteliklere sahip olmayan bir sistem oluşturarak paramızı riske atmak istemiyoruz. Unutulmamalıdır ki GERÇEK ZAMANLI çalışan bir sistemle karşı karşıyayız ve bu bir geliştiricinin içerisine girebileceği en zor senaryodur, çünkü her zaman son derece hızlı bir sisteme sahip olmaya çalışmalıyız: olaylara anında tepki vermeli, aynı zamanda onu iyileştirmeye çalıştığımızda çökmeyecek kadar güvenilirlik göstermelidir. Dolayısıyla, görevin oldukça zor olduğu açıktır.

Hız, fonksiyonların en uygun şekilde çağrılmasını ve yürütülmesini sağlayarak ve gereksiz zamanlarda gereksiz çağrılardan kaçınarak elde edilebilir. Bu, sistemi dilin sınırları dahilinde olabildiğince hızlı hale getirecektir. Ancak, daha da hızlı bir şey istiyorsak, o zaman makine dili seviyesine inmemiz gerekir, bu durumda assembly'i kastediyoruz. Ancak bu genellikle gereksizdir, C dilini kullanabilir ve eşit derecede iyi sonuçlar elde edebiliriz.

İstenilen sağlamlığı elde etmenin yollarından biri, kodu mümkün olduğunca yeniden kullanmaya çalışmak ve böylece farklı durumlarda sürekli olarak test edilmesini sağlamaktır. Ancak bu, yöntemlerden yalnızca bir tanesidir. Diğer bir yol ise nesne yönelimli programlama (Object Oriented Programming, OOP) kullanmaktır. Her bir nesne sınıfının, kalıtım durumu hariç, nesne sınıfı verilerini doğrudan manipüle etmemesi sağlanırsa (doğru ve düzgün bir şekilde yapılırsa), bu çok sağlam bir sisteme sahip olmak için yeterli olacaktır. Bu bazen yürütme hızını azaltır, ancak bu azalma, sınıf tarafından sağlanan kapsülleme tarafından oluşturulan üstel artış nedeniyle göz ardı edilebilecek kadar küçüktür. Bu kapsülleme, ihtiyacımız olan sağlamlığı kazandırır.

Gördüğünüz gibi, hem hız hem de güvenilirlik elde etmek o kadar da kolay değildir. Ancak işin güzel yanı, ilk bakışta düşünüldüğü gibi bazı şeylerden çok fazla fedakârlık etmemiz gerekmiyor. Basitçe sistem dokümantasyonunu kontrol edebilir ve iyileştirme yapmak için nelerin değiştirilebileceğini görebiliriz. Tekerleği yeniden icat etmeye çalışmadığımız gerçeği zaten iyi bir başlangıçtır. Ancak programların ve sistemlerin sürekli iyileştiğini unutmayın. Bu nedenle, her zaman mümkün olduğunca mevcut şeyleri kullanmaya çalışmalı ve yalnızca son durumda tekerleği gerçekten yeniden icat etmeliyiz.

Bazı kişiler bu makalede yapılan değişiklikleri sunmayı gereksiz bulmadan veya kodu gerçekten taşımadan çok fazla değiştirdiğimi düşünmeden önce açıklamama izin verin: bir şeyi kodlarken nihai kodun nasıl çalışacağı hakkında hiçbir fikrimiz yoktur. Elimizde sadece ulaşılması gereken hedefler vardır. Bu hedefe ulaşıldıktan sonra, bu hedefe nasıl ulaştığımıza bakmaya başlar ve her şeyi daha iyi hale getirmek için iyileştirmeye çalışırız.

Satışı yapılan bir sistem söz konusu olduğunda, ister çalıştırılabilir ister kütüphane olsun, değişiklikleri yapar ve bir güncelleme olarak yayınlarız. Böyle bir sistemde kullanıcının hedefe ulaşmak için izlenen yolları bilmesine gerek yoktur. Aslında bilmemesi de iyi bir şeydir. Ancak bizim sistemimiz açık bir sistem olduğundan, en başından itibaren hemen yüksek verimli bir sistem geliştirebileceğinizi düşünmenizi istemiyorum. Bu şekilde düşünmek uygun değildir, hatta bir hakarettir, çünkü bir programcı veya geliştirici kullanılacak dil hakkında ne kadar bilgi sahibi olursa olsun, zaman içinde her zaman iyileştirilebilecek şeyler olacaktır.

Bu nedenle, bu konuyu 3 veya 4 makalede özetlenebilecek bir şey olarak görmeyin, çünkü durum böyle olsaydı, en uygun olduğunu düşündüğüm şekilde kalarak kodu oluşturmak ve ticari olarak yayınlamak daha iyi olurdu. Ancak niyetim bu değil. Programlamayı kendimden daha deneyimli programcıların kodlarını izleyerek öğrendim ve bunun ne kadar değerli olduğunu biliyorum. Bir şeyin zaman içinde nasıl geliştiğini bilmek, sadece bitmiş çözümü alıp nasıl çalıştığını anlamaya çalışmaktan çok daha önemlidir.

Bu gözlemlerden sonra, şimdi geliştirmeye devam edelim.


2.0. Uygulama

2.0.1. Pozisyon göstergelerinin yeni modellemesi

Yeni kod formatında dikkat edilmesi gereken ilk şey, makro haline gelen bir fonksiyonun değişmesidir.

inline string MountName(ulong ticket, eIndicatorTrade it, eEventType ev, bool isGhost = false)
{
        return StringFormat("%s%c%c%c%llu%c%c%c%s", def_NameObjectsTrade, def_SeparatorInfo, (char)it, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)(isGhost ? ev + 32 : ev), def_SeparatorInfo, (isGhost ? def_IndicatorGhost : def_IndicatorReal));
}

Derleyici, 'inline' ayrılmış sözcüğü sayesinde bu kodu başvurulduğu her noktada kullansa bile, bu fonksiyon kodda birçok kez çağrıldığı için ona kesin gözüyle bakmamalıyız. Gerçekten olabildiğince hızlı çalıştığından emin olmamız gerekiyor, bu yüzden yeni kodumuz şu şekilde görünecektir:

#define macroMountName(ticket, it, ev, Ghost) 								 \
		StringFormat("%s%c%llu%c%c%c%c%c%c%c", def_NameObjectsTrade, def_SeparatorInfo,          \                                                                                                                                                                                                                                                                                                
                                                       ticket, def_SeparatorInfo,                        \                                                                                                                                                                                                                                        
                                                       (char)it, def_SeparatorInfo,                      \ 
                                                       (char)(Ghost ? ev + 32 : ev), def_SeparatorInfo,  \ 
                                                       (Ghost ? def_IndicatorGhost : def_IndicatorReal))

Makronun eski sürümündeki veriler ile bu sürümdeki verilerin farklı olduğuna dikkat edin. Bu değişikliğin, bu makalenin ilerleyen bölümlerinde tartışacağımız bir nedeni vardır.

Ancak bu değişiklik nedeniyle, başka bir fonksiyonun kodunda da küçük bir değişiklik yapmamız gerekiyor.

inline bool GetIndicatorInfos(const string sparam, ulong &ticket, eIndicatorTrade &it, eEventType &ev)
                        {
                                string szRet[];
                                char szInfo[];
                                
                                if (StringSplit(sparam, def_SeparatorInfo, szRet) < 2) return false;
                                if (szRet[0] != def_NameObjectsTrade) return false;
                                ticket = (ulong) StringToInteger(szRet[1]);
                                StringToCharArray(szRet[2], szInfo);
                                it = (eIndicatorTrade)szInfo[0];
                                StringToCharArray(szRet[3], szInfo);
                                ev = (eEventType)szInfo[0];

                                return true;
                        }

Buradaki değişiklik sadece neyin fiş neyin gösterge olduğunu belirtmek için kullanılacak olan indekstir. Karmaşık bir şey değil. Yapılması gereken sadece basit bir ayrıntıdır, aksi takdirde bu fonksiyonu kullanırken tutarsız veriler elde ederiz.

Merak edebilirsiniz: "Bu değişikliklere neden ihtiyacımız var? Sistem mükemmel çalışmıyor muydu?". Evet çalışıyordu. Ancak kontrol edemediğimiz şeyler vardır. Örneğin, MetaTrader 5 platformu geliştiricilerinin Uzman Danışmanda kullanılmayan bazı fonksiyonlarda yapacakları bir iyileştirme, Uzman Danışmanımıza fayda sağlamayacaktır. Kural, tekerleği yeniden icat etmekten kaçınmak ve bunun yerine mevcut kaynakları kullanmaktır. Bu nedenle, her zaman dil tarafından sağlanan fonksiyonları kullanmaya çalışmalı, ki bizim durumumuzda bu MQL5'tir, ve kendi fonksiyonlarımızı yaratmaktan kaçınmalıyız. Bu saçma görünebilir, ancak aslında durup düşünürseniz, platformun zaman zaman bazı fonksiyonlarda iyileştirmeler sağladığını göreceksiniz ve aynı fonksiyonları kullanıyorsanız, ekstra çaba sarf etmek zorunda kalmadan programlarınızda daha iyi performans ve daha fazla güvenlik elde edeceksiniz.

Dolayısıyla, amaca giden her yol mübahtır. Ancak, yukarıda yapılan değişiklikler Uzman Danışmanın MQL5 kütüphanesindeki herhangi bir iyileştirmeden yararlanmasına yardımcı olacak mı? Bu sorunun cevabı HAYIRdır. Yukarıdaki değişiklikler, MQL5 ve MetaTrader 5 geliştiricilerinden gelecek olası iyileştirmeleri etkin bir şekilde kullanabilmemiz için nesne adı modellemesinin doğru olmasını sağlamak için gereklidir. Aşağıda faydalı olabilecek öğelerden biri yer almaktadır:

inline void RemoveIndicator(ulong ticket, eIndicatorTrade it = IT_NULL)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
                ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, (ticket > 1 ? '*' : def_SeparatorInfo)));
        else ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, def_SeparatorInfo, (char)it));
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
        m_InfoSelection.bIsMovingSelect = false;
        ChartRedraw();
}

Hatırlamayanlar veya daha önce karşılaşmamış olanlar için aynı kodun önceki versiyonu aşağıda verilmiştir. Kod şu şekildedir:

inline void RemoveIndicator(ulong ticket, eIndicatorTrade it = IT_NULL)
{
#define macroDestroy(A, B)      {                                                                               \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_GROUND, B));                            \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_LINE, B));                              \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_CLOSE, B));                             \
                ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_EDIT, B));                              \
                if (A != IT_RESULT)     ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_MOVE, B));      \
                else ObjectDelete(Terminal.Get_ID(), MountName(ticket, A, EV_PROFIT, B));                       \
                                }

        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
        {
                macroDestroy(IT_RESULT, true);
                macroDestroy(IT_RESULT, false);
                macroDestroy(IT_PENDING, true);
                macroDestroy(IT_PENDING, false);
                macroDestroy(IT_TAKE, true);
                macroDestroy(IT_TAKE, false);
                macroDestroy(IT_STOP, true);
                macroDestroy(IT_STOP, false);
        } else
        {
                macroDestroy(it, true);
                macroDestroy(it, false);
        }
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
#undef macroDestroy
}

Kod daha kompakt hale gelmiş gibi görünebilir. Ama yalnızca bu değildir. Koddaki azalma bariz bir şeydir, ancak gerçek çok daha derindir. Eski kod, platform kaynaklarını daha iyi kullanan yeni bir kodla değiştirildi. Ancak daha önce kullanılan nesne adları modeli bu iyileştirmeye izin vermediğinden, modellemeyi MQL5 fonksiyonlarından yararlanmayı bekleyebileceğimiz şekilde değiştiriyoruz. Bu fonksiyon herhangi bir nedenle iyileştirilirse, Uzman Danışman yapısında herhangi bir değişiklik yapmamıza gerek kalmadan Uzman Danışman bu değişiklikten faydalanacaktır. ObjectsDeleteAll fonksiyonundan bahsediyorum. Doğru kullanırsak, MetaTrader 5 temizliği yapacaktır. Çok fazla ayrıntı belirtmemize gerek yoktur, sadece nesnenin veya nesnelerin adını belirtiyoruz ve gerisini MetaTrader 5'in yapmasına bırakıyoruz. Bu fonksiyonun kullanıldığı kısımlar yeni kodda vurgulanmıştır. Kullanılacak önek hakkında bilgilendirmek için modellemeyi nasıl yaptığımıza dikkat edin. Bu, nesne adı modellemesinin değiştirilmesinden önce mümkün değildi.

Aşağıda vurgulanan yeni kod parçasındaki bir ayrıntıya dikkatinizi çekmek istiyorum.

if ((it == IT_NULL) || (it == IT_PENDING) || (it == IT_RESULT))
        ObjectsDeleteAll(Terminal.Get_ID(), StringFormat("%s%c%llu%c", def_NameObjectsTrade, def_SeparatorInfo, ticket, (ticket > 1 ? '*' : def_SeparatorInfo)));

Vurgulanan kısmı neden eklediğimi düşünüyorsunuz?

Bunun nedeni, sistem 1'e eşit bir değerle başlayan bir fiş oluşturursa, bekleyen emir yerleştirilir yerleştirilmez tüm nesnelerin ekrandan kaldırılacak olmasıdır. Net değil mi? Bir bekleyen emir yerleştirmek için kullanılan girdi 1 değerine sahiptir, yani 0 göstergesi aslında 0 değil 1 değerine sahiptir, çünkü 0 Uzman Danışmandaki diğer testleri gerçekleştirmek için kullanılır. Bu nedenle başlangıç değeri 1'dir. Şimdi bir sorunumuz vardır: ticaret sisteminin bir fiş oluşturduğunu varsayalım: 1221766803. Daha sonra bu fişi temsil eden nesne önek olarak şu değere sahip olacaktır: SMD_OT#1221766803. Uzman Danışman, 0 göstergesini silmek için ObjectsDeleteAll fonksiyonunu yürüttüğünde, nesne adı SMD_OT#1 olacaktır ve bu, yeni oluşturulan sistem de dahil olmak üzere bu değerle başlayan tüm nesneleri silecektir. Bu sorunu çözmek için, ObjectsDeleteAll fonksiyonunu bilgilendirmek üzere adın sonuna fazladan bir karakter ekleyerek adda küçük bir ayarlama yapacağız, böylece fonksiyon 0 göstergesini mi yoksa başka bir göstergeyi mi sildiğimizi anlayacaktır.

Böylece, eğer 0 göstergesi silinecekse, fonksiyon şu değeri alacaktır: SMD_OT#1#. Bu, sorunu önleyecektir. Aynı zamanda, yukarıdaki örnekte olduğu gibi, fonksiyon şu adı alacaktır: SMD_OT#1221766803*. Basit bir şey gibi görünüyor, ancak bu nedenle Uzman Danışmanın yeni yerleştirilen bir emrin gösterge nesnelerini neden silmeye devam ettiği konusunda kafanız karışabilir.

Şimdi ilginç bir ayrıntıdan bahsedelim. Fonksiyonun sonunda bir ChartRedraw çağrısı var. Neden burada kullanılıyor? MetaTrader 5 grafiğin kendisini yenilemiyor mu? Evet yeniliyor. Ancak bunun tam olarak ne zaman olacağını bilmiyoruz. Başka bir sorun daha vardır: grafikteki nesneleri yerleştirmek veya silmek için yapılan tüm çağrılar eşzamanlıdır, yani belirli bir zamanda yürütülürler, bu da beklediğimiz zaman olmayabilir. Ancak, emir sistemimiz emirleri görüntülemek veya yönetmek için nesneleri kullanacaktır ve nesnenin grafikte olduğundan emin olmamız gerekir. MetaTrader 5'in nesneleri grafikte zaten yerleştirdiğini veya kaldırdığını düşünemeyiz, çünkü bundan emin olmamız gerekiyor, bu yüzden platformu bu yenilemeyi yapmaya zorluyoruz.

Böylece, ChartRedraw'ı çağırdığımızda, platformu grafikteki nesnelerin listesini yenilemeye zorlarız, böylece belirli bir nesnenin grafikte mevcut olduğundan veya olmadığından emin olabiliriz. Eğer bu hala net değilse, bir sonraki bölüme geçelim.


2.0.2. Daha az nesne - daha yüksek hız

Önceki sürümdeki başlatma fonksiyonu kullanışsızdı. Çok sayıda tekrarlayan kontrol vardı ve bazı şeyler tekrarlanıyordu. Bazı küçük sorunların yanı sıra, sistem halihazırda var olan kapasitenin çok azını yeniden kullanmıştı. Bu nedenle, yeni modellemeden yararlanmak için, başlatma sırasında oluşturulan nesnelerin sayısını azaltmaya karar verdim. Yani, şimdi sistem şöyle görünüyor:

void Initilize(void)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_OBJECT_DESCR, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_SHOW_TRADE_LEVELS, false);
        ChartSetInteger(Terminal.Get_ID(), CHART_DRAG_TRADE_LEVELS, false);
        for (int c0 = OrdersTotal(); c0 >= 0; c0--) IndicatorInfosAdd(OrderGetTicket(c0));
        for (int c0 = PositionsTotal(); c0 >= 0; c0--) IndicatorInfosAdd(PositionGetTicket(c0));
}

Görünüşe göre her şey farklıydı ve gerçekten de öyleydi. Şimdi yeterince kullanılmayan fonksiyonu yeniden kullanıyoruz - bu, grafiğe göstergeler ekleyen fonksiyondur. Şimdi bu özel fonksiyona bir göz atalım.

inline void IndicatorAdd(ulong ticket)
{
        char ret;
                                
        if (ticket == def_IndicatorTicket0) ret = -1; else
        {
                if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_PENDING, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                if (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_RESULT, EV_LINE, false), OBJPROP_PRICE) != 0) return;
                if ((ret = GetInfosTradeServer(ticket)) == 0) return;
        }
        switch (ret)
        {
                case  1:
                        CreateIndicatorTrade(ticket, IT_RESULT);
                        PositionAxlePrice(ticket, IT_RESULT, m_InfoSelection.pr);
                        break;
                case -1:
                        CreateIndicatorTrade(ticket, IT_PENDING);
                        PositionAxlePrice(ticket, IT_PENDING, m_InfoSelection.pr);
                        break;
        }
        ChartRedraw();
        UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
}

Yukarıdaki koda yakından bakın. Kod gereksiz kontroller içeriyor gibi görünebilir. Ancak çok basit bir nedenden dolayı varlar. Bu fonksiyon, gerçekten bir bekleyen emir veya pozisyon göstergesi oluşturmanın tek yoludur. Vurgulanan iki satır göstergenin var olup olmadığını kontrol edecektir. Bunu yapmak için, çizgi olarak kullanılan nesnede herhangi bir değerin saklanıp saklanmadığı kontrol edilir. Burada, nesnenin bulunduğu fiyatın değeridir. Gösterge nesnesi grafik üzerindeyse bu değer sıfırdan farklı olmalıdır. Diğer tüm durumlarda, nesne mevcut olmadığı için ya da önemli olmayan başka bir nedenden dolayı sıfıra eşit olacaktır. Grafiği yenilemeye neden zorlamamız gerektiği şimdi anlaşıldı mı? Bu yapılmazsa, Uzman Danışman gereksiz yere nesne ekleyecektir, bu nedenle platformun bilinmeyen bir zamanda bu eylemi gerçekleştirmesini bekleyemeyiz. Grafiğin güncellendiğinden emin olmalıyız. Aksi takdirde, bu kontroller yapıldığında, nesnelerin mevcut durumuyla gerçekte eşleşmeyen şeyleri rapor edecek ve sistemi daha az güvenilir hale getirecektir.

Bu kontroller Uzman Danışman hızını yavaşlatıyor gibi görünse de, bu kavramsal bir hatadır. Bu tür kontroller yaptığımızda ve platformu zaten oluşturma kuyruğunda olabilecek bir nesneyi oluşturmaya zorlamaya çalışmadığımızda, platforma "ŞİMDİ GÜNCELLE" diyoruz. Daha sonra, ihtiyaç duyduğumuzda, nesnenin zaten oluşturulup oluşturulmadığını kontrol ederiz ve zaten oluşturulmuşsa, gerektiği gibi kullanırız. Buna "doğru şekilde programlama" denir. Bu şekilde platformun daha az çalışmasını sağladığımız ve nesnenin oluşturulup oluşturulmadığına dair gereksiz kontrollerden kaçındığımız için Uzman Danışmanı daha güvenilir hale getiriyoruz, çünkü çalışmak istediğimiz verilere sahip olduğumuzu biliyoruz.

Kontroller, belirtilen fişle eşleşen bir nesne olmadığını belirteceğinden, nesne oluşturulacaktır. Başlangıçta 0 göstergesi mi yoksa başka bir gösterge mi oluşturduğumuza dair başka bir kontrol olduğuna dikkat edin. Bu, MetaTrader 5 tarafından desteklenen gereksiz nesnelere sahip olmamamızı sağlar; yalnızca grafikte gerçekten kullandığımız nesnelere sahip oluruz. 0 göstergesini oluşturursak, çok özel ve spesifik koşullarda oluşturacağımız için daha fazla teste gerek yoktur. 0 nesnesi, SHIFT veya CTRL + fare kullanılarak emirleri konumlandırmak için kullanılır. Merak etmeyin, yakında nasıl çalıştığını göreceğiz.

Yukarıdaki kodda önemli bir ayrıntı vardır: güncelleme fonksiyonunu çağırmadan önce neden grafiği güncelliyoruz? Bu hiç mantıklı değil, değil mi? Bunu anlamak için aşağıdaki UpdateIndicators fonksiyonuna bakalım.

void UpdateIndicators(ulong ticket, double tp, double sl, double vol, bool isBuy)
{
        double pr;
        bool b0 = false;
                                
        pr = macroGetLinePrice(ticket, IT_RESULT);
        pr = (pr > 0 ? pr : macroGetLinePrice(ticket, IT_PENDING));
        SetTextValue(ticket, IT_PENDING, vol);
        if (tp > 0)
        {
                if (b0 = (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_TAKE, EV_LINE, false), OBJPROP_PRICE) == 0 ? true : b0))
                        CreateIndicatorTrade(ticket, IT_TAKE);
                PositionAxlePrice(ticket, IT_TAKE, tp);
                SetTextValue(ticket, IT_TAKE, vol, (isBuy ? tp - pr : pr - tp));
        }
        if (sl > 0)
        {
                if (b0 = (ObjectGetDouble(Terminal.Get_ID(), macroMountName(ticket, IT_STOP, EV_LINE, false), OBJPROP_PRICE) == 0 ? true : b0))
                        CreateIndicatorTrade(ticket, IT_STOP);
                PositionAxlePrice(ticket, IT_STOP, sl);
                SetTextValue(ticket, IT_STOP, vol, (isBuy ? sl - pr : pr - sl));
        }
        if (b0) ChartRedraw();
}

Bu fonksiyon temel olarak eşiklere işaret eden göstergelerle ilgilenecektir. Şimdi vurgulanan iki satıra bir göz atalım: grafik güncellenmezse, bu satırlar tetiklenmeyecek ve 0 değerini geri dönecektir ve eğer güncellenirse, kodun geri kalanı çalışmayacak ve eşik göstergeleri ekranda doğru şekilde görüntülenmeyecektir.

Ancak eşik göstergelerini oluşturmadan önce, gerçekten oluşturulmaları gerekip gerekmediğini veya sadece ayarlanmaları gerekip gerekmediğini anlamak için bazı kontroller yapmalıyız. Bu, temel nesneyi oluştururken yapılanla aynı şekilde yapılır. Ve burada bile, nesneleri oluştururken, grafiğin her zaman güncel olması için grafiği de güncellenmeye zorlayacağız.

Merak edebilirsiniz: "Neden bu kadar çok zorunlu güncelleme var, bunlar gerçekten gerekli mi?" Ve bunun cevabı KESİNLİKLE EVET ve bunun nedeni aşağıdaki fonksiyondur:

inline double SecureChannelPosition(void)
{
        double Res = 0, sl, profit, bid, ask;
        ulong ticket;
                                
        bid = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_BID);
        ask = SymbolInfoDouble(Terminal.GetSymbol(), SYMBOL_ASK);
        for (int i0 = PositionsTotal() - 1; i0 >= 0; i0--) if (PositionGetSymbol(i0) == Terminal.GetSymbol())
        {
                IndicatorAdd(ticket = PositionGetInteger(POSITION_TICKET));
                SetTextValue(ticket, IT_RESULT, PositionGetDouble(POSITION_VOLUME), profit = PositionGetDouble(POSITION_PROFIT), PositionGetDouble(POSITION_PRICE_OPEN));
                sl = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                        if (ask < sl) ClosePosition(ticket);
                }else
                {
                        if ((bid > sl) && (sl > 0)) ClosePosition(ticket);
                }
                Res += profit;
        }
        return Res;
};

Bu fonksiyonun özel bir yanı olmadığını düşünebilirsiniz. Emin misiniz? YANLIŞ! Bu fonksiyon önemli bir ayrıntı içerir: nesnenin grafikte olduğundan emin olmalıyız, aksi takdirde onu oluşturmak için tüm kod birkaç kez çağrılacak ve MetaTrader 5 tarafından yönetilecek büyük bir kuyruk oluşturacaktır ve bazı veriler kaybolabilir veya eski hale gelebilir. Tüm bunlar sistemi istikrarsız ve daha az güvenilir hale getirecektir. Nesneyi oluşturan fonksiyon çağrısı vurgulanmıştır. MetaTrader 5'i grafiği stratejik anlarda güncellemeye zorlamasaydık, yukarıdaki fonksiyon OnTick olayı tarafından çağrıldığından, yüksek volatilite dönemlerinde OnTick'ten gelen çağrı sayısı oldukça fazla olduğundan ve bu da kuyrukta fazla nesneye yol açabileceğinden, ki bu hiç de iyi değildir, sorun yaşayabilirdik. Bu nedenle, veriler ChartRedraw çağrısı aracılığıyla yenilenmeye ve ObjectGetDouble aracılığıyla doğrulanmaya zorlanır, böylece kuyrukta çok fazla nesne olma olasılığı azaltılır.

Sistemin nasıl işlediğine bakmadan bile şöyle düşünebilirsiniz: "Artık, TradeLine nesnesinin yanlışlıkla silinmesi durumunda, Uzman Danışman bunu fark edecek ve ObjectGetDouble aracılığıyla yapılan kontrol başarısız olursa, gösterge yeniden oluşturulacaktır." Fikir budur. Ancak, kullanıcının nesnenin ne olduğunu gerçekten bilmeden nesneler listesi penceresinde bulunan nesneleri silmesi önerilmez, çünkü herhangi bir nesneyi silerseniz (TradeLine hariç), Uzman Danışman hiçbir gösterge olmadığını fark etmeyebilir ve üzerinde bulunan düğmeler dışında başka bir erişim yolu olmadığı için ona erişim araçlarından yoksun bırakabilir.

Hemen ardından gelen ve sınıf içerisindeki tüm mesaj akışını sürdürmekten sorumlu olan fonksiyon olmasaydı, yukarıdaki senaryo gerçek bir kabus olurdu. Ancak yine de tek giriş noktası bu değildir. DispatchMessage fonksiyonundan bahsediyorum, ona bir göz atalım.

void DispatchMessage(int id, long lparam, double dparam, string sparam)
{
        ulong   ticket;
        double  price;
        bool    bKeyBuy,
                bKeySell,
                bEClick;
        datetime        dt;
        uint            mKeys;
        char            cRet;
        eIndicatorTrade it;
        eEventType      ev;
                                
        static bool bMounting = false, bIsDT = false;
        static double valueTp = 0, valueSl = 0, memLocal = 0;
                                
        switch (id)
        {
                case CHARTEVENT_MOUSE_MOVE:
                        Mouse.GetPositionDP(dt, price);
                        mKeys   = Mouse.GetButtonStatus();
                        bEClick  = (mKeys & 0x01) == 0x01;    //Left mouse click
                        bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT pressed
                        bKeySell = (mKeys & 0x08) == 0x08;    //CTRL pressed
                        if (bKeyBuy != bKeySell)
                        {
                                if (!bMounting)
                                {
                                        Mouse.Hide();
                                        bIsDT = Chart.GetBaseFinance(m_InfoSelection.vol, valueTp, valueSl);
                                        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
                                        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
                                        m_InfoSelection.it = IT_PENDING;
                                        m_InfoSelection.pr = price;
                                }
                                m_InfoSelection.tp = m_InfoSelection.pr + (bKeyBuy ? valueTp : (-valueTp));
                                m_InfoSelection.sl = m_InfoSelection.pr + (bKeyBuy ? (-valueSl) : valueSl);
                                m_InfoSelection.bIsBuy = bKeyBuy;
                                if (!bMounting)
                                {
                                        IndicatorAdd(m_InfoSelection.ticket = def_IndicatorTicket0);
                                        m_TradeLine.SpotLight(macroMountName(def_IndicatorTicket0, IT_PENDING, EV_LINE, false));
                                        m_InfoSelection.bIsMovingSelect = bMounting = true;
                                }
                                MoveSelection(price);
                                if ((bEClick) && (memLocal == 0))
                                {
                                        RemoveIndicator(def_IndicatorTicket0);
                                        CreateOrderPendent(m_InfoSelection.vol, bKeyBuy, memLocal = price,  price + m_InfoSelection.tp - m_InfoSelection.pr, price + m_InfoSelection.sl - m_InfoSelection.pr, bIsDT);
                                }
                        }else if (bMounting)
                        {
                                RemoveIndicator(def_IndicatorTicket0);
                                Mouse.Show();
                                memLocal = 0;
                                bMounting = false;
                        }else if ((!bMounting) && (bKeyBuy == bKeySell))
                        {
                                if (bEClick) SetPriceSelection(price); else MoveSelection(price);
                        }
                        break;
                case CHARTEVENT_OBJECT_DELETE:
                        if (GetIndicatorInfos(sparam, ticket, it, ev))
                        {
                                if (GetInfosTradeServer(ticket) == 0) break;
                                CreateIndicatorTrade(ticket, it);
                                if ((it == IT_PENDING) || (it == IT_RESULT))
                                        PositionAxlePrice(ticket, it, m_InfoSelection.pr);
                                ChartRedraw();
				m_TradeLine.SpotLight();
                                m_InfoSelection.bIsMovingSelect = false;
                                UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
                        }
                        break;
                case CHARTEVENT_CHART_CHANGE:
                        ReDrawAllsIndicator();
                        break;
                case CHARTEVENT_OBJECT_CLICK:
                        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
                        {
                                case EV_CLOSE:
                                        if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)
                                        {
                                                case IT_PENDING:
                                                case IT_RESULT:
                                                        if (cRet < 0) RemoveOrderPendent(ticket); else ClosePosition(ticket);
                                                        break;
                                                case IT_TAKE:
                                                case IT_STOP:
							m_InfoSelection.ticket = ticket;
							m_InfoSelection.it = it;
                                                        m_InfoSelection.bIsMovingSelect = true;
                                                        SetPriceSelection(0);
                                                        break;
                                        }
                                        break;
                                case EV_MOVE:
                                        if (m_InfoSelection.bIsMovingSelect)
                                        {
                                                m_TradeLine.SpotLight();
                                                m_InfoSelection.bIsMovingSelect = false;
                                        }else
                                        {
                                                m_InfoSelection.ticket = ticket;
                                                m_InfoSelection.it = it;
                                                if (m_InfoSelection.bIsMovingSelect = (GetInfosTradeServer(ticket) != 0))
                                                m_TradeLine.SpotLight(macroMountName(ticket, it, EV_LINE, false));
                                        }
                                        break;
                        }
                        break;
        }
}

Bu fonksiyon o kadar çok değişiklik geçirdi ki, içerisinde neler olup bittiğini açıklamak için onu küçük parçalara ayırmam gerekecek. Zaten programlama deneyiminiz varsa, ne işe yaradığını anlamak sizin için zor olmayacaktır. Ancak, sadece bir meraklı veya acemi bir MQL5 programcısıysanız, bu fonksiyonu anlamak biraz zor olabilir, bu yüzden bir sonraki bölümde sakince açıklayacağım.


2.0.3. DispatchMessage fonksiyonunu parçalara ayırma

Bu bölüm DispatchMessage fonksiyonunda neler olduğunu açıklamaktadır. Sadece koda bakarak nasıl çalıştığını anlıyorsanız, bu bölüm size yeni bir şey sunmayacaktır.

Yerel değişkenlerden sonra sahip olduğumuz ilk şey statik değişkenlerdir.

static bool bMounting = false, bIsDT = false;
static double valueTp = 0, valueSl = 0, memLocal = 0;

Sınıfta private değişkenler olarak bildirilebilirler, ancak kodun yalnızca bu noktasında kullanılacaklarından, sınıftaki diğer fonksiyonların bu değişkenleri görmesinin bir anlamı yoktur. Statik olarak bildirilmelidirler, çünkü fonksiyon tekrar çağrıldığında değerlerini hatırlamaları gerekir. Eğer 'static' anahtar kelimesini eklemezsek, fonksiyon biter bitmez değerlerini kaybedeceklerdir. Bu yapıldıktan sonra, MetaTrader 5'in Uzman Danışmana belirttiği olayları işlemeye başlayacağız.

İlk olay aşağıda görülebilir:

case CHARTEVENT_MOUSE_MOVE:
        Mouse.GetPositionDP(dt, price);
        mKeys   = Mouse.GetButtonStatus();
        bEClick  = (mKeys & 0x01) == 0x01;    //Left mouse click
        bKeyBuy  = (mKeys & 0x04) == 0x04;    //SHIFT pressed
        bKeySell = (mKeys & 0x08) == 0x08;    //CTRL pressed

Burada fareden ve fare ile ilişkili bazı tuşlardan (klavyeden) veri topluyor ve izole ediyoruz. Bunu yaptıktan sonra, bir testle başlayan uzun bir kod geliyor.

if (bKeyBuy != bKeySell)

SHIFT veya CTRL tuşuna basarsanız, ancak ikisine aynı anda basmazsanız, bu Uzman Danışmanın belirli bir fiyattan emir yerleştirmek istediğinizi anlamasını sağlayacaktır. Eğer öyleyse, ileri kontrol gerçekleştirilir.

if (!bMounting)
{
        Mouse.Hide();
        bIsDT = Chart.GetBaseFinance(m_InfoSelection.vol, valueTp, valueSl);
        valueTp = Terminal.AdjustPrice(valueTp * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
        valueSl = Terminal.AdjustPrice(valueSl * Terminal.GetAdjustToTrade() / m_InfoSelection.vol);
        m_InfoSelection.it = IT_PENDING;
        m_InfoSelection.pr = price;
}

0 göstergesinin henüz ayarlanmamış olması durumunda, bu test geçecektir. Fare gizlenecek, ardından ticaret arayüzündeki değerler yakalanacaktır. Bu değerler daha sonra yatırımcının ticaret arayüzü aracılığıyla belirttiği kaldıraç seviyesine göre puanlara dönüştürülür. Emrin yerleştirileceği ilk değer gösterilecektir. Bu sekans her kullanım döngüsünde yalnızca bir kez gerçekleşmelidir.

Bir sonraki adım, Kârı Al ve Zararı Durdur seviyelerini oluşturmak ve alış mı yoksa satış mı yapacağımızı belirtmektir.

m_InfoSelection.tp = m_InfoSelection.pr + (bKeyBuy ? valueTp : (-valueTp));
m_InfoSelection.sl = m_InfoSelection.pr + (bKeyBuy ? (-valueSl) : valueSl);
m_InfoSelection.bIsBuy = bKeyBuy;

Döngünün dışında oluşturulurlar çünkü fareyi farklı bir fiyat aralığına hareket ettirdiğimizde, Kârı Al ve Zararı Durduru da hareket ettirmemiz gerekecektir. Ancak yukarıdaki bu kod neden yapılandırma testinin içerisinde değil? Bunun nedeni, ekranda göstergeler varken fareyi hareket ettirmeden SHIFT tuşunu değiştirir, bırakır ve CTRL tuşuna basarsanız veya tam tersini yaparsanız, Kârı Al ve Zararı Durdur göstergelerinin değerlerinin değiştirilmesidir. Bundan kaçınmak için parçanın testin dışında kalması gerekir. Ancak bu bizi aşağıda görülen yeni bir yapılandırma testi yapmaya zorlar:

if (!bMounting)
{
        IndicatorAdd(m_InfoSelection.ticket = def_IndicatorTicket0);
        m_TradeLine.SpotLight(macroMountName(def_IndicatorTicket0, IT_PENDING, EV_LINE, false));
        m_InfoSelection.bIsMovingSelect = bMounting = true;
}

Neden iki teste sahibiz? Sadece bir taneye sahip olamaz mıyız? Bu ideal olurdu, ancak yukarıdaki kodda vurgulanan fonksiyon bunu yapmamıza izin vermiyor. Bu gerçeği anlamak için IndicatorAdd’e bakmamız gerekiyor. 0 göstergesini oluşturduktan sonra, onu seçili olarak ayarlıyoruz ve halihazırda çalıştığını ve inşa edildiğini gösteriyoruz. Daha sonra aşağıdaki satırı kullanarak onu hareket ettirebiliriz.

MoveSelection(price);

Bununla birlikte, bir bekleyen emir yerleştirmek için SHIFT veya CTRL tuşuna basmakla aynı kriterler dahilinde bile, son bir adımımız vardır.

if ((bEClick) && (memLocal == 0))
{
        RemoveIndicator(def_IndicatorTicket0);
        CreateOrderPendent(m_InfoSelection.vol, bKeyBuy, memLocal = price,  price + m_InfoSelection.tp - m_InfoSelection.pr, price + m_InfoSelection.sl - m_InfoSelection.pr, bIsDT);
}

Bu, tam olarak hedeflediğimiz noktaya bir bekleyen emir ekleyecektir. İki koşulun karşılanması gerekmektedir. Birincisi farenin sol tuşuna tıklamak, ikincisi ise bunu tek seferde aynı fiyata yapmamış olmamızdır. Yani, aynı fiyata iki veya daha fazla emir yerleştirmek için, bu yeni emri farklı bir çağrı ile yerleştirmeliyiz, çünkü bu aynı çağrıda gerçekleşmeyecektir. 

0 göstergesinin grafikten kaldırılmasıyla eşzamanlı olarak, işlem sunucusuna uygun şekilde doldurulmuş parametrelere sahip bir emir gönderilir.

Şimdi bir sonraki adıma geçelim.

if (bKeyBuy != bKeySell)
{

// ... code described so far ....

}else if (bMounting)
{
        RemoveIndicator(def_IndicatorTicket0);
        Mouse.Show();
        memLocal = 0;
        bMounting = false;
}

0 göstergesi ayarlanmış ancak yalnızca SHIFT veya CTRL tuşuna basıldığı için koşul karşılanmamışsa, vurgulanan kod 0 göstergesini nesne listesinden kaldırmak için çalıştırılır, aynı anda fare sıfırlanır ve statik değişkenler başlangıç durumlarında bırakılır. Başka bir deyişle, sistem temiz hale gelecektir.

Fare olayı işleme içerisindeki bir sonraki ve son adım aşağıda gösterilmektedir:

if (bKeyBuy != bKeySell)
{

// ... previously described code ...

}else if (bMounting)
{

// ... previously described code ...

}else if ((!bMounting) && (bKeyBuy == bKeySell))
{
        if (bEClick) SetPriceSelection(price); else MoveSelection(price);
}


Vurgulanan kod, mesaj işlemedeki son fare adımıdır. Ne 0 göstergesini ne de SHIFT veya CTRL tuşlarını farklı bir durumda ayarlamamışsak, yani aynı anda basılabilir veya bırakılabilirlerse, şu davranışa sahip oluruz: sol tıklarsak fiyat göstergeye gönderilir ve sadece fareyi hareket ettirirsek, fiyat göstergeyi hareket ettirmek için kullanılır. Ancak bu durumda bir soru ortaya çıkıyor: hangi gösterge? Endişelenmeyin, yakında hangi gösterge olduğunu göreceğiz, ancak merak ediyorsanız, 0 göstergesi bu seçimi kullanmaz. Anlamadıysanız, bu bölümün başına dönün ve bu mesaj işlemenin nasıl çalıştığını okuyun.

Bir sonraki mesaj aşağıdadır:

case CHARTEVENT_OBJECT_DELETE:
        if (GetIndicatorInfos(sparam, ticket, it, ev))
        {
                if (GetInfosTradeServer(ticket) == 0) break;
                CreateIndicatorTrade(ticket, it);
                if ((it == IT_PENDING) || (it == IT_RESULT))
                        PositionAxlePrice(ticket, it, m_InfoSelection.pr);
                ChartRedraw();
		m_TradeLine.SpotLight();
                m_InfoSelection.bIsMovingSelect = false;
                UpdateIndicators(ticket, m_InfoSelection.tp, m_InfoSelection.sl, m_InfoSelection.vol, m_InfoSelection.bIsBuy);
        }
        break;

Yukarıda, Uzman Danışmanın göstergelerin yanlış kaldırılmasını önlemek için küçük bir güvenlik sistemine sahip olduğunu söylediğimi hatırlıyor musunuz? Bu sistem, bir nesne silindiğinde MetaTrader 5 tarafından gönderilen olaylarla ilgili mesajları işleme kodunda bulunur.

Bu olduğunda, MetaTrader 5, sparam parametresini kullanarak silinen nesnenin adını bildirir; bu, bir gösterge olup olmadığını ve eğer öyleyse hangisinin olduğunu kontrol etmek için kullanılır. Hangi nesnenin etkilendiği önemli değildir. Bilmek istediğimiz şey hangi göstergenin etkilendiğidir, bundan sonra gösterge ile ilişkili herhangi bir emir veya pozisyon olup olmadığını kontrol edeceğiz ve eğer öyleyse tüm göstergeyi tekrar oluşturacağız. Olağanüstü bir durumda, etkilenen gösterge temel gösterge ise, onu hemen yeniden konumlandırırız ve MetaTrader 5'i göstergenin ne olduğuna bakılmaksızın göstergeyi hemen grafiğe yerleştirmeye zorlarız. Seçim göstergesini kaldırırız ve gösterge eşik verilerinin güncellenmesini talep ederiz. 

İşlenecek bir sonraki olay çok basittir, sadece ekrandaki tüm göstergeleri yeniden boyutlandırmak için bir istekte bulunur, kodu aşağıda gösterilmiştir.

case CHARTEVENT_CHART_CHANGE:
        ReDrawAllsIndicator();
        break;

İşte nesneye tıklama olayı.

case CHARTEVENT_OBJECT_CLICK:
        if (GetIndicatorInfos(sparam, ticket, it, ev)) switch (ev)
        {
//....
        }
        break;

Yukarıda gösterildiği gibi başlar: MetaTrader 5 bize hangi nesnenin tıklandığını söyler, böylece Uzman Danışman ne tür bir olayın işleneceğini kontrol edebilir. Şimdiye kadar CLOSE ve MOVE olmak üzere 2 olayımız vardır. İlk olarak, ekrandaki göstergenin sonunu kapatacak ve tanımlayacak olan CLOSE olayını ele alalım.

case EV_CLOSE:
        if ((cRet = GetInfosTradeServer(ticket)) != 0) switch (it)
        {
                case IT_PENDING:
                case IT_RESULT:
                        if (cRet < 0) RemoveOrderPendent(ticket); else ClosePosition(ticket);
                        break;
                case IT_TAKE:
                case IT_STOP:
			m_InfoSelection.ticket = ticket;
			m_InfoSelection.it = it;
                        m_InfoSelection.bIsMovingSelect = true;
                        SetPriceSelection(0);
                        break;
        }
        break;

Kapatma olayı şunları yapacaktır: sunucuda neyin kapatılması gerektiğini aramak ve kapatılacak bir şey olup olmadığını kontrol etmek için fişi kullanacaktır, çünkü bu zamana kadar sunucu bunu zaten yapmış olabilir, ancak Uzman Danışman henüz bunu bilmiyor olabilir. Kapatılacak bir şeyimiz olduğuna göre, bunu doğru bir şekilde yapalım, böylece gerekli kontrollere ve sınıfa bir göstergeyi grafikten kapatması veya kaldırması için bilgi vermenin doğru yoluna sahip oluruz.

Böylelikle, aşağıda gösterilen bu bölümdeki son adıma gelmiş bulunuyoruz.

case EV_MOVE:
        if (m_InfoSelection.bIsMovingSelect)
        {
                m_TradeLine.SpotLight();
                m_InfoSelection.bIsMovingSelect = false;
        }else
        {
                m_InfoSelection.ticket = ticket;
                m_InfoSelection.it = it;
                if (m_InfoSelection.bIsMovingSelect = (GetInfosTradeServer(ticket) != 0))
                m_TradeLine.SpotLight(macroMountName(ticket, it, EV_LINE, false));
        }
        break;

MOVE olayı hareket ettirilecek göstergeyi seçer. Yani, yalnızca seçer, ancak hareketin kendisi bir fare hareketi olayı sırasında gerçekleştirilir. Hatırlayın, bölümün başında, 0 göstergesi ile ilgilenmeyeceğimiz ve buna rağmen bir şeyin hareket edeceği bir durum olduğunu söylemiştim. Bu şey, bu noktada, hareket olayında belirtilir. Burada herhangi bir şeyin hareket ettirilmek üzere seçilip seçilmediğini kontrol ederiz. Eğer öyleyse, seçilen gösterge seçili olmaktan çıkacak ve fare hareketi olaylarını almayacak ve yeni gösterge seçilecektir. Bu durumda, fare verilerini alacak yeni göstergenin verileri bir yapıda saklanacak ve bu gösterge seçildiğini ifade edecek bir değişiklik alacaktır. Bu değişim çizgi kalınlığında görülecektir.


2.0.4. Yeni fare nesne sınıfı

Yukarıda ele aldığımız iyileştirmelere ek olarak, bahsedilmeyi hak eden başka iyileştirmelerimiz daha vardır.

Çoğu yatırımcı, bir Uzman Danışmanda uygulanan fare tabanlı bir gösterge sistemine ihtiyaç duymazken, diğer birçok kişi de sistemin mükemmel bir şekilde çalışmasına ihtiyaç duyabilir ve bunu isteyebilir. Ancak yatırımcı, fare göstergesini oluşturan bazı nesneleri yanlışlıkla silebilir ve bu da başarısız olmasına neden olur. Neyse ki, olay sistemini kullanarak bundan kaçınabiliriz. Bir nesne silme olayı tespit edilip Uzman Danışmana gönderildikten sonra, nesnenin ait olduğu sınıf nesneyi yeniden oluşturabilir ve bu da sisteme kararlılık kazandırır. Ancak listeyi mümkün olduğunca küçük tutmak, ihtiyaç duyuldukça oluşturmak ve artık ihtiyaç duyulmadığında silmek faydalıdır. Şimdiye kadar yaptığımız buydu, ancak fare sınıfı eksikti.

Sabit adlar oluşturma sistemini değiştirmek için bazı tanımlar oluşturarak başlayalım.

#define def_MousePrefixName "MOUSE "
#define def_NameObjectLineH def_MousePrefixName + "H"
#define def_NameObjectLineV def_MousePrefixName + "TMPV"
#define def_NameObjectLineT def_MousePrefixName + "TMPT"
#define def_NameObjectBitMp def_MousePrefixName + "TMPB"
#define def_NameObjectText  def_MousePrefixName + "TMPI"

Sonrasında, yeni başlatma fonksiyonu aşağıdaki gibi görünmektedir:

void Init(color c1, color c2, color c3)
{
        m_Infos.cor01 = c1;
        m_Infos.cor02 = c2;
        m_Infos.cor03 = c3;
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_MOUSE_MOVE, true);
        ChartSetInteger(Terminal.Get_ID(), CHART_CROSSHAIR_TOOL, false);
        Show();
}

Önceki versiyona göre çok daha basit olduğuna lütfen dikkat edin. Burada, fare sistemini gösterecek bir çağrıya sahibiz. Çağrı, önceki kodda vurgulanan kısımda gerçekleştirilir. Aslında fiyat ekseninde bir gösterge sistemi oluşturacak kodu çağıracaktır.

inline void Show(void)
{
        if (ObjectGetDouble(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_PRICE) == 0)
        {
                ObjectCreate(Terminal.Get_ID(), def_NameObjectLineH, OBJ_HLINE, 0, 0, 0);
                ObjectSetString(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_TOOLTIP, "\n");
                ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_BACK, false);
        }
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_COLOR, m_Infos.cor01);
}

Bu kod çok ilginçtir: fare işaretçisi nesnesinin fiyatta var olup olmadığını kontrol eder. Kontrol başarılı olursa, grafikte bir çizgi veya fareyle ilgili bir şey olduğu anlamına gelir, bu nedenle tek yapacağımız şey yatay çizginin rengini ayarlamaktır. Bu kontrolü neden yapıyoruz? Bunu anlamak için, fareye bağlı nesneleri gizlemekten veya daha doğrusu kaldırmaktan sorumlu fonksiyona bakalım:

inline void Hide(void)
{
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, false);
        ObjectsDeleteAll(Terminal.Get_ID(), def_MousePrefixName + "T");
        ChartSetInteger(Terminal.Get_ID(), CHART_EVENT_OBJECT_DELETE, true);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineH, OBJPROP_COLOR, clrNONE);
}

Bu ilginç bir çalışma tarzıdır. Fareye bağlı ve belirtilen ada sahip tüm nesneler MetaTrader 5 grafiğinden silinecek ve böylece nesnelerin listesi her zaman küçük olacaktır. Ancak yatay çizgi silinmeyecek, sadece rengi değişecektir. Bu nedenle, fareyi gösteren fonksiyon nesneyi oluşturmadan önce bir kontrol gerçekleştirir, çünkü aslında nesne listesinden çıkarılmamıştır, sadece gizlenmiştir. Ancak diğer tüm nesneler nesne listesinden silinir. Ama o zaman bu diğer nesneleri çalışmalar sırasında nasıl kullanacağız? Çalışmalar sadece bazı detayları öğrenmek istediğimiz kısa anlar olduğundan, nesneleri sadece 1-2 kez kullanmak için listede tutmanın bir anlamı yoktur. Onları oluşturmak, çalışmayı yapmak ve daha sonra listeden çıkarmak daha iyidir, böylece daha güvenilir bir sistem elde ederiz.

Bu aptalca görünebilir, ancak gösterdiğimiz emir sistemi nesnelerin kullanımına dayanıyor ve listede ne kadar çok nesne olursa, MetaTrader 5'in belirli bir nesneye erişmek istediğimizde listede arama yapmak için o kadar çok iş yapması gerekecektir. Bu nedenle, grafikte veya nesneler listesinde fazladan nesne bırakmayacağız, sistemi olabildiğince hafif tutalım.

Şimdi, aşağıdaki gibi başlayan DispatchMessage fonksiyonuna dikkat edin:

void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
{
        int     w = 0;
        uint    key;
        static int b1 = 0;
        static double memPrice = 0;

Bundan hemen sonra, ilk olayı işlemeye başlayacak kodumuz vardır.

switch (id)
{
        case CHARTEVENT_MOUSE_MOVE:
                Position.X = (int)lparam;
                Position.Y = (int)dparam;
                ChartXYToTimePrice(Terminal.Get_ID(), Position.X, Position.Y, w, Position.dt, Position.price);
                ObjectMove(Terminal.Get_ID(), def_NameObjectLineH, 0, 0, Position.price = Terminal.AdjustPrice(Position.price));
                if (b1 > 0) ObjectMove(Terminal.Get_ID(), def_NameObjectLineV, 0, Position.dt, 0);
                key = (uint) sparam;
                if ((key & 0x10) == 0x10)    //Middle button....
                {
                        CreateObjectsIntern();
                        b1 = 1;
                }

Farenin orta düğmesine tıkladığımızda bir çağrı oluştururuz. Ancak şu anda durum böyle değildir. Daha sonra bu fonksiyonun ne yaptığını göreceğiz. MetaTrader 5 tarafından desteklenen nesneler listesinde olmadığı için var olmayan bir nesneyi taşımaya çalıştığımızı unutmayın. Bu çağrı yalnızca farenin orta düğmesine tıklandığında gerçekleşir. b1 değişkenine dikkat edin; bu değişken, yatırımcının çalışmanın oluşturulmasında yer alan setin hangi noktasında olduğunu kontrol eder.

Kullanıcı farenin sol düğmesine tıkladığında ve ilk adım tamamlandığında, aşağıdaki kod çalışacaktır:

if (((key & 0x01) == 0x01) && (b1 == 1))
{
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, false);
        ObjectMove(Terminal.Get_ID(), def_NameObjectLineT, 0, Position.dt, memPrice = Position.price);
        b1 = 2;
}

Trend çizgisini konumlandıracak ve b1 değişkeninin değerinin değiştirildiği bir sonraki adımı çağıracaktır. Bu noktada bir sonraki parçaya geçebiliriz.

if (((key & 0x01) == 0x01) && (b1 == 2))
{
        ObjectMove(Terminal.Get_ID(), def_NameObjectLineT, 1, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectLineT, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectText, OBJPROP_COLOR, (memPrice > Position.price ? m_Infos.cor03 : m_Infos.cor02));
        ObjectMove(Terminal.Get_ID(), def_NameObjectBitMp, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectBitMp, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
        ObjectSetString(Terminal.Get_ID(), def_NameObjectText, OBJPROP_TEXT, StringFormat("%.2f ", Position.price - memPrice));
        ObjectMove(Terminal.Get_ID(), def_NameObjectText, 0, Position.dt, Position.price);
        ObjectSetInteger(Terminal.Get_ID(), def_NameObjectText, OBJPROP_ANCHOR, (memPrice > Position.price ? ANCHOR_RIGHT_UPPER : ANCHOR_RIGHT_LOWER));
}

Yukarıdaki bu parça aslında çalışmayı ekranda gösterecek olan parçadır. Bu parçadaki tüm bu nesneler çalışma bittiğinde var olmayacaktır: bu fonksiyon içerisinde oluşturulacak ve yok edileceklerdir. Bunu yapmak çok verimli görünmese de, çalışma aşamasında yürütme süresinde herhangi bir azalma veya artış fark etmedim. Aslında, emir sisteminde hafif bir iyileşme fark ettim, çok ince bir şey, pratik olarak karşılaştırmalı değerlendirmenin hata payı dahilinde. Dolayısıyla, bu değişikliklerin yürütme açısından gerçekten iyileştirmeler getirdiğini söyleyemem.

Ancak, çalışmanın farenin sol düğmesi basılıyken gerçekleştirileceğini unutmayın; bıraktığımız anda bir sonraki parça yürütülecektir.

if (((key & 0x01) != 0x01) && (b1 == 2))
{
        b1 = 0;
        ChartSetInteger(Terminal.Get_ID(), CHART_MOUSE_SCROLL, true);
        Hide();
        Show();
}
Position.ButtonsStatus = (b1 == 0 ? key : 0);

Burada, çalışmayı oluşturmak için kullanılan tüm nesneleri nesneler listesinden kaldırıyoruz. Fare çizgisini ekranda tekrar gösterelim. Vurgulanan kod, fare düğmelerini yakaladığımızda Uzman Danışman içerisindeki herhangi bir fonksiyonun veya alt kod parçasının yanlış okumalar almasını önlediği için harika bir fikirdir. Eğer herhangi bir çalışma yapılıyorsa, Uzman Danışman düğme durumlarını göz ardı etmelidir. Bu amaçla, vurgulanan satırları kullanıyoruz. Mükemmel bir çözüm değil ama hiç yoktan iyidir.

Çalışmayı yürütmek için nesneleri oluşturan kodu dikkate almadık. Bu oldukça basit bir fonksiyon olduğu için makalede buna odaklanmayacağım.


Sonuç

Değişiklikler küçük gibi görünse de, hepsi sistemin kendisinde büyük bir fark yaratmaktadır. Unutulmaması gereken bir şey vardır: komut sistemimiz ekrandaki grafik nesnelerine dayanmaktadır, bu nedenle Uzman Danışman ne kadar çok nesne işlerse, belirli bir nesneyi talep ettiğimizde performansı o kadar düşük olacaktır. Durumu daha da karmaşık hale getirmek için, sistem gerçek zamanlı olarak çalışmaktadır, yani Uzman Danışmanımızın sistemi ne kadar hızlı olursa, performansı da o kadar iyi olacaktır. Dolayısıyla, Uzman Danışman ne kadar az şey yapmak zorunda kalırsa o kadar iyidir. İdeal olarak, yalnızca emir sistemiyle çalışabilmelidir; diğer her şeyi başka bir seviyeye taşımalıyız ve MetaTrader 5 bununla ilgilenmelidir. Bunu elbette kademeli olarak yapacağız, çünkü birçok küçük değişiklik yapmamız gerekecektir, ancak çok karmaşık bir şey olmayacaktır. Bu, yalnızca Uzman Danışmanın güvenilirliğini artırmaya adanmış önümüzdeki birkaç makalede yapılacaktır.

Kesin olarak söyleyebileceğim bir şey var: gelecekte Uzman Danışman sadece emir sisteminden sorumlu olacak. Bir sonraki makalede, Uzman Danışmana çok ilginç bir son görünüm vereceğiz: Uzman Danışmanın çalışması sırasında listede bulunan nesnelerin sayısını daha da azaltacağız, çünkü emir sistemi büyük bir nesne oluşturucudur ve bu sistemi MetaTrader 5 üzerinde yarattığı yükü en aza indirecek şekilde nasıl değiştireceğimizi göreceğiz.

Bu nedenle, kodun kendisi hala değişime tabi olacağından, bu makaleye herhangi bir değişiklik eklemiyorum. Ama endişelenmeyin, bir sonraki makaleyi beklemeye değer. Bu değişiklikler Uzman Danışmanımızın genel performansını önemli ölçüde artıracaktır. Bu serinin bir sonraki makalesinde görüşmek üzere.


MetaQuotes Ltd tarafından Portekizceden çevrilmiştir.
Orijinal makale: https://www.mql5.com/pt/articles/10593

Sıfırdan bir ticaret Uzman Danışmanı geliştirme (Bölüm 25): Sistemin sağlamlığını artırma (II) Sıfırdan bir ticaret Uzman Danışmanı geliştirme (Bölüm 25): Sistemin sağlamlığını artırma (II)
Bu makalede, Uzman Danışmanın performansına yönelik son adımı atacağız. Bu yüzden uzun bir okumaya hazırlıklı olun. Uzman Danışmanımızı sağlam hale getirmek için, öncelikle ticaret sisteminin bir parçası olmayan her şeyi koddan kaldıracağız.
Alligator göstergesine dayalı bir ticaret sistemi nasıl geliştirilir? Alligator göstergesine dayalı bir ticaret sistemi nasıl geliştirilir?
Bu makalede, yine bir başka popüler teknik göstergeye odaklanacağız ve ona dayalı bir ticaret sisteminin nasıl geliştirileceğini öğreneceğiz. Alligator göstergesini tanıyacağız ve ona dayalı bir ticaret sistemi tasarlayacağız.
MQL5'te ONNX modellerinin nasıl bir araya getirileceğine dair bir örnek MQL5'te ONNX modellerinin nasıl bir araya getirileceğine dair bir örnek
Açık sinir ağı santrali (Open Neural Network eXchange, ONNX), sinir ağlarını temsil etmek için oluşturulmuş açık bir formattır. Bu makalede, bir Uzman Danışmanda aynı anda iki ONNX modelinin nasıl kullanılacağını göstereceğiz.
Accelerator Oscillator göstergesine dayalı bir ticaret sistemi nasıl geliştirilir? Accelerator Oscillator göstergesine dayalı bir ticaret sistemi nasıl geliştirilir?
En popüler teknik göstergelere dayalı ticaret sistemleri geliştirdiğimiz serimizin yeni makalesindeyiz. Bu makalede, Accelerator Oscillator göstergesini inceleyeceğiz ve onu kullanarak bir ticaret sistemini nasıl tasarlayacağımızı öğreneceğiz.