Diğer Uygulamalar için MetaTrader 5 Fiyat Teklifleri Nasıl Hazırlanır?
İçindekiler
Giriş1. Ele Alınan Konular
2. Veri Biçimi
3. Programın Harici Parametreleri
4. Bir Kullanıcı Tarafından Girilen Parametreleri Kontrol Etme
5. Genel Değişkenler
6. Bilgi Paneli
7. Uygulamanın Ana Bloğu
8. Klasörler Oluşturma ve Verileri Dosyalama
Sonuç
Giriş
MQL5 ile çalışmaya başlamadan önce, alım satım sistemlerinin geliştirilmesi için birçok başka uygulama denedim. Zamanımı boşa harcadığımı söyleyemem. Bunların bazıları, kullanıcıların zamandan tasarruf etmesine, birçok sorunla başa çıkmasına, bazı yanlış kanılardan kurtulmasına ve programlama dilleri bilgisi olmadan geliştirme için hızlı bir şekilde başka yollar seçmesine olanak tanıyan birkaç faydalı araç içerir.
Bu uygulamalar, geçmiş verilere ihtiyaç duyar. Bazı belirli standart veri biçimlerinin olmaması nedeniyle, gerekli programa uygulanabilir biçimle uyumlu olmaları için (örneğin Excel'de) kullanılmadan önce genellikle düzenlenmeleri gerekti. Gerekli tüm ayrıntıları çözebilseniz dahi, birçok şey yine de manuel olarak yapılmalıdır. Kullanıcılar, MetaTrader 4'ten fiyat tekliflerini gerekli biçime kopyalamak için tasarlanmış farklı script dosyası sürümleri bulabilir. Böyle bir talep olması halinde, MQL5 için script dosyasının sürümünü de geliştirebiliriz.
1. Ele Alınan Konular
Makalede, aşağıdaki konuları ele alınmaktadır:
- Piyasa İzleme penceresindeki sembol listesi ve sunucudaki ortak sembol listesi ile çalışma.
- Mevcut veri derinliğini kontrol etme ve gerekirse çeşitli durumların doğru şekilde ele alınmasıyla eksik miktarı indirme.
- Özel panel grafiğinde ve günlüğünde istenen verilerle ilgili bilgileri görüntüleme.
- Kullanıcı tanımlı biçimde dosyalama için veri hazırlama.
- Dosya dizinleri oluşturma.
- Veri dosyalama.
2. Veri Biçimi
NeuroShell DayTrader Professional'da (NSDT) kullanılması amaçlanan verilerin hazırlanmasına bir örnek vereceğim. NSDT sürüm 5 ve 6'yı denedim ve veri biçimi için farklı gereklilikleri olduğunu öğrendim. NSDT sürümü 5'in tarih ve saat verileri farklı sütunlarda olmalıdır. Dosyadaki ilk satır aşağıdaki görünüme sahip olmalıdır:
"Tarih" "Saat" "Açılış" "Yüksek" "Düşük" "Kapanış" "Hacim"
NSDT sürümü 6'daki başlık satırı, uygulamanın bir dosyayı kabul etmesine izin vermek için farklı bir görünüme sahip olmalıdır. Bu, tarih ve saatin aynı sütunda olması gerektiği anlamına gelir:
Tarih,Açılış,Yüksek,Düşük,Kapanış,Hacim
MetaTrader 5, kullanıcıların fiyat tekliflerini *.csv dosyalarına kaydetmesine olanak tanır. Bir dosyadaki veriler, aşağıdaki gibi görünür:
Şek. 1. MetaTrader 5 terminali tarafından kaydedilen veriler
Ancak, tarihin başka bir biçimi olması gerektiği için, yalnızca başlık satırını düzenleyemeyiz. NSDT s.5 için:
gg.aa.yyyy,ss:dd,Açılış,Yüksek,Düşük,Kapanış,HacimNSDT s.6 için:
gg/aa/yyyy ss:dd,Açılış,Yüksek,Düşük,Kapanış,Hacim
Açılır listeler, kullanıcıların gerekli biçimi seçebilecekleri script dosyası harici parametrelerinde kullanılacaktır. Başlık ve tarih biçimlerini seçmenin yanı sıra, kullanıcılara dosyalara yazmak istedikleri verileri, sembollerin sayısını seçme imkanı sunacağız. Bunu yapmak için, üç sürüm hazırlayacağız:
- Verileri yalnızca (YALNIZCA MEVCUT SEMBOL) script dosyası başlatılmış olan mevcut sembolün üzerine yazın.
- Verileri, Piyasa İzleme penceresinde yer alan sembollerin üzerine yazın (MARKETWATCH SEMBOLLERİ).
- Verileri sunucuda bulunan tüm sembollerin üzerine yazın (TÜM LİSTE SEMBOLLERİ).
Bu tür listeleri oluşturmak için script dosyası kodunda harici parametrelerden önce aşağıdaki kodu girelim:
//_________________________________ // HEADER_FORMATS_ENUMERATION enum FORMAT_HEADERS { NSDT_5 = 0, // "Date" "Time" "Open" "High" "Low" "Close" "Volume" NSDT_6 = 1 // Date,Open,High,Low,Close,Volume }; //--- //___________________________ // ENUMERATION_OF_DATA_FORMATS enum FORMAT_DATETIME { SEP_POINT1 = 0, // dd.mm.yyyy hh:mm SEP_POINT2 = 1, // dd.mm.yyyy, hh:mm SEP_SLASH1 = 2, // dd/mm/yyyy hh:mm SEP_SLASH2 = 3 // dd/mm/yyyy, hh:mm }; //--- //____________________________ // ENUMERATION_OF_FILING_MODES enum CURRENT_MARKETWATCH { CURRENT = 0, // ONLY CURRENT SYMBOLS MARKETWATCH = 1, // MARKETWATCH SYMBOLS ALL_LIST_SYMBOLS = 2 // ALL LIST SYMBOLS };
Numaralandırmalar hakkında daha fazla bilgi MQL5 Referansı'nda bulunabilir.
3. Programın Harici Parametreleri
Şimdi, script dosyasının tüm harici parametrelerinin tam listesini oluşturabiliriz:
//____________________________________________________________________ //+------------------------------------------------------------------+ //| EXTERNAL_PARAMETERS | //+------------------------------------------------------------------+ input datetime start_date = D'01.01.2011'; // Start Date input datetime end_date = D'18.09.2012'; // End Date input FORMAT_HEADERS format_headers = NSDT_5; // Format Headers input FORMAT_DATETIME format_date = SEP_POINT2; // Format Datetime input CURRENT_MARKETWATCH curr_mwatch = CURRENT; // Mode Write Symbols input bool clear_mwatch = true; // Clear Market Watch input bool show_progress = true; // Show Progress (%)
Harici parametreler, aşağıdaki amaçlar için kullanılır:
- Kullanıcılar, Başlangıç Tarihi (start_date) ve Bitiş Tarihi (end_date) parametrelerini kullanarak bir tarih aralığı belirleyebilir.
- Başlık Biçimi (format_headers) açılır listesi, kullanıcıların bir başlık biçimi seçmesine olanak tanır.
- Tarih Saat Biçimi (format_date) açılır listesi, kullanıcıların tarih ve saat biçimlerini seçmesine olanak tanır.
- Mod Yazma Sembolleri (curr_mwatch) açılır listesi, kullanıcıların dosyalama için sembol sayısını seçmesine olanak tanır.
- Piyasa İzlemeyi Temizle (clear_mwatch) parametresi true ise, kullanıcıların dosyalamadan sonra Piyasa İzleme penceresinden tüm sembolleri silmesine olanak tanır. Bu, yalnızca şu anda etkin olmayan grafikleri içeren sembollerle ilgilidir.
- İlerlemeyi Göster (%) (show_progress) parametresi, veri panelinde dosyalama ilerlemesini gösterir. Bu parametre devre dışı bırakılırsa dosyalama daha hızlı gerçekleştirilecektir.
Başlatma sırasında harici parametrelerin nasıl görüneceği aşağıda açıklanmıştır:
Şek. 2. Uygulamanın harici parametreleri
4. Bir Kullanıcı Tarafından Girilen Parametreleri Kontrol Etme
Temel koddan önce kullanıcılar tarafından girilen parametreleri kontrol etmek için işlev oluşturalım. Örneğin, Başlangıç Tarihi parametresindeki başlangıç tarihi, Bitiş Tarihindekinden önce olmalıdır. Başlıkların biçimi, tarih ve saat biçimleriyle eşleşmelidir. Bir kullanıcı, parametreleri ayarlarken bazı hatalar yaptıysa aşağıdaki uyarı mesajı görüntülenir ve program durur.
Örnek uyarı mesajı:
Şek. 3. Hatalı şekilde belirtilen parametrelere ilişkin örnek hata mesajı
ValidationParameters() işlevi:
//____________________________________________________________________ //+------------------------------------------------------------------+ //| CHECKING_CORRECTNESS_OF_PARAMETERS | //+------------------------------------------------------------------+ bool ValidationParameters() { if(start_date>=end_date) { MessageBox("The start date should be earlier than the ending one!\n\n" "Application cannot continue. Please retry.", //--- "Parameter error!",MB_ICONERROR); //--- return(true); } //--- if(format_headers==NSDT_5 && (format_date==SEP_POINT1 || format_date==SEP_SLASH1)) { MessageBox("For the headers of the following format:\n\n" "\"Date\" ""\"Time\" ""\"Open\" ""\"High\" ""\"Low\" ""\"Close\" ""\"Volume\"\n\n" "Date/time format can be selected out of two versions:\n\n" "dd.mm.yyyy, hh:mm\n" "dd/mm/yyyy, hh:mm\n\n" "Application cannot continue. Please retry.", //--- "Header and date/time formats do not match!",MB_ICONERROR); //--- return(true); } //--- if(format_headers==NSDT_6 && (format_date==SEP_POINT2 || format_date==SEP_SLASH2)) { MessageBox("For the headers of the following format:\n\n" "Date,Open,High,Low,Close,Volume\n\n" "Date/time format can be selected out of two versions:\n\n" "dd.mm.yyyy hh:mm\n" "dd/mm/yyyy hh:mm\n\n" "Application cannot continue. Please retry.", //--- "Header and date/time formats do not match!",MB_ICONERROR); //--- return(true); } //--- return(false); }
5. Genel Değişkenler
Ardından, script dosyasında kullanılacak tüm genel değişkenleri ve dizileri belirlemeliyiz:
//____________________________________________________________________ //+------------------------------------------------------------------+ //| GLOBAL_VARIABLES_AND_ARRAYS | //+------------------------------------------------------------------+ MqlRates rates[]; // Array for copying data //--- string symbols[]; // Symbol array //--- // Array of graphic object names string arr_nmobj[22]= { "fon","hd01", "nm01","nm02","nm03","nm04","nm05","nm06","nm07","nm08","nm09","nm10", "nm11","nm12","nm13","nm14","nm15","nm16","nm17","nm18","nm19","nm20" }; //--- // Array of displayed text containing graphic objects string arr_txtobj[21]; //--- string path=""; // File path int cnt_symb=0; // Number of symbols int sz_arr_symb=0; // Symbol array size int bars=0; // Number of bars according to the specified TF int copied_bars=0; // Number of bars copied for writing double pgs_pcnt=0; // Writing progress int hFl=INVALID_HANDLE; // File handle //--- string // Variables for data formatting sdt="", // Date line dd="", // Day mm="", // Month yyyy="", // Year tm="", // Time sep=""; // Separator //--- int max_bars=0; // Maximum number of bars in the terminal settings //--- datetime first_date=0, // First available data in a specified period first_termnl_date=0, // First available data in the terminal's database first_server_date=0, // First available data in the server's database check_start_date=0; // Checked correct date value
6. Bilgi Paneli
Şimdi, bilgi panelinde görüntülenecek olan öğelerle ilgilenmeliyiz. Arka plan olarak üç tür grafik nesnesi kullanılabilir:
- En basit ve bariz olanı – "Dikdörtgen etiket" (OBJ_RECTANGLE_LABEL).
- Arayüzlerinin benzersiz bir görünüme sahip olmasını isteyenler "Bitmap" nesnesini (OBJ_BITMAP) kullanabilirler.
- "Düzenle" nesnesi (OBJ_EDIT) arka plan olarak da kullanılabilir. Metin girme olasılığını kaldırmak için "salt okunur" özelliğini ayarlayın. "Düzenle" nesnesini kullanmanın başka bir avantajı daha vardır. Bir Expert Advisor'da bir bilgi paneli oluşturduysanız ve görselleştirme modundaki testler sırasında aynı görünüme sahip olmasını istiyorsanız, son tür bunu başarmanın tek yöntemidir. Görselleştirme modunda testler sırasında ne OBJ_RECTANGLE_LABEL ne de OBJ_BITMAP görüntülenmez.
Bizim durumumuzda Expert Advisor yerine yalnızca bir script dosyası geliştirilse de, örnek olarak OBJ_EDIT nesneli arka plan yapılacaktır. Sonuç, aşağıdaki şekilde gösterilmiştir:
Şek. 4. Bilgi paneli
Panelde gösterilen tüm verileri açıklayalım:
- Sembol (geçerli/toplam) – O anda indirilen/kopyalanan/yazılan verinin bulunduğu sembol. Parantez içindeki soldaki sayı, geçerli sembol numarasını gösterir. Sağdaki sayı, script dosyasının çalışacağı ortak sembol sayısını gösterir.
- Yol Sembolü – Ait olduğu sembol yolu veya kategorisi. Piyasa İzleme penceresinde sağ tıklayıp "Semboller…" öğesini seçerek bağlam menüsünü açarsanız, tüm sembollerin listesini içeren pencere görüntülenir. Bununla ilgili daha fazla bilgiyi terminalin Kullanım Kılavuzunda bulabilirsiniz.
- Zaman dilimi – Dönem (zaman dilimi). Script dosyasının başlatılacağı zaman dilimi kullanılacaktır.
- Giriş Başlangıç Tarihi – Script dosyası parametrelerinde bir kullanıcı tarafından belirtilen veri başlangıç tarihi.
- İlk Tarih (H1) – Mevcut zaman diliminin ilk kullanılabilir veri tarihi (çubuk).
- İlk Terminal Tarihi (M1) – Halihazırda mevcut terminal verilerinde M1 zaman diliminin ilk kullanılabilir tarihi.
- İlk Sunucu Tarihi (M1) – Sunucudaki M1 zaman diliminin ilk kullanılabilir tarihi.
- Seçenekler Terminalindeki Maks. Çubuk – Terminal ayarlarında belirtilen grafikte görüntülenecek maksimum çubuk sayısı.
- Kopyalanan Çubuklar – Yazılmak üzere kopyalanan çubukların sayısı.
- İlerleme Değeri Mevcut Sembolü – Mevcut sembolün yazılı verilerinin yüzde değeri.
Aşağıda, bu tür bilgi panelinin kodu bulunmaktadır:
//____________________________________________________________________ //+------------------------------------------------------------------+ //| INFORMATION_PANEL | //|------------------------------------------------------------------+ void InfoTable(int s) { int fnt_sz=8; // Font size string fnt="Calibri"; // Header font color clr=clrWhiteSmoke; // Color //--- int xH=300; int height_pnl=0; int yV1=1,yV2=12,xV1=165,xV2=335,xV3=1; //--- string sf="",stf="",ssf=""; bool flg_sf=false,flg_stf=false,flg_ssf=false; //--- if(show_progress) { height_pnl=138; } else { height_pnl=126; } //--- flg_sf=SeriesInfoInteger(symbols[s],_Period,SERIES_FIRSTDATE,first_date); flg_stf=SeriesInfoInteger(symbols[s],PERIOD_M1,SERIES_TERMINAL_FIRSTDATE,first_termnl_date); flg_ssf=SeriesInfoInteger(symbols[s],PERIOD_M1,SERIES_SERVER_FIRSTDATE,first_server_date); //--- if(flg_sf) { sf=TSdm(first_date); } else { sf="?"; } if(flg_stf) { stf=TSdm(first_termnl_date); } else { stf="?"; } if(flg_ssf) { ssf=TSdm(first_server_date); } else { ssf="?"; } //--- if(cnt_symb==0) { cnt_symb=1; } //--- int anchor1=ANCHOR_LEFT_UPPER,anchor2=ANCHOR_RIGHT_UPPER,corner=CORNER_LEFT_UPPER; //--- string path_symbol=SymbolInfoString(symbols[s],SYMBOL_PATH); path_symbol=StringSubstr(path_symbol,0,StringLen(path_symbol)-StringLen(symbols[s])); //--- arr_txtobj[0]="INFO TABLE"; arr_txtobj[1]="Symbol (current / total) : "; arr_txtobj[2]=""+symbols[s]+" ("+IS(s+1)+"/"+IS(cnt_symb)+")"; arr_txtobj[3]="Path Symbol : "; arr_txtobj[4]=path_symbol; arr_txtobj[5]="Timeframe : "; arr_txtobj[6]=gStrTF(_Period); arr_txtobj[7]="Input Start Date : "; arr_txtobj[8]=TSdm(start_date); arr_txtobj[9]="First Date (H1) : "; arr_txtobj[10]=sf; arr_txtobj[11]="First Terminal Date (M1) : "; arr_txtobj[12]=stf; arr_txtobj[13]="First Server Date (M1) : "; arr_txtobj[14]=ssf; arr_txtobj[15]="Max. Bars In Options Terminal : "; arr_txtobj[16]=IS(max_bars); arr_txtobj[17]="Copied Bars : "; arr_txtobj[18]=IS(copied_bars); arr_txtobj[19]="Progress Value Current Symbol : "; arr_txtobj[20]=DS(pgs_pcnt,2)+"%"; //--- Create_Edit(0,0,arr_nmobj[0],"",corner,fnt,fnt_sz,clrDimGray,clrDimGray,345,height_pnl,xV3,yV1,2,C'15,15,15'); //--- Create_Edit(0,0,arr_nmobj[1],arr_txtobj[0],corner,fnt,8,clrWhite,C'64,0,0',345,12,xV3,yV1,2,clrFireBrick); //--- Create_Label(0,arr_nmobj[2],arr_txtobj[1],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2,0); Create_Label(0,arr_nmobj[3],arr_txtobj[2],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2,0); //--- Create_Label(0,arr_nmobj[4],arr_txtobj[3],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*2,0); Create_Label(0,arr_nmobj[5],arr_txtobj[4],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*2,0); //--- Create_Label(0,arr_nmobj[6],arr_txtobj[5],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*3,0); Create_Label(0,arr_nmobj[7],arr_txtobj[6],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*3,0); //--- Create_Label(0,arr_nmobj[8],arr_txtobj[7],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*4,0); Create_Label(0,arr_nmobj[9],arr_txtobj[8],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*4,0); //--- Create_Label(0,arr_nmobj[10],arr_txtobj[9],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*5,0); Create_Label(0,arr_nmobj[11],arr_txtobj[10],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*5,0); //--- Create_Label(0,arr_nmobj[12],arr_txtobj[11],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*6,0); Create_Label(0,arr_nmobj[13],arr_txtobj[12],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*6,0); //--- Create_Label(0,arr_nmobj[14],arr_txtobj[13],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*7,0); Create_Label(0,arr_nmobj[15],arr_txtobj[14],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*7,0); //--- Create_Label(0,arr_nmobj[16],arr_txtobj[15],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*8,0); Create_Label(0,arr_nmobj[17],arr_txtobj[16],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*8,0); //--- Create_Label(0,arr_nmobj[18],arr_txtobj[17],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*9,0); Create_Label(0,arr_nmobj[19],arr_txtobj[18],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*9,0); //--- if(show_progress) { Create_Label(0,arr_nmobj[20],arr_txtobj[19],anchor2,corner,fnt,fnt_sz,clr,xV1,yV1+yV2*10,0); Create_Label(0,arr_nmobj[21],arr_txtobj[20],anchor2,corner,fnt,fnt_sz,clr,xV2,yV1+yV2*10,0); } } //____________________________________________________________________ //+------------------------------------------------------------------+ //| CREATING_LABEL_OBJECT | //+------------------------------------------------------------------+ void Create_Label(long chrt_id, // chart id string lable_nm, // object name string rename, // displayed name long anchor, // anchor point long corner, // attachment corner string font_bsc, // font int font_size, // font size color font_clr, // font color int x_dist, // X scale coordinate int y_dist, // Y scale coordinate long zorder) // priority { if(ObjectCreate(chrt_id,lable_nm,OBJ_LABEL,0,0,0)) // creating object { ObjectSetString(chrt_id,lable_nm,OBJPROP_TEXT,rename); // set name ObjectSetString(chrt_id,lable_nm,OBJPROP_FONT,font_bsc); // set font ObjectSetInteger(chrt_id,lable_nm,OBJPROP_COLOR,font_clr); // set font color ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ANCHOR,anchor); // set anchor point ObjectSetInteger(chrt_id,lable_nm,OBJPROP_CORNER,corner); // set attachment corner ObjectSetInteger(chrt_id,lable_nm,OBJPROP_FONTSIZE,font_size); // set font size ObjectSetInteger(chrt_id,lable_nm,OBJPROP_XDISTANCE,x_dist); // set X coordinates ObjectSetInteger(chrt_id,lable_nm,OBJPROP_YDISTANCE,y_dist); // set Y coordinates ObjectSetInteger(chrt_id,lable_nm,OBJPROP_SELECTABLE,false); // unable to highlight the object, if FALSE ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ZORDER,zorder); // Higher/lower priority ObjectSetString(chrt_id,lable_nm,OBJPROP_TOOLTIP,"\n"); // no tooltip, if "\n" } } //____________________________________________________________________ //+------------------------------------------------------------------+ //| CREATING_EDIT_OBJECT | //+------------------------------------------------------------------+ void Create_Edit(long chrt_id, // chart id int nmb_win, // window (subwindow) index string lable_nm, // object name string text, // displayed text long corner, // attachment corner string font_bsc, // font int font_size, // font size color font_clr, // font color color font_clr_brd, // font color int xsize, // width int ysize, // height int x_dist, // X scale coordinate int y_dist, // Y scale coordinate long zorder, // priority color clr) // background color { if(ObjectCreate(chrt_id,lable_nm,OBJ_EDIT,nmb_win,0,0)) // creating object { ObjectSetString(chrt_id,lable_nm,OBJPROP_TEXT,text); // set name ObjectSetInteger(chrt_id,lable_nm,OBJPROP_CORNER,corner); // set attachment corner ObjectSetString(chrt_id,lable_nm,OBJPROP_FONT,font_bsc); // set font ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ALIGN,ALIGN_CENTER); // center alignment ObjectSetInteger(chrt_id,lable_nm,OBJPROP_FONTSIZE,font_size); // set font size ObjectSetInteger(chrt_id,lable_nm,OBJPROP_COLOR,font_clr); // font color ObjectSetInteger(chrt_id,lable_nm,OBJPROP_BORDER_COLOR,font_clr_brd); // background color ObjectSetInteger(chrt_id,lable_nm,OBJPROP_BGCOLOR,clr); // background color ObjectSetInteger(chrt_id,lable_nm,OBJPROP_XSIZE,xsize); // width ObjectSetInteger(chrt_id,lable_nm,OBJPROP_YSIZE,ysize); // height ObjectSetInteger(chrt_id,lable_nm,OBJPROP_XDISTANCE,x_dist); // set X coordinate ObjectSetInteger(chrt_id,lable_nm,OBJPROP_YDISTANCE,y_dist); // set Y coordinate ObjectSetInteger(chrt_id,lable_nm,OBJPROP_SELECTABLE,false); // unable to highlight the object, if FALSE ObjectSetInteger(chrt_id,lable_nm,OBJPROP_ZORDER,zorder); // Higher/lower priority ObjectSetInteger(chrt_id,lable_nm,OBJPROP_READONLY,true); // Read only ObjectSetString(chrt_id,lable_nm,OBJPROP_TOOLTIP,"\n"); // no tooltip if "\n" } }
Script dosyası işlemi tamamlandıktan veya script dosyası önceden bir kullanıcı tarafından silindikten sonra, script dosyası tarafından oluşturulan tüm grafik nesneleri silinmelidir. Bunun için aşağıdaki işlevler kullanılacaktır:
//____________________________________________________________________ //+------------------------------------------------------------------+ //| DELETE_ALL_GRAPHICAL_OBJECTS_CREATED_BY_THE_SCRIPT | //+------------------------------------------------------------------+ void DelAllScriptObjects() { // Receive the size of graphical object names array int sz_arr1=ArraySize(arr_nmobj); //--- // Delete all objects for(int i=0; i<sz_arr1; i++) { DelObjbyName(arr_nmobj[i]); } } //____________________________________________________________________ //+------------------------------------------------------------------+ //| DELETE_OBJECTS_BY_NAME | //+------------------------------------------------------------------+ int DelObjbyName(string Name) { int nm_obj=0; bool res=false; //--- nm_obj=ObjectFind(ChartID(),Name); //--- if(nm_obj>=0) { res=ObjectDelete(ChartID(),Name); //--- if(!res) { Print("Object deletion error: - "+ErrorDesc(Error())+""); return(false); } } //--- return(res); }
7. Uygulamanın Ana Bloğu
Script dosyalarının ana işlevi OnStart()'tır. Bu, yürütme için diğer tüm işlevleri çağırmak için kullanılan işlevdir. Programın çalışmasına ilişkin ayrıntılar kodda gösterilmiştir:
//____________________________________________________________________ //+------------------------------------------------------------------+ //| SCRIPT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> | //+------------------------------------------------------------------+ void OnStart() { // If user-defined parameters are incorrect, // error message is shown and the program is closed if(ValidationParameters()) { return; } //--- max_bars=TerminalInfoInteger(TERMINAL_MAXBARS); // Receive available number of bars in the window //--- GetSymbolsToArray(); // Filling symbol array with names sz_arr_symb=ArraySize(symbols); // Receive symbol array size //--- SetSeparateForFormatDate(); // Set a separator for date format //--- // Revise all symbols and write their data to file for(int s=0; s<=sz_arr_symb-1; s++) { copied_bars=0; // Reset copied bars variable to zero for writing pgs_pcnt=0.0; // Reset variable of the symbol data writing progress //--- InfoTable(s); ChartRedraw(); //--- // Receive current symbol data int res=GetDataCurrentSymbol(s); //--- if(res==0) { BC } // If zero, break the loop or start the next iteration //--- if(res==2) // Program operation interrupted by user { DelAllScriptObjects(); // Deleted objects created by the script from the chart //--- Print("------\nUser deleted the script!"); break; } //--- // Receive the path for creating the file and create directories for them // If the string is empty, break the loop or start the next iteration if((path=CheckCreateGetPath(s))=="") { BC } //--- WriteDataToFile(s); // Write data to file } //--- // Delete symbols from Market Watch window if necessary DelSymbolsFromMarketWatch(); //--- // Delete objects created by the script from the chart Sleep(1000); DelAllScriptObjects(); }
Temel etkinliklerin gerçekleştiği işlevleri inceleyelim.
Sembol dizisi (symbols[]), GetSymbolsToArray() işlevinde sembol adlarıyla doldurulur. Dizi boyutu ve içindeki sembol sayısı, bir kullanıcı tarafından Mod Yazma Sembolleri (curr_mwatch) parametresinde seçilen değişkene bağlıdır.
Kullanıcının yalnızca bir sembolden gelen verilere sahip olması gerekiyorsa, dizi boyutu 1'e eşittir.
ArrayResize(symbols,1); // Set the array size to be equal to 1 symbols[0]=_Symbol; // Specify the current symbol's name
Kullanıcı, Piyasa İzleme penceresinden tüm sembollere veya mevcut tüm sembollere ilişkin verileri almak isterse, dizi boyutu aşağıdaki işlev ile tanımlanacaktır:
int SymbolsTotal( bool selected // true – only MarketWatch symbols );
Neredeyse aynı koda sahip iki değişken için iki blok oluşturulmasını önlemek için, true veya false değerini döndürecek olan MWatchOrAllList() işaretçi işlevi yapacağız. Bu değer, sembol listesinin nereden alınacağını tanımlar - yalnızca Piyasa İzleme penceresinden (true) veya mevcut sembollerin ortak listesinden (false).
//____________________________________________________________________ //+------------------------------------------------------------------+ //| POINTER_TO_MARKET_WATCH_WINDOW_OR_TO_COMMON_LIST | //+------------------------------------------------------------------+ bool MWatchOrAllList() { if(curr_mwatch==MARKETWATCH) { return(true); } if(curr_mwatch==ALL_LIST_SYMBOLS) { return(false); } //--- return(true); }
Döngüdeki sembol sayısını elde ettikten sonra, tüm listeyi gözden geçirmeli ve dizi boyutunu bir değer artırarak her yinelemede sembol adını diziye eklemeliyiz. Sembol adı ise SymbolName() işlevi kullanılarak indeks numarasıyla elde edilir.
int SymbolName( int pos, // list index number bool selected // true – only MarketWatch symbols );
Sembol listesini seçmek için SymbolName() işlevinde MWatchOrAllList() işaretçi işlevi de kullanılır. GetSymbolsToArray() işlevi tam kodu:
//____________________________________________________________________ //+------------------------------------------------------------------+ //| FILLING_SYMBOL_ARRAY_WITH_NAMES | //+------------------------------------------------------------------+ void GetSymbolsToArray() { // If only the current symbol data is required if(curr_mwatch==CURRENT) { ArrayResize(symbols,1); symbols[0]=_Symbol; } //--- // If data on all symbols from Market Watch window or // or the entire symbol list is required if(curr_mwatch==MARKETWATCH || curr_mwatch==ALL_LIST_SYMBOLS) { // Receive the number of symbols in Market Watch window cnt_symb=SymbolsTotal(MWatchOrAllList()); //--- for(int i=0; i<=cnt_symb-1; i++) { string nm_symb=""; //--- ArrayResize(symbols,i+1); // Increase the array size by one once again //--- // Receive a name of a symbol from Market Watch window nm_symb=SymbolName(i,MWatchOrAllList()); symbols[i]=nm_symb; // Put the symbol name into the array } } }
SetSeparateForFormatDate() işlevi çok basittir. Tarih Saat Biçimi (format_date) parametresinin açılır listesinde kullanıcının tercihine göre tarihte ne tür bir ayırıcı kullanılacağını tanımlamak için kullanılır.
//____________________________________________________________________ //+------------------------------------------------------------------+ //| DEFINING_SEPARATOR_FOR_DATE_FORMAT | //+------------------------------------------------------------------+ void SetSeparateForFormatDate() { switch(format_date) { case SEP_POINT1 : case SEP_POINT2 : sep="."; break; // Full point as a separator case SEP_SLASH1 : case SEP_SLASH2 : sep="/"; break; // Slash as a separator } }
Ardından çeşitli kontroller içeren temel döngü gelir. Tüm kontroller başarılı olursa veriler dosyaya yazılır. Aksi takdirde, döngü bozulur, tüm nesneler grafikten silinir ve script dosyası kaldırılır (bir sembol olması durumunda) veya bir sonraki yineleme başlar (birden fazla sembol olması durumunda). symbols[] dizisinin her sembolü döngüde tutarlı bir şekilde çağrılır. İndeks numarası, döngünün her işlevine gönderilir. Böylece, tüm işlevlerdeki doğru dizi korunur.
Döngüdeki mevcut sembol üzerindeki veriler, döngü gövdesinin en başında her yinelemede alınır. Bunun için GetDataCurrentSymbol() işlevi kullanılır. Bu işlevde neler olacağına bakalım.
Veri kullanılabilirliği, sembol verilerini rate[] dizisine kopyalamadan önce CheckLoadHistory() işlevi kullanılarak kontrol edilir. Bu işlev, geliştiriciler tarafından örnek olarak verilmiştir. İlk sürümü MQL5 Referansı'nda bulunabilir. Bu script dosyasında kullanmak için yalnızca küçük düzeltmeler yaptım. Referans, ayrıntılı bir açıklama içermektedir (onu incelemek de iyi bir fikir olabilir); bu nedenle, hemen hemen aynı olduğu için burada kendi sürümümü göstermeyeceğim. Ayrıca, ayrıntılı açıklamalar içeren kodda bulunabilir.
Şu anda söz edilecek tek şey, CheckLoadHistory() işlevinin hata veya başarılı yürütme kodunu döndürmesidir; buna göre switch işleç bloğundan gelen uygun mesaj günlüğe kaydedilir. Alınan koda göre GetDataCurrentSymbol() işlevi ya işlemine devam eder ya da kodunu döndürür.
Her şey yolunda giderse, geçmiş verileri CopyRates() işlevi kullanılarak kopyalanır. Dizi boyutu genel değişkene kaydedilir. Ardından, 1 kodunun döndürülmesi eşliğinde işlevden çıkış gerçekleştirilir. Bir şeyler ters giderse, işlev, switch işecinde çalışmasını durdurur ve 0 veya 2 kodunu döndürür.
//____________________________________________________________________ //+------------------------------------------------------------------+ //| RECEIVE_SYMBOL_DATA | //+------------------------------------------------------------------+ int GetDataCurrentSymbol(int s) { Print("------\n№"+IS(s+1)+" >>>"); // Save a symbol number in the journal //--- // Check and download the necessary amount of requested data int res=CheckLoadHistory(s,_Period); //--- InfoTable(s); ChartRedraw(); // Update the data in the data table //--- switch(res) { case -1 : Print("Unknown symbol "+symbols[s]+" (code: -1)!"); return(0); case -2 : Print("Number of requested bars exceeds the maximum number that can be displayed on a chart (code: -2)!...\n" "...The available amount of data will be used for writing."); break; //--- case -3 : Print("Execution interrupted by user (code: -3)!"); return(2); case -4 : Print("Download failed (code: -4)!"); return(0); case 0 : Print("All symbol data downloaded (code: 0)."); break; case 1 : Print("Time series data is sufficient (code: 1)."); break; case 2 : Print("Time series created based on existing terminal data (code: 2)."); break; //--- default : Print("Execution result is not defined!"); } //--- // Copy data to the array if(CopyRates(symbols[s],_Period,check_start_date,end_date,rates)<=0) { Print("Error when copying symbol data "+symbols[s]+" - ",ErrorDesc(Error())+""); return(0); } else { copied_bars=ArraySize(rates); // Receive array size //--- Print("Symbol: ",symbols[s],"; Timeframe: ",gStrTF(_Period),"; Copied bars: ",copied_bars); } //--- return(1); // Return 1, if all is well }
Bunun ardından, program tekrar OnStart() işlevinin ana döngü gövdesinde yer alır. Kod res yerel değişkeni tarafından atanır ve değerine göre kontrol yapılır. Sıfır değeri, bir hata olduğu anlamına gelir. Bu, döngüdeki mevcut sembolün verilerinin yazılamadığı anlamına gelir. Hata açıklaması günlüğe kaydedilir ve döngünün kesilip kesilmeyeceği (bozulup bozulmayacağı) veya bir sonraki yinelemenin başlatılıp başlatılmayacağı (devam) kararı verilir.
if(res==0) { BC } // If zero, the loop is interrupted or the next iteration starts
Yukarıdaki kod satırı, bu seçimin bazı gizemli BC karakterleri tarafından yapıldığını göstermektedir. Bu, bir makro açılımdır. Bununla ilgili daha fazla bilgi MQL5 Referansı'nda bulunabilir. Burada belirtilmesi gereken tek şey, yukarıdaki örnekte (BC) gösterildiği gibi tüm ifadelerin (bir satıra yazılan) kısa bir girişe yapıştırılabilmesidir. Bazı durumlarda, bu yöntem bir işlevden daha kullanışlı ve kompakt olabilir. Mevcut durumda, bu, aşağıdaki gibi görünür:
// Macro expansion with further action selection #define BC if(curr_mwatch==CURRENT) { break; } if(curr_mwatch==MARKETWATCH || curr_mwatch==ALL_LIST_SYMBOLS) { continue; }
Aşağıda, bu script dosyasında kullanılan diğer makro açılım örnekleri verilmiştir:
#define nmf __FUNCTION__+": " // Macro expansion of the function name before sending the message to the journal //--- #define TRM_DP TerminalInfoString(TERMINAL_DATA_PATH) // Folder for storing the terminal data
GetDataCurrentSymbol(), 2 değerini döndürürse, program bir kullanıcı tarafından silinmiş demektir. MQL5, bu olayı tanımlamak için IsStopped() işlevini içerir. Bu işlev, programın çalışmasını doğru ve zamanında durdurmak için döngülerde çok yardımcı olabilir. İşlev true değerini döndürürse, program zorla silinmeden önce tüm işlemleri gerçekleştirmek için yaklaşık üç saniye vardır. Bizim durumumuzda, tüm grafik nesneleri kaldırılır ve mesaj günlüğe gönderilir:
if(res==2) // Program execution interrupted by user { DelAllScriptObjects(); // Delete all objects created by the script from the chart //--- Print("------\nUser deleted the script!"); break; }
8. Klasörler Oluşturma ve Verileri Dosyalama
CheckCreateGetPath() işlevi, kök veri klasörünün varlığını kontrol eder. Bunu DATA_OHLC olarak adlandıralım ve C:\Metatrader 5\MQL5\Files'a yerleştirelim. Bu, sembol adlarına sahip klasörler içerecektir. Verileri yazmak için dosyalar orada oluşturulacaktır.
Döngüdeki mevcut sembol için kök klasör veya klasör yoksa, işlev onu oluşturur. Her şey yolunda giderse, işlev, dosya oluşturma yolunu içeren bir dize döndürür. Bir hata durumunda veya kullanıcı tarafından grafikten bir hata veya programı silme girişimi olması durumunda, işlev boş bir dize döndürür.
Aşağıdaki kod, anlaşılmasını kolaylaştıran ayrıntılı açıklamalar içerir:
//____________________________________________________________________ //+------------------------------------------------------------------+ //| CHECK_DIRECTORY_AND_CREATE_NECESSARY_DATA_FOLDERS | //+------------------------------------------------------------------+ string CheckCreateGetPath(int s) { int i=1; long search=-1; string ffname="",lpath=""; string file="*.csv",folder="*"; string root="DATA_OHLC\\", // Root data folder fSmb=symbols[s]+"\\", // Symbol name fTF=gStrTF(_Period)+"\\"; // Symbol time frame //--- bool flgROOT=false,flgSYMBOL=false; //--- //+------------------------------------------------------------------+ //| SEARCHING_FOR_DATA_OHLC_ROOT_FOLDER | //+------------------------------------------------------------------+ lpath=folder; search=FileFindFirst(lpath,ffname); // Set search handle in Metatrader 5\MQL5\Files //--- Print("Directory: ",TRM_DP+"\\MQL5\\Files\\"); //--- // Set the flag if the first folder is a root one if(ffname==root) { flgROOT=true; Print("Root folder "+root+" present"); } //--- if(search!=INVALID_HANDLE) // If search handle received { if(!flgROOT) // If the first folder is not a root one { // Sort out all files searching for the root folder while(FileFindNext(search,ffname)) { if(IsStopped()) // Execution interrupted by user { // Delete objects created by the script from the chart DelAllScriptObjects(); //--- Print("------\nUser deleted the script!"); return(""); } //--- if(ffname==root) // Set the flag if found { flgROOT=true; Print("Root folder "+root+" present"); break; } } } //--- FileFindClose(search); search=-1; // Close root folder search handle } else { Print("Error when receiving the search handle or directory "+TRM_DP+" is empty: ",ErrorDesc(Error())); } //--- //+------------------------------------------------------------------+ //| SEARCHING_SYMBOL_FOLDER | //+------------------------------------------------------------------+ lpath=root+folder; //--- // Set search handle in the root folder ..\Files\DATA OHLC\ search=FileFindFirst(lpath,ffname); //--- // Set the flag if the first folder of the current symbol if(ffname==fSmb) { flgSYMBOL=true; Print("Symbol folder "+fSmb+" present"); } //--- if(search!=INVALID_HANDLE) // If search handle is received { if(!flgSYMBOL) // If the first folder is not of the current symbol { // Sort out all the files in the root folder searching the symbol folder while(FileFindNext(search,ffname)) { if(IsStopped()) // Execution interrupted by user { // Delete objects created by the script from the chart DelAllScriptObjects(); //--- Print("------\nUser deleted the script!"); return(""); } //--- if(ffname==fSmb) // Set the flag if found { flgSYMBOL=true; Print("Symbol folder"+fSmb+" present"); break; } } } //--- FileFindClose(search); search=-1; // Close symbol folder search handle } else { Print("Error when receiving search handle or the directory "+path+" is empty"); } //--- //+------------------------------------------------------------------+ //| CREATE_NECESSARY_DIRECTORIES_ACCORDING_TO_CHECK_RESULTS | //+------------------------------------------------------------------+ if(!flgROOT) // If there is no DATA_OHLC... root folder { if(FolderCreate("DATA_OHLC")) // ...we should create it { Print("..\DATA_OHLC\\ root folder created"); } else { Print("Error when creating DATA_OHLC: root folder",ErrorDesc(Error())); return(""); } } //--- if(!flgSYMBOL) // If there is no folder of the symbol, the values of which should be received... { if(FolderCreate(root+symbols[s])) // ...we should create it { Print("..\DATA_OHLC\\" symbol folder created+fSmb+""); //--- return(root+symbols[s]+"\\"); // Return the path for creating the file for writing } else { Print("Error when creating ..\DATA_OHLC\\ symbol folder"+fSmb+"\: ",ErrorDesc(Error())); return(""); } } //--- if(flgROOT && flgSYMBOL) { return(root+symbols[s]+"\\"); // Return the path for creating the file for writing } //--- return(""); }
CheckCreateGetPath() işlevi boş bir satır döndürürse, döngü kesintiye uğrar veya sonraki yineleme zaten bilinen makro açılımı (BC) kullanmaya başlar:
// Receive the path for creating a file and create directories for them // If the line is empty, the loop is interrupted or the next iteration starts if((path=CheckCreateGetPath(s))=="") { BC }
Bu aşamaya ulaştıysanız, bu, verilerin başarıyla kopyalandığı anlamına gelir ve path string değişkeni, mevcut sembolün verilerini döngüye yazmak için dosya oluşturma yolunu içerir.
Dosyaya veri yazmak için WriteDataToFile() işlevi oluşturun. [Yol]+[dosya adı], işlevin başında oluşturulur. Dosya adı, bir sembol adından ve geçerli zaman diliminden oluşur. Örneğin, EURUSD_H1.csv. Böyle bir ada sahip dosya zaten mevcutsa, yalnızca yazmak için açılır. Daha önce yazılmış veriler silinecektir. Bunun yerine yeni veriler yazılacaktır. Dosya başarıyla oluşturulur/açılırsa, FileOpen() işlevi, dosyaya erişmek için kullanılacak tanıtıcıyı döndürür.
Tanıtıcı kontrol ediliyor. Varsa, başlık satırı yazılır. Uygun satır, kullanıcı tarafından hangi başlıkların seçildiğine bağlı olarak yazılacaktır. Geçmiş verilerini yazma ana döngüsü daha sonra başlar.
Bir sonraki satırı yazmadan önce, bu, kullanıcı tarafından belirtilen biçime dönüştürülmelidir. Bunu yapmak için, çubuğun açılış zamanını almalı ve StringSubstr() işlevini kullanarak gün, ay, yıl ve saat değişkenlere göre ayrı ayrı sıralamalıyız. Ardından, tarih ve saatin tek sütunda mı yoksa kullanıcının belirttiği biçime göre ayrı sütunlarda mı yer alacağını belirlemeliyiz. Ardından tüm parçalar StringConcatenate() işlevi kullanılarak tek bir satırda birleştirilir. Tüm satırlar yazıldıktan sonra dosya FileClose() işlevi ile kapatılır.
WriteDataToFile() işlev kodunun tamamı aşağıda gösterilmiştir:
//____________________________________________________________________ //+------------------------------------------------------------------+ //| WRITE_DATA_TO_FILE | //+------------------------------------------------------------------+ void WriteDataToFile(int s) { // Number of decimal places in the symbol price int dgt=(int)SymbolInfoInteger(symbols[s],SYMBOL_DIGITS); //--- string nm_fl=path+symbols[s]+"_"+gStrTF(_Period)+".csv"; // File name //--- // Receive file handle for writing hFl=FileOpen(nm_fl,FILE_WRITE|FILE_CSV|FILE_ANSI,','); //--- if(hFl>0) // If the handle is received { // Write the headers if(format_headers==NSDT_5) { FileWrite(hFl,"\"Date\" ""\"Time\" ""\"Open\" ""\"High\" ""\"Low\" ""\"Close\" ""\"Volume\""); } //--- if(format_headers==NSDT_6) { FileWrite(hFl,"Date","Open","High","Low","Close","Volume"); } //--- // Write the data for(int i=0; i<=copied_bars-1; i++) { if(IsStopped()) // If program execution interrupted by a user { DelAllScriptObjects(); // Delete objects created by the script from the chart //--- Print("------\nUser deleted the script!"); break; } //--- sdt=TSdm(rates[i].time); // Bar open time //--- // Divide the date by year, month and time yyyy=StringSubstr(sdt,0,4); mm=StringSubstr(sdt,5,2); dd=StringSubstr(sdt,8,2); tm=StringSubstr(sdt,11); //--- string sep_dt_tm=""; // Separator of Date and Time columns //--- // Join the data with the separator in the necessary order if(format_date==SEP_POINT1 || format_date==SEP_SLASH1) { sep_dt_tm=" "; } if(format_date==SEP_POINT2 || format_date==SEP_SLASH2) { sep_dt_tm=","; } //--- // Join everything in one line StringConcatenate(sdt,dd,sep,mm,sep,yyyy,sep_dt_tm,tm); //--- FileWrite(hFl, sdt,// Date-time DS_dgt(rates[i].open,dgt), // Open price DS_dgt(rates[i].high,dgt), // High price DS_dgt(rates[i].low,dgt), // Low price DS_dgt(rates[i].close,dgt), // Close price IS((int)rates[i].tick_volume)); // Tick volume price //--- // Update writing progress value for the current symbol pgs_pcnt=((double)(i+1)/copied_bars)*100; //--- // Update data in the table InfoTable(s); if(show_progress) { ChartRedraw(); } } //--- FileClose(hFl); // Close the file } else { Print("Error when creating/opening file!"); } }
Bu, OnStart() işlevinin temel döngüsündeki son işlevdi. Son sembol değilse, bir sonraki için her şey tekrarlanır. Aksi takdirde, döngü bozulur. Kullanıcı, script dosyası parametrelerinde Piyasa İzleme penceresindeki sembol listesini temizlemeyi belirtirse, o anda aktif olmayan grafikleri içeren semboller DelSymbolsFromMarketWatch() işlevi tarafından silinecektir. Bunun ardından, script dosyası tarafından oluşturulan tüm grafik nesneleri silinir ve program durur. Veriler, kullanıma hazırdır.
Verilerin NeuroShell DayTrader Professional'a nasıl indirileceğine ilişkin ayrıntılar blogumda bulunabilir. Script dosyasının çalışmasını gösteren video aşağıda verilmiştir:
Sonuç
Alım satım stratejileri geliştirmek için hangi programı kullanırsam kullanayım, her zaman fikirlerimi daha fazla geliştirmemi engelleyen bazı sınırlamalarla karşılaştım. Nihayetinde, programlamanın bu noktada çok önemli olduğunu anladım. MQL5, gerçekten başarılı olmak isteyen kişiler için en iyi çözümdür. Ancak, yeni fikir arayışı aşamasında, veri analizi ve alım satım stratejilerinin geliştirilmesi için diğer programlar da faydalı olabilir. Tek bir araç kullanmış olsaydım, onları bulmam çok daha uzun sürerdi.
İyi şanslar!
MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/502
- Ücretsiz ticaret uygulamaları
- İşlem kopyalama için 8.000'den fazla sinyal
- Finansal piyasaları keşfetmek için ekonomik haberler
Gizlilik ve Veri Koruma Politikasını ve MQL5.com Kullanım Şartlarını kabul edersiniz