Uzman Danışmandan herhangi bir göstergeyi okumak: CopyBuffer'da oklar, çizgiler ve renk indeksleri
Giriş
Bir gösterge kullanan her yatırımcı er ya da geç şunu ister: "aynısı, ama kendi kendine işlem yapsın". Programcının cevabı basit görünür: göstergeyi iCustom() veya IndicatorCreate() ile oluşturun, değerlerini CopyBuffer() ile okuyun ve sinyal belirdiğinde bir pozisyon açın. Pratikte ilk soru işlemle hiç ilgili değildir. Soru şudur: sinyal hangi tamponda?
Bir ok göstergesinin alış okları için bir tamponu, satış okları için başka bir tamponu olabilir ya da ikisi için tek bir tamponu ve onun yanında bir renk indeksi olabilir. Bir "trend" göstergesi rengi değişen tek bir çizgi çizebilir; renk ise hiç çizilmeyen ayrı bir tamponda saklanır. Market'ten satın alınmış derlenmiş bir gösterge kaynak kodu olmadan gelir, bu yüzden SetIndexBuffer() çağrılarını okumak mümkün değildir. Veri Penceresi biraz yardımcı olur, ama her seferinde yalnızca tek bir mumun çizilen değerlerini gösterir ve renk ve hesaplama tamponlarını tamamen gizler.
Bu yazı, bu soruyu herhangi bir gösterge için dışarıdan nasıl yanıtlayacağınızı gösteriyor: tamponların nasıl saklandığını, hepsinin başka bir programdan nasıl okunacağını, bir ok tamponunun bir çizgiden ve bir renk indeksinden yalnızca değerlerine bakarak nasıl ayırt edileceğini, sinyallerin yeniden çizilip çizilmediğinin nasıl kontrol edileceğini ve kullanıcının hangi numaranın hangi oka karşılık geldiğini görmesi için cevabın grafiğe nasıl geri yazılacağını. Sonuç, kaynak koduyla birlikte üç küçük programdır: bir grafikteki her göstergeyi inceleyen bir betik (Code Base'de ücretsiz), her türden tamponu olan bir demo göstergesi ve herhangi bir göstergeyle dört genel kurala göre işlem yapan bir Uzman Danışman (son ikisi yazının sonunda eksiksiz olarak verilmiştir).
1. Gösterge tamponu aslında nedir
Bir gösterge çıktısını SetIndexBuffer() ile bağlanan dizilerde saklar. Üç türü vardır:
- INDICATOR_DATA: çizilen (çizgiler, histogramlar, oklar) ve Veri Penceresinde gösterilen değerler;
- INDICATOR_COLOR_INDEX: DRAW_COLOR_* stilleri için her mumda kullanılan rengin numarası;
- INDICATOR_CALCULATIONS: ne çizilen ne de gösterilen ara diziler.
Üçü de göstergenin onları bildirdiği sırayla, tek bir ortak sıra içinde numaralandırılır ve üçü de CopyBuffer() ile okunabilir. Bu, birçok programcı için ilk sürprizdir: grafikte hiç görmediğiniz bir tampona da erişilebilir ve renkli göstergelerde çoğu zaman en işe yarayan tampon odur.
Çizimler ve tamponlar aynı şey değildir. Bir DRAW_COLOR_LINE çizimi iki tampon kullanır (değer ve renk indeksi), DRAW_COLOR_CANDLES beş tampon (dört fiyat ve renk indeksi), DRAW_COLOR_ARROW ise iki tampon (okun fiyatı ve rengi). Terminalle birlikte gelen standart Heiken Ashi göstergesi iyi bir örnektir: 0'dan 3'e kadar olan tamponlar mum fiyatlarıdır, 4 numaralı tampon ise renk indeksidir; yükselen mum için 0, düşen mum için 1.
İkinci nokta "hiçbir şeyin" nasıl saklandığıdır. Bir çizgi tamponu genellikle her mumda bir değer tutar. Bir ok tamponu yalnızca ok olan mumlarda bir fiyat, diğer mumlarda ise "boş" bir değer tutar. Hangi değerin boş anlamına geldiğine göstergenin yazarı karar verir: genellikle PlotIndexSetDouble(PLOT_EMPTY_VALUE) ile ayarlanan EMPTY_VALUE (DBL_MAX) seçilir, ama birçok gösterge 0.0 kullanır. 0.0'ı geçerli bir ok fiyatı sayan bir okuma kodu her mumda bir ok görür.
Bakılacak somut bir şey olması için DemoBuffers.mq5 göstergesi (kaynak kodu sonda) her türden tamponu bildirir: çizgi olarak bir basit hareketli ortalama, kapanışın ortalamayı yukarı kestiği yerlerde oklar, renkli çizgi olarak çizilen bir üstel ortalama ve hesaplama tamponu olarak bu ortalamanın eğimi.
SetIndexBuffer(0,ExtSma,INDICATOR_DATA); // line SetIndexBuffer(1,ExtCross,INDICATOR_DATA); // arrows, EMPTY_VALUE where there is none SetIndexBuffer(2,ExtEma,INDICATOR_DATA); // colour line: the value SetIndexBuffer(3,ExtEmaColor,INDICATOR_COLOR_INDEX); // colour line: 0 rising, 1 falling SetIndexBuffer(4,ExtSlope,INDICATOR_CALCULATIONS); // not drawn, not in the Data Window PlotIndexSetInteger(1,PLOT_ARROW,233); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE);
Veri Penceresi bu gösterge için üç değer gösterir: ortalama, ok ve üstel ortalama. 3 ve 4 numaralı tamponlar orada görünmez; oysa çizginin rengi - bu göstergenin en doğal "trend" sinyali - 3 numaralı tamponda bulunur. Şekil 1, bir programın beş tamponun hepsini okuduğunda ne gördüğünü gösteriyor.

Şekil 1. EURUSD H1'de DemoBuffers: Buffer Inspector Kb betiğinin okuduğu haliyle iki çizgi, bir ok tamponu, bir renk indeksi ve bir hesaplama tamponu.
2. Bir göstergenin tüm tamponlarını okumak
Bir grafiğe zaten eklenmiş bir gösterge için tanıtıcı (handle), girdi parametreleri bilinmeden de elde edilebilir:
for(int w=0; w<(int)ChartGetInteger(0,CHART_WINDOWS_TOTAL); w++)
for(int i=0; i<ChartIndicatorsTotal(0,w); i++)
{
string name=ChartIndicatorName(0,w,i);
int h=ChartIndicatorGet(0,w,name); // the same instance the user sees
...
IndicatorRelease(h); // releases our handle, not the indicator
} Bu, göründüğünden daha önemlidir: kullanıcı göstergenin girdi parametrelerini grafikte zaten seçmiştir ve program tam olarak o örneği okur. Grafikte olmayan bir gösterge için IND_CUSTOM ile çağrılan IndicatorCreate(), MQL5\Indicators klasörüne göre göreli adı ve bir MqlParam dizisini alır. Girdi parametreleri bir metin alanından geldiğinde, örneğin "14,0,1.5,true", her öğeye doğru türün verilmesi gerekir:
int CreateByName(const string name,const string params)
{
MqlParam p[];
ArrayResize(p,1);
p[0].type=TYPE_STRING; // the first element is always the name
p[0].string_value=name;
string parts[];
const int n=(StringLen(params)>0) ? StringSplit(params,',',parts) : 0;
ArrayResize(p,n+1);
for(int i=0; i<n; i++)
{
string s=parts[i];
StringTrimLeft(s);
StringTrimRight(s);
if(s=="true" || s=="false")
{ p[i+1].type=TYPE_BOOL; p[i+1].integer_value=(s=="true"); }
else if(StringFind(s,".")>=0 && MathIsValidNumber(StringToDouble(s)))
{ p[i+1].type=TYPE_DOUBLE; p[i+1].double_value=StringToDouble(s); }
else if(s=="0" || StringToInteger(s)!=0)
{ p[i+1].type=TYPE_INT; p[i+1].integer_value=StringToInteger(s); }
else
{ p[i+1].type=TYPE_STRING; p[i+1].string_value=s; }
}
return(IndicatorCreate(_Symbol,_Period,IND_CUSTOM,ArraySize(p),p));
} Bu fonksiyonla çalışırken üç kural çok zaman kazandırır. Değerler, göstergenin girdi parametreleri penceresindeki sırayı izlemelidir ve bir parametre atlanamaz: üçüncü parametreyi ayarlamak için ilk ikisinin de verilmesi gerekir. Bir numaralandırma kendi sayısal değeriyle aktarılır; yani MODE_EMA 1'dir, PRICE_CLOSE da 1'dir. Verilmeyen parametreler ise varsayılan değerlerini korur; bu yüzden boş bir dize göstergeyi tam olarak geldiği haliyle oluşturur.
Kaç tampon var? Tanıtıcı bunu söylemez, ama CopyBuffer() söyler: var olmayan bir tampon numarası için -1 döndürür. 0, 1, 2 ve sonraki tamponları ilk başarısızlığa kadar okumak, veri, renk ve hesaplama tamponlarının toplam sayısını verir.
İki pratik ayrıntı:
- tanıtıcı oluşturulduktan sonra göstergenin hesaplama yapması için zamana ihtiyacı vardır; BarsCalculated(handle) hesaplama bitene kadar -1 döndürür, bu yüzden bir betik Sleep() ile kısa bir döngüde bekler, bir Uzman Danışman ise basitçe bir sonraki tikte yeniden dener;
- kopyalamaya son kapanmış mum olan 1 numaralı mumdan başlayın. Birçok ok göstergesinde oluşmakta olan mum, kapanana kadar değişir ve ona dayanan bir analiz daha sonra kaybolan oklar gösterir.
3. Okları, çizgileri ve renk indekslerini birbirinden ayırmak
Bir tamponun N kapanmış mumu bir dizideyken üç basit istatistik yeterlidir:
- geçerli: ne EMPTY_VALUE ne de NaN olan değerler;
- oklar: geçerli olan ve 0.0'a eşit olmayan değerler;
- küçük tam sayılar: mutlak değeri küçük olan tam sayı değerler.
Sınıflandırma da bunlardan çıkar:
| Tamponun içeriği | 500 mum boyunca nasıl görünür |
|---|---|
| hiçbir şey | geçerli değer yok ya da yalnızca sıfırlar var |
| oklar | bazı değerler var, ama mumların en fazla %30'unda |
| renk indeksi | dar bir aralıkta yalnızca küçük tam sayılar (örneğin 0 ve 1) |
| çizgi | geri kalan her şey: (neredeyse) her mumda bir değer |
int valid=0, arrows=0, ints=0;
double lo=DBL_MAX, hi=-DBL_MAX;
for(int i=0; i<n; i++)
{
if(v[i]==EMPTY_VALUE || !MathIsValidNumber(v[i])) continue;
valid++; lo=MathMin(lo,v[i]); hi=MathMax(hi,v[i]);
if(v[i]!=0.0) arrows++;
if(MathAbs(v[i]-MathRound(v[i]))<1e-9 && MathAbs(v[i])<64) ints++;
}
if(valid==0 || (arrows==0 && hi==0.0 && lo==0.0)) kind="empty";
else if(arrows>0 && arrows<=n*0.3) kind="arrows";
else if(ints==valid && hi-lo<=16) kind="colour index";
else kind="line"; Neden %30? Ok göstergeleri dönüş noktalarını işaretler ve bunlar doğası gereği seyrektir: Fractals mumların yüzde birkaçını, tipik "sinyal" göstergeleri %10'undan azını işaretler. Mumların bir kısmında boş kalan bir çizgi (geçmişin başındaki bir hareketli ortalama) yine de mumların çoğunda değere sahiptir. Eşik bilerek geniş tutulmuştur; eşiğe yakın durumlar sessizce tahmin edilmez, kullanıcıya bildirilir.
Renkler için neden "dar bir aralıkta küçük tam sayılar"? Bir renk indeksi, çizimin her rengi için bir tane olmak üzere 0, 1, 2 vb. değerler alır. Bir çizgi de tam sayı olabilir (yazarının yuvarladığı bir osilatör, bir basamak fonksiyonu), ama o zaman bir çizimin sahip olabileceği bir avuç renkten çok daha geniş bir aralıkta hareket eder.
Şekil 2, Heiken Ashi için sonucu gösteriyor: dört fiyat çizgisi ve 4 numaralı tamponda 0 ve 1 değerlerini alan renk indeksi - bir Uzman Danışmanın mum renginin değişmesine tepki vermek için tam olarak ihtiyaç duyduğu şey.

Şekil 2. Heiken Ashi: dört fiyat çizgisi ve 4 numaralı tamponda Veri Penceresinin göstermediği renk indeksi.
Birkaç durum ayrıca söz etmeyi hak ediyor, çünkü hızlı bir bakışın yanlış cevap verdiği yerler tam da bunlardır:
- EMPTY_VALUE yerine 0.0 ile saklanan oklar. Yukarıdaki sınıflandırma 0.0'ı zaten "ok yok" olarak ele alır, ama yalnızca EMPTY_VALUE ile karşılaştırma yapan bir program her mumda tetiklenir. Her zaman ikisini de kontrol edin.
- Parabolic SAR gibi her mumda bulunan noktalar. Grafikte ok gibi görünürler, ama tamponun her mumda bir değeri vardır; dolayısıyla bu bir çizgidir ve sinyal bir noktanın varlığı değil, noktaların fiyatın diğer tarafına sıçradığı andır.
- Renkli oklar (DRAW_COLOR_ARROW). Ok tamponu mumu ve fiyatı verir, yanındaki renk indeksi ise bunun bir alış oku mu yoksa satış oku mu olduğunu söyler. Tek bir sinyal için iki tampon da gereklidir.
- Hesaplama tamponları. Genellikle çizgi olarak sınıflandırılırlar ve değerleri çoğu zaman fiyattan çok uzaktır. Yine de bakmaya değerler: bazı göstergeler "ham" sinyali, yumuşatma veya filtrelemeden önceki haliyle orada tutar.
4. Kullanıcıya hangi okun hangisi olduğunu göstermek
"tampon 1: oklar, 37, sonuncusu 2026.10.07 08:00" şeklinde bir tablo doğrudur, ama yatırımcının 1 numaralı tamponun yukarı ok mu yoksa aşağı ok mu olduğunu görmek için tarihleri yine de grafikle karşılaştırması gerekir. Tampon numarasını okların hemen yanına yazmak çok daha hızlıdır:
for(int i=n-1, done=0; i>=0 && done<20; i--)
{
if(v[i]==EMPTY_VALUE || v[i]==0.0) continue;
int shift=n-i; // v[] runs from shift n (oldest) to shift 1
datetime t=iTime(_Symbol,_Period,shift);
string obj=StringFormat("BufInspector_%d_%d",buffer,shift);
ObjectCreate(0,obj,OBJ_TEXT,0,t,v[i]); // at the arrow's own price
ObjectSetString(0,obj,OBJPROP_TEXT,"B"+(string)buffer);
done++;
} Etiket, tamponda saklanan değere yerleştirilir; ok göstergelerinde bu, okun çizildiği fiyattır, böylece "B0" ve "B1" tam okların üzerinde belirir. Her tampona ayrı bir renk vermek, iki ok tamponunu birbirinden ayırmayı kolaylaştırır.

Şekil 3. Examples\Fractals: B0 üst fraktalların üstüne, B1 alt fraktalların altına yazılmıştır.
5. Gösterge yeniden çiziyor mu (repaint)?
Ok tutan bir tamponu bulmak cevabın yalnızca yarısıdır. Diğer yarısı, kapanmış bir mumda bir kez beliren bir okun orada kalıp kalmadığıdır. Bazı göstergeler yeni mumlar geldiğinde oklarını taşır veya siler - Fractals bunu tasarımı gereği son iki mum için yapar, ZigZag benzeri göstergeler ise çok daha geriye kadar. Sonradan kaybolan bir okla işlem yapan bir Uzman Danışman, geriye dönüp bakıldığında hiç var olmamış bir sinyalle işlem yapar ve Strateji Test Edicisindeki sonuçları grafikle uyuşmaz.
Kontrolü otomatikleştirmek basittir: son kapanmış mumların değerlerini açılış zamanlarına göre hatırlayın, birkaç yeni mum bekleyin, aynı mumları yeniden okuyun ve karşılaştırın.
datetime ExtTimes[]; // open times of the remembered bars
double ExtValues[]; // buffer values on those bars
//--- remember the last 100 closed bars of buffer b
int Remember(const int handle,const int b)
{
if(CopyTime(_Symbol,_Period,1,100,ExtTimes)!=100) return(0);
return(CopyBuffer(handle,b,1,100,ExtValues));
}
//--- later: how many of the remembered bars changed their value?
int Changed(const int handle,const int b)
{
int changed=0;
for(int i=0; i<ArraySize(ExtTimes); i++)
{
double v[];
if(CopyBuffer(handle,b,ExtTimes[i],1,v)==1 && v[0]!=ExtValues[i])
changed++;
}
return(changed);
} Burada indekse göre değil, zamana göre okumak önemlidir: yeni mumlar geldikten sonra aynı mumun indeksi büyür. Sonuç, göstergenin kaç mum geriye kadar hâlâ fikrini değiştirdiğini söyler ve kullanılacak sinyal mumunu bu sayı belirler. Fractals için cevap her zaman iki mumdur; bir fraktalın 1 numaralı mumda değil de 3 numaralı mumda okunmasının nedeni budur. Oklarını on mum geriye kadar değiştirmeye devam eden bir göstergeyle, Strateji Test Edicisindeki görüntüsü ne olursa olsun, okun ilk belirdiği mumda işlem yapılamaz.
6. Okunacak bir tampon olmadığında
Bazı göstergeler oklarını tamponlar yerine ObjectCreate() ile çizer. Oklar grafikte görünür, Veri Penceresi boştur ve CopyBuffer() yalnızca çizgiler döndürür ya da hiçbir şey döndürmez. Hiçbir program bu tür sinyalleri gösterge tanıtıcısı üzerinden okuyamaz. Bunu kullanıcıya açıkça söylemek gerekir, çünkü aksi halde "doğru tampon numarası" arayışı hiç bitmez. Bir betik bu durumu dolaylı olarak tespit edebilir: kullanıcı okları gördüğü halde ok tamponu olmayan bir gösterge neredeyse kesinlikle nesne çiziyordur. Buffer Inspector Kb raporunda bunu belirtir.
7. Tamponlardan işlem kurallarına
Tampon bilindikten sonra, dört kural bir yatırımcının getirdiği göstergelerin neredeyse tamamını kapsar:
| Kural | Tipik göstergeler | Sinyal mumundaki koşul |
|---|---|---|
| bir ok belirir | Fractals, ok göstergeleri | değer boş değil ve 0 değil |
| bir tampon bir seviyeyi keser | RSI 30/70, CCI +-100, 0 etrafındaki osilatörler | önceki < seviye <= mevcut |
| bir tampon diğerini keser | MACD ana/sinyal, Stochastic %K/%D, Alligator çizgileri | önceki a < b ve mevcut a >= b |
| bir renk indeksi belirli bir değeri alır | Heiken Ashi, renkli ortalamalar, "trend" mumları | önceki != k ve mevcut == k |
BufferSignalEA.mq5 (kaynak kodu sonda) tam olarak bu kuralları uygular; gösterge adı, girdi parametreleri, kural ve tampon numaraları Uzman Danışmanın girdi parametreleridir, böylece tek bir Uzman Danışman herhangi bir göstergeye yönlendirilebilir. Kalbinde, sinyal mumunun ve ondan önceki mumun iki değerini okuyan tek bir fonksiyon vardır:
bool RuleFires(const ENUM_SIGNAL_RULE rule,const int buffer,const double level,const int buffer2)
{
if(rule==SIGNAL_OFF)
return(false);
double a[],b[];
//--- a[0] = the bar before the signal bar, a[1] = the signal bar
if(CopyBuffer(ExtHandle,buffer,InpSignalBar,2,a)!=2)
return(false);
switch(rule)
{
case SIGNAL_ARROW:
return(!IsEmpty(a[1]) && a[1]!=0.0);
case SIGNAL_CROSS_UP:
return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]<level && a[1]>=level);
case SIGNAL_CROSS_DOWN:
return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]>level && a[1]<=level);
case SIGNAL_BECOMES:
return(!IsEmpty(a[1]) && MathAbs(a[1]-level)<0.5 && (IsEmpty(a[0]) || MathAbs(a[0]-level)>=0.5));
case SIGNAL_LINE_UP:
case SIGNAL_LINE_DOWN:
{
if(CopyBuffer(ExtHandle,buffer2,InpSignalBar,2,b)!=2)
return(false);
if(IsEmpty(a[0]) || IsEmpty(a[1]) || IsEmpty(b[0]) || IsEmpty(b[1]))
return(false);
if(rule==SIGNAL_LINE_UP)
return(a[0]<b[0] && a[1]>=b[1]);
return(a[0]>b[0] && a[1]<=b[1]);
}
}
return(false);
} Renk indeksi double olarak saklandığı için 0,5 toleransla karşılaştırılır. Karar her yeni mumda bir kez, OnTick() içinde her şeyden önce verilir:
const datetime bar=iTime(_Symbol,_Period,0); if(bar==ExtLastBar) return; if(BarsCalculated(ExtHandle)<InpSignalBar+2) return; // the indicator is not ready yet: try on the next tick ExtLastBar=bar; const bool buy =RuleFires(InpBuyRule, InpBuyBuffer, InpBuyLevel, InpBuyBuffer2); const bool sell=RuleFires(InpSellRule,InpSellBuffer,InpSellLevel,InpSellBuffer2); if(buy==sell || HasPosition()) return; // no signal, both at once, or already in the market
Oluşmakta olan mumun her tikinde değerlendirilen bir kural birkaç kez ve sonradan kaybolan sinyallerde tetiklenirdi; kapanmış bir mumda mum başına tek karar, Strateji Test Edicisini ve canlı grafiği uyum içinde tutar.
Her kuralın gerçekten tetiklendiğini kontrol etmek için Uzman Danışman Strateji Test Edicisinde beş göstergeyle, hepsi için aynı basit çıkışla çalıştırıldı (0,1 lot, zarar durdur 300 puan, kâr al 600 puan, aynı anda tek pozisyon). Aşağıdaki rakamlar bir işlem sonucu değil, kuralların işlevsel bir testidir: hiçbir filtre veya pozisyon yönetimi olmadan işlem yapılan bir gösterge bir strateji değildir.
| Gösterge ve kural | Tamponlar | İşlem | Kârlı |
|---|---|---|---|
| Fractals, bir ok belirir (sinyal mumu 3) | Alış 1, Satış 0 | 184 | 53 |
| MACD, ana çizgi sinyal çizgisini keser | 0 ve 1 | 160 | 46 |
| RSI(14), 30'u yukarı / 70'i aşağı keser | 0 | 113 | 36 |
| Heiken Ashi, renk 0 / 1 olur | 4 | 170 | 53 |
| DemoBuffers, alış için ok, satış için renk 1 | 1 ve 3 | 152 | 39 |
EURUSD H1, 2025.10.01 - 2026.10.01, "1 minute OHLC" modeli. Beşi de tek bir başarısız istek olmadan çalışıyor; farklı işlem sayıları, açık pozisyon yokken her göstergenin ne sıklıkta sinyal verdiğinden kaynaklanıyor.
8. Bir göstergeyi başka bir zaman diliminde okumak
Aynı okuma yöntemi daha yüksek bir zaman dilimindeki bir gösterge için de çalışır; çoğu filtre de böyle kurulur ("yalnızca fiyat H4'te 200 EMA'nın üzerindeyken al"). Tanıtıcı zaman dilimi belirtilerek oluşturulur ve bu tanıtıcının 1 numaralı mumu, o zaman diliminin son kapanmış mumudur:
int h4=iMA(_Symbol,PERIOD_H4,200,0,MODE_EMA,PRICE_CLOSE); ... double ma[]; if(CopyBuffer(h4,0,1,1,ma)==1 && iClose(_Symbol,PERIOD_H4,1)>ma[0]) allowBuy=true;
İki not. Birincisi, program başladığında başka bir zaman diliminin verileri hazır olmayabilir: terminal geçmişi oluşturana kadar BarsCalculated() -1 döndürür ve CopyBuffer() başarısız olur; bu yüzden bu durum "sinyal yok" olarak kabul edilmemeli, kontrol tekrarlanmalıdır. İkincisi, düşük zaman diliminde verilen bir kararda yüksek zaman diliminin 0 numaralı mumunu asla okumayın. Bir H1 grafiğinde oluşmakta olan H4 mumu her saat değişir; bu yüzden onu okuyan bir filtre, H4 mumunun nasıl biteceğini zaten bilen Strateji Test Edicisinde kusursuz görünür, canlı grafikte ise farklı davranır.
9. Programlar
- Buffer Inspector Kb (betik, MQL5\Scripts, Code Base'de ücretsiz): onu göstergenin zaten bulunduğu bir grafiğe sürükleyin. Her penceredeki her göstergeyi inceler, tabloyu Uzmanlar günlüğüne yazdırır, Comment() ile grafikte gösterir ve her ok tamponunun son oklarını etiketler. Girdi parametrelerine bir gösterge adı yazıldığında göstergeyi kendisi oluşturur; bu, Market'ten alınan derlenmiş göstergeler için kullanışlıdır. "Only remove the labels" seçeneğiyle çalıştırıldığında grafiği temizler.
- DemoBuffers.mq5 (gösterge, MQL5\Indicators): deneyler için bir çizgi, oklar, renk indeksiyle birlikte renkli bir çizgi ve bir hesaplama tamponu içeren tek bir gösterge. Kaynak kodu aşağıda.
- BufferSignalEA.mq5 (Uzman Danışman, MQL5\Experts): yukarıdaki dört kurala göre herhangi bir göstergeyle işlem yapar; gösterge, onun girdi parametreleri, kurallar ve tampon numaraları Uzman Danışmanın girdi parametreleridir. Kaynak kodu aşağıda.
Sonuç
Bir göstergenin tamponları, kaynak kodu mevcut olmasa bile onun herkese açık arayüzüdür. Hepsini okumak, değerlerine göre sınıflandırmak, grafikteki okları etiketlemek ve değerlerin ne kadar geriye kadar değiştiğini kontrol etmek, "hangi tampon numarasını kullanmalıyım?" sorusunu göstergenin yazarına sorulacak bir sorudan on saniyelik bir kontrole dönüştürür. Aynı sınıflandırma, dört genel işlem kuralının - ok, seviye, çizgi kesişimi ve renk değişimi - temelidir; bu kurallar tek bir Uzman Danışmanın daha önce hiç görmediği göstergelerle kapanmış mumlar üzerinde çalışmasını sağlar, böylece Strateji Test Edicisinin gösterdiği, canlı grafikte olacak olanla aynıdır.
Kaynak kodu
Her kod listesini kendi adıyla gösterilen klasöre kaydedin (terminalde Dosya → Veri Klasörünü Aç) ve MetaEditor'da F7 ile derleyin.
DemoBuffers.mq5 (MQL5\Indicators)
//+------------------------------------------------------------------+ //| DemoBuffers.mq5 | //| Bohdan Klok | //| https://www.mql5.com/en/users/kbposht | //+------------------------------------------------------------------+ #property copyright "Bohdan Klok" #property link "https://www.mql5.com/en/users/kbposht" #property version "1.00" #property description "Example: one indicator with every kind of buffer - a line, arrows," #property description "a colour line with its colour index, and a calculation buffer that is not drawn." #property indicator_chart_window #property indicator_buffers 5 #property indicator_plots 3 //--- plot 0: a plain line (buffer 0) #property indicator_label1 "SMA" #property indicator_type1 DRAW_LINE #property indicator_color1 clrSilver #property indicator_width1 1 //--- plot 1: arrows (buffer 1), drawn only where the close crosses the SMA upward #property indicator_label2 "Cross up" #property indicator_type2 DRAW_ARROW #property indicator_color2 clrDodgerBlue #property indicator_width2 2 //--- plot 2: a colour line (buffer 2 = value, buffer 3 = colour index) #property indicator_label3 "Trend EMA" #property indicator_type3 DRAW_COLOR_LINE #property indicator_color3 clrSeaGreen,clrOrangeRed #property indicator_width3 2 //--- input parameters input int InpSmaPeriod=20; // SMA period input int InpEmaPeriod=50; // EMA period //--- indicator buffers double ExtSma[]; // 0: line double ExtCross[]; // 1: arrows, EMPTY_VALUE where there is none double ExtEma[]; // 2: colour line value double ExtEmaColor[]; // 3: colour index, 0 rising, 1 falling double ExtSlope[]; // 4: calculations, EMA change over one bar //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0,ExtSma,INDICATOR_DATA); SetIndexBuffer(1,ExtCross,INDICATOR_DATA); SetIndexBuffer(2,ExtEma,INDICATOR_DATA); SetIndexBuffer(3,ExtEmaColor,INDICATOR_COLOR_INDEX); SetIndexBuffer(4,ExtSlope,INDICATOR_CALCULATIONS); PlotIndexSetInteger(1,PLOT_ARROW,233); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE); PlotIndexSetInteger(0,PLOT_DRAW_BEGIN,InpSmaPeriod); PlotIndexSetInteger(2,PLOT_DRAW_BEGIN,InpEmaPeriod); IndicatorSetString(INDICATOR_SHORTNAME,StringFormat("DemoBuffers(%d,%d)",InpSmaPeriod,InpEmaPeriod)); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { if(rates_total<InpEmaPeriod+2) return(0); const double k=2.0/(InpEmaPeriod+1.0); int start=(prev_calculated>1) ? prev_calculated-1 : 1; if(prev_calculated==0) { ExtEma[0]=close[0]; ExtSlope[0]=0.0; ExtEmaColor[0]=0.0; } for(int i=start; i<rates_total; i++) { //--- SMA, empty until there are enough bars if(i>=InpSmaPeriod-1) { double sum=0.0; for(int j=0; j<InpSmaPeriod; j++) sum+=close[i-j]; ExtSma[i]=sum/InpSmaPeriod; } else ExtSma[i]=EMPTY_VALUE; //--- EMA, its slope and the colour of the slope ExtEma[i]=ExtEma[i-1]+k*(close[i]-ExtEma[i-1]); ExtSlope[i]=ExtEma[i]-ExtEma[i-1]; ExtEmaColor[i]=(ExtSlope[i]>=0.0) ? 0.0 : 1.0; //--- an arrow under the bar where the close crosses the SMA upward ExtCross[i]=EMPTY_VALUE; if(i>=InpSmaPeriod && ExtSma[i-1]!=EMPTY_VALUE && close[i-1]<ExtSma[i-1] && close[i]>=ExtSma[i]) ExtCross[i]=low[i]; } return(rates_total); } //+------------------------------------------------------------------+
BufferSignalEA.mq5 (MQL5\Experts)
//+------------------------------------------------------------------+ //| BufferSignalEA.mq5 | //| Bohdan Klok | //| https://www.mql5.com/en/users/kbposht | //+------------------------------------------------------------------+ #property copyright "Bohdan Klok" #property link "https://www.mql5.com/en/users/kbposht" #property version "1.00" #property description "Example: trades the signals of any indicator by four generic rules on its" #property description "buffers - an arrow appears, a level is crossed, one buffer crosses another, a colour" #property description "index becomes a value. Signals are read on the closed bar, once per bar." #include <Trade\Trade.mqh> //--- the four generic rules enum ENUM_SIGNAL_RULE { SIGNAL_OFF=0, // Disabled SIGNAL_ARROW=1, // Arrow appears in the buffer SIGNAL_CROSS_UP=2, // Buffer crosses the level upward SIGNAL_CROSS_DOWN=3, // Buffer crosses the level downward SIGNAL_LINE_UP=4, // Buffer crosses the second buffer upward SIGNAL_LINE_DOWN=5, // Buffer crosses the second buffer downward SIGNAL_BECOMES=6 // Buffer becomes equal to the level (colour index) }; //--- input parameters input string InpIndicator="Examples\\Fractals"; // Indicator name inside MQL5\Indicators input string InpParams=""; // Its inputs, comma separated (empty = defaults) input int InpSignalBar=3; // Signal bar (1 = last closed bar) input ENUM_SIGNAL_RULE InpBuyRule=SIGNAL_ARROW; // Buy rule input int InpBuyBuffer=1; // Buy: buffer input double InpBuyLevel=0.0; // Buy: level input int InpBuyBuffer2=0; // Buy: second buffer input ENUM_SIGNAL_RULE InpSellRule=SIGNAL_ARROW; // Sell rule input int InpSellBuffer=0; // Sell: buffer input double InpSellLevel=0.0; // Sell: level input int InpSellBuffer2=1; // Sell: second buffer input double InpLots=0.1; // Lot input int InpStopPoints=300; // Stop loss, points input int InpTakePoints=600; // Take profit, points input ulong InpMagic=20261010; // Magic number //--- global variables CTrade ExtTrade; int ExtHandle=INVALID_HANDLE; datetime ExtLastBar=0; //+------------------------------------------------------------------+ //| Indicator inputs from "14,0,1.5,true,text" | //+------------------------------------------------------------------+ int CreateByName(const string name,const string params) { MqlParam p[]; ArrayResize(p,1); p[0].type=TYPE_STRING; p[0].string_value=name; string parts[]; const int n=(StringLen(params)>0) ? StringSplit(params,',',parts) : 0; ArrayResize(p,n+1); for(int i=0; i<n; i++) { string s=parts[i]; StringTrimLeft(s); StringTrimRight(s); if(s=="true" || s=="false") { p[i+1].type=TYPE_BOOL; p[i+1].integer_value=(s=="true"); } else if(StringFind(s,".")>=0 && MathIsValidNumber(StringToDouble(s))) { p[i+1].type=TYPE_DOUBLE; p[i+1].double_value=StringToDouble(s); } else if(s=="0" || StringToInteger(s)!=0) { p[i+1].type=TYPE_INT; p[i+1].integer_value=StringToInteger(s); } else { p[i+1].type=TYPE_STRING; p[i+1].string_value=s; } } return(IndicatorCreate(_Symbol,_Period,IND_CUSTOM,ArraySize(p),p)); } //+------------------------------------------------------------------+ //| A value is empty if it is EMPTY_VALUE or not a number | //+------------------------------------------------------------------+ bool IsEmpty(const double v) { return(v==EMPTY_VALUE || !MathIsValidNumber(v)); } //+------------------------------------------------------------------+ //| Does the rule fire on the signal bar? | //+------------------------------------------------------------------+ bool RuleFires(const ENUM_SIGNAL_RULE rule,const int buffer,const double level,const int buffer2) { if(rule==SIGNAL_OFF) return(false); double a[],b[]; //--- two values: a[0] = the bar before the signal bar, a[1] = the signal bar if(CopyBuffer(ExtHandle,buffer,InpSignalBar,2,a)!=2) return(false); switch(rule) { case SIGNAL_ARROW: return(!IsEmpty(a[1]) && a[1]!=0.0); case SIGNAL_CROSS_UP: return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]<level && a[1]>=level); case SIGNAL_CROSS_DOWN: return(!IsEmpty(a[0]) && !IsEmpty(a[1]) && a[0]>level && a[1]<=level); case SIGNAL_BECOMES: return(!IsEmpty(a[1]) && MathAbs(a[1]-level)<0.5 && (IsEmpty(a[0]) || MathAbs(a[0]-level)>=0.5)); case SIGNAL_LINE_UP: case SIGNAL_LINE_DOWN: { if(CopyBuffer(ExtHandle,buffer2,InpSignalBar,2,b)!=2) return(false); if(IsEmpty(a[0]) || IsEmpty(a[1]) || IsEmpty(b[0]) || IsEmpty(b[1])) return(false); if(rule==SIGNAL_LINE_UP) return(a[0]<b[0] && a[1]>=b[1]); return(a[0]>b[0] && a[1]<=b[1]); } } return(false); } //+------------------------------------------------------------------+ //| Is there an open position of this EA? | //+------------------------------------------------------------------+ bool HasPosition() { for(int i=PositionsTotal()-1; i>=0; i--) if(PositionGetSymbol(i)==_Symbol && PositionGetInteger(POSITION_MAGIC)==(long)InpMagic) return(true); return(false); } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if(InpSignalBar<1) { Print("Signal bar must be 1 or more: the forming bar can still change"); return(INIT_PARAMETERS_INCORRECT); } ExtHandle=CreateByName(InpIndicator,InpParams); if(ExtHandle==INVALID_HANDLE) { PrintFormat("Cannot load %s (error %d)",InpIndicator,GetLastError()); return(INIT_FAILED); } ExtTrade.SetExpertMagicNumber(InpMagic); ExtLastBar=0; return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(ExtHandle!=INVALID_HANDLE) IndicatorRelease(ExtHandle); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- one decision per bar const datetime bar=iTime(_Symbol,_Period,0); if(bar==ExtLastBar) return; if(BarsCalculated(ExtHandle)<InpSignalBar+2) return; ExtLastBar=bar; const bool buy=RuleFires(InpBuyRule,InpBuyBuffer,InpBuyLevel,InpBuyBuffer2); const bool sell=RuleFires(InpSellRule,InpSellBuffer,InpSellLevel,InpSellBuffer2); if(buy==sell || HasPosition()) return; const double point=SymbolInfoDouble(_Symbol,SYMBOL_POINT); if(buy) { const double ask=SymbolInfoDouble(_Symbol,SYMBOL_ASK); ExtTrade.Buy(InpLots,_Symbol,ask,ask-InpStopPoints*point,ask+InpTakePoints*point); } else { const double bid=SymbolInfoDouble(_Symbol,SYMBOL_BID); ExtTrade.Sell(InpLots,_Symbol,bid,bid+InpStopPoints*point,bid-InpTakePoints*point); } } //+------------------------------------------------------------------+


