
ORDER_MAGIC'in Tek Enstrüman Üzerinde Farklı Expert Advisor'larla Alım Satım Yapmak İçin Kullanımı
Giriş
MQL5'te, talimatı tanımlamak üzere bu bilgileri kullanmak için, bekleyen her talimata sihirli bir sayı atama olanağımız bulunmaktadır. Bu, farklı Expert Advisor'lar arasındaki büyük etkileşim imkanlarını ve daha da karmaşık sistemlerin geliştirilmesini sağlar. Bu makalede, Sihirli sayının değeri bilinmeyen fırsatları hakkında kamuoyunu bilgilendirmek istiyorum.
Ancak bu makalenin konusunun özüne geçmeden önce, Sihirli sayıyı neyin oluşturduğunu daha iyi anlamamız gerekiyor. Hangi Expert Advisor tarafından ayarlandığını belirleyen bir sayıda sihirli ne olabilir? "Mucizeler", geliştiricilerin, Sihirli sayı yoluyla bildirilen ulong, türüne yerleştirdiği fırsatlarla başlar.
ulong türü en uzundur
long tamsayı türünü ayrıntılı olarak incelersek, bu türün maksimum değerinin olağanüstü olduğunu görürüz:
Tür | Bayt cinsinden boyut | Minimum değer | Maksimum değer | C dilinde benzer + + |
long | 8 | -9 223 372 036 854 775 808 | 9 223 372 036 854 775 807 | __int64 |
ulong | 8 | 0 | 18 446 744 073 709 551 615 | unsigned __int64 |
Tablo 1. long ve ulong veri türlerinin özellikleri
fakat ulong türü, pozitif ve negatif mantisi birleştirerek ona üstün geldi.
Belirtilen uzunluk çok büyük, ancak daha önce nasıl kullanılıyordu?
mql 4'te çalışma deneyimim sırasında, çoğu geliştirici tarafından Sihirli sayıyı kodlamanın anlamsızlığını sıklıkla fark ettim. Sihirli sayı mantıklı bir şekilde kullanıldı fakat kodlaması çok saçma görünüyordu. 12345 numaralı Sihirli sayının kendine özgülüğü hakkında söylenebilecek şey, bu Sihrin, gelişmekte olan birliğin neredeyse yarısı tarafından kullanılıyor oluşu. Diğer yarısı Sihir numarası 55555, 33333 ve 77777'yi kullanıyor ve bu hemen hemen tüm kümedir. Okuyucunun dikkatini, bilgisayarında 1.000'den fazla Expert Advisor'a sahip olmasının olası olmadığı gerçeğine çekmek istiyorum; bu nedenle 1000 sayısı, tüm Expert Advisor'larınızın bireysel adını kodlamak için yeterli olacaktır.
1000 - yalnızca 3 tam kategoridir; öyleyse ulong türünde bulunan kalan 15 tam kategoriyle ne yapmalıyız? Yanıt basit: Onları kodlayın.
Wikipidia, kod sözcüğünü şu şekilde tanımlıyor:
Kod - kural (algoritma), sembollerinin harfiyen belirli bir kombinasyonunun her bir mesajının karşılaştırması (karakterler) (veya sinyaller).
Bu nedenle, kuralları biz koyacağız. Sihirli sayının kodunda yalnızca Expert Advisor'ın kimliğini değil, aynı zamanda üzerinde çalıştığı enstrümanı da belirtmeyi öneriyorum. Expert Advisor'ın örneğin EURUSD, üzerinde çalışıyor olması, Expert Advisor'ın yalnızca o enstrümanda bir sıra göstereceği anlamına gelmez. Ayrıca Expert Advisor'ların etkileşim kodunu örneğin "sizinki / haricen gelen" şeklinde yazmanın faydalı olacağını düşünüyorum; böylece Expert Advisor, pozisyonları kontrol ederken mevcut sıranın yakın bir Expert Advisor tarafından oluşturulduğunu anlayabilir. Bunun çok karmaşık bir sistem oluşturmak için yeterli olacağını düşünüyorum.
O halde elimizdekileri özetleyelim: Sisteme hangi fırsatları sunuyoruz?
- İki veya daha fazla Expert Advisor'ın tek bir enstrüman üzerinde çalışması ve karışmama olasılığı.
- İki veya daha fazla Expert Advisor'ın farklı enstrümanlar üzerinde çalışması ve birbirini tamamlama olasılığı.
- Expert Advisor ile birlikte çalışarak, sırayı enstrümana göre belirleme yeteneği.
Böylece, görev belirlendi; şimdi uygulamaya başlayalım.
Basit Expert Advisor
Basit Expert Advisor kodunun taslağını oluşturun- Örneğin, pozisyonu Hareket yönünde tutun. Sihirli sayıyı ayrıştırmaya karar veren okuyucuların Yeni Başlayanlar için MQL5'te Expert Advisor Yazmak için Adım Adım Kılavuz makalesini zaten okuduğunu düşünüyorum, şayet okumadıysanız okumanızı şiddetle tavsiye ederim; zira Expert Advisor'ın oluşturulması konusunda ayrıntılara girmeyeceğim. Temel olarak, Expert Advisor pozisyonu bir kez açacak ve diğer tüm zamanlar için çevirecektir. Bu nedenle, pozisyonu açmak için, yani alım satım talebinde (alım satım talimatı) bulunmak için işleve ihtiyacımız olacak.
Alım satım talebi yapısının alanlarını doldurmak için parametreleri bizim için hesaplayacak bir yardımcı sınıf oluşturun.
//+------------------------------------------------------------------+ //| The class provides auxiliary trading calculations | //+------------------------------------------------------------------+ class CProvision { protected: MqlTradeRequest trades; // pointer to the request structure of OrderSend public: int TYPE(const double &v[]); // determines the type, in respect to the readings of the moving double pricetype(int type); // calculates the level of the opening, in respect to the type double SLtype(int type); // calculates the level of the stop-loss in respect to the type double TPtype(int type); // calculates the level of the take-profit, in respect to the type long spread(); // returns the spread of the current instrument int SendOrder(ENUM_ORDER_TYPE type,double volume); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CProvision::SendOrder(ENUM_ORDER_TYPE type,double volume) { trades.action =TRADE_ACTION_DEAL; // Type of the implemented actions trades.magic =magic; // Stamp of the Expert Advisor (identifier of the magic number) trades.symbol =_Symbol; // Name of the trading instrument trades.volume =volume; // Request the volume of the trade in lots trades.price =pricetype((int)type); // Price trades.sl =SLtype((int)type); // Level of Stop Loss order trades.tp =TPtype((int)type); // Level of Take Profit order trades.deviation=(int)spread(); // Maximum acceptable deviation from the requested price trades.type=type; // Order type trades.type_filling=ORDER_FILLING_FOK; if(OrderSend(trades,res)){return(res.retcode);} return(-1); } //+------------------------------------------------------------------+ //| Determines the type, in respect to the reading of the moving | //+------------------------------------------------------------------+ int CProvision::TYPE(const double &v[]) { double t=v[0]-v[1]; if(t==0.0)t=1.0; return((int)(0.5*t/fabs(t)+0.5)); } //+------------------------------------------------------------------+ //| Calculates the level of opening in respect to the type | //+------------------------------------------------------------------+ double CProvision::pricetype(int type) { if(SymbolInfoTick(_Symbol,tick)) { if(type==0)return(tick.ask); if(type==1)return(tick.bid); } return(-1); } //+------------------------------------------------------------------+ //| Calculates the level of stop-loss in respect to the type | //+------------------------------------------------------------------+ double CProvision::SLtype(int type) { if(SymbolInfoTick(_Symbol,tick)) { if(type==0)return(tick.bid-SL*SymbolInfoDouble(Symbol(),SYMBOL_POINT)); if(type==1)return(tick.ask+SL*SymbolInfoDouble(Symbol(),SYMBOL_POINT)); } return(0); } //+------------------------------------------------------------------+ //| Calculates the level of timeframe in respect to the type | //+------------------------------------------------------------------+ double CProvision::TPtype(int type) { if(SymbolInfoTick(_Symbol,tick)) { if(type==0)return(tick.bid+TP*SymbolInfoDouble(Symbol(),SYMBOL_POINT)); if(type==1)return(tick.ask-TP*SymbolInfoDouble(Symbol(),SYMBOL_POINT)); } return(0); } //+------------------------------------------------------------------+ //| Returns the spread | //+------------------------------------------------------------------+ long CProvision::spread() { return(SymbolInfoInteger(_Symbol,SYMBOL_SPREAD)); }
Böyle bir sınıfa sahip olarak, basit bir Expert Advisor için sorunsuz bir kod yazabiliriz:
//+------------------------------------------------------------------+ //| Code of the Expert Advisor | //+------------------------------------------------------------------+ //--- Input parameters input ulong magic =1; // magic input int SL =300; // Stop Loss input int TP =1000; // Take Profit input int MA_Period =25; // MA period input double lot =0.1; // Volume of position input int MA_shift =0; // Shift of indicator input ENUM_MA_METHOD MA_smooth =MODE_SMA; // Smoothing type input ENUM_APPLIED_PRICE price =PRICE_OPEN; // Price type //--- We will store the indicator's handle int MA_handle, // Handle of the indicator type_MA, // Type that specify the direction of MA rezult; // The variable takes the value of the result of the OrderSend operation double v[2]; // Buffer for receiving values of MA MqlTradeResult res; // Pointer to the structure of responding by OrderSend MqlTick tick; // Pointer to the structure of last market information CProvision prov; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Create the indicator's handle MA_handle=iMA(Symbol(),0,MA_Period,MA_shift,MA_smooth,price); return(0); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(CopyBuffer(MA_handle,0,0,2,v)<=0) {Print("#",magic,"Error of copying");return;} type_MA=prov.TYPE(v); // Determine type depending on MA indication if(PositionSelect(_Symbol))// If there is an open position { if(PositionGetInteger(POSITION_TYPE)!=type_MA)// Check if its time to close { Print("#",magic,"Position by magic number has volume ",PositionGetDouble(POSITION_VOLUME), " reverse position of type ",PositionGetInteger(POSITION_TYPE)," by ",type_MA); rezult=prov.SendOrder((ENUM_ORDER_TYPE)type_MA,PositionGetDouble(POSITION_VOLUME)+lot); // reverse the position if(rezult!=-1)Print("#",magic," Code of the operation result ",rezult," volume ",res.volume); else{Print("#",magic,"Error",GetLastError()); return;} } } else // If there is no open position then open { Print("#",magic,"Position by magic number has volume ",PositionGetDouble(POSITION_VOLUME), " open position of type ",type_MA); rezult=prov.SendOrder((ENUM_ORDER_TYPE)type_MA,lot); // open position if(rezult!=-1)Print("#",magic," Code of operation result ",rezult," volume ",res.volume); else{Print("#",magic,"Error",GetLastError()); return;} } }
Çalıştırın ve Expert Advisor'ın karlılık açısından farklılık göstermediğinden, tam olarak ondan ihtiyacımız olan şey olan belirtilen mantığa göre işlem yaptığından emin olun.
Şekil 1. Bir Expert Advisor'ın tek bir enstrüman üzerinde çalışması
Şimdi bu EA'yı çalıştırmayı deneyeceğiz fakat bunu bir enstrümanın farklı zaman dilimlerinde yapacağız (denemek için EURUSD: o olan rastgele bir enstrüman seçtik)
Şekil 2. Aynı enstrüman üzerinde farklı zaman dilimlerinde iki Expert Advisor'ın çakışması
Her iki Expert Advisor da tek bir enstrüman üzerinde çalıştığı ve kod, pozisyonların paylaşımını belirtmediği için her iki Expert Advisor da göstergelerinin okumalarına bağlı olarak alım satım pozisyonunu düzeltmeye çalışıyor ve bunun sonucunda, bir çakışma ortaya çıkıyor. M1 üzerinde çalışan Expert Advisor, rakibi onu durdurmaya çalışırken, Hücredeki pozisyonu değiştirmeye çalışıyor. Ayrı bir pozisyon hesaplamasına ihtiyacımız olduğu açık; şimdi yapacağımız şey de tam olarak bu.
Pozisyon mu, sanal pozisyon mu?
MetaTrader 5'te geliştiriciler talimatlardan pozisyonları dikkate almaya geçtiği için kayıt pozisyonlarıyla ilişkili işlevleri daha ayrıntılı olarak düşünmek mantıklıdır.
// Returns the number of open positions. int PositionsTotal(); // Returns the symbol of the open position by the number in the list of positions. string PositionGetSymbol(int index); // Selects the open position for further working with it. bool PositionSelect(string symbol, uint timeout=0); // Function returns the requested property of the open position. double PositionGetDouble(ENUM_POSITION_PROPERTY property_id); // The function returns the requested property of the open position. long PositionGetInteger(ENUM_POSITION_PROPERTY property_id); // The function returns the requested property of the open position. string PositionGetString(ENUM_POSITION_PROPERTY property_id);
Tablo 2-4'te verilen PositionGetDouble, PositionGetInteger ve PositionGetString uyumlu pozisyon özelliklerinin talebini alma işlevlerine ilişkin numaralandırma tanımlayıcıları.
Tanımlayıcı | Açıklama | Tür |
POSITION_VOLUME | Pozisyon hacmi | çift |
POSITION_PRICE_OPEN | Pozisyon fiyatı | çift |
POSITION_SL | Açık pozisyon için Zararı Durdur Düzeyi | çift |
POSITION_TP | Açık pozisyon için Kar Al Düzeyi | çift |
POSITION_PRICE_CURRENT | Sembole göre mevcut fiyat | çift |
POSITION_COMMISSION | Komisyon | çift |
POSITION_SWAP | Birikmiş swap | çift |
POSITION_PROFIT | Mevcut kar | çift |
Tablo 2. Numaralandırma değeri ENUM_POSITION_PROPERTY_DOUBLE
Tanımlayıcı | Açıklama | Tür |
POSITION_TIME | Pozisyonların açılış zamanı | datetime |
POSITION_TYPE | Pozisyon türü | |
POSITION_MAGIC | Pozisyon için sihirli sayı (bkz. ORDER_MAGIC ) | long |
POSITION_IDENTIFIER | Pozisyonun tanımı - Bu, yeniden açılan her pozisyona atanan ve yaşam döngüsü boyunca değişmeyen benzersiz bir sayıdır. Bir pozisyonun cirosu kimliğini değiştirmez. | long |
Tablo 3. Numaralandırma değerleri ENUM_POSITION_PROPERTY_INTEGER
Tanımlayıcı | Açıklama | Tür |
POSITION_SYMBOL | Pozisyonun açıldığı sembol | dize |
POSITION_COMMENT | Pozisyona ilişkin açıklama | dize |
Tablo 4. Numaralandırma değerleri ENUM_POSITION_PROPERTY_STRING
İşlevlerden, "talimatı kim verdi?" prensibine dayalı olarak, dilin pozisyon ayrımını içermediğini açıkça görebiliriz fakat bu tür kayıtların olasılığı mevcuttur; zira ORDER_MAGIC, POSITION_MAGIC ve DEAL_MAGIC aynı tam sayılardır ve kullanıcı tarafından belirtilen sihirli sayıdan alınırlar. POSITION_MAGIC, pozisyonu açan DEAL_MAGIC'ten, DEAL_MAGIC ise verilen talimata ait olan ORDER_MAGIC'ten alınır.
Bir talimat, işlem veya pozisyonun belirlenmesi sorunsuz bir şekilde yapılabilir fakat belirli bir Sihirli sayı ile bir pozisyonun dışarı çıkarılması mümkün değildir. Şimdi bu eksikliği ortadan kaldırmaya çalışacağız . Yerleşik işlevlerin benzerlerini oluşturalım, ancak bunu Sihirli sayıyla tanımlayarak yapalım. Sihirli sayı üzerinde sanal bir pozisyonla çalışmak için bir sınıf bildirin.
OOP ile çalışma fırsatımız olduğu için kendi yapımızı da bildirelim (objektif olarak ek yazma pratiği kazanarak).
//+------------------------------------------------------------------+ //| Structure of the CPositionVirtualMagic class | //+------------------------------------------------------------------+ struct SPositionVirtualMagic { double volume; // volume of virt. position ENUM_POSITION_TYPE type; // type of virt. position }; //+--------------------------------------------------------------------------------+ //| The class calculates the virtual position of an Expert Advisor by magic number | //+--------------------------------------------------------------------------------+ class CPositionVirtualMagic { protected: SPositionVirtualMagic pvm; public: double cVOLUME(){return(pvm.volume);} // Returns the volume of virtual position of an Expert Advisor ENUM_POSITION_TYPE cTYPE(){return(pvm.type);} // Returns the type of virtual position of an Expert Advisor bool PositionVirtualMagic(ulong Magic, string symbol, datetime CurrentTime ); // the method of calculation virt. position returns the presence or absence of virt. position private: void prHistory_Deals(ulong &buf[],int HTD); // Fills the array of tickets }; //+-------------------------------------------------------------------------------------+ //| Method of calculation of virt. position, returns true if there is a virt. position | //+-------------------------------------------------------------------------------------+ bool CPositionVirtualMagic::PositionVirtualMagic(ulong Magic, string symbol, datetime CurrentTime ) { int DIGITS=(int)-log10(SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP)); if(DIGITS<0)DIGITS=0; ulong Dticket=0; int History_Total_Deals=-1; double volume=0,volume_BUY=0,volume_SELL=0; ulong DTicketbuf[]; do { if(HistorySelect(0,TimeCurrent())) { History_Total_Deals=HistoryDealsTotal(); prHistory_Deals(DTicketbuf,History_Total_Deals); } HistorySelect(0,TimeCurrent()); } while(History_Total_Deals!=HistoryDealsTotal()); for(int t=0;t<History_Total_Deals;t++) { Dticket=DTicketbuf[t]; if(HistoryDealSelect(Dticket)) { if(HistoryDealGetInteger(Dticket,DEAL_TIME)>=CurrentTime) { if(HistoryDealGetInteger(Dticket,DEAL_MAGIC)==Magic) { if(HistoryDealGetInteger(Dticket,DEAL_TYPE)==DEAL_TYPE_BUY) { volume_BUY+=HistoryDealGetDouble(Dticket,DEAL_VOLUME); } else { if(HistoryDealGetInteger(Dticket,DEAL_TYPE)==DEAL_TYPE_SELL) { volume_SELL+=HistoryDealGetDouble(Dticket,DEAL_VOLUME); } } } } } else{HistorySelect(0,TimeCurrent());t--;} // if there is a fault, load history data and pass the step again } volume=NormalizeDouble(volume_BUY-volume_SELL,DIGITS); if(volume<0)pvm.type=POSITION_TYPE_SELL; else { if(volume>0)pvm.type=POSITION_TYPE_BUY; } pvm.volume=fabs(volume); if(pvm.volume==0)return(false); else return(true); }
Yukarıdaki metinde (CProvision sınıfının kodunun verildiği kısımda) her şeyin nereden geldiği ve nereye gittiği açıklanmamıştır; zira Expert Advisor'ın geliştirilmesi bu makalenin konusu değildir.
Ancak CPositionVirtualMagic sınıfını ayrıntılı olarak ele alacağız.
Sınıfa yapı verilir:
struct SPositionVirtualMagic
Bu, sınıf içinde böyle bir genel bildirim hesaplama sonuçlarını almak için kullanılan, pvm (yapının değişkeni) sayesinde bu yapı, sınıfın herhangi bir yönteminde her yerde mevcut olacaktır.
Ardından sınıfın iki yöntemini takip edin:
double cVOLUME(){return(pvm.volume);} // Returns the volume of the virtual position of the EA ENUM_POSITION_TYPE cTYPE() {return(pvm.type);} // Returns the type of the virtual position of the EA
Bu yöntemler herkese açık olarak bildirilir ve bu nedenle, programdaki herhangi bir yerde çağrılan sınıf değişkeni aracılığıyla kullanılabilir olacaklardır; bunlar, istenen konumdaki yapı değerlerinin çıktısı için tasarlanmıştır.
Bu aynı zamanda aşağıdaki yöntemin bildirildiği bölümdür:
bool PositionVirtualMagic(ulong Magic,string symbol,datetime CurrentTime);
Bu, sınıfın ana işlevi olup ayrıntılı analizlerine daha fazla odaklanacağız ve bu arada, önce gideceğim ve işlevi özel erişim belirticisi kapsamında tanımlayacağım:
void prHistory_Deals(ulong &buf[],int HTD);
Bu yöntem, temelde bir döngü olan ve çağrılan işlevde tanımlanabilen diziye yapılan işlemlerin bir bilet kaydını üretir fakat ben PositionVirtualMagic() işlevinin boyutunu küçültmek istedim (kodun okunabilirliğini artırmak için); böylece bu döngüyü işlevin sınırlarının ötesine taşıdım ve özel erişim belirticisinin nasıl kullanılacağını gösterdim.
Öyleyse PositionVirtualMagic() işlevine geri dönelim. Bu işlev, en başında, hesaplanan pozisyonun hacminin çift değerini yuvarlamanız gereken tek satırlık bir doğruluk hesaplamasına sahiptir.
int DIGITS=(int)-log10(SymbolInfoDouble(symbol,SYMBOL_VOLUME_STEP)); if(DIGITS<0)DIGITS=0;
Bu, karşılaştırma işlemini sıfırla yapmak için gereklidir; aksi takdirde ondalık noktadan sonraki 8. basamakta bir miktar denge olması, değeri sıfıra eşitlememizi engelleyecek ve bir yürütme hatasına neden olacaktır.
Pozisyon hacmi minimum adıma yuvarlanır. Minimum adım 1'den büyükse yuvarlama integral kısma göre yapılır. Bir sonraki while döngüsüdür fakat doğru ifadenin doğrulanması döngünün başından ziyade sonunda yapıldığı için yeni bir şekilde (mql4'tekinden farklı olarak) kullanılır:
do { if(HistorySelect(0,TimeCurrent())) { History_Total_Deals=HistoryDealsTotal(); prHistory_Deals(DTicketbuf,History_Total_Deals); } HistorySelect(0,TimeCurrent()); } while(History_Total_Deals!=HistoryDealsTotal());
Doğruluk ifadesi döngü içinde hesaplandığı ve başlangıcında bu doğrulama için henüz hazır olmadığı için bu tip bir yaklaşım izlenir.
Döngü Geçmişin yüklenmesini içerir; yerleşik işlevlerin geçmişle çalışmasını sağlamak için bunun gerekli bir koşul olduğu gerçeğine okuyucunun dikkatini çekmek istiyorum.
HistorySelect(0,TimeCurrent())
Değişken isimlerini seçme sistemim konusunda bir açıklamada bulunmam gerektiğini düşünüyorum.
Dikkatli okuyucular, sınıfların adlarının "C" baş harfi ile tanımlandığını, sözdiziminde bunun gerekli olmadığını ve herhangi bir ad verilebileceğini fark etmiş olmalıdır, ancak böyle bir yol okumayı çok daha kolay hale getirir. Adın önünde "C" harfi varsa bunun sınıfın adı olduğunu, "S" harfi varsa bunun bir yapı olduğunu hemen anlarız. Değişken yerleşik bir işlevin değerini alırsa işlevin adının bileşenlerini değiştiririm ve değişken adını alırım; buna şunu örnek verebilirim:
CurrentTime = TimeCurrent();
Bu, basit ve okunaklıdır; değişkenin ne içerdiğini hemen görebiliriz. Özellikle MetaEditor, belirli bir kod parçasını belirli bir konuma sürükleme işlevini içerdiği için.
Kodu daha fazla gözden geçirdiğimizde, geçmişin yüklenmesinden sonra işlev çağrısını takip ettiğini görüyoruz:
History_Total_Deals=HistoryDealsTotal();
İşlem sayısının değişkene depolanması ile. Aynı koşulla, döngüden çıkmak için doğrulamayı uygulayacağız. Bu doğrulamaya ne için ihtiyacımız var? Ve neden yalnızca geçmişi yükleyemiyoruz ve bunun ardından işlemleri geri alamıyoruz?
Sorun, Expert Advisor'ların çalışması sırasında her EA tarafından geçmişin ayrı ayrı talep edileceği ve dolayısıyla Expert Advisor'ların farklı zamanlarda çalışması halinde geçmişin derinliğinin farklı olacağı gerçeğinde yatmaktadır. Bu, bir Expert Advisor döngüye girdiğinde ve dönemine ilişkin geçmişi yüklediğinde, döngünün sonuna gelmeden önce, bu geçmişin başka bir Expert Advisor'ın talebiyle halihazırda yüklendiğini keşfedebilir; bu nedenle özgünlük açısından bir doğrulama yapılması gerekir.
Bu arada, bu, en iyi doğrulama türü olmayabilir, ancak işe yarıyor. O halde devam edelim. Döngüde, işlemlerin bilet değerlerini özel olarak hazırlanmış bir arabelleğe giren sınıf yöntemini çağırıyoruz. prHistory_Deals() işlevini çağırdıktan sonra yine geçmişin yüklemesini üretiyoruz.
prHistory_Deals () işlevinin çalışması sırasında, alım satım işlemleri geçmişinde herhangi bir değişiklik olup olmadığının doğrulanması bu şekilde düzenlenir. Herhangi bir değişiklik olmadıysa, History_Total_Deals değişkeni HistoryDealsTotal() değerine eşit olacak ve tek bir geçiş için döngüden bir çıkış gerçekleşecektir. Değişiklik varsa sistem ikinci bir döngü başlatacak ve bilet geçmişi hatasız bir şekilde yüklenene kadar tekrar etmeye devam edecektir (ve sonuna ";" koymayı unutmayın):
while(History_Total_Deals!=HistoryDealsTotal());
for döngüsünde ayrıca, sanal pozisyonların hesaplanması gerçekleşir.
İşlem bir dizi filtreyi (işlemin zamanı ve işlemin Sihirli sayısı) başarıyla geçtiyse hacmi, işlemin ait olduğu tür olan sanal pozisyonun o kısmını artırır.
Diğer seçenekler mümkün olsa da, sanal pozisyon hesaplamalarını yalnızca Expert Advisor'ın başlatılmasından itibaren kaydettiğimi belirtmek isterim.
Burada, pozisyonun tam olarak nasıl hesaplandığına dikkat edilmelidir. Hepimizin çok eski zamanlardan beri ve günümüzde dahi kullandığı gider ve karımızı içeren kayıt defterine göre, bakiye sayımı bu değerler arasındaki fark olarak tutulur, pozisyon hesabında da aynı şema geçerlidir: 0,2 Satış ve 0,3 Alış için lot açarsanız bu, Alış için 0,1 pozisyonunu tuttuğunuz anlamına gelir. Açılış zamanı ve düzeylerdeki fark, kar kategorileridir fakat tutacağınız pozisyon, Alış türü 0,1 lottur.
Bu nedenle, Expert Advisor tarafından Alış ve ayrı olarak Satış'ta yapılan tüm işlemleri basitçe özetliyoruz, daha sonra bunları karşılaştırıyor ve genel pozisyonu alıyoruz (aslında, bu, incelenen işlevin geri kalanının ne ile ilgili olduğudur).
Pozisyonların hacminin hesaplanması:
volume=NormalizeDouble(volume_BUY-volume_SELL,DIGITS);
Yapıdaki değerin çıktısı ile pozisyon türünün tanınması:
if(volume<0)pvm.type=POSITION_TYPE_SELL; else { if(volume>0)pvm.type=POSITION_TYPE_BUY; }
Yapıya hacim çıktısı:
pvm.volume=fabs(volume);
İşlevin değer çıktısı: Pozisyonun hacmi 0 ise false'tur; aksi takdirde pozisyon mevcutsa true'dur:
if(pvm.volume==0)return(false); else return(true);
Artık sanal pozisyon işlevine sahip olarak, "komşuları" ile çakışmayacak olan Expert Advisor kodunu kolayca ayarlayabiliriz.
Yer kazanmak için, kodun tamamı yerine, yukarıda belirtilmeyen kodun belirli kısımlarını sağlayacağım.
//+------------------------------------------------------------------+ //| Code of the Expert Advisor | //+------------------------------------------------------------------+ //--- input parameters input ulong magic =1; // magic input int SL =300; // Stop Loss input int TP =1000; // Take Profit input int MA_Period =25; // MA period input double lot =0.1; // Volume of position input int MA_shift =0; // Shift of indicator input ENUM_MA_METHOD MA_smooth =MODE_SMA; // Smoothing type input ENUM_APPLIED_PRICE price =PRICE_OPEN; // Price type //--- we will store the indicator's handle int MA_handle,type_MA,rezult; double v[2]; datetime CurrentTime; // The variable stores the time of start of the Expert Advisor MqlTradeResult res; // Pointer to the structure of responding by OrderSend MqlTick tick; // Pointer to the structure of last market information CPositionVirtualMagic cpvm; CProvision prov; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { CurrentTime=TimeCurrent();// The variable stores the time of start of the Expert Advisor //--- Create the indicator's handle MA_handle=iMA(Symbol(),0,MA_Period,MA_shift,MA_smooth,price); return(0); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(CopyBuffer(MA_handle,0,0,2,v)<=0) {Print("#",magic,"Error of copying");return;} type_MA=prov.TYPE(v); // Determine type depending on MA indication if(cpvm.PositionVirtualMagic(magic,_Symbol,CurrentTime))// If there is ab open position { if((int)cpvm.cTYPE()!=type_MA)// Check if it is time to close { Print("#",magic,"Position by magic number has volume ",cpvm.cVOLUME(), " reverse position of type ",(int)cpvm.cTYPE()," by ",type_MA); //cpvm.cVOLUME() - volume of virtual position rezult=prov.SendOrder((ENUM_ORDER_TYPE)type_MA,cpvm.cVOLUME()+lot);// reverse the poistion if(rezult!=-1)Print("#",magic," Code of the operation result ",rezult," volume ",res.volume); else{Print("#",magic,"Error",GetLastError()); return;} } } else // If there is no open position then open { Print("#",magic,"Poistion by magic number has volume ",cpvm.cVOLUME()," open position of type ",type_MA); rezult=prov.SendOrder((ENUM_ORDER_TYPE)type_MA,lot);// Open position if(rezult!=-1)Print("#",magic," Code of the operation result ",rezult," volume ",res.volume); else{Print("#",magic,"Error",GetLastError()); return;} } }
Expert Advisor'ı tek bir enstrüman üzerinde fakat farklı zaman dilimlerinde olmasına dikkat ederek üç kez çalıştırın ve her seferinde farklı sihirli sayılar atayın:
Şekil 3. İki özdeş Expert Advisor'a farklı sihirli sayılar atayalım, (bir enstrüman, farklı zaman dilimleri) ilk Expert Advisor'ı başlatma
Şekil 4. İki özdeş Expert Advisor'a farklı sihirli sayılar atayalım (bir enstrüman, farklı zaman dilimleri) ikinci Expert Advisor'ı başlatma
Şekil 5. Sonuç, çeşitli sihirli sayılarla tek bir enstrüman üzerinde Expert Advisor'ların çakışma olmadan çalışmasıdır.
Deneme çalıştırması başarıyla geçilmiş olup Expert Advisor'lar zorunlu olarak birbirine yol vermiştir; bu durumda herhangi bir çelişkili konu yok gibi görünüyor.
Teknik Şartnamenin ilk maddesi uygulandı, ancak daha fazlası da var.
Sihri kodlamak
Aşağıdaki bölümlerin uygulanması için, bilgileri kodlayacak / kodlarını çözecek ve ayrıca yerleşik işlevlerden değerleri alıp bunları belirli bir biçime dönüştürecek bir yöntemler sınıfı geliştirmemiz gerekecek.
Bunu yapmak için, kodlama şartlarını tekrarlayın (yani, geliştirme için teknik özellikler):
- Yöntemlerin Expert Advisor'ın adını (dijital adı diyebiliriz) kodlaması gerekir.
- Sizinkini / haricen geleni tanıma kodu (etkileşim kodu diyebiliriz)
- Expert Advisor'ın üzerinde çalıştığı sembol kodu (işlemden EA'nın nereden çalıştığını belirleyebilmek için).
Öncelikle, yeni sınıfın adını seçelim, - sihir (genel bir ad) olsun, kodu görsel olarak daha anlaşılır hale getirmek için numaralandırmamızı atayın.
enum Emagic { ENUM_DIGITAL_NAME, // digital name if the Expert Advisor ENUM_CODE_INTERACTION,// code of interaction ENUM_EXPERT_SYMBOL // symbol, on which the EA is launched };
Numaralandırma basit bir şekilde çalışır: Adları virgülle ayırarak tanımlarsınız ve derleyici onlara sıraya göre numaralar atar.
İlk olarak, farklı türde bir değişken atarsanız (bu, sayılar için geçerli değildir), numaralandırmadan bir parametre belirtirken, derleme sırasında bir hata alırsınız ve ikinci olarak, netlik elde edersiniz: Yalnızca 0 atamazsınız, aynı zamanda ENUM_DIGITAL_NAME atama komutunu da verirsiniz.
Bir yapının veya sınıfın oluşturulmasında olduğu gibi, numaralandırma için basit bir ad seçtim. Genel olarak seçilen ada E ekledim ve sırasıyla Emagic elde ettim; karşılık gelen yapı Smagic ve sınıf Cmagic olacaktır.
Yine, bu konunun zorunlu olmadığına dikkat edin; Enumerator numaralandırmasını, Structurer yapısını ve Classifier sınıfını çağırabilirsiniz. Ancak bu, adlarda bir müştereklik sağlamayacak ve bu tür kodları okumak rahatsız edici olacaktır.
Şimdi kodlarımızı saklamak için bir yapı oluşturalım.
struct Smagic { ulong magicnumber; // magic in an assembled form - how it is written in the order int digital_name; // digital name int code_interaction; // code of interaction int expert_symbol; // symbol, on which the Expert Advisor is launched };
Bundan sonra, önceki Expert Advisor'daki yöntemler de dahil olmak üzere Magic'i kodlama ve kod çözme yöntemlerinin tümünü kaydettiğimiz Cmagic sınıfını bildirin (bunları geçerli sınıfta bildirin ve başlıkları yeniden yazın)
class Cmagic { protected: Smagic mag; SPositionVirtualMagic pvm; public: // the function returns the assembled magic, assembled from the incoming data ulong SetMagic_request(int digital_name=0,int code_interaction=0); // the function obtains the assembled magic and divides it according to the assembly logic ulong SetMagic_result(ulong magicnumber); // the function obtains the return identification and returns the requested part of the assembled magic ulong GetMagic_result(Emagic enum_); // the function obtains the return identification and returns the textual interpretation of the request part of the assembled magic string sGetMagic_result(Emagic enum_); // returns the voulme of the virtual position of the Expert Advisor double cVOLUME(){return(pvm.volume);} // returns the type of the virtual position of the Expert Advisor ENUM_POSITION_TYPE cTYPE(){return(pvm.type);} // method of calculating the virtual position, returns the presence of absence of the virtual position bool PositionVirtualMagic(Emagic enum_, string symbol, datetime CurrentTime); private: // function divides the magic into three parts of three charges, and returns the part to which the category points to int decodeMagic_result(int category); // interpretor of instrument symbols into the digital code int symbolexpert(); // interpretor of the digital code into the prescribed text (Expert Advisors) string expertcode(int code); // interpretor of the digital code into the prescribed text (interaction) string codeinterdescript(int code); // interpretor of the digital code into the instrument symbol string symbolexpert(int code); // cycle of recording tickets into the buffer void prHistory_Deals(ulong &buf[],int HTD); };
Şimdi yöntemleri geliştireceğiz.
Sınıftaki ilk yöntem:
//+---------------------------------------------------------------------+ //| Function returns the assembled magic, assembled from the input data | //+---------------------------------------------------------------------+ ulong Cmagic::SetMagic_request(int digital_name=0,int code_interaction=0) { if(digital_name>=1000)Print("Incorrectly specified digital name of the Expert Advisor (more than 1000)"); if(code_interaction>=1000)Print("Incorrectly specified the code of recognizing yours-foreign (more than 1000)"); mag.digital_name =digital_name; mag.code_interaction =code_interaction; mag.expert_symbol =symbolexpert(); mag.magicnumber =mag.digital_name*(int)pow(1000,2)+ mag.code_interaction*(int)pow(1000,1)+ mag.expert_symbol; return(mag.magicnumber); }
Bu yöntem iki değer alır: Expert Advisor'ın dijital adı ve etkileşim kodu.
ulong Cmagic::SetMagic_request(int digital_name=0,int code_interaction=0)
Ve doğruluğunu derhal doğrular:
if(digital_name>=1000)Print("Incorrectly specified the digital name of the Expert Advisor(more than 1000)"); if(code_interaction>=1000)Print("Incorrectly specifies the code of recognizing yours-foreign (more than 1000)");
Fakat kullanıcının eylemlerine karşı herhangi bir şekilde karşılık verme durumu yoktur; bir hata durumunda dahi uysal bir şekilde çalışmaya devam eder.
Ardından, kullanıcının belirttiği, ancak enstrüman sembolü belirtilmemiş ve özel yöntemden elde edilen giriş verilerinin yapısına atama gelir:
int Cmagic::symbolexpert()
Kodunu vermeyeceğim; zira söz konusu kod çok uzun ve ekli dosyada zaten verilmiş. Bu yöntemin temel olarak "piyasa görünümü" penceresindeki her sembole karşılık gelen bir sayı atayan bir tablo olduğunu söyleyebilirim: Örneğin, EURUSD için bu, 1'dir, vb.
"Piyasa görünümü" penceresinde hangi para birimlerinin bulunduğuna dair bir anket için bir kod yazarak bu verileri kesinlikle dinamik olarak elde edebilirsiniz fakat çözüm, sorunun karmaşıklığına karşılık gelmelidir ve pencereleri çağırmakla uğraşmak mantıklı değildir; bu nedenle şu yolu takip edeceğiz: - Bir para birimi listesi oluşturmak ve her birine bir indeks atamak.
Ve son olarak, yöntemin tamamına ilişkin en önemli satır şu şekildedir:
mag.magicnumber =mag.digital_name*(int)pow(1000,2)+ mag.code_interaction*(int)pow(1000,1)+ mag.expert_symbol;
tüm Magic'in farklı parçalarından birleştirilir. Bu, Expert Advisor'ımızın talimatına tahsis edilecek olan Magic'tir.
Sınıftaki bir sonraki genel yöntem:
//+------------------------------------------------------------------+ //| Function obtains the assembled magic | //| and divides it according to the logic of the assembly | //+------------------------------------------------------------------+ ulong Cmagic::SetMagic_result(ulong magicnumber) { mag.magicnumber =magicnumber; mag.expert_symbol =decodeMagic_result(1); mag.code_interaction =decodeMagic_result(2); mag.digital_name =decodeMagic_result(3); return(mag.magicnumber); }
Aslında bu yöntem, tek bir özel yöntemin üç çağrısının sonuçlarını yapı aracılığıyla dağıtan bir kabuk işlevi görür. Bu tür bir belirtici altındaki bildirim, bir sınıf değişkenini çağırdığınızda, tüm işin bir genel işlev tarafından yapıldığı izlenimini yaratarak, açılır komut istemi mesajında gösterilmemeleri gerçeğinden dolayı iyidir.
Ama özel işlevlerimize dönelim:
//+------------------------------------------------------------------+ //| Function divides the magic into three parts of three charges | //| and returns the part, which the category points to | //+------------------------------------------------------------------+ int Cmagic::decodeMagic_result(int category) { string string_value=(string)mag.magicnumber; int rem=(int)MathMod(StringLen(string_value),3); if(rem!=0) { rem=3-rem; string srem="0"; if(rem==2)srem="00"; string_value=srem+string_value; } int start_pos=StringLen(string_value)-3*category; string value=StringSubstr(string_value,start_pos,3); return((int)StringToInteger(value)); }
Görsel olarak, bu yöntem belirtilen alandan üç basamaklı bir sayının okunması olarak temsil edilebilir; örneğin elimizde Magic 123456789 varsa bunu | 123 | 456 | 789 | olarak gösterebiliriz, belirtilen alan 1 ise, alanlar sağdan sola doğru numaralandırıldığı için sonuç 789 şeklinde olacaktır.
Dolayısıyla, çağrılan yöntemdeki üç alanı da kullandıktan sonra elde edilen tüm verilerin yapısına dağıtıyoruz. Bu, Magic'i string: gibi küçük bir türe getirme prosedürüyle yapılır.
string string_value=(string)mag.magicnumber;
Tek tek hat bileşenlerinin ayrılmasını takiben.
Ardından, özünde switch anahtarları olan ve yalnızca çıkış değerlerinin türünde farklılık gösteren iki benzer işlevi takip edin:
//+------------------------------------------------------------------+ //| Function obtains the identifier of the return | //| and returns the requested part of the assembled magic | //+------------------------------------------------------------------+ ulong Cmagic::GetMagic_result(Emagic enum_) { switch(enum_) { case ENUM_DIGITAL_NAME : return(mag.digital_name); break; case ENUM_CODE_INTERACTION : return(mag.code_interaction); break; case ENUM_EXPERT_SYMBOL : return(mag.expert_symbol); break; default: return(mag.magicnumber); break; } } //+------------------------------------------------------------------------------+ //| Function obtains the identifier of the return and returns | //| a textual interpretation of the requested type of the assembled magic | //+------------------------------------------------------------------------------+ string Cmagic::sGetMagic_result(Emagic enum_) { switch(enum_) { case ENUM_DIGITAL_NAME : return(expertcode(mag.digital_name)); break; case ENUM_CODE_INTERACTION : return(codeinterdescript(mag.code_interaction)); break; case ENUM_EXPERT_SYMBOL : return(symbolexpert(mag.expert_symbol)); break; default: return((string)mag.magicnumber); break; } }
İşlevler, Magic'in Emagic türünün parametresini belirten kısmını döndürür; ilki sonucu hesaplamalarda kullanılan ulong biçiminde, ikincisi ise görselleştirme için kullanılabilecek string türünde sonuçlar verir.
GetMagic_result () işlevinde her şey basit bir şekilde düzenlenir; bu işlev, yapının değerlerini anahtar, dalları boyunca dağıtırken sGetMagic_result () biraz daha karmaşıktır. Her durum dalı, yapının değerini görsel bir forma aktaran bir tablo işlevini çağırır. Böylece, mag.expert_symbol = 1 değeri ise ilk işlev 1 öğesini ve ikincisi EURUSD öğesini verecektir.
Bilgileri kodlamada / kodlarını çözmede tablo işlevlerinin avantajlarını zaten açıkladım; bu nedenle yalnızca, tablosuz bir yöntemin uygulanmasının karmaşıklığına ve tabloları yazmak için gereken süreye olan avantajlarına bağlı olarak her bir durumun ayrı ayrı ele alınması gerektiğini belirteceğim. Durum tablosu yazmak daha kolaysa meseleleri karmaşıklaştırmaya gerek yok. Ancak tablonun yazılması çok zaman alacaksa, açıkçası, prosedürel yöntemler tercih edilmelidir. Yerden tasarruf sağlamak için burada tabloları vermiyorum (tabloları ekteki dosyalarda bulabilirsiniz).
Bunun, temeli budur; sınıfımız geliştirildi, ancak önceki Expert Advisor'ın geliştirilmesinde kullandığımız kalan dört işlev daha var.
Özellikle biraz değiştirilmeleri gerektiğini düşünerek onları yeni bir sınıfta tekrar bildirdim.
Şimdi ana yöntemi ele alalım:
bool Cmagic::PositionVirtualMagic(Emagic enum_, string symbol, datetime CurrentTime)
Yalnızca Cmagic sınıfının bir yöntemi olarak bildirilmekle kalmaz, aynı zamanda farklı bir parametre kümesi de içerir.
Magic'in yerine, artık, pozisyonun hesaplandığı Magic alanına göre tanımlama alır. Ayrıca son seçenekte sembol mevcut olmasına rağmen yalnızca sembol ile lotun adımı hakkında bilgi edinmek için kullanılmıştır. Ve şimdi bu, filtrede belirtilir ve pozisyon sayımının filtrelenmesine diğerleriyle eşit olarak katılabilir.
Bu bize ne veriyor? Artık aynı Expert Advisor tarafından farklı bir enstrümanda açılmış olan işlemleri aynı anda filtreleyebiliyoruz. Böylelikle, farklı bir enstrüman üzerinde çalışan diğer benzer Expert Advisor'larla karıştırılmayacaklardır. Açık söylemek gerekirse, bu yeni hesaplama sistemini kullanmanın tüm farklı yollarını tarif etmek çok zor. Ve okuyucu, bu kadar karmaşık bir sisteme ne için ihtiyaç duyduğuna kişisel olarak karar verebilir. Yalnızca basit bir şekilde yazabileceğiniz durumları karmaşıklaştırmamanızı ve buna bariz bir ihtiyaç olduğunda bu tür karmaşıklıklar konusunda endişelenmemenizi şiddetle tavsiye ediyorum.
Sınıf tasarlandığına göre, o halde yeni bir Expert Advisor üzerinde test etmenin zamanı geldi:
//+------------------------------------------------------------------+ //| Code of the Expert Advisor | //+------------------------------------------------------------------+ //--- input parameters input ulong digital_name_ =4; // Digital name of Expert Advisor input ulong code_interaction_ =1; // Code of interaction input Emagic _enum =0; // Model of magic number input int SL =300; // Stop Loss input int TP =1000; // Take Profit input int MA_Period =25; // MA period input double lot =0.4; // Volume of position input int MA_shift =0; // Shift of indicator input ENUM_MA_METHOD MA_smooth =MODE_SMA; // Smoothing type input ENUM_APPLIED_PRICE price =PRICE_OPEN; // Price type //--- we will store the indicator's handle int MA_handle,type_MA,rezult; static ulong magic; double v[2]; datetime CurrentTime;// The variable stores the time of start of the Expert Advisor MqlTradeResult res; // Pointer to the structure of responding by OrderSend MqlTick tick; // Pointer to the structure of last market information CProvision prov; Cmagic mg; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { magic=mg.SetMagic_request(digital_name_,code_interaction_); // Stamp of Expert Advisor (the magic number identifier) the magic variable is declared at the global scope // used in int CProvision::SendOrder(ENUM_ORDER_TYPE type,double volume) CurrentTime=TimeCurrent();// The variable stores the time of start of the Expert Advisor //--- Create the indicator's handle MA_handle=iMA(Symbol(),0,MA_Period,MA_shift,MA_smooth,price); return(0); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(CopyBuffer(MA_handle,0,0,2,v)<=0) {Print("#",magic,"Error of copying");return;} type_MA=prov.TYPE(v); // Determine type depending on MA indication mg.SetMagic_result(magic);// put the information into the structure if(mg.PositionVirtualMagic(_enum,_Symbol,CurrentTime))// If three is an open position { if((int)mg.cTYPE()!=type_MA)// Check if it is time to close { mg.SetMagic_result(magic);// put the information into the structure Print("#",mg.GetMagic_result(_enum),"Position by magic number has volume ",mg.cVOLUME(), " reverse position of type ",(int)mg.cTYPE()," by ",type_MA); //cpvm.cVOLUME() - volume of virtual position rezult=prov.SendOrder((ENUM_ORDER_TYPE)type_MA,mg.cVOLUME()+lot);// reverse position if(rezult!=-1)Print("№",magic," Code of the operation result ",rezult," volume ",res.volume); else{Print("№",magic,"Error",GetLastError()); return;} } } else // If there is no open position then open { Print("#",magic,"Position by magic number has volume ",mg.cVOLUME()," open position of type",type_MA); rezult=prov.SendOrder((ENUM_ORDER_TYPE)type_MA,lot);// Open position if(rezult!=-1)Print("#",magic," Code of the operation result ",rezult," volume ",res.volume); else{Print("#",magic,"Error",GetLastError()); return;} } }
Daha önce de belirtildiği gibi, bu Expert Advisor son derece basittir ve yalnızca farklı özellikleri göstermek için oluşturulmuştur; bunu tek bir enstrüman üzerinde üç kez çalıştırın:
Şekil 6. Farklı grafiklerde farklı sihirli sayılarla üç Expert Advisor'ın kurulumu
Şekil 7. Sonuç, farklı sihirli sayılarla üç Expert Advisor'ın çakışma olmadan alım satım işlemi yapmasıdır
Expert Advisor'ların mesajlarının çıktılarından da görülebileceği üzere, üç katılımcı da başarılı bir şekilde başlatıldı ve herhangi bir çakışma göstermedi.
Sonuç
MQL5'in yaratıcıları, alım satım işlemlerine sihirli talimatlar atama fırsatı sağlayarak, Expert Advisor yazarlarının hayatını büyük ölçüde kolaylaştırdı. Ancak geliştiriciler size yalnızca enstrümanlar sağlayabilir - Elmasları gerçekten elde eden kişi olmanız gerekir.
Tekrar buluşuncaya kadar iyi şanslar.
MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/112





- Ücretsiz alım-satım 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