MetaTrader 5'te L1 Trend Filtrelemeyi Uygulama
- Verilerin uzun vadeli dinamikleri korunur;
- Kısa vadeli dalgalanmalar ve gürültü bastırılır;
- Yapısal kırılma noktaları (trend eğimindeki değişiklikler) otomatik olarak tespit edilir.

İçindekiler
- Giriş
- 1. Trend filtrelemenin problem formülasyonu
1.1. Hodrick-Prescott filtresi
1.2. L1 trend filtreleme yöntemi
1.3. Düzenlileştirme parametresi λ'nın rolü
1.4. Geometrik yorumlama
1.5. λmax'ı hesaplama algoritması - 2. L1 trendini hesaplamak için MQL5 metotları
2.1. L1TrendFilterLambdaMax
2.2. L1TrendFilter - 3. L1 trendini hesaplama örnekleri
3.1. Sentetik veriler (rastgele yürüyüş) üzerinde L1 trendi
3.2. S&P 500 fiyat serisi için L1 trendi
3.3. λmax'ın ölçeklendirme özellikleri
3.3.1. Brown hareketi için sayısal deney
3.3.2. Finansal zaman serileri için ölçeklendirme
3.3.3. Ölçeklendirmenin pratik sonuçları
3.4. L1 trendi göstergeleri
3.4.1. L1TrendFilter.mq5 - L1 trendi göstergesi
3.4.2. L1TrendFilterSlope.mq5 - L1 trendi eğim göstergesi
3.4.3. L1TrendFilterSlopeSign.mq5 - trend yönü göstergesi - 3.4.4. L1 Trendine Dayalı Volatilite Göstergeleri
3.4.4.1. L1Volatility.mq5 - artık volatilite göstergesi
3.4.4.2. L1VolatilitySmoothed.mq5 - yumuşatılmış artık volatilite göstergesi
3.4.4.3. L1VolatilityAbsolute.mq5 - mutlak volatilite göstergesi
3.4.4.4. L1VolatilityNormalized.mq5 - normalleştirilmiş volatilite göstergesi
3.4.4.5. L1VolatilityNormalizedSmoothed.mq5 - yumuşatılmış normalleştirilmiş volatilite göstergesi
3.4.4.6. L1VolatilityRegime.mq5 - volatiliteye dayalı piyasa rejimi tespiti -
3.5. Alım-satım stratejilerinde L1 trendinin uygulanması
3.5.1. Moving Average stratejisi
3.5.1.1. L1 trend filtreleme verimliliğini değerlendirmek için genel metodoloji
3.5.1.2. Moving Average stratejisi için sonuçlar
3.5.2. MACD stratejisi
3.5.2.1. MACD stratejisi için sonuçlar
3.5.3. ADX stratejisi
3.5.3.1. ADX stratejisi için sonuçlar
3.5.4. EMA stratejisi
3.5.4.1. EMA stratejisi için sonuçlar
3.5.5. Moving Average, MACD, ADX ve EMA alım-satım stratejilerinde L1 filtresinin kullanımına ilişkin özet - Sonuç
Giriş
Finansal zaman serileri yüksek gürültü seviyeleri, sıkça görülen aykırı değerler ve değişen piyasa rejimleri ile karakterizedir. Pratikte alım-satım sistemlerinde bu durum basit ve ölçülebilir bir şekilde ortaya çıkar: klasik "yumuşatma" filtreleri (hareketli ortalamalar, HP) geriden gelir, eğim değişikliklerinin anlarını bulanıklaştırır ve genellikle yerel düzeltmeleri geri dönüşler olarak yorumlar - sonuç olarak yanlış giriş/çıkışların sayısı artar, Kar Faktörü azalır ve düşüş büyür. Ek olarak, λ düzenlileştirme parametresinin seçimi genellikle manuel ayarlamaya indirgenir ve enstrümanlar, zaman dilimleri ve geçmiş uzunlukları arasında iyi aktarılmaz.Bu makale, bu sorunlara L1 trend filtrelemeye dayalı pratik bir çözüm önermektedir: ikinci farkların L1 düzenlileştirmesi ile optimizasyon, otomatik olarak net kırılma noktalarına sahip parçalı doğrusal bir yakınlaşma oluşturur. Temel avantajlar, kırılma noktalarının rejim değişiklikleri olarak net bir şekilde yorumlanması, λmax'ı hesaplayarak ve λ = coef · λmax göreceli parametresine geçerek düzenlileştirme ölçeğini ayarlama yeteneği ve MQL5'te uygulamaya uygun doğrusal hesaplama karmaşıklığıdır.
Yalnızca teoriyi değil, aynı zamanda eksiksiz bir pratik yol haritası da sunuyoruz: λmax ve L1 trendini hesaplama metotları, üç gösterge (trend, eğim, eğim işareti), yedi L1 trendi volatilite göstergesi, Uzman Danışmanlara entegrasyon ve tekrarlanabilir bir test protokolü (dört filtreleme modu, bakiye/varlık dışa aktarımı ve görselleştirme).
1. Trend Filtreleme Probleminin Formülasyonu
İki bileşenin toplamı olarak temsil edilen bir skaler zaman serisini ele alıyoruz:
Burada
trend bileşeni,
ise bir gürültü veya düzensiz bir bileşendir.
Amaç,
gözlemlenen verilerden
trendini hesaplamaktır.
Problem, orijinal verilere bağlılık ile hesaplanan trendin yumuşaklığı arasında bir dengeleme olarak ifade edilebilir.
1.1. Hodrick-Prescott Filtresi
Hodrick-Prescott filtresi, trendi minimizasyon probleminin çözümü olarak tanımlar:

Burada λ parametresi yumuşatma derecesini kontrol eder.
HP filtresinin temel özellikleri:
- Verilere göre doğrusallık;
- Hesaplama karmaşıklığı O(n);
- λ küçük olduğunda, trend orijinal verilere yaklaşır;
- λ büyük olduğunda, trend en iyi doğrusal yaklaşmaya yönelir.
Ancak, HP filtresi her zaman yumuşak bir trend üretir ve eğimdeki keskin değişiklikleri zayıf bir şekilde algılar.
1.2. L1 Trend Filtreleme Yöntemi
L1 trend filtrelemenin ana fikri, orijinal verilere yakın olan ancak eğimde mümkün olduğunca az değişiklik içeren bir trend bulmaktır. Karesel eğriliği en aza indiren klasik yumuşatma yöntemlerinin aksine, L1 yaklaşımı ikinci farkların mutlak değerlerinin toplamını en aza indirir.
Bu da temelde farklı bir sonuca yol açmaktadır:
- İkinci farkların çoğu sıfıra eşit olur,
- Trend otomatik olarak doğrusal segmentlere ayrılır.
Dolayısıyla, L1 filtresi trendi yumuşak hale getirmeye çalışmaz, bunun yerine gözlemlenen verileri açıklayan minimum sayıda yapısal değişikliği bulur. Bu da yöntemi, dinamiklerin genellikle yarı doğrusal yükseliş ve düşüş aşamalarından oluştuğu finansal zaman serileri için özellikle uygun hale getirmektedir.
L1 trend filtrelemede, ikinci farklar üzerindeki karesel ceza L1 normu ile değiştirilir ve trend bir dışbükey optimizasyon probleminin çözümü olarak tanımlanır:

Matris formunda:

Tanımlamalar:
- y - girdi zaman serisi;
- x - hesaplanan trend;
- D - ikinci fark matrisi;
- λ>=0 - düzenlileştirme parametresi.
L1 normunun kullanılması temelde farklı bir sonuca yol açar: birçok ikinci fark sıfır olur, bu da trendin parçalı doğrusal olduğu anlamına gelir.
İkinci fark şu şekilde tanımlanır:

ise,
noktaları düz bir çizgi üzerinde yer alır.
Bu nedenle, sıfır ikinci fark trendin doğrusal bir bölümüne karşılık gelirken, sıfır olmayan ikinci fark bir kırılma noktasına karşılık gelir. L1 normu, Dx vektöründe seyrekliği destekler, yani çoğu ikinci fark sıfır olur. Bu, ilgili aralıklar boyunca trendin doğrusal olduğu anlamına gelir. İkinci farkların sıfır olmadığı noktalar trend kırılma noktaları olarak yorumlanır.
Böylece, L1 Trend Filtreleme yöntemi, trendi otomatik olarak yapısal değişiklik noktalarıyla birbirine bağlanan bir dizi doğrusal segment olarak oluşturur.
L1 trend filtrelemenin temel özellikleri:
- Trend doğrusal segmentlerden oluşur;
- Kırılma noktaları, zaman serisindeki yapısal değişiklikler olarak yorumlanır;
- λ = 0 olduğunda, trend orijinal verilerle çakışır;
- λ yeterince büyük olduğunda, trend tam olarak en iyi doğrusal yaklaşma haline gelir;
- Hesaplama karmaşıklığı gözlem sayısında doğrusal kalır.
1.3. Düzenlileştirme Parametresi λ'nın Rolü
λ parametresi, yaklaşma doğruluğu ile trend karmaşıklığı arasındaki dengeyi kontrol eder:
| λ'nın değeri | Çözümün niteliği |
|---|---|
| λ=0 | x=y, yumuşatma yok |
| Küçük λ | Zayıf yumuşatma, birçok kırılma noktası |
| Orta λ | Parçalı doğrusal trend |
| Büyük λ | Neredeyse doğrusal trend |
| λ≥λmax | Tamamen doğrusal trend |
Tablo 1. L1 trendinin düzenlileştirme parametresi λ'ya bağımlılığı
Böylece, λ trend kırılma noktalarının sayısını ve konumlarını kontrol eder.
1.4. Problemin Geometrik Yorumu
İstenen trend x, n boyutlu bir uzayda bir nokta olarak düşünülebilir. Yaklaşma doğruluğundan sorumlu olan amaç fonksiyonunun ilk terimi, y gözlem noktasını merkez alan bir Öklid topu tanımlar: x, y'ye ne kadar yakınsa hata o kadar küçük olur.İkinci farkların L1 normu ile düzenlileştirme terimi, dışbükey bir çokyüzlü küme (polihedron) tanımlar. L2 düzenlileştirmede ortaya çıkan düzgün elipsoidlerin aksine, bu çokyüzlünün keskin köşeleri vardır. Bu köşeler, trendin bazı ikinci farklarının sıfıra eşit olduğu durumlara karşılık gelir.
Seyrek çözümlere yol açan tam da L1 normunda keskin köşelerin varlığıdır: optimum çözüm, sadece bazı kısıtlamaların aktif olduğu çokyüzlünün bir tepe noktasında yer alma eğilimindedir. Bu, çoğu ikinci farkın sıfır olduğu ve trendin otomatik olarak parçalı doğrusal bir form aldığı anlamına gelir.
Optimum çözüm, Öklid topu ile L1 çokyüzlüsü arasındaki ilk temas noktasına karşılık gelir. Bu noktada trend, sınırlı sayıda kırılma noktasında birbirine bağlanan doğrusal segmentlerden oluşur.
λmax parametresi, Öklid topunun L1 çokyüzlüsüne bir tepe noktasında değil, doğrusal fonksiyonların alt uzayı boyunca temas ettiği duruma karşılık gelir. Bu durumda, tüm ikinci farklar sıfırdır ve trend tam anlamıyla doğrusaldır.
λ ≥ λmax olduğunda, L1 kısıtlamalarının hiçbiri aktif hale gelmez, bu nedenle düzenlileştirmedeki daha fazla artış çözümü değiştirmez ve trend doğrusal kalır.
1.5. λmax'ı Hesaplama Algoritması
N uzunluğundaki bir girdi vektörü y için maksimum düzenlileştirme parametresi λmax'ın hesaplanmasını ele alalım.
1. (N-2)×N büyüklüğünde ikinci fark matrisi D'yi oluşturalım:

2. Dy eğrilik vektörünü hesaplayalım.
3. Doğrusal denklem sistemini çözelim:
![]()
4. v vektörünün maksimum (mutlak değere göre) elemanını alalım:

Finansal zaman serileri için λmax parametresi önemli bir pratik anlama sahiptir:
- Düzenlileştirme parametresinin normalleştirilmesine olanak sağlar;
- λ seçimini veri ölçeğinden bağımsız hale getirir;
- Farklı zaman serileri arasında karşılaştırma yapmayı kolaylaştırır;
- λ'nın maksimum düzenlileştirmenin bir kesri olarak yorumlanmasına olanak tanır.
λ=coef_lambda_max⋅λmax şeklinde göreceli bir parametre kullanmak, burada coef_lambda_max ∈ (0, 1), pratik uygulamayı büyük ölçüde basitleştirir.
Aşağıdaki gösterge ve Uzman Danışman örneklerinde, λ, λmax biriminde kullanılacak, parametre ayarları ise coef_lambda_max çarpanını belirtecektir.
2. L1 Trendinin Hesaplanması için MQL5 Metotları
L1 trend filtrelemenin pratik kullanımı için double ve float türünde vektörler için iki metot uygulanmaktadır.
- L1TrendFilterLambdaMax maksimum düzenlileştirme parametresini hesaplar;
- L1TrendFilter, λmax biriminde de belirtilebilen λ düzenlileştirme parametresinin belirli bir değeri için L1 trendini hesaplar.
2.1. L1TrendFilterLambdaMax
Bir veri vektörü için maksimum düzenlileştirme parametresi λmax'ı hesaplama metodu.
vector<double> için hesaplama:
bool vector::L1TrendFilterLambdaMax( double &lambda_max // the maximum value of the regularization parameter lambda )vector<float> için hesaplama:
bool vectorf::L1TrendFilterLambdaMax( float &lambda_max // the maximum value of the regularization parameter lambda );
Parametreler
lambda
[out] Düzenlileştirme parametresi λmax'ın maksimum değeri veya hata durumunda -1.
Geri dönüş değeri
Başarılı olursa true geri döndürür.
Not
Bellek tüketimi vektör büyüklüğüyle doğrusal olarak artar.
2.2. L1TrendFilter
Bir veri vektörü için L1 trendini hesaplama metodu.
vector<double> için hesaplama:
bool vector::L1TrendFilter( double lambda, // regularization parameter bool relative, // flag indicating lambda is in λmax units vector& result // output vector with L1 filtering result );
vector<float> için hesaplama:
bool vectorf::L1TrendFilter( float lambda, // regularization parameter bool relative, // flag indicating lambda is in λmax units vectorf& result // output vector with L1 filtering result );
Parametreler
lambda
[in] Düzenlileştirme parametresi lambda'nın değeri (relative = true ise lambda, λmax'ın kesri olarak [0, 1] aralığında tanımlanır).
relative
[in] λ'nın nasıl belirtildiğini gösteren bayrak. True ise, λ, λmax biriminde belirtilir; aksi takdirde, mutlak değer kullanılır.
result
[out] L1 filtreleme sonucunu içeren vektör.
Geri dönüş değeri
Başarılı olursa true geri döndürür.
Not
Bellek tüketimi vektör büyüklüğüyle doğrusal olarak artar.
λ için önerilen aralıklar (relative modu).
| λ çarpanı | Sonuç |
|---|---|
| 0.005 – 0.015 | neredeyse L2, gürültülü |
| 0.02 – 0.04 | mikro segmentler |
| 0.04 – 0.07 | sinyaller için en uygun |
| 0.07 – 0.12 | orta vadeli trendler |
| 0.12 – 0.25 | piyasa rejimleri |
| > 0.3 | birkaç segment |
Tablo 2. λmax birimi cinsinden λ çalışma aralıkları
Pratikteki uygulamalar için 0.04-0.25 aralığındaki çarpanların kullanılması tavsiye edilir.
3. Uygulama Örnekleri
Bu bölümde, simüle edilmiş Brown hareketi verileri ve S&P 500 fiyat verileri üzerinde L1 trend hesaplamalarını ele alıyoruz ve hem Brown hareketi hem de FOREX piyasası verileri için λmax'ın ölçeklendirme özelliklerini inceliyoruz.
Ayrıca, belirli semboller ve zaman dilimleri için en iyi L1 trend ayrışmasını elde etmek için optimum düzenlileştirme parametrelerinin (λmax çarpanları) belirlenmesine yardımcı olan üç gösterge varyantı sunuyoruz.
Ayrıca, Moving Average, MACD, ADX ve EMA stratejileri için işlem sinyallerini filtreleme sonuçları da (L1 trendiyle uyum) sunulmuştur.
3.1. Simüle Edilmiş Veriler (Rastgele Yürüyüş) Üzerinde L1 Trendinin Hesaplanması
Örnek olarak, L1 trendini simüle edilmiş Brown hareketi verileri üzerinde λ düzenlileştirme parametresinin farklı değerleri ile hesaplamayı ele alalım.
Komut dosyası kodu:
//+------------------------------------------------------------------+ //| TestL1Trend.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property script_show_inputs #include <Graphics\Graphic.mqh> //+------------------------------------------------------------------+ //| Generate Brown movement data | //+------------------------------------------------------------------+ void BMData(vector<double> &data,int &data_count) { data.Resize(data_count); data[0] = 0.0; for(int i=1; i<data_count; i++) data[i] = data[i-1] + (MathRand()/32767.0 - 0.5); } //+------------------------------------------------------------------+ //| CopyValues | //+------------------------------------------------------------------+ bool CopyValues(vector<double> &data_v,double &data[]) { int data_count=(int)data.Size(); if(data_count==0) return(false); ArrayResize(data,data.Size()); for(int i=0; i<data_count; i++) data[i]=data_v[i]; return(true); } //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { MathSrand(1); int data_count=1000; vector<double> data_test; BMData(data_test,data_count); //--- prepare arrays for chart double x[],y[]; ArrayResize(x,data_count); ArrayResize(y,data_count); for(int i=0; i<data_count; i++) x[i]=i; //--- CGraphic graphic; long chart=0; string name="test"; if(ObjectFind(chart,name)<0) graphic.Create(chart,name,0,0,0,1000,600); else graphic.Attach(chart,name); graphic.BackgroundMain("L1 Trend filtering (random walk) with different lambda"); graphic.BackgroundMainSize(16); graphic.HistoryNameWidth(60); graphic.HistoryColor(ColorToARGB(clrGray,255)); graphic.XAxis().AutoScale(false); graphic.XAxis().Min(0); graphic.XAxis().Max(data_count); //--- CopyValues(data_test,y); graphic.CurveAdd(x,y,CURVE_LINES,"Data").LinesWidth(1); //--- L1TrendFilterLambdaMax double lambda_max=0.0; if(data_test.L1TrendFilterLambdaMax(lambda_max)) PrintFormat("lambda_max=%f",lambda_max); //--- vector<double> data_l1; const double lambda_factors[]= {1.0,0.9,0.8,0.5,0.25,0.1,0.01,0.05,0.001,0.0005}; for(int i=0; i<ArraySize(lambda_factors); i++) { double lambda=lambda_max*lambda_factors[i]; PrintFormat("%d. lambda=%f",i+1,lambda); bool res=data_test.L1TrendFilter(lambda_factors[i],true,data_l1); if(res) { CopyValues(data_l1,y); graphic.CurveAdd(x,y,CURVE_LINES,"lambda="+DoubleToString(lambda,0)).LinesWidth(3); } } //--- graphic.CurvePlotAll(); graphic.Update(); DebugBreak(); } //+------------------------------------------------------------------+Çıktı:
TestL1Trend (EURUSD,H1) lambda_max=51703.353749 TestL1Trend (EURUSD,H1) 1. lambda=51703.353749 TestL1Trend (EURUSD,H1) 2. lambda=46533.018374 TestL1Trend (EURUSD,H1) 3. lambda=41362.682999 TestL1Trend (EURUSD,H1) 4. lambda=25851.676874 TestL1Trend (EURUSD,H1) 5. lambda=12925.838437 TestL1Trend (EURUSD,H1) 6. lambda=5170.335375 TestL1Trend (EURUSD,H1) 7. lambda=517.033537 TestL1Trend (EURUSD,H1) 8. lambda=2585.167687 TestL1Trend (EURUSD,H1) 9. lambda=51.703354 TestL1Trend (EURUSD,H1) 10. lambda=25.851677
Bu örnekte, λ düzenlileştirme parametresinin azaltılmasının trend segmentlerine daha ayrıntılı bir ayrışma sağladığı görülebilir (Şekil 1).
Eğer λ ≥ λmax ise, çözüm doğrusal regresyona (global trend) karşılık gelen düz bir çizgi haline gelir.

Şekil 1. Brown hareketi verileri üzerinde λ'nın farklı değerleri ile L1 filtresi hesaplama örneği
L1 trendini hesaplamaya yönelik fonksiyonlar hem double hem de float vektörler için kullanılabilir.
Hesaplamaların karşılaştırılması için test komut dosyası aşağıda sunulmuştur.
//+------------------------------------------------------------------+ //| TestL1TrendFloatDouble.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Graphics\Graphic.mqh> uint32_t ExtSeed=1; //+------------------------------------------------------------------+ //| Generate Brown movement data | //+------------------------------------------------------------------+ template<typename T> void BMData(vector<T> &data,uint64_t data_count) { MathSrand(ExtSeed); data.Resize(data_count); data[0] = 0.0; for(uint64_t i=1; i<data_count; i++) data[i] = data[i-1] + T(MathRand()/32767.0 - 0.5); } //+------------------------------------------------------------------+ //| CopyValues | //+------------------------------------------------------------------+ template<typename T> bool CopyValues(double &data[],const vector<T> &data_v) { if(ArrayResize(data,data.Size())!=data.Size()) return(false); for(uint64_t i=0; i<data.Size(); i++) data[i]=data_v[i]; return(true); } //+------------------------------------------------------------------+ //| L1TrendCalculate | //+------------------------------------------------------------------+ template<typename T> bool L1TrendCalculate(double &result[],uint64_t data_count,double lambda,bool lambda_is_relative) { vector<T> data_test; BMData(data_test,data_count); vector<T> vres; if(!data_test.L1TrendFilter((T)lambda,lambda_is_relative,vres)) return(false); if(ArrayResize(result,(uint32_t)vres.Size())!=vres.Size()) return(false); for(uint64_t n=0; n<result.Size(); n++) result[n]=vres[n]; return(true); } //+------------------------------------------------------------------+ //| TestRun | //+------------------------------------------------------------------+ bool TestRun(uint32_t data_count,uint32_t mode) { //--- create graph CGraphic graphic; long chart=0; string name="L1TrendTest"; if(ObjectFind(chart,name)<0) graphic.Create(chart,name,0,0,0,1280,600); else graphic.Attach(chart,name); string mode_name="("; if((mode&1)==1) mode_name+="DOUBLE"; if((mode&3)==3) mode_name+=" & "; if((mode&2)==2) mode_name+="FLOAT"; mode_name+=")"; graphic.BackgroundMain("L1Trend filtering (random walk) with different lambda "+mode_name); graphic.BackgroundMainSize(16); graphic.HistoryNameWidth(60); graphic.HistoryColor(ColorToARGB(clrGray,255)); graphic.XAxis().AutoScale(false); graphic.XAxis().Min(0); graphic.XAxis().Max(data_count); //--- prepare arrays double x[]; double y[]; if(ArrayResize(x,data_count)!=data_count) return(false); for(uint32_t i=0; i<data_count; i++) x[i]=i; vector<double> v; BMData(v,data_count); v.Swap(y); graphic.CurveAdd(x,y,CURVE_LINES,"Data").LinesWidth(1); //--- calculate const double lambda_factors[]= {1.0,0.9,0.8,0.5,0.25,0.1,0.01,0.05,0.001,0.0005}; //--- double if((mode&1)==1) { for(uint64_t i=0; i<lambda_factors.Size(); i++) { if(L1TrendCalculate<double>(y,data_count,lambda_factors[i],true)) graphic.CurveAdd(x,y,CURVE_LINES,"DBL="+DoubleToString(lambda_factors[i],4)).LinesWidth(4); } } //--- float if((mode&2)==2) { for(uint64_t i=0; i<lambda_factors.Size(); i++) { if(L1TrendCalculate<float>(y,data_count,(float)lambda_factors[i],true)) graphic.CurveAdd(x,y,CURVE_LINES,"FLT="+DoubleToString(lambda_factors[i],4)).LinesWidth(2); } } //--- update graphic.CurvePlotAll(); graphic.Update(); return(true); } //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { for(uint32_t n=0; !IsStopped(); n++,Sleep(1000)) { TestRun(1000,1+n%3); if((n%3)==2) ExtSeed++; } } //+------------------------------------------------------------------+
Çıktı:

3.2. S&P 500 Fiyat Serisi için L1 Trendinin Hesaplanması
"l_1 Trend Filtering, S.J. Kim, K. Koh, S. Boyd, and D. Gorinevsky, SIAM Review, problems and techniques section, 51(2):339–360, May 2009" orijinal makalesinden log(S&P 500) hesaplamasına bakalım.
Komut dosyasını çalıştırmak için "snp500.txt" dosyasındaki veriler kullanılır. Şu klasöre yerleştirilmelidir: terminal_veri_klasörü\MQL5\Files
//+------------------------------------------------------------------+ //| TestL1TrendFilterSP500.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property script_show_inputs #include <Graphics\Graphic.mqh> //+------------------------------------------------------------------+ //| LoadData | //+------------------------------------------------------------------+ void LoadData(string filename,vector<double> &data,int &data_count) { data_count=0; ResetLastError(); int file_handle=FileOpen(filename,FILE_READ|FILE_TXT|FILE_ANSI); if(file_handle!=INVALID_HANDLE) { while(!FileIsEnding(file_handle)) { string str=FileReadString(file_handle); if(data.Size()<=(ulong)data_count) data.Resize(data_count+1); data[data_count]=StringToDouble(str); data_count++; } FileClose(file_handle); } else PrintFormat("Failed to open %s file, Error code = %d",filename,GetLastError()); //--- data.Resize(data_count); } //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { long chart=0; string name="log SP500"; int data_count=0; vector<double> data_sp500; LoadData("snp500.txt",data_sp500,data_count); vector<double> data_l1_sp500; data_l1_sp500.Resize(data_count); //--- L1TrendFilterLambdaMax double lambda_max=0.0; if(data_sp500.L1TrendFilterLambdaMax(lambda_max)) PrintFormat("Lambda_max=%f",lambda_max); double lambda=50; //--- L1TrendFilter if(data_sp500.L1TrendFilter(lambda,false,data_l1_sp500)) { //--- prepare arrays for chart double x[],y[],y2[]; ArrayResize(x,data_count); ArrayResize(y,data_count); ArrayResize(y2,data_count); for(int i=0; i<data_count; i++) { x[i]=i; y[i]=data_sp500[i]; y2[i]=data_l1_sp500[i]; } //--- CGraphic graphic; if(ObjectFind(chart,name)<0) graphic.Create(chart,name,0,0,0,1000,600); else graphic.Attach(chart,name); graphic.BackgroundMain("log SP500 L1 trend filtering"); graphic.BackgroundMainSize(16); graphic.HistoryNameWidth(60); graphic.HistoryColor(ColorToARGB(clrGray,255)); graphic.XAxis().AutoScale(false); graphic.XAxis().Min(0); graphic.XAxis().Max(data_count); graphic.XAxis().DefaultStep(100); graphic.CurveAdd(x,y,CURVE_LINES,"SP500").LinesWidth(1); graphic.CurveAdd(x,y2,CURVE_LINES,"L1 trend").LinesWidth(3); graphic.CurvePlotAll(); graphic.Update(); DebugBreak(); } } //+------------------------------------------------------------------+
Komut dosyasının yürütülmesinin sonucu Şekil 2'de gösterilmiştir.

Şekil 2. S&P 500 endeksinin log fiyat serisi için L1 trendini hesaplama örneği
Uzmanlar sekmesinde, söz konusu zaman serisi için λmax değeri görüntülenecektir:
TestL1TrendFilterSP500 (EURUSD,H1) Lambda_max=37394.835512
Bu komut dosyası, L1TrendFilterLambdaMax ve L1TrendFilter metotlarının, yöntemin yazarlarının orijinal makalesinde olduğu gibi sabit bir λ = 50 değeri ile kullanımını göstermektedir.
Aşağıdaki örneklerde, λ düzenlileştirme parametresinin mutlak değerleri yerine, relative = true bayrağı ile göreceli değerler (λmax biriminde) kullanılacaktır.
3.3. λmax'ın Ölçekleme Özellikleri
λmax parametresi, çözümün global bir doğrusal yaklaşmaya dönüştüğü düzenlileştirmenin üst sınırını tanımladığı için L1 filtrelemede önemli bir rol oynar. Bu niceliğin ilginç bir özelliği, zaman serisinin uzunluğuna bağlı olarak ölçeklenmesidir.
Sayısal deneyler, λmax'ın gözlem sayısına uygun olarak bir kuvvet yasasına göre arttığını göstermektedir:
![]()
Tanımlamalar: T - zaman serisinin uzunluğu, α - ölçeklendirme üssü.
Rastgele bir yürüyüş (Brown hareketi) için, üssün α ≈ 2.5'e yakın olması gerektiği gösterilebilir. Brown hareketinin genliği
Sonuç olarak, birleşik ölçeklendirme şu ilişkiye yol açar:
![]()
Bu da α ≈ 2.5 üssüne karşılık gelir.
Dolayısıyla, zaman serisinin uzunluğu arttıkça, λmax değeri doğrusal bir artıştan çok daha hızlı bir şekilde artar.
3.3.1. Brown Hareketi için Sayısal Deney
Ölçekleme yasasını doğrulamak için sayısal bir deney yapıldı.
Farklı T zaman serisi uzunlukları için Brown hareketinin gerçekleşmeleri oluşturuldu ve ardından λmax'ın ortalama değeri hesaplandı.
Logaritmik bir yaklaşma kullanıldı:
![]()
Bu da α üssünün doğrusal regresyon kullanılarak hesaplanmasını sağlar.
Deney için kod aşağıda verilmiştir.
//+------------------------------------------------------------------+ //| TestScalingLambdaMaxBrownMovement.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Graphics\Graphic.mqh> //+------------------------------------------------------------------+ //| Generate Brownian motion | //+------------------------------------------------------------------+ void GenerateBrownian(int N,vector<double> &data) { data.Resize(N); data[0] = 0.0; for(int i=1; i<N; i++) data[i] = data[i-1] + (MathRand()/32767.0 - 0.5); } //+------------------------------------------------------------------+ //| LinearRegression | //+------------------------------------------------------------------+ void LinearRegression(const double &x[], const double &y[], int n, double &a, double &b) { double sx = 0.0, sy = 0.0, sxx = 0.0, sxy = 0.0; for(int i = 0; i < n; i++) { sx += x[i]; sy += y[i]; sxx += x[i] * x[i]; sxy += x[i] * y[i]; } double denom = n * sxx - sx * sx; a = (n * sxy - sx * sy) / denom; b = (sy - a * sx) / n; } //+------------------------------------------------------------------+ //| TestScaling with statistics | //+------------------------------------------------------------------+ void TestScalingStatistics() { MathSrand(42); int RUNS = 10; // int MC = 10; // Monte Carlo double alpha_values[]; ArrayResize(alpha_values, RUNS); // --- geometric grid of T int nT = 8; int Tvals[]; ArrayResize(Tvals, nT); int T0 = 64; for(int i = 0; i < nT; i++) Tvals[i] = T0 << i; Print("Scaling test with statistics"); //--- double logT[]; double logLambda[]; vector<double> bm; vector<double> l1_trend; for(int run = 0; run < RUNS; run++) { ArrayResize(logT, nT); ArrayResize(logLambda, nT); //--- for(int i = 0; i < nT; i++) { int T = Tvals[i]; double lambda_sum = 0.0; l1_trend.Resize(T); for(int k = 0; k < MC; k++) { GenerateBrownian(T, bm); double lambda_max=0.0; if (bm.L1TrendFilterLambdaMax(lambda_max)) lambda_sum += lambda_max; bm.L1TrendFilter(0.2,true,l1_trend); } double lambda_avg = lambda_sum / MC; logT[i] = MathLog((double)T); logLambda[i] = MathLog(lambda_avg); } // --- regression double alpha, c; LinearRegression(logT, logLambda, nT, alpha, c); alpha_values[run] = alpha; PrintFormat("run %d -> alpha = %.6f", run+1, alpha); } //--- statistics double mean = 0.0; for(int i=0;i<RUNS;i++) mean += alpha_values[i]; mean /= RUNS; // --- standard deviation double var = 0.0; for(int i=0;i<RUNS;i++) var += (alpha_values[i]-mean)*(alpha_values[i]-mean); var /= (RUNS - 1); double stddev = MathSqrt(var); // --- standard error of mean double sem = stddev / MathSqrt((double)RUNS); // --- theoretical comparison double alpha_theory=2.5; double percent_error=MathAbs(mean-alpha_theory)/alpha_theory*100.0; //--- results PrintFormat("mean alpha = %.6f", mean); PrintFormat("std deviation = %.6f", stddev); PrintFormat("standard error = %.6f", sem); PrintFormat("theory = %.4f", alpha_theory); PrintFormat("percent error from theory = %.4f %%", percent_error); } //+------------------------------------------------------------------+ //| TestScaling | //+------------------------------------------------------------------+ void TestScaling() { MathSrand(1); // --- geometric grid of T int nT = 8; int Tvals[]; ArrayResize(Tvals,nT); //--- int T0 = 64; for(int i=0; i<nT; i++) Tvals[i]=T0<<i; // 64 * 2^i //--- double logT[], logLambda[]; ArrayResize(logT,nT); ArrayResize(logLambda,nT); //--- Print("scaling test for lambda_max"); for(int i=0; i<nT; i++) { int T = Tvals[i]; //--- Monte-Carlo simulations int MC=1000; double lambda_sum = 0.0; for(int k=0; k<MC; k++) { vector<double> bm; GenerateBrownian(T, bm); double lambda_max=0.0; if(bm.L1TrendFilterLambdaMax(lambda_max)) lambda_sum += lambda_max; } double lambda_avg=lambda_sum/MC; logT[i]= MathLog((double)T); logLambda[i]=MathLog(lambda_avg); PrintFormat("T=%5d <lambda_max>=%.6f",T,lambda_avg); } // --- linear regression in log-log double alpha, c; LinearRegression(logT,logLambda,nT,alpha,c); //--- PrintFormat("estimated scaling exponent alpha = %.4f",alpha); double alpha_theory=2.5; PrintFormat("theoretical value = %.4f",alpha_theory); //--- plot scaling law CGraphic g; g.Create(0, "ScalingLaw",0,0,0,1000,600); g.BackgroundMain("Scaling law of lambda_max (Brownian motion)"); g.BackgroundMainSize(16); g.CurveAdd(logT, logLambda, CURVE_POINTS, "Simulation"); //--- double xfit[2], yfit[2]; xfit[0] = logT[0]; xfit[1] = logT[nT-1]; //--- yfit[0] = alpha*xfit[0] + c; yfit[1] = alpha*xfit[1] + c; //---least squares fit g.CurveAdd(xfit, yfit, CURVE_LINES, "LS fit"); g.CurvePlotAll(); g.Update(); DebugBreak(); } //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- calculate scaling with statistics TestScalingStatistics(); //--- show sample results TestScaling(); } //+------------------------------------------------------------------+
Çıktı:
TestScalingLambdaMaxBrownMovement (EURUSD,H1) Scaling test with statistics TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 1 -> alpha = 2.480774 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 2 -> alpha = 2.530977 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 3 -> alpha = 2.435511 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 4 -> alpha = 2.461984 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 5 -> alpha = 2.467093 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 6 -> alpha = 2.487965 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 7 -> alpha = 2.532371 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 8 -> alpha = 2.455831 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 9 -> alpha = 2.483485 TestScalingLambdaMaxBrownMovement (EURUSD,H1) run 10 -> alpha = 2.420283 TestScalingLambdaMaxBrownMovement (EURUSD,H1) mean alpha = 2.475627 TestScalingLambdaMaxBrownMovement (EURUSD,H1) std deviation = 0.036281 TestScalingLambdaMaxBrownMovement (EURUSD,H1) standard error = 0.011473 TestScalingLambdaMaxBrownMovement (EURUSD,H1) theory = 2.5000 TestScalingLambdaMaxBrownMovement (EURUSD,H1) percent error from theory = 0.9749 % TestScalingLambdaMaxBrownMovement (EURUSD,H1) scaling test for lambda_max TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 64 <lambda_max>=97.302362 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 128 <lambda_max>=566.626861 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 256 <lambda_max>=3162.076116 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 512 <lambda_max>=18271.204936 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 1024 <lambda_max>=100057.796790 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 2048 <lambda_max>=578620.887399 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 4096 <lambda_max>=3192555.936035 TestScalingLambdaMaxBrownMovement (EURUSD,H1) T= 8192 <lambda_max>=17895314.647170 TestScalingLambdaMaxBrownMovement (EURUSD,H1) estimated scaling exponent alpha = 2.4967 TestScalingLambdaMaxBrownMovement (EURUSD,H1) theoretical value = 2.5000
Çift log grafiği (çift logaritmik ölçekte), Brown hareketi için λmax fonksiyonunun veri noktası sayısına bağlı olarak bir kuvvet yasası ilişkisi gösterdiğini yansıtmaktadır.

Şekil 3. Brown hareketi için LambdaMax'ın kuvvet yasası bağımlılığı
Simülasyon sonuçları şunları göstermektedir:
mean alpha = 2.4756 std deviation = 0.036 theory = 2.5 percent error ≈ 1%
Dolayısıyla, deney teorik ilişkiyi doğruluyor:
![]()
Çift logaritmik ölçekteki çift log grafiği log(λmax) ve log(T) arasında doğrusal bir ilişki olduğunu ortaya koymaktadır.
3.3.2. Finansal Zaman Serileri için Ölçeklendirme
Benzer bir deney FOREX piyasası fiyat serileri için de gerçekleştirildi. Farklı döviz pariteleri ve zaman dilimleri için α üssü hesaplandı.
Sonuçlar, gerçek finansal veriler için α değerinin de α ≈ 2.45-2.60 aralığında olduğunu ve bunun Brown hareketi için teorik değere çok yakın olduğunu göstermektedir. Bu, λmax'ın ölçekleme davranışının neredeyse evrensel olduğu ve farklı piyasalar ve zaman dilimlerinde geçerli olduğu anlamına gelir.
TestScalingLambdaMaxSymbol.mq5 komut dosyası, M1-H1 standart zaman dilimlerinde belirli bir sembol için λmax üssünü hesaplar.
//+------------------------------------------------------------------+ //| TestScalingLambdaMaxSymbol.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property script_show_inputs //--- input parameters input string WorkSymbol = "EURUSD"; // Symbol input int YearStart = 2024; input int YearEnd = 2025; #include <Graphics\Graphic.mqh> //+------------------------------------------------------------------+ //| GetHistoricalData | //+------------------------------------------------------------------+ bool GetHistoricalData(double &data[], string symbol, ENUM_TIMEFRAMES tf, int year_start, int year_end) { datetime from = StringToTime(IntegerToString(year_start) + ".01.01 00:00"); datetime to = StringToTime(IntegerToString(year_end) + ".12.31 23:59"); int copied = CopyClose(symbol, tf, from, to, data); if(copied <= 0) { Print("Error in CopyClose: ", GetLastError()); ArrayResize(data, 0); return false; } //PrintFormat("Loaded bars: %d (%s %s)", ArraySize(data), symbol, EnumToString(tf)); return true; } //+------------------------------------------------------------------+ //| LinearRegression | //+------------------------------------------------------------------+ void LinearRegression(const double &x[], const double &y[], int n, double &a, double &b) { double sx = 0, sy = 0, sxx = 0, sxy = 0; for(int i = 0; i < n; i++) { sx += x[i]; sy += y[i]; sxx += x[i] * x[i]; sxy += x[i] * y[i]; } double denom = n*sxx - sx*sx; if(denom!=0) { a = (n*sxy-sx*sy)/denom; b = (sy-a*sx)/n; } } //+------------------------------------------------------------------+ //| Scaling test for one timeframe | //+------------------------------------------------------------------+ bool TestScalingLambaMaxTF(string symbol, ENUM_TIMEFRAMES tf, double &logT_out[], double &logLambda_out[], double &alpha_out) { MathSrand(42); double prices[]; if(!GetHistoricalData(prices, symbol, tf, YearStart, YearEnd)) return false; int Tvals[]; int nT=8; int T0=64; ArrayResize(Tvals, nT); for(int i = 0; i < nT; i++) Tvals[i] = T0 << i; ArrayResize(logT_out, nT); ArrayResize(logLambda_out, nT); int data_size = ArraySize(prices); vector<double> data_prices; for(int i = 0; i < nT; i++) { int T = Tvals[i]; int MC = 1000; double lambda_sum = 0.0; for(int k = 0; k < MC; k++) { if(data_size < T) break; int start = MathRand() % (data_size - T); data_prices.Resize(T); for(int j=0; j<T; j++) data_prices[j]=prices[start+j]; double lambda_max=0.0; if(data_prices.L1TrendFilterLambdaMax(lambda_max)) lambda_sum += lambda_max; } double lambda_avg = lambda_sum / MC; logT_out[i]=MathLog((double)T); logLambda_out[i]=MathLog(lambda_avg); //PrintFormat("TF=%s T=%5d <lambda_max>=%.6f", EnumToString(tf), T, lambda_avg); } double c; LinearRegression(logT_out, logLambda_out, nT, alpha_out, c); PrintFormat("%s (%s) estimated scaling exponent = %.4f", symbol,EnumToString(tf), alpha_out); return true; } //+------------------------------------------------------------------+ //| TestScalingLambdaMaxSymbol | //+------------------------------------------------------------------+ void TestScalingLambdaMaxSymbol(string symbol) { ENUM_TIMEFRAMES timeframes[] = {PERIOD_M1, PERIOD_M2, PERIOD_M3, PERIOD_M4, PERIOD_M5, PERIOD_M6, PERIOD_M10, PERIOD_M12, PERIOD_M15, PERIOD_M20, PERIOD_M30, PERIOD_H1 }; uint colors[] = {clrRed,clrBlue,clrGreen,clrOrange,clrPurple,clrDarkGreen,clrCyan, clrNavy,clrOrangeRed,clrDodgerBlue,clrCrimson,clrDarkRed }; //--- CGraphic g; g.Create(0,"ScalingLawTest",0,0,0,1000,600); g.BackgroundMain("Scaling law of lambda_max ("+symbol+")"); g.BackgroundMainSize(16); PrintFormat("%s scaling test for standard timeframes",symbol); for(int i = 0; i < ArraySize(timeframes); i++) { double logT[], logLambda[], alpha; // Print("processing timeframe: ", EnumToString(timeframes[i]), " -----"); if(TestScalingLambaMaxTF(symbol,timeframes[i],logT,logLambda,alpha)) { g.CurveAdd(logT,logLambda,ColorToARGB(colors[i % ArraySize(colors)],255),CURVE_POINTS_AND_LINES,EnumToString(timeframes[i])); } } g.CurvePlotAll(); g.Update(); //--- DebugBreak(); } //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- estimate lambda_max scale exponent for price data TestScalingLambdaMaxSymbol(WorkSymbol); } //+------------------------------------------------------------------+
EURUSD için sonuçlar:
TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD scaling test for standard timeframes TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M1) estimated scaling exponent = 2.5038 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M2) estimated scaling exponent = 2.5350 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M3) estimated scaling exponent = 2.5034 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M4) estimated scaling exponent = 2.5422 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M5) estimated scaling exponent = 2.5341 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M6) estimated scaling exponent = 2.5132 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M10) estimated scaling exponent = 2.5188 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M12) estimated scaling exponent = 2.5126 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M15) estimated scaling exponent = 2.5208 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M20) estimated scaling exponent = 2.4887 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_M30) estimated scaling exponent = 2.5695 TestScalingLambdMaxSymbol (EURUSD,H1) EURUSD (PERIOD_H1) estimated scaling exponent = 2.6118
EURUSD için sonuçlar (standart zaman dilimleri M1-H1) Şekil 4'te gösterilmiştir.

Şekil 4. Farklı EURUSD zaman dilimlerinde λmax'ın kuvvet yasası bağımlılığı
Benzer şekilde, diğer döviz pariteleri de analiz edilebilir.
USDJPY için:
TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY scaling test for standard timeframes TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M1) estimated scaling exponent = 2.5851 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M2) estimated scaling exponent = 2.5825 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M3) estimated scaling exponent = 2.4889 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M4) estimated scaling exponent = 2.5099 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M5) estimated scaling exponent = 2.5059 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M6) estimated scaling exponent = 2.4939 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M10) estimated scaling exponent = 2.5548 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M12) estimated scaling exponent = 2.5641 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M15) estimated scaling exponent = 2.5525 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M20) estimated scaling exponent = 2.5390 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_M30) estimated scaling exponent = 2.5805 TestScalingLambdMaxSymbol (EURUSD,H1) USDJPY (PERIOD_H1) estimated scaling exponent = 2.4645
USDJPY için sonuçlar da kuvvet yasası ilişkisiyle iyi bir şekilde yaklaşmaktadır.

Şekil 5. Farklı USDJPY zaman dilimlerinde λmax'ın kuvvet yasası bağımlılığı
GBPUSD için:
TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD scaling test for standard timeframes TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M1) estimated scaling exponent = 2.5235 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M2) estimated scaling exponent = 2.5449 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M3) estimated scaling exponent = 2.5439 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M4) estimated scaling exponent = 2.5427 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M5) estimated scaling exponent = 2.5248 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M6) estimated scaling exponent = 2.5308 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M10) estimated scaling exponent = 2.5293 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M12) estimated scaling exponent = 2.5235 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M15) estimated scaling exponent = 2.5069 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M20) estimated scaling exponent = 2.4977 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_M30) estimated scaling exponent = 2.5659 TestScalingLambdMaxSymbol (EURUSD,H1) GBPUSD (PERIOD_H1) estimated scaling exponent = 2.5524
Benzer bir durum GBPUSD fiyat serisi için de gözlemlenmektedir (Şekil 6).

Şekil 6. Farklı GBPUSD zaman dilimlerinde λmax'ın kuvvet yasası bağımlılığı
Dikkate alınan EURUSD, USDJPY ve GBPUSD serileri için hesaplanan üs değerleri de 2.5'e yakındır.
Çoklu zaman dilimlerinde ve döviz paritelerinde λmax fonksiyonu için log-log ölçeğinde doğrusal ilişkiler, λmax'ın gözlem sayısına göre bir kuvvet yasası bağımlılığı gösterdiğini ortaya koymaktadır.
3.3.3. Ölçeklendirmenin Pratik Sonuçları
λmax için bir kuvvet yasası bağımlılığının varlığının önemli bir pratik anlamı vardır.
λmax ∝ T^2.5 olduğundan, λ'nın mutlak değeri büyük ölçüde aşağıdakilere bağlıdır:
- Veri penceresinin uzunluğu,
- Zaman dilimi,
- Zaman serisinin ölçeği.
Bu nedenle, λ'nın mutlak değerini kullanmak pratikte uygun değildir.
Çok daha sağlam bir yaklaşım, 0<c<1 olan λ=c⋅λmax göreceli parametresini kullanmaktır.
Böyle bir yaklaşım:
- düzenlileştirme parametresini ölçekten bağımsız hale getirir,
- farklı enstrümanlar arasında parametre aktarımını kolaylaştırır,
- farklı zaman dilimlerinde aynı ayarların kullanılmasına olanak tanır.
Dolayısıyla, sonraki tüm örneklerde λ parametresi λmax birimi cinsinden belirtilecektir.
3.4. L1 Trendi Göstergeleri
Bu bölümde üç tür gösterge ele alınmaktadır:
- Kapanış fiyatlarına dayalı olarak L1 trendinin hesaplanması;
- L1 trendinin doğrusal artış katsayılarının (eğim) hesaplanması;
- L1 trend eğiminin işaretinin hesaplanması.
3.4.1. L1TrendFilter.mq5 - L1 trendi göstergesi
Bu örnekte, L1 filtresi, λmax biriminde belirtilen lambda katsayısı ile belirli sayıda çubuk için (örnekte, BarsToShow = 1000) kapanış fiyatları kullanılarak hesaplanır.
Hesaplama L1TrendFilter(relative = true) metot çağrısını kullanır; burada λ parametresi λmax biriminde tanımlanır. Gösterge değerleri doğrudan grafik penceresinde görüntülenir.
L1TrendFilter.mq5 göstergesinin kodu aşağıda verilmiştir.
//+------------------------------------------------------------------+ //| L1TrendFilter.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_chart_window #property indicator_buffers 1 #property indicator_plots 1 //--- #property indicator_label1 "L1TrendFilter" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_width1 2 //--- input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units //--- double Trend[]; //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0,Trend,INDICATOR_DATA); ArrayInitialize(Trend,EMPTY_VALUE); //--- PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 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[]) { //--- check bars static bool warned=false; if(rates_total < BarsToShow) { if(!warned) { Print("Waiting for enough bars: ",BarsToShow); warned=true; } ArrayInitialize(Trend,EMPTY_VALUE); return(0); } //--- check new bar static datetime last_bar_time=0; bool new_bar=(time[0]!=last_bar_time); bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated); if(!need_recalc) return(prev_calculated); last_bar_time=time[0]; //--- range int start=rates_total-BarsToShow; //--- hide old bars for(int i=0; i<start; i++) Trend[i]=EMPTY_VALUE; //--- int data_count=BarsToShow; //--- copy Close vector<double> DataClose; DataClose.Resize(data_count); for(int i=0; i<data_count; i++) DataClose[i]=close[start+i]; //--- lambda max double lambda_max=0.0; bool res=DataClose.L1TrendFilterLambdaMax(lambda_max); if(res) { PrintFormat("lambda_max=%f (%s,%s) Coef=%f lambda=%f", lambda_max,Symbol(),EnumToString(Period()),CoefLambda,lambda_max*CoefLambda); } //--- L1 trend filtering vector<double> filtered_data; filtered_data.Resize(data_count); if(DataClose.L1TrendFilter(CoefLambda,true,filtered_data)) { for(int i=0; i<data_count; i++) Trend[start+i]=filtered_data[i]; } //--- return(rates_total); } //+------------------------------------------------------------------+
Şekil 7'de, CoefLambda = 0.015 ile L1TrendFilter.mq5 göstergesinin hesaplanmasına ilişkin bir örnek gösterilmektedir.

Şekil 7. CoefLambda = 0.015 ile L1TrendFilter.mq5 göstergesini hesaplama örneği
Karşılaştırma için, farklı düzenlileştirme parametreleri ile birkaç varyant hesaplanabilir.
Şekil 8, CoefLambda = 0.015, CoefLambda = 0.025 ve CoefLambda = 0.055 parametreleri ile yapılan hesaplamaları göstermektedir.

Şekil 8. Farklı CoefLambda değerleri ile L1TrendFilter.mq5 göstergesini hesaplama örnekleri
3.4.2. L1TrendFilterSlope.mq5 - L1 trend dinamiklerinin göstergesi
Trend eğimini görüntülemek için, L1TrendFilter göstergesi değerlerinin artışı kullanılabilir.
Örnek olarak, değerleri ayrı bir pencerede görüntüleyen L1TrendFilterSlope göstergesine bakalım.
Göstergenin kodu aşağıda verilmiştir.
//+------------------------------------------------------------------+ //| L1TrendFilterSlope.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 //--- #property indicator_label1 "L1TrendFilterSlope" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_width1 2 //--- input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units //--- double Trend[]; //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0,Trend,INDICATOR_DATA); ArrayInitialize(Trend,EMPTY_VALUE); //--- PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 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[]) { //--- check bars static bool warned=false; if(rates_total < BarsToShow) { if(!warned) { Print("Waiting for enough bars: ",BarsToShow); warned=true; } ArrayInitialize(Trend,EMPTY_VALUE); return(0); } //--- check new bar static datetime last_bar_time=0; bool new_bar=(time[0]!=last_bar_time); bool need_recalc= (prev_calculated==0) || new_bar || (rates_total!=prev_calculated); if(!need_recalc) return(prev_calculated); last_bar_time=time[0]; //--- int start=rates_total-BarsToShow; int data_count=BarsToShow; //--- hide old bars for(int i=0;i<start;i++) Trend[i]=EMPTY_VALUE; //--- copy Close vector<double> DataClose; DataClose.Resize(data_count); for(int i=0;i<data_count;i++) DataClose[i]=close[start+i]; //--- lambda max double lambda_max=0.0; if(DataClose.L1TrendFilterLambdaMax(lambda_max)) { PrintFormat("lambda_max=%f (%s,%s) Coef=%f lambda=%f", lambda_max,Symbol(),EnumToString(Period()),CoefLambda,lambda_max*CoefLambda); } //--- L1 filtering vector<double> filtered_data; filtered_data.Resize(data_count); bool res=DataClose.L1TrendFilter(CoefLambda,true,filtered_data); if(res) { //--- slope (first difference) for(int i=1; i<data_count; i++) { double delta=filtered_data[i]-filtered_data[i-1]; Trend[start+i]=delta; } //--- copy first element Trend[start]=Trend[start+1]; } return(rates_total); } //+------------------------------------------------------------------+
L1TrendFilter.mq5 ve L1TrendFilterSlope.mq5 göstergelerinin sonucu Şekil 9'da gösterilmiştir.

Şekil 9. Şekil 9. CoefLambda = 0.015 ile L1TrendFilter.mq5 ve L1TrendFilterSlope.mq5 göstergelerini hesaplama örneği
3.4.3. L1TrendFilterSlopeSign.mq5 - L1 trend yönü göstergesi
Benzer şekilde, L1TrendFilterSlope.mq5 göstergesinin artışının işaretini görüntüleyen bir gösterge hesaplanabilir.
L1TrendFilterSlopeSign.mq5 göstergesinin kodu:
//+------------------------------------------------------------------+ //| L1TrendFilterSlopeSign.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 //--- #property indicator_label1 "L1TrendFilterSlope" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_width1 2 //--- input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units //--- double Trend[]; //+------------------------------------------------------------------+ //| Signum | //+------------------------------------------------------------------+ double Signum(const double value) { return((value>0)-(value<0)); } //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0,Trend,INDICATOR_DATA); ArrayInitialize(Trend,EMPTY_VALUE); //--- PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 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[]) { //--- check bars static bool warned=false; if(rates_total < BarsToShow) { if(!warned) { Print("Waiting for enough bars: ",BarsToShow); warned=true; } ArrayInitialize(Trend,EMPTY_VALUE); return(0); } //--- check new bar static datetime last_bar_time=0; bool new_bar=(time[0]!=last_bar_time); bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated); if(!need_recalc) return(prev_calculated); last_bar_time=time[0]; //--- int start=rates_total-BarsToShow; int data_count=BarsToShow; //--- hide old bars for(int i=0; i<start; i++) Trend[i]=EMPTY_VALUE; //--- copy Close vector<double> DataClose; DataClose.Resize(data_count); for(int i=0; i<data_count; i++) DataClose[i]=close[start+i]; //--- lambda max double lambda_max=0.0; bool res=DataClose.L1TrendFilterLambdaMax(lambda_max); if(res) { PrintFormat("lambda_max=%f (%s,%s) Coef=%f lambda=%f", lambda_max,Symbol(),EnumToString(Period()),CoefLambda,lambda_max*CoefLambda); } //--- L1 filtering vector<double> filtered_data; filtered_data.Resize(data_count); res=DataClose.L1TrendFilter(CoefLambda,true,filtered_data); if(res) { Trend[start]=0; for(int i=1; i<data_count; i++) { double delta=filtered_data[i]-filtered_data[i-1]; Trend[start+i]=Signum(delta); } } return(rates_total); } //+------------------------------------------------------------------+
Her üç göstergenin yer aldığı bir örnek Şekil 10'da gösterilmiştir (aynı katsayı değeri CoefLambda = 0.015 kullanılmıştır).

Şekil 10. Şekil 10. CoefLambda = 0.015 ile L1TrendFilter.mq5, L1TrendFilterSlope.mq5 ve L1TrendFilterSlopeSign.mq5 göstergelerini hesaplama örneği
3.4.4. L1 Trendine Dayalı Volatilite Göstergeleri
Bu bölümde, L1 trendine dayalı olarak bir finansal enstrümanın volatilitesini değerlendirmek için tasarlanmış göstergeler sunulmaktadır.
Bu araçlar, piyasadaki istikrarsızlık ve istikrar dönemlerinin belirlenmesini, mevcut piyasa dinamiklerinin analiz edilmesini ve daha bilinçli işlem kararları verilmesini mümkün kılar.
Bu bölümde ele alınan göstergeler şunlardır:
- L1Volatility.mq5 - L1 trendine göre artık volatilite;
- L1VolatilitySmoothed.mq5 - yumuşatılmış artık volatilite;
- L1VolatilityAbsolute.mq5 - mutlak volatilite;
- L1VolatilityNormalized.mq5 - normalleştirilmiş volatilite;
- L1VolatilityNormalizedSmoothed.mq5 - yumuşatılmış normalleştirilmiş volatilite;
- L1VolatilityRegime.mq5 - volatiliteye dayalı piyasa rejimi tespiti.
Tüm göstergeler, analitik tutarlılık sağlayan ve elde edilen sonuçların yorumlanmasını basitleştiren birleşik bir L1 trendi çerçevesi üzerine inşa edilmiştir.
Bu göstergelerin kullanımı, yüksek ve düşük volatilite dönemlerinin görsel olarak tanımlanmasına ve mevcut piyasa rejiminin - yatay seyir, trend, genişleme veya panik - belirlenmesine olanak tanır.
Sonuç olarak, bir yatırımcı, örneğin düşük volatilite dönemlerinde daha temkinli yaklaşımlar veya güçlü piyasa hareketleri sırasında daha aktif stratejiler uygulayarak alım-satım stratejilerini mevcut piyasa koşullarına uyarlayabilir.
3.4.4.1. L1Volatility.mq5 - L1 volatilite göstergesi
Gösterge, artık volatiliteyi kapanış fiyatları ile L1 trendinin karşılık gelen değeri arasındaki fark olarak hesaplar.
Bu yaklaşım, istikrarsız piyasa dönemlerinin ve kesin giriş veya çıkış anlarının belirlenmesine olanak tanır.
Görsel olarak, gösterge ayrı bir grafik penceresinde turuncu bir çizgi olarak görüntülenir.
Gösterge aşağıdakilere yardımcı olur:
- L1 trendinden fiyat sapmalarını değerlendirme ve piyasa hareketinin gücünü ölçme;
- Daha doğru risk yönetimi için yerel volatilite artışlarını tespit etme;
- Aynı zaman dilimi içinde farklı enstrümanların dinamiklerini karşılaştırma.
Gösterge, özellikle kısa vadeli volatilite değişikliklerinin daha geniş trend bağlamını kaybetmeden izlenmesi gereken sistemlerde kullanışlıdır.
L1Volatility.mq5 göstergesinin kodu aşağıda verilmiştir.
//+------------------------------------------------------------------+ //| L1Volatility.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 //--- #property indicator_label1 "L1Volatility" #property indicator_type1 DRAW_LINE #property indicator_color1 clrOrangeRed #property indicator_width1 2 //--- input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units //--- double Volatility[]; //--- //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0,Volatility,INDICATOR_DATA); ArrayInitialize(Volatility,EMPTY_VALUE); //--- PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 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[]) { //--- check bars static bool warned=false; if(rates_total<BarsToShow) { if(!warned) { Print("Waiting for enough bars: ",BarsToShow); warned=true; } ArrayInitialize(Volatility,EMPTY_VALUE); return(0); } //--- check new bar static datetime last_bar_time=0; bool new_bar=(time[0]!=last_bar_time); bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated); if(!need_recalc) return(prev_calculated); last_bar_time=time[0]; //--- int start=rates_total-BarsToShow; int data_count=BarsToShow; //--- hide old bars for(int i=0;i<start;i++) Volatility[i]=EMPTY_VALUE; //--- copy Close vector<double> DataClose; DataClose.Resize(data_count); for(int i=0; i<data_count; i++) DataClose[i]=close[start+i]; //--- lambda max double lambda_max=0.0; bool res=DataClose.L1TrendFilterLambdaMax(lambda_max); if(res) { PrintFormat("lambda_max=%f (%s,%s) Coef=%f lambda=%f", lambda_max,Symbol(),EnumToString(Period()),CoefLambda,lambda_max*CoefLambda); } //--- L1 filter vector<double> filtered_data; filtered_data.Resize(data_count); res=DataClose.L1TrendFilter(CoefLambda,true,filtered_data); if(res) { for(int i=0; i<data_count; i++) { double residual=close[start+i]-filtered_data[i]; Volatility[start+i]=residual; } } //--- return(rates_total); } //+------------------------------------------------------------------+
Hesaplama sonucu Şekil 11'de gösterilmiştir.

Şekil 11. L1Volatility.mq5 göstergesi
3.4.4.2. L1VolatilitySmoothed.mq5 - yumuşatılmış artık volatilite göstergesi
Bu gösterge, Simple Moving Average'ın (SMA) uygulandığı L1Volatility'nin yumuşatılmış bir versiyonunu temsil eder.
Yumuşatma şunları sağlar:
- Kısa vadeli gürültü ve aykırı değerlerin azaltılması;
- Daha net ve daha yorumlanabilir görselleştirme;
- Volatilitedeki kalıcı değişikliklere odaklanma.
Gösterge, örneğin uyarlanabilir alım-satım sistemlerinde veya trend ve yatay seyir piyasa aşamaları sırasında yanlış sinyalleri filtrelerken daha uzun vadeli volatilite trendlerinin değerlendirilmesini gerektiren stratejiler için kullanışlıdır.
L1VolatilitySmoothed.mq5 göstergesinin kodu aşağıda verilmiştir.
//+------------------------------------------------------------------+ //| L1VolatilitySmoothed.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 #property indicator_label1 "L1VolatilitySmoothed" #property indicator_type1 DRAW_LINE #property indicator_color1 clrMediumVioletRed #property indicator_width1 2 //--- input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units input int SmoothPeriod = 10; // Smooth period //--- double VolSmoothed[]; //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0, VolSmoothed, INDICATOR_DATA); ArrayInitialize(VolSmoothed, EMPTY_VALUE); PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS, _Digits); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 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<BarsToShow) { ArrayInitialize(VolSmoothed,EMPTY_VALUE); return(0); } //--- recalc only on new bar static datetime last_bar_time = 0; if(time[0] == last_bar_time && prev_calculated > 0) return(prev_calculated); last_bar_time=time[0]; //--- int start=rates_total-BarsToShow; for(int i=0; i<start; i++) VolSmoothed[i]=EMPTY_VALUE; //--- copy close prices vector<double> price(BarsToShow); for(int i=0; i<BarsToShow; i++) price[i] = close[start+i]; vector<double> l1(BarsToShow); bool res=price.L1TrendFilter(CoefLambda,true,l1); if(res) { //--- calculate raw volatility vector<double> rawVol(BarsToShow); for(int i=0; i<BarsToShow; i++) rawVol[i]=close[start+i]-l1[i]; //--- apply simple moving average smoothing for(int i=0; i<BarsToShow; i++) { double sum = 0.0; int count = 0; for(int j=MathMax(0,i-SmoothPeriod+1); j<=i; j++) { sum+=rawVol[j]; count++; } VolSmoothed[start+i]=sum/count; } } //--- return(rates_total); } //+------------------------------------------------------------------+
Şekil 12 hem L1Volatility.mq5 hem de L1VolatilitySmoothed.mq5 göstergelerini göstermektedir.

Şekil 12. L1Volatility.mq5 ve L1VolatilitySmoothed.mq5 göstergeleri
3.4.4.3. L1VolatilityAbsolute.mq5 - mutlak volatilite göstergesi
Gösterge, kapanış fiyatları ile L1 trendi arasındaki farkın mutlak değerini hesaplar.
Özellikler ve uygulamalar:
- Hareket yönünü göz ardı eder ve sadece dalgalanma büyüklüğünü değerlendirir;
- Trend yönünden bağımsız olarak fiyat salınım genliğini analiz etmek için uygundur;
- Uç değer istatistikleri ve risk analizine dayalı sistemler için kullanışlıdır.
Mutlak volatilite, fiyat sapmalarının gerçek büyüklüğünü yansıtır ve yatırımcının piyasa hareketinin yönünden etkilenmeden gücünü gözlemlemesini sağlar.
L1VolatilityAbsolute.mq5 göstergesinin kodu aşağıda verilmiştir.
//+------------------------------------------------------------------+ //| L1VolatilityAbsolute.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 //--- #property indicator_label1 "L1VolatilityAbsolute" #property indicator_type1 DRAW_LINE #property indicator_color1 clrOrange #property indicator_width1 2 //--- input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units //--- double Vol[]; //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0,Vol,INDICATOR_DATA); ArrayInitialize(Vol,EMPTY_VALUE); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 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[]) { //--- static bool warned=false; if(rates_total < BarsToShow) { if(!warned) { Print("Waiting bars ",BarsToShow); warned=true; } ArrayInitialize(Vol,EMPTY_VALUE); return(0); } static datetime last_bar=0; bool new_bar=(time[0]!=last_bar); //--- if(!(prev_calculated==0 || new_bar || rates_total!=prev_calculated)) return(prev_calculated); //--- last_bar=time[0]; int start=rates_total-BarsToShow; int N=BarsToShow; for(int i=0; i<start; i++) Vol[i]=EMPTY_VALUE; //--- vector<double> price; price.Resize(N); for(int i=0; i<N; i++) price[i]=close[start+i]; vector<double> l1; l1.Resize(N); bool res=price.L1TrendFilter(CoefLambda,true,l1); if(res) { for(int i=0; i<N; i++) Vol[start+i]=MathAbs(close[start+i]-l1[i]); } //--- return(rates_total); } //+------------------------------------------------------------------+
Göstergenin hesaplanmasının bir örneği Şekil 13'te gösterilmiştir.

Şekil 13. L1VolatilityAbsolute.mq5 göstergesi
3.4.4.4. L1VolatilityNormalized.mq5 - normalleştirilmiş volatilite göstergesi
Gösterge, L1 trendi ile birlikte ATR (Average True Range) kullanarak volatiliteyi normalleştirir.
Trendden mutlak fiyat sapmasının ATR periyodu boyunca ortalama fiyat aralığına oranını hesaplar. Normalleştirme, fiyat ölçeği bağımlılığını ortadan kaldırarak farklı enstrümanlar ve zaman dilimleri arasında karşılaştırma yapılmasına olanak tanır.
Uygulama alanları şunları içerir:
- Nispeten güçlü ve zayıf piyasa hareketlerinin belirlenmesi;
- Farklı varlıklar arasındaki volatilitenin karşılaştırılması;
- Piyasa koşullarının fiyat seviyesinden bağımsız olarak değerlendirilmesi.
L1VolatilityNormalized.mq5 göstergesinin kodu aşağıda verilmiştir.
//+------------------------------------------------------------------+ //| L1VolatilityNormalized.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 #property indicator_label1 "L1VolatilityNormalized" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_width1 2 //--- input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units //--- double VolNormalized[]; //--- //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- prepare SetIndexBuffer(0, VolNormalized,INDICATOR_DATA); ArrayInitialize(VolNormalized,EMPTY_VALUE); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 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[]) { //--- check bars static bool warned=false; if(rates_total<BarsToShow) { if(!warned) { Print("Waiting for enough bars: ",BarsToShow); warned=true; } ArrayInitialize(VolNormalized,EMPTY_VALUE); return(0); } //--- check new bar static datetime last_bar_time=0; bool new_bar=(time[0]!=last_bar_time); bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated); if(!need_recalc) return(prev_calculated); last_bar_time=time[0]; int start=rates_total-BarsToShow; //--- for(int i=0; i<start; i++) VolNormalized[i]=EMPTY_VALUE; //--- copy close prices vector<double> price(BarsToShow); for(int i=0; i<BarsToShow; i++) price[i]=close[start+i]; //--- vector<double> l1(BarsToShow); bool res=price.L1TrendFilter(CoefLambda,true,l1); if(res) { //--- compute normalized volatility double mean=0.0; double stddev=0.0; for(int i=0; i<BarsToShow; i++) mean+=close[start+i]-l1[i]; mean/=BarsToShow; //--- for(int i=0; i<BarsToShow; i++) stddev+=MathPow(close[start+i]-l1[i]-mean,2); stddev=MathSqrt(stddev/BarsToShow); //--- for(int i=0; i<BarsToShow; i++) VolNormalized[start+i]=stddev>0?(close[start+i]-l1[i])/stddev:0; } //--- return(rates_total); } //+------------------------------------------------------------------+
Hesaplama sonucu Şekil 14'de gösterilmiştir.

Şekil 14. L1VolatilityNormalized.mq5 göstergesi
3.4.4.5. L1VolatilityNormalizedSmoothed.mq5 - yumuşatılmış normalleştirilmiş volatilite göstergesi
Bu gösterge, Exponential Moving Average (EMA) yumuşatmasını ekleyerek normalleştirme yaklaşımını genişletir.
Avantajlar:
- Kısa süreli gürültü ve keskin sıçramaların etkisini azaltır;
- Daha net ve daha yorumlanabilir bir volatilite profili oluşturur;
- Kalıcı volatilitenin ve mevcut piyasa rejiminin değerlendirilmesine yardımcı olur.
Gösterge özellikle istikrarlı volatilite hesaplaması gerektiren uyarlanabilir stratejiler için, örneğin işlem modlarını otomatik olarak seçerken kullanışlıdır.
L1VolatilityNormalizedSmoothed.mq5 göstergesinin kodu aşağıda verilmiştir.
//+------------------------------------------------------------------+ //| L1VolatilityNormalizedSmoothed.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 #property indicator_label1 "L1VolatilityNormalizedSmoothed" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDeepSkyBlue #property indicator_width1 2 //--- input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units input int SmoothPeriod = 10; // EMA smoothing period (1=no smoothing) //--- double NormVolSmooth[]; //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- prepare SetIndexBuffer(0,NormVolSmooth,INDICATOR_DATA); ArrayInitialize(NormVolSmooth,EMPTY_VALUE); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS,_Digits); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 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[]) { //--- check bars static bool warned=false; if(rates_total < BarsToShow) { if(!warned) { Print("Waiting for enough bars: ",BarsToShow); warned=true; } ArrayInitialize(NormVolSmooth,EMPTY_VALUE); return(0); } //--- check new bar static datetime last_bar_time=0; bool new_bar=(time[0]!=last_bar_time); bool need_recalc= (prev_calculated==0) || new_bar || (rates_total!=prev_calculated); if(!need_recalc) return(prev_calculated); last_bar_time=time[0]; int start=rates_total-BarsToShow; //--- for(int i=0; i<start; i++) NormVolSmooth[i]=EMPTY_VALUE; //--- copy close prices vector<double> price(BarsToShow); for(int i=0; i<BarsToShow; i++) price[i]=close[start+i]; //--- vector<double> l1(BarsToShow); bool res=price.L1TrendFilter(CoefLambda,true,l1); if(res) { //--- compute normalized volatility vector<double> VolNormalized(BarsToShow); double mean = 0, stddev = 0; for(int i=0; i<BarsToShow; i++) mean += close[start+i]-l1[i]; mean /= BarsToShow; //--- for(int i=0; i<BarsToShow; i++) stddev += MathPow(close[start+i]-l1[i]-mean,2); stddev = MathSqrt(stddev/BarsToShow); //--- for(int i=0; i<BarsToShow; i++) VolNormalized[i]=stddev>0 ? (close[start+i]-l1[i])/stddev: 0; //--- EMA smoothing vector<double> Smooth(BarsToShow); double alpha=(SmoothPeriod<=1) ? 1.0: 2.0/(SmoothPeriod+1.0); //--- Smooth[0] = VolNormalized[0]; for(int i=1; i<BarsToShow; i++) Smooth[i]=alpha*VolNormalized[i]+(1.0-alpha)*Smooth[i-1]; //--- copy to indicator buffer for(int i=0; i<BarsToShow; i++) NormVolSmooth[start+i]=Smooth[i]; } //--- return(rates_total); } //+------------------------------------------------------------------+
Hesaplama sonucu Şekil 15'te gösterilmiştir.

Şekil 15. L1VolatilityNormalized.mq5 ve L1VolatilityNormalizedSmoothed.mq5 göstergeleri
3.4.4.6. L1VolatilityRegime.mq5 - piyasa rejimi tespiti göstergesi
Gösterge, normalleştirilmiş ve yumuşatılmış volatiliteye dayalı olarak mevcut piyasa rejimini sınıflandırarak dört piyasa durumunu tanımlar.
Gösterge özellikleri:
- Tamamen otonomdur ve harici veri gerektirmez;
- Uyarlanabilir stratejiler için piyasa dinamiklerinin net bir şekilde görselleştirilmesini sağlar;
- Eşik parametreleri LowVolThresh ve HighVolThresh farklı enstrümanlar ve zaman dilimleri için ayarlanabilir.
| Değer | Rejim | Açıklama |
|---|---|---|
| 0 | Yatay seyir | Düşük volatilite, yatay piyasa |
| 1 | Trend | Orta düzeyde volatilite, bir trendin varlığı |
| 2 | Genişleme | Güçlü hareket, piyasa genişlemesi |
| 3 | Panik | Aşırı volatilite, keskin hareketler |
Tablo 3. L1VolatilityRegime.mq5 göstergesinin rejimleri
- Mevcut piyasa rejimini hızlı bir şekilde belirleyin;
- Alım-satım stratejilerini mevcut koşullara uyarlayın;
- Aşırı hareketler sırasında riski azaltın ve alım-satım verimliliğini artırın.
L1VolatilityRegime.mq5 göstergesinin kodu aşağıda verilmiştir.
//+------------------------------------------------------------------+ //| L1VolatilityRegime.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 1 #property indicator_plots 1 //--- #property indicator_label1 "L1 Volatility Regime" #property indicator_type1 DRAW_LINE #property indicator_color1 clrRoyalBlue #property indicator_width1 2 //--- input parameters input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units input int ATRPeriod = 14; // ATR period input int SmoothPeriod = 10; // Smooth period input double L1MoveThresh = 0.0; // Move volatility input double LowVolThresh = 0.5; // Low volatility input double HighVolThresh = 1.5; // High volatility input double PanicMult = 2.0; // Panic volatility //--- double Regime[]; //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0, Regime, INDICATOR_DATA); PlotIndexSetDouble(0,PLOT_EMPTY_VALUE, EMPTY_VALUE); IndicatorSetInteger(INDICATOR_DIGITS, 0); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 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[]) { //--- check bars if(rates_total<BarsToShow+ATRPeriod) { ArrayInitialize(Regime,EMPTY_VALUE); return(0); } //--- check new bar static datetime last_bar_time=0; bool new_bar=(time[0]!=last_bar_time); bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated); if(!need_recalc) return(prev_calculated); last_bar_time=time[0]; //--- int start=rates_total-BarsToShow; int count=BarsToShow; //--- for(int i=0; i<start; i++) Regime[i]=EMPTY_VALUE; //--- vector<double> DataClose(count),DataHigh(count),DataLow(count); for(int i=0; i<count; i++) { DataClose[i]=close[start+i]; DataHigh[i]=high[start+i]; DataLow[i]=low[start+i]; } //--- vector<double> L1(count); bool res=DataClose.L1TrendFilter(CoefLambda,true,L1); if(!res) return(prev_calculated); //--- vector<double> TR(count),ATR(count); for(int i=0; i<count; i++) { if(i==0) TR[i]=DataHigh[i]-DataLow[i]; else { double h_l=DataHigh[i]-DataLow[i]; double h_pc=MathAbs(DataHigh[i]-DataClose[i-1]); double l_pc=MathAbs(DataLow[i]-DataClose[i-1]); TR[i]=MathMax(h_l,MathMax(h_pc,l_pc)); } int from=MathMax(0,i-ATRPeriod+1); double sumTR=0.0; int n = i-from+1; for(int j=from; j<=i;j++) sumTR += TR[j]; ATR[i]=sumTR/n; } //--- vector<double> NormVol(count), SmoothVol(count); for(int i=0; i<count; i++) NormVol[i]=(ATR[i]>0) ? MathAbs(DataClose[i]-L1[i])/ATR[i] : 0; double alpha=2.0/(SmoothPeriod+1.0); SmoothVol[0]=NormVol[0]; for(int i=1; i<count; i++) SmoothVol[i]=alpha*NormVol[i]+(1.0-alpha)*SmoothVol[i-1]; //--- for(int i=0; i<count; i++) { double vol=SmoothVol[i]; double deltaL1=(i>0) ? (L1[i]-L1[i-1]): 0.0; if(vol<LowVolThresh) Regime[start+i]=0; // Range else if(vol>=LowVolThresh && vol<HighVolThresh) Regime[start+i]=(MathAbs(deltaL1)>L1MoveThresh) ? 1:0; // Trend/Range else if(vol>=HighVolThresh && vol<HighVolThresh*PanicMult) Regime[start+i]=2; // Expansion else Regime[start+i]=3; // Panic } //--- return(rates_total); } //+------------------------------------------------------------------+
Hesaplama sonucu Şekil 16'da gösterilmiştir.

Şekil 16. L1VolatilityRegime.mq5 göstergesi
Kolaylık sağlamak için, renk kodlu rejim görselleştirmesine sahip bir versiyon da kullanılabilir.
L1VolatilityRegimeColor.mq5 göstergesinin kodu aşağıda verilmiştir.
//+------------------------------------------------------------------+ //| L1VolatilityRegimeColor.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 4 #property indicator_plots 4 //--- #property indicator_label1 "Range" #property indicator_type1 DRAW_LINE #property indicator_color1 clrDodgerBlue #property indicator_width1 2 //--- #property indicator_label2 "Trend" #property indicator_type2 DRAW_LINE #property indicator_color2 clrLime #property indicator_width2 2 //--- #property indicator_label3 "Expansion" #property indicator_type3 DRAW_LINE #property indicator_color3 clrOrange #property indicator_width3 2 //--- #property indicator_label4 "Panic" #property indicator_type4 DRAW_LINE #property indicator_color4 clrRed #property indicator_width4 2 //--- input parameters input int BarsToShow = 1000; // Number of bars to calculate L1 input double CoefLambda = 0.015; // Lambda in lambda_max units input int ATRPeriod = 14; // ATR period input int SmoothPeriod = 10; // Smooth period input double L1MoveThresh = 0.0; // Move volatility input double LowVolThresh = 0.5; // Low volatility input double HighVolThresh = 1.5; // High volatility input double PanicMult = 2.0; // Panic volatility //--- buffers double Regime[]; double BufRange[], BufTrend[], BufExpansion[], BufPanic[]; //+------------------------------------------------------------------+ //| Indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { SetIndexBuffer(0,BufRange,INDICATOR_DATA); SetIndexBuffer(1,BufTrend,INDICATOR_DATA); SetIndexBuffer(2,BufExpansion,INDICATOR_DATA); SetIndexBuffer(3,BufPanic,INDICATOR_DATA); //--- PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE); PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE); PlotIndexSetDouble(2,PLOT_EMPTY_VALUE,EMPTY_VALUE); PlotIndexSetDouble(3,PLOT_EMPTY_VALUE,EMPTY_VALUE); //--- IndicatorSetInteger(INDICATOR_DIGITS,0); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| 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<BarsToShow+ATRPeriod) { ArrayInitialize(Regime,EMPTY_VALUE); ArrayInitialize(BufRange,EMPTY_VALUE); ArrayInitialize(BufTrend,EMPTY_VALUE); ArrayInitialize(BufExpansion,EMPTY_VALUE); ArrayInitialize(BufPanic,EMPTY_VALUE); return(0); } //--- new bars static datetime last_bar_time=0; bool new_bar=(time[0]!=last_bar_time); bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated); if(!need_recalc) return(prev_calculated); last_bar_time=time[0]; //--- int start=rates_total-BarsToShow; int count=BarsToShow; //--- ArrayResize(Regime,rates_total); for(int i=0;i<start;i++) Regime[i]=EMPTY_VALUE; //--- vector<double> DataClose(count),DataHigh(count),DataLow(count); for(int i=0; i<count; i++) { DataClose[i]=close[start+i]; DataHigh[i]=high[start+i]; DataLow[i]=low[start+i]; } //--- vector<double> L1(count); bool res=DataClose.L1TrendFilter(CoefLambda,true,L1); if(!res) return(prev_calculated); //--- vector<double> TR(count),ATR(count); for(int i=0; i<count; i++) { if(i==0) TR[i]=DataHigh[i]-DataLow[i]; else { double h_l = DataHigh[i]-DataLow[i]; double h_pc = MathAbs(DataHigh[i]-DataClose[i-1]); double l_pc = MathAbs(DataLow[i]-DataClose[i-1]); TR[i] = MathMax(h_l, MathMax(h_pc, l_pc)); } int from=MathMax(0,i-ATRPeriod+1); double sumTR=0; int n=i-from+1; for(int j=from; j<=i; j++) sumTR+=TR[j]; ATR[i]=sumTR/n; } //--- vector<double> NormVol(count), SmoothVol(count); for(int i=0;i<count;i++) NormVol[i]=(ATR[i]>0) ? MathAbs(DataClose[i]-L1[i])/ATR[i] : 0; //--- double alpha=2.0/(SmoothPeriod+1.0); SmoothVol[0]=NormVol[0]; for(int i=1; i<count; i++) SmoothVol[i]=alpha*NormVol[i]+(1.0-alpha)*SmoothVol[i-1]; //--- calc Regime[] for(int i=0; i<count; i++) { double vol=SmoothVol[i]; double deltaL1=(i>0) ? (L1[i]-L1[i-1]):0.0; if(vol<LowVolThresh) Regime[start+i]=0; else if(vol<HighVolThresh) Regime[start+i]=(MathAbs(deltaL1)>L1MoveThresh) ? 1:0; else if(vol<HighVolThresh*PanicMult) Regime[start+i]=2; else Regime[start+i]=3; } //--- buffers for(int i=0; i<rates_total; i++) { BufRange[i] = (Regime[i]==0) ? Regime[i]: EMPTY_VALUE; BufTrend[i] = (Regime[i]==1) ? Regime[i]: EMPTY_VALUE; BufExpansion[i] = (Regime[i]==2) ? Regime[i]: EMPTY_VALUE; BufPanic[i] = (Regime[i]==3) ? Regime[i]: EMPTY_VALUE; } //--- return(rates_total); } //+------------------------------------------------------------------+
Birleşik hesaplama sonucu Şekil 17'de gösterilmiştir.

Şekil 17. L1VolatilityRegime.mq5 ve L1VolatilityRegimeColor.mq5 göstergeleri
Şekil 18-20, EURGBP, AUDCAD ve CHFJPY için tüm volatilite göstergelerinin örneklerini bir arada sunmaktadır.

Şekil 18. EURGBP için volatilite göstergeleri

Şekil 19. AUDCAD için volatilite göstergeleri

Şekil 20. CHFJPY için volatilite göstergeleri
3.5. Alım-Satım Stratejilerinde L1 Trendini Kullanma
Bu bölümde, işlem sinyali filtreleri uygulamak için farklı seçeneklere sahip Moving Average, MACD, ADX ve EMA alım-satım stratejilerini ele alıyoruz.
İşlem sinyallerine filtreler eklemek, alım-satım sistemlerinin özelliklerini iyileştirmeye olanak tanır. Sunulan Uzman Danışmanlarda filtre kullanımının etkinliğini analiz etmek için, bakiye ve varlık verilerini (her yeni çubukta) ayrı dosyalara kaydedeceğiz ve farklı modlar altında alım-satım sistemlerinin sonuçlarını görselleştirmek için bir Python komut dosyası kullanacağız.
Sunulan tüm Uzman Danışmanlar aynı mimariye sahiptir.
L1 trendini işlem sinyalleriyle uyumlu hale getirmek için genel ilke
- Açılış filtresi (L1FilterOpen = true), işlemlerin yalnızca baskın trend yönünde açılmasına izin verir.
- Kapanış filtresi (L1FilterClose = true) güçlü trendler sırasında pozisyonların tutulmasına yardımcı olur ve yerel düzeltmeler sırasında erken çıkışları azaltır.
Girdi parametreleri (tüm Uzman Danışmanlar için ortaktır):
//--- L1 filter parameters input int L1TotalBars = 1000; // Total bars for L1 filter input bool L1FilterOpen = false; // Use filter for Open input bool L1FilterClose = false; // Use filter for Close input double L1CoefLambda = 0.2; // Lambda in lambda_max units //--- save statistics input bool SaveStatistics = false; // Save statistics to file
Bakiye ve varlık verilerini dosyaya kaydetme
SaveStatistics girdi parametresi her yeni çubukta zaman, kapanış fiyatı, bakiye, varlık vb. mevcut değerlerin bir dosyaya kaydedilmesini sağlar: terminal_veri_klasörü\Tester\Agent-127.0.0.1-3000\MQL5\Files.
Verileri kaydetme fonksiyonu OnTick() içinde çağrılır ve bool SaveStatistics girdi parametresinin değerine bağlıdır.
//+------------------------------------------------------------------+ //| Expert OnTick function | //+------------------------------------------------------------------+ void OnTick() { //--- trade only at new bar if(!IsNewBar()) return; //--- check trade conditions if(SelectPosition()) CheckForClose(); else CheckForOpen(); //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); }
Kaydedilen dosya adındaki önek L1FilterOpen ve L1FilterClose kombinasyonuna bağlıdır.
Dosya adı stratejiye ve sembole bağlıdır ve Uzman Danışmanın başlatma fonksiyonunda oluşturulur:
//+------------------------------------------------------------------+ //| PrepareStrategyFileName | //+------------------------------------------------------------------+ string PrepareStrategyFileName(string strategy_name) { int v=0; if(L1FilterOpen) v=v | 1; //--- if(L1FilterClose) v=v | 2; //--- string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt"; return filename; } //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { //--- prepare filename ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName); //--- delete old file if exists if(FileIsExist(ExtStrategyFileName)) FileDelete(ExtStrategyFileName); //--- return INIT_SUCCEEDED; }
Her yeni çubukta istatistikleri bir dosyaya kaydetme fonksiyonu:
//+------------------------------------------------------------------+ //| Save account statistics to file | //+------------------------------------------------------------------+ void SaveAccountStatistics() { //--- check file name if(ExtStrategyFileName=="") return; //--- int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI); if(file==INVALID_HANDLE) { Print("File open error: ",GetLastError()); return; } //--- append FileSeek(file,0,SEEK_END); //--- account data double balance = AccountInfoDouble(ACCOUNT_BALANCE); double equity = AccountInfoDouble(ACCOUNT_EQUITY); double margin = AccountInfoDouble(ACCOUNT_MARGIN); double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double margin_lvl = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); //--- volume double volume=0.0; if(PositionSelect(_Symbol)) volume=PositionGetDouble(POSITION_VOLUME); //--- time datetime t[1]; if(CopyTime(_Symbol,_Period,0,1,t)<=0) { FileClose(file); return; } double current_close[1]; if(CopyClose(_Symbol,_Period,0,1,current_close)<=0) { FileClose(file); return; } string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS), balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]); //--- FileWrite(file,line); //--- FileClose(file); }
İşlem sinyali filtrelerinin farklı modlarda uygulanması
Uzman Danışmanın strateji sınayıcıda 4 kombinasyonun tümü ile sıralı yürütülmesi:
- L1FilterOpen = false, L1FilterClose = false (filtresiz alım-satım);
- L1FilterOpen = true, L1FilterClose = false (giriş filtreleme);
- L1FilterOpen = false, L1FilterClose = true (çıkış filtreleme);
- L1FilterOpen = true, L1FilterClose = true (giriş ve çıkış filtreleme).
şu dosyaları oluşturulur: 0_MA_EURUSD.txt, 1_MA_EURUSD.txt, 2_MA_EURUSD.txt, 3_MA_EURUSD.txt.
Bu dosyalar bakiye/varlık verilerini içerir ve L1 trend uyumunu kullanarak işlem sinyali filtrelemenin etkinliğini karşılaştırmaya olanak tanır.
Veri görselleştirme
Birleşik grafikler oluşturmak için 4 dosyayı ayrı bir klasöre kopyalarız ve Python komut dosyasını çalıştırırız (örneğin, "C:\data\").
import pandas as pd import matplotlib.pyplot as plt import os # --- folder for charts output_dir = "C:\\data\\charts\\" os.makedirs(output_dir, exist_ok=True) symbol = "EURUSD" name_strategy = "MA" file_strategy = name_strategy+"_"+symbol title_strategy = " ("+symbol+" "+name_strategy+" strategy+filters)" file_prefix = symbol+"_"+name_strategy+"_" # --- files files = [ "C:\\data\\0_"+file_strategy+".txt", "C:\\data\\1_"+file_strategy+".txt", "C:\\data\\2_"+file_strategy+".txt", "C:\\data\\3_"+file_strategy+".txt" ] # --- labels labels = [ "No filters", "Open L1 filter", "Close L1 filter", "Open+Close L1 filter" ] # --- load data def load_file(filename): df = pd.read_csv( filename, sep=";", header=None, names=[ "time", "balance", "equity", "margin", "free_margin", "margin_level", "volume", "close" ] ) df["time"] = pd.to_datetime(df["time"]) return df # --- close price chart plt.figure(figsize=(10,6), dpi=100) for file, label in zip(files, labels): df = load_file(file) plt.plot(df["time"], df["close"], color='gray') plt.title(symbol+" Close Price") plt.xlabel("Time") plt.ylabel("closing price") plt.legend() plt.grid() plt.tight_layout() plt.savefig(output_dir + file_prefix+"close_price.png", dpi=100) plt.show() # --- balance chart plt.figure(figsize=(10,6), dpi=100) for file, label in zip(files, labels): df = load_file(file) plt.plot(df["time"], df["balance"], label=label) plt.title("Balance" + title_strategy) plt.xlabel("Time") plt.ylabel("Balance") plt.legend() plt.grid() plt.tight_layout() plt.savefig(output_dir + file_prefix+"balance.png", dpi=100) plt.show() plt.close() # --- equity chart plt.figure(figsize=(10,6), dpi=100) for file, label in zip(files, labels): df = load_file(file) plt.plot(df["time"], df["equity"], label=label) plt.title("Equity" + title_strategy) plt.xlabel("Time") plt.ylabel("Equity") plt.legend() plt.grid() plt.tight_layout() plt.savefig(output_dir + file_prefix+"equity.png", dpi=100) plt.show() plt.close() #--- balance + equity chart plt.figure(figsize=(10,6), dpi=100) for i, (file, label) in enumerate(zip(files, labels)): df = load_file(file) # --- get matplotlib color color = plt.rcParams["axes.prop_cycle"].by_key()["color"][i % 10] #--- equity — solid line plt.plot( df["time"], df["equity"], color=color, linestyle="-", label=f"{label} equity" ) #--- balance — dashed line plt.plot( df["time"], df["balance"], color=color, linestyle="--", label=f"{label} balance" ) plt.title("Balance + Equity" + title_strategy) plt.xlabel("Time") plt.ylabel("Value") plt.legend() plt.grid() plt.tight_layout() plt.savefig(output_dir+file_prefix+"balance_equity.png", dpi=100) plt.show() plt.close()
İşlem sinyali filtrelerinin uygulanması
L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda parametreleri L1 trendi hesaplama ayarlarını ve işlem sinyallerini filtrelemede kullanımını tanımlar.
L1 trendini hesaplama ve işlem sinyalleriyle uyumlu hale getirme
Tüm Uzman Danışmanlarda, L1 trendi analizi kullanılarak ek filtreleme uygulanır:
- Son L1TotalBars kullanılarak yumuşatılmış bir fiyat serisi oluşturulur;
- Trend büyüme katsayısı delta, filtrelenen son iki değer arasındaki fark olarak hesaplanır.
Delta > 0 ise - yükseliş trendi; delta < 0 ise - düşüş trendi.
//+------------------------------------------------------------------+ //| CheckTrendL1 | //+------------------------------------------------------------------+ double CheckTrendL1() { int max_bars=L1TotalBars; MqlRates rates_data[]; ArrayResize(rates_data,max_bars); ArraySetAsSeries(rates_data,false); if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars) { Print("CopyRates failed for L1Trend"); return(0); } //--- prepare data (close prices vector) int data_count=max_bars; vector<double> data_close; data_close.Resize(data_count); for(int i=0; i<data_count; i++) data_close[i] = rates_data[i].close; //--- calculate L1 filter vector<double> data_filtered; data_filtered.Resize(data_count); double dp=0.0; bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered); if(res) dp = data_filtered[data_count-1]-data_filtered[data_count-2]; //--- return(dp); }
L1CoefLambda parametresi λmax biriminde belirtilir ve filtrelemeyi volatiliteye ve çubuk sayısına karşı dayanıklı hale getirir.
İşleme giriş filtresi
L1FilterOpen = true ise:
- L1 trendi negatif olduğunda ALIŞ sinyalleri göz ardı edilir;
- L1 trendi pozitif olduğunda SATIŞ sinyalleri göz ardı edilir.
İşlemler yalnızca baskın trend yönünde açılır.
//+------------------------------------------------------------------+ //| CheckForOpen | //+------------------------------------------------------------------+ void CheckForOpen() { ENUM_ORDER_TYPE signal; if(!GetTradeSignal(signal)) return; if(signal == WRONG_VALUE) return; //--- L1 filter if(L1FilterOpen) { double delta = CheckTrendL1(); if(signal == ORDER_TYPE_BUY && delta < 0) { signal = WRONG_VALUE; PrintFormat("Open BUY signal cancelled by L1 trend delta=%.5f", delta); } if(signal == ORDER_TYPE_SELL && delta > 0) { signal = WRONG_VALUE; PrintFormat("Open SELL signal cancelled by L1 trend delta=%.5f", delta); } } //--- if(signal == WRONG_VALUE) return; //--- if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol,_Period)<L1TotalBars) return; //--- double price = (signal==ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol,SYMBOL_ASK): SymbolInfoDouble(_Symbol,SYMBOL_BID); //--- ExtTrade.PositionOpen(_Symbol, signal, TradeLot, price, 0, 0); }
İşlemden çıkış filtresi
L1FilterClose = true ise:
- L1 trendi yukarı yönlü olmaya devam ederken ALIŞ pozisyonları ters sinyallerle kapatılmaz;
- L1 trendi aşağı yönlü olmaya devam ederken SATIŞ pozisyonları kapatılmaz.
Bu, güçlü trendlerde pozisyonları tutmaya yardımcı olur ve yerel düzeltmeler sırasında erken çıkışları azaltır.
//+------------------------------------------------------------------+ //| CheckForClose | //+------------------------------------------------------------------+ void CheckForClose() { //--- check position if(!PositionSelect(_Symbol)) return; //--- check position magic if(PositionGetInteger(POSITION_MAGIC)!=MA_MAGIC) return; //--- check trade signal ENUM_ORDER_TYPE signal; if(!GetTradeSignal(signal)) return; //--- long type = PositionGetInteger(POSITION_TYPE); bool close_signal = false; //--- if(type == POSITION_TYPE_BUY && signal == ORDER_TYPE_SELL) close_signal = true; if(type == POSITION_TYPE_SELL && signal == ORDER_TYPE_BUY) close_signal = true; //--- check L1 filter if(L1FilterClose) { double delta = CheckTrendL1(); if(type == POSITION_TYPE_BUY && delta > 0) { close_signal = false; PrintFormat("Close BUY signal cancelled by L1 trend delta=%.5f", delta); } if(type == POSITION_TYPE_SELL && delta < 0) { close_signal = false; PrintFormat("Close SELL signal cancelled by L1 trend delta=%.5f", delta); } } //--- if(close_signal && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>=L1TotalBars) ExtTrade.PositionClose(_Symbol,3); }
3.5.1. Moving Average Alım-Satım Stratejisi
İlk örnek olarak, klasik Moving Average trend takip stratejisine dayalı bir Uzman Danışmanı inceleyelim.
İşlem sinyalleri, kapanış fiyatının hareketli ortalamayı çaprazlamasına dayalı olarak oluşturulur:
- ALIŞ - kapanış fiyatı hareketli ortalamayı aşağıdan yukarıya doğru çaprazladığında;
- SATIŞ - kapanış fiyatı hareketli ortalamayı yukarıdan aşağıya doğru çaprazladığında.
Sinyaller son iki kapanmış çubuk kullanılarak değerlendirilir; bu sayede, mevcut tamamlanmamış çubuğun etkisi ortadan kaldırılır ve yanlış sinyaller azaltılır.
Ayrıca, L1 trendi ayarlarına göre (L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda) işlem giriş ve çıkış filtreleri uygulanır.
MovingAverageFilteredL1.mq5 Uzman Danışmanının kodu:
//+------------------------------------------------------------------+ //| MovingAverageFilteredL1.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- best MovingAverage parameters for EURUSD,H1,2025 input int MovingPeriod = 61; // MA period input int MovingShift = 0; // MA shift //--- trade volume input double TradeLot = 0.1; // Lot size //--- L1 filter parameters input int L1TotalBars = 1000; // Total bars for L1 filter input bool L1FilterOpen = false; // Use filter for Open input bool L1FilterClose = false; // Use filter for Close input double L1CoefLambda = 0.2; // Lambda in lambda_max units //--- save statistics input bool SaveStatistics = false; // Save statistics to file //--- #define MA_MAGIC 1234501 #include <Trade\Trade.mqh> CTrade ExtTrade; int ExtHandle = INVALID_HANDLE; bool ExtHedging = false; string ExtStrategyName="MA"; string ExtStrategyFileName=""; //+------------------------------------------------------------------+ //| Check new bar | //+------------------------------------------------------------------+ bool IsNewBar() { static datetime last_time=0; datetime t[1]; //--- if(CopyTime(_Symbol,_Period,0,1,t)>0) { if(t[0]!=last_time) { last_time=t[0]; return(true); } } return(false); } //+------------------------------------------------------------------+ //| CheckTrendL1 | //+------------------------------------------------------------------+ double CheckTrendL1() { int max_bars=L1TotalBars; MqlRates rates_data[]; ArrayResize(rates_data,max_bars); ArraySetAsSeries(rates_data,false); if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars) { Print("CopyRates failed for L1Trend"); return(0); } //--- prepare data (close prices vector) int data_count=max_bars; vector<double> data_close; data_close.Resize(data_count); for(int i=0; i<data_count; i++) data_close[i] = rates_data[i].close; //--- calculate L1 filter vector<double> data_filtered; data_filtered.Resize(data_count); double dp=0.0; bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered); if(res) dp = data_filtered[data_count-1]-data_filtered[data_count-2]; //--- return(dp); } //+------------------------------------------------------------------+ //| GetTradeSignal | //+------------------------------------------------------------------+ bool GetTradeSignal(ENUM_ORDER_TYPE &signal) { signal = WRONG_VALUE; MqlRates bars[]; double ma[]; ArraySetAsSeries(bars,true); ArraySetAsSeries(ma,true); ArrayResize(bars,2); ArrayResize(ma,2); //-- two last closed bars if(CopyRates(_Symbol,_Period,2,2,bars) != 2) { Print("CopyRates failed"); return(false); } if(CopyBuffer(ExtHandle,0,2,2,ma) != 2) { Print("CopyBuffer failed"); return(false); } double close_prev = bars[1].close; double close_last = bars[0].close; double ma_prev = ma[1]; double ma_last = ma[0]; //--- check MA crossover if(close_prev < ma_prev && close_last > ma_last) signal = ORDER_TYPE_BUY; else if(close_prev > ma_prev && close_last < ma_last) signal = ORDER_TYPE_SELL; //--- log // PrintFormat("PrevBar: time=%s close=%.5f ma=%.5f | LastBar: time=%s close=%.5f ma=%.5f | Signal=%s", // TimeToString(bars[0].time,TIME_DATE|TIME_MINUTES), close_prev, ma_prev, // TimeToString(bars[1].time,TIME_DATE|TIME_MINUTES), close_last, ma_last, // (signal==ORDER_TYPE_BUY?"BUY":signal==ORDER_TYPE_SELL?"SELL":"NONE")); //--- return(true); } //+------------------------------------------------------------------+ //| CheckForOpen | //+------------------------------------------------------------------+ void CheckForOpen() { ENUM_ORDER_TYPE signal; if(!GetTradeSignal(signal)) return; if(signal == WRONG_VALUE) return; //--- L1 filter if(L1FilterOpen) { double delta = CheckTrendL1(); if(signal == ORDER_TYPE_BUY && delta < 0) { signal = WRONG_VALUE; PrintFormat("Open BUY signal cancelled by L1 trend delta=%.5f", delta); } if(signal == ORDER_TYPE_SELL && delta > 0) { signal = WRONG_VALUE; PrintFormat("Open SELL signal cancelled by L1 trend delta=%.5f", delta); } } //--- if(signal == WRONG_VALUE) return; //--- if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol,_Period)<L1TotalBars) return; //--- double price = (signal==ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol,SYMBOL_ASK): SymbolInfoDouble(_Symbol,SYMBOL_BID); //--- ExtTrade.PositionOpen(_Symbol, signal, TradeLot, price, 0, 0); } //+------------------------------------------------------------------+ //| CheckForClose | //+------------------------------------------------------------------+ void CheckForClose() { //--- check position if(!PositionSelect(_Symbol)) return; //--- check position magic if(PositionGetInteger(POSITION_MAGIC)!=MA_MAGIC) return; //--- check trade signal ENUM_ORDER_TYPE signal; if(!GetTradeSignal(signal)) return; //--- long type = PositionGetInteger(POSITION_TYPE); bool close_signal = false; //--- if(type == POSITION_TYPE_BUY && signal == ORDER_TYPE_SELL) close_signal = true; if(type == POSITION_TYPE_SELL && signal == ORDER_TYPE_BUY) close_signal = true; //--- check L1 filter if(L1FilterClose) { double delta = CheckTrendL1(); if(type == POSITION_TYPE_BUY && delta > 0) { close_signal = false; PrintFormat("Close BUY signal cancelled by L1 trend delta=%.5f", delta); } if(type == POSITION_TYPE_SELL && delta < 0) { close_signal = false; PrintFormat("Close SELL signal cancelled by L1 trend delta=%.5f", delta); } } //--- if(close_signal && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>=L1TotalBars) ExtTrade.PositionClose(_Symbol,3); } //+------------------------------------------------------------------+ //| SelectPosition | //+------------------------------------------------------------------+ bool SelectPosition() { bool res = false; if(ExtHedging) { uint total = PositionsTotal(); for(uint i=0; i<total; i++) { string sym = PositionGetSymbol(i); if(sym == _Symbol && PositionGetInteger(POSITION_MAGIC)==MA_MAGIC) { res = true; break; } } } else { if(PositionSelect(_Symbol)) res = (PositionGetInteger(POSITION_MAGIC)==MA_MAGIC); } return(res); } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- check parameters if(MovingPeriod<=0) { Print("Error: MovingPeriod parameter must be positive"); return(INIT_PARAMETERS_INCORRECT); } ExtHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); ExtTrade.SetExpertMagicNumber(MA_MAGIC); ExtTrade.SetMarginMode(); ExtTrade.SetTypeFillingBySymbol(_Symbol); //--- prepare indicator ExtHandle = iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE); if(ExtHandle==INVALID_HANDLE) { Print("Failed to create MA handle"); return(INIT_FAILED); } //--- prepare filename ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName); //--- delete old file if exists if(FileIsExist(ExtStrategyFileName)) FileDelete(ExtStrategyFileName); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| PrepareStrategyFileName | //+------------------------------------------------------------------+ string PrepareStrategyFileName(string strategy_name) { int v=0; if(L1FilterOpen) v=v | 1; //--- if(L1FilterClose) v=v | 2; //--- string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt"; return(filename); } //+------------------------------------------------------------------+ //| Save account statistics to file | //+------------------------------------------------------------------+ void SaveAccountStatistics() { //--- check file name if(ExtStrategyFileName=="") return; //--- int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI); if(file==INVALID_HANDLE) { Print("File open error: ",GetLastError()); return; } //--- append FileSeek(file,0,SEEK_END); //--- account data double balance = AccountInfoDouble(ACCOUNT_BALANCE); double equity = AccountInfoDouble(ACCOUNT_EQUITY); double margin = AccountInfoDouble(ACCOUNT_MARGIN); double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double margin_lvl = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); //--- volume double volume=0.0; if(PositionSelect(_Symbol)) volume=PositionGetDouble(POSITION_VOLUME); //--- time datetime t[1]; if(CopyTime(_Symbol,_Period,0,1,t)<=0) { FileClose(file); return; } double current_close[1]; if(CopyClose(_Symbol,_Period,0,1,current_close)<=0) { FileClose(file); return; } string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS), balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]); //--- FileWrite(file,line); //--- FileClose(file); } //+------------------------------------------------------------------+ //| Expert OnTick function | //+------------------------------------------------------------------+ void OnTick() { //--- trade only at new bar if(!IsNewBar()) return; //--- check trade conditions if(SelectPosition()) CheckForClose(); else CheckForOpen(); //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); //--- IndicatorRelease(ExtHandle); } //+------------------------------------------------------------------+
3.5.1.1. L1 Trend Filtreleme Verimliliğini Değerlendirmek için Genel Metodoloji
L1 filtresinin etkinliğini değerlendirmek için şunların yapılması gerekir:
- Maksimum kar sağlayan alım-satım stratejisinin en iyi parametre setini bulma.
En iyi performans gösteren stratejilerin işlem sinyallerinin iyileştirilmesi tavsiye edilir. - Filtre kullanımının 4 çeşidi için test sonuçlarını inceleme (L1FilterOpen/L1FilterClose parametrelerini ayarlayarak):
- Filtresiz alım-satım;
- Giriş filtreleme ile alım-satım;
- Çıkış filtreleme ile alım-satım;
- Hem giriş hem de çıkış filtreleme ile alım-satım.
- Birleşik grafikler oluşturmak için bir Python komut dosyası çalıştırma.
MovingAverageFilteredL1.mq5 Uzman Danışmanı örneğini kullanarak, EURUSD, H1 zaman dilimi, 2025 yılı test dönemi üzerinde işlem yaparak bu adımları gözden geçirelim.
Filtre kullanımı için, trend hesaplamasında sabit sayıda çubuk kullanacağız: L1TotalBars = 1000. Düzenlileştirme parametresi λ, L1CoefLambda = 0.2 sabit değeri kullanılarak λmax biriminde belirtilecektir.
Girdi parametreleri:
//--- best MovingAverage parameters for EURUSD,H1,2025 input int MovingPeriod = 64; // MA period input int MovingShift = 0; // MA shift //--- trade volume input double TradeLot = 0.1; // Lot size //--- L1 filter parameters input int L1TotalBars = 1000; // Total bars for L1 filter input bool L1FilterOpen = false; // Use filter for Open input bool L1FilterClose = false; // Use filter for Close input double L1CoefLambda = 0.2; // Lambda in lambda_max units //--- save statistics input bool SaveStatistics = false; // Save statistics to file
Optimizasyon ayarlarında, EURUSD sembolünü, H1 zaman dilimini ve 2025.01.01 ile 2025.12.31 arasındaki test dönemini belirtelim.

Şekil 21. MovingAverageFilteredL1.mq5 Uzman Danışmanının optimizasyon ayarları
Hızlı optimizasyon için “1 dakikalık OHLC” modunu kullanacağız (bu strateji yalnızca yeni çubuklarda çalışır, bu nedenle bu yaklaşım kabul edilebilir) ve “Bakiye maks” ayarıyla “Hızlı genetik tabanlı algoritma” optimizasyon algoritmasını seçeceğiz.

Şekil 22. MovingAverageFilteredL1.mq5 Uzman Danışmanının optimizasyon parametreleri
Bu aşamadaki amacımız alım-satım stratejisinin en iyi parametrelerini bulmak olduğundan, optimizasyon parametrelerindeki tüm filtreleri ve dosya kaydetmeyi devre dışı bırakıyoruz.
Basit olması için, “MA Period” adlı tek bir parametreyi 1 ile 800 arasındaki aralıkta, 1'er adımlık artışlarla optimize edeceğiz.

Şekil 23. MovingAverageFilteredL1.mq5 Uzman Danışmanının optimizasyon sonuçları
“MA Period” parametresinin optimizasyonu 1 dakikadan az sürdü; sonuçlar ve en iyi değerlerin listesi Şekil 23'te gösterilmiştir.
Daha doğru test için, test ayarlarında “Gerçek tiklere dayanan tüm tikler” seçeneğini seçiyoruz:

Şekil 24. MovingAverageFilteredL1.mq5 Uzman Danışmanının test ayarları
“MA Period” için en iyi değer olan 64 ile bir tek test çalıştıralım:

Şekil 25. MovingAverageFilteredL1.mq5 Uzman Danışmanının en iyi optimizasyon parametreleri

Şekil 26. MovingAverageFilteredL1.mq5 Uzman Danışmanı için en iyi parametrelerle test sonuçları
Şimdi verileri dosyalara kaydederek test çalıştırmamız gerekiyor.
Bunu yapmak için SaveStatistics = true olarak ayarlayalım ve 4 işlem sinyali filtresi kombinasyonuyla test çalıştıralım.

Şekil 27. MovingAverageFilteredL1.mq5 Uzman Danışmanı için sonuçları dosyalara kaydetme parametrelerini test etme
Tüm 4 kombinasyonla testleri çalıştırdıktan sonra, sınayıcı dizini şu dosyaları içerecektir: 0_MA_EURUSD.txt, 1_MA_EURUSD.txt, 2_MA_EURUSD.txt ve 3_MA_EURUSD.txt.
Test aralığının her çubuğu için zaman, kapanış fiyatı, bakiye ve varlık bilgilerini içerirler.
Ayrı bir dizin oluşturalım ve bunları oraya kopyalayalım (bu örnekte C:\Data).

Şekil 28. Tüm 4 filtre modu için test sonuçlarını içeren dosyalar
Veri analizi bir Python komut dosyası kullanılarak gerçekleştirilecektir:
import pandas as pd import matplotlib.pyplot as plt import os # --- folder for charts output_dir = "C:\\data\\charts\\" os.makedirs(output_dir, exist_ok=True) symbol = "EURUSD" name_strategy = "MA" file_strategy = name_strategy+"_"+symbol title_strategy = " ("+symbol+" "+name_strategy+" strategy+filters)" file_prefix = symbol+"_"+name_strategy+"_" # --- files files = [ "C:\\data\\0_"+file_strategy+".txt", "C:\\data\\1_"+file_strategy+".txt", "C:\\data\\2_"+file_strategy+".txt", "C:\\data\\3_"+file_strategy+".txt" ] # --- labels labels = [ "No filters", "Open L1 filter", "Close L1 filter", "Open+Close L1 filter" ] # --- load data def load_file(filename): df = pd.read_csv( filename, sep=";", header=None, names=[ "time", "balance", "equity", "margin", "free_margin", "margin_level", "volume", "close" ] ) df["time"] = pd.to_datetime(df["time"]) return df # --- close price chart plt.figure(figsize=(10,6), dpi=100) for file, label in zip(files, labels): df = load_file(file) plt.plot(df["time"], df["close"], color='gray') plt.title(symbol+" Close Price") plt.xlabel("Time") plt.ylabel("closing price") plt.legend() plt.grid() plt.tight_layout() plt.savefig(output_dir + file_prefix+"close_price.png", dpi=100) plt.show() # --- balance chart plt.figure(figsize=(10,6), dpi=100) for file, label in zip(files, labels): df = load_file(file) plt.plot(df["time"], df["balance"], label=label) plt.title("Balance" + title_strategy) plt.xlabel("Time") plt.ylabel("Balance") plt.legend() plt.grid() plt.tight_layout() plt.savefig(output_dir + file_prefix+"balance.png", dpi=100) plt.show() plt.close() # --- equity chart plt.figure(figsize=(10,6), dpi=100) for file, label in zip(files, labels): df = load_file(file) plt.plot(df["time"], df["equity"], label=label) plt.title("Equity" + title_strategy) plt.xlabel("Time") plt.ylabel("Equity") plt.legend() plt.grid() plt.tight_layout() plt.savefig(output_dir + file_prefix+"equity.png", dpi=100) plt.show() plt.close() #--- balance + equity chart plt.figure(figsize=(10,6), dpi=100) for i, (file, label) in enumerate(zip(files, labels)): df = load_file(file) # --- get matplotlib color color = plt.rcParams["axes.prop_cycle"].by_key()["color"][i % 10] #--- equity — solid line plt.plot( df["time"], df["equity"], color=color, linestyle="-", label=f"{label} equity" ) #--- balance — dashed line plt.plot( df["time"], df["balance"], color=color, linestyle="--", label=f"{label} balance" ) plt.title("Balance + Equity" + title_strategy) plt.xlabel("Time") plt.ylabel("Value") plt.legend() plt.grid() plt.tight_layout() plt.savefig(output_dir+file_prefix+"balance_equity.png", dpi=100) plt.show() plt.close()
Python komut dosyasını MetaEditor'da çalıştırmak için “Derle” düğmesine basın (Şekil 29).

Şekil 29. MetaEditor'da PlotData.py komut dosyası
PlotData.py çalıştırıldıktan sonra aşağıdaki grafikler görüntülenecektir:
- EURUSD fiyat serisi (her çubuktaki kapanış fiyatları);
- Tüm filtre modları için bakiye grafikleri;
- Tüm filtre modları için varlık grafikleri;
- Birleşik Bakiye + Varlık grafikleri (düşüş azaltımını değerlendirmek için).
Grafikler ayrıca PNG formatında C:\Data\Charts\ klasörüne kaydedilecektir.

Şekil 30. Test dönemi için EURUSD fiyat grafiği

Şekil 31. Farklı filtre modlarına göre MovingAverageFilteredL1.mq5 Uzman Danışmanı için bakiye grafikleri

Şekil 32. Farklı filtre modlarına göre MovingAverageFilteredL1.mq5 Uzman Danışmanı için varlık grafikleri

Şekil 33. Farklı filtre modlarına göre MovingAverageFilteredL1.mq5 Uzman Danışmanı için birleşik Bakiye ve Varlık grafikleri
Python komut dosyalarını MetaEditor'da çalıştırmak için Python'ı yükleyin (örnekte sürüm 3.14) ve ayarlarda yolunu belirtin.

Şekil 34. MetaEditor'da Python ayarları
Komut dosyası pandas ve matplotlib kütüphanelerini kullanır. Yüklü değillerse:
pip install pandas pip install matplotlib
3.5.1.2. L1 filtrelerini uygulama sonuçları (Moving Average stratejisi)
Sonuçlar (bakiye, varlık ve birleşik grafikler) Şekil 35-37'de gösterilmiştir.
Renkler:
- Mavi (filtresiz strateji);
- Yeşil (pozisyon kapanışında L1 filtresi);
- Kırmızı (hem açılış hem de kapanışta L1 filtresi);
- Turuncu (açılışta L1 filtresi).

Şekil 35. Farklı filtre modlarına göre MovingAverageFilteredL1.mq5 Uzman Danışmanı için bakiye grafikleri

Şekil 36. Farklı filtre modlarına göre MovingAverageFilteredL1.mq5 Uzman Danışmanı için varlık grafikleri

Şekil 37. Farklı filtre modlarına göre MovingAverageFilteredL1.mq5 Uzman Danışmanı için birleşik Bakiye ve Varlık grafikleri
3.5.2. MACD Alım-Satım Stratejisi
Başka bir örnek olarak, MACD (Moving Average Convergence/Divergence) göstergesi üzerine kurulu bir alım-satım stratejisinin sinyallerine göre işlem yapan bir Uzman Danışmanı inceleyelim.
İşlem sinyalleri
MACD ana çizgisi sinyal çizgisini çaprazladığında sinyaller üretilir:
- ALIŞ - MACD ana çizgisi sinyal çizgisini aşağıdan yukarıya doğru çaprazladığında.
- SATIŞ - MACD ana çizgisi sinyal çizgisini yukarıdan aşağıya doğru çaprazladığında.
Sinyaller yalnızca çubuk kapanışında analiz edilir, bu da çubuk içindeki yanlış tetiklemelerin sayısını azaltır.
Ayrıca, L1 trendi ayarlarına göre (L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda) işlem giriş ve çıkış filtreleri kullanılır.
MACDFilteredL1.mq5 Uzman Danışmanının kodu:
//+------------------------------------------------------------------+ //| MACDFilteredL1.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- best MACD parameters for USDCHF,H1,2025 input int FastEMA = 43; // Fast EMA input int SlowEMA = 59; // Slow EMA input int SignalEMA = 37; // SignalEMA //--- trade volume input double TradeLot = 0.1; // Lot size //--- L1 filter parameters input int L1TotalBars = 1000; // Total bars for L1 filter input bool L1FilterOpen = false; // Use filter for Open input bool L1FilterClose = false; // Use filter for Close input double L1CoefLambda = 0.2; // Lambda in lambda_max units //--- save statistics input bool SaveStatistics = false; // Save statistics to file //--- #define MACD_MAGIC 1234502 #include <Trade\Trade.mqh> int ExtHandle = INVALID_HANDLE; bool ExtHedging = false; CTrade ExtTrade; string ExtStrategyName="MACD"; string ExtStrategyFileName=""; //+------------------------------------------------------------------+ //| Check new bar | //+------------------------------------------------------------------+ bool IsNewBar() { static datetime last_time=0; datetime t[1]; //--- if(CopyTime(_Symbol,_Period,0,1,t)>0) { if(t[0]!=last_time) { last_time=t[0]; return(true); } } return(false); } //+------------------------------------------------------------------+ //| CheckTrendL1 | //+------------------------------------------------------------------+ double CheckTrendL1() { int max_bars=L1TotalBars; MqlRates rates_data[]; ArrayResize(rates_data,max_bars); ArraySetAsSeries(rates_data,false); if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars) { Print("CopyRates failed for L1Trend"); return(0); } //--- prepare data (close prices vector) int data_count=max_bars; vector<double> data_close; data_close.Resize(data_count); for(int i=0; i<data_count; i++) data_close[i] = rates_data[i].close; //--- calculate L1 filter vector<double> data_filtered; data_filtered.Resize(data_count); double dp=0.0; bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered); if(res) dp = data_filtered[data_count-1]-data_filtered[data_count-2]; //--- return(dp); } //+------------------------------------------------------------------+ //| GetTradeSignal(MACD) | //+------------------------------------------------------------------+ bool GetTradeSignal(ENUM_ORDER_TYPE &signal) { signal = WRONG_VALUE; double macd_main[]; double macd_signal[]; //--- ArrayResize(macd_main,2); ArrayResize(macd_signal,2); //--- ArraySetAsSeries(macd_main, true); ArraySetAsSeries(macd_signal, true); //--- buffer 0 = MACD main, buffer 1 = signal line if(CopyBuffer(ExtHandle,0,1,2,macd_main)!=2) return(false); if(CopyBuffer(ExtHandle,1,1,2,macd_signal)!=2) return(false); //--- double main_prev = macd_main[1]; double main_last = macd_main[0]; double signal_prev = macd_signal[1]; double signal_last = macd_signal[0]; //--- MACD crossover if(main_prev < signal_prev && main_last > signal_last) signal = ORDER_TYPE_BUY; else if(main_prev > signal_prev && main_last < signal_last) signal = ORDER_TYPE_SELL; //--- return(true); } //+------------------------------------------------------------------+ //| CheckForOpen | //+------------------------------------------------------------------+ void CheckForOpen() { ENUM_ORDER_TYPE signal; if(!GetTradeSignal(signal) || signal == WRONG_VALUE) return; //--- if(L1FilterOpen) { double dp = CheckTrendL1(); if(signal == ORDER_TYPE_BUY && dp < 0) return; if(signal == ORDER_TYPE_SELL && dp > 0) return; } //--- if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol, _Period) < L1TotalBars) return; //--- double price = (signal == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); //--- ExtTrade.PositionOpen(_Symbol, signal, TradeLot, price, 0, 0); } //+------------------------------------------------------------------+ //| CheckForClose | //+------------------------------------------------------------------+ void CheckForClose() { //--- check position if(!PositionSelect(_Symbol)) return; //--- check position magic if(PositionGetInteger(POSITION_MAGIC)!=MACD_MAGIC) return; //--- check trade signal ENUM_ORDER_TYPE signal; if(!GetTradeSignal(signal)) return; //--- long type = PositionGetInteger(POSITION_TYPE); bool close_signal = false; //--- if(type == POSITION_TYPE_BUY && signal == ORDER_TYPE_SELL) close_signal = true; if(type == POSITION_TYPE_SELL && signal == ORDER_TYPE_BUY) close_signal = true; //--- check L1 filter if(L1FilterClose) { double dp = CheckTrendL1(); if(type == POSITION_TYPE_BUY && dp > 0) { close_signal = false; PrintFormat("Close BUY signal cancelled by L1 trend dp=%.5f", dp); } if(type == POSITION_TYPE_SELL && dp < 0) { close_signal = false; PrintFormat("Close SELL signal cancelled by L1 trend dp=%.5f", dp); } } //--- if(close_signal) ExtTrade.PositionClose(_Symbol, 3); } //+------------------------------------------------------------------+ //| SelectPosition | //+------------------------------------------------------------------+ bool SelectPosition() { bool res = false; if(ExtHedging) { uint total = PositionsTotal(); for(uint i=0; i<total; i++) { string sym = PositionGetSymbol(i); if(sym == _Symbol && PositionGetInteger(POSITION_MAGIC)==MACD_MAGIC) { res = true; break; } } } else { if(PositionSelect(_Symbol)) res = (PositionGetInteger(POSITION_MAGIC)==MACD_MAGIC); } return(res); } //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { //--- check parameters if(FastEMA <= 0 || SlowEMA <= 0 || SignalEMA <= 0) { Print("Error: MACD parameters must be positive"); return(INIT_PARAMETERS_INCORRECT); } if(FastEMA >= SlowEMA) { Print("FastEMA must be less than SlowEMA"); return(INIT_PARAMETERS_INCORRECT); } //--- ExtHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); ExtTrade.SetExpertMagicNumber(MACD_MAGIC); ExtTrade.SetMarginMode(); ExtTrade.SetTypeFillingBySymbol(_Symbol); //--- prepare indicator ExtHandle=iMACD(_Symbol,_Period,FastEMA,SlowEMA,SignalEMA,PRICE_CLOSE); if(ExtHandle==INVALID_HANDLE) { Print("Failed to create MACD handle"); return(INIT_FAILED); } //--- prepare filename ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName); //--- delete old file if exists if(FileIsExist(ExtStrategyFileName)) FileDelete(ExtStrategyFileName); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| PrepareStrategyFileName | //+------------------------------------------------------------------+ string PrepareStrategyFileName(string strategy_name) { int v=0; if(L1FilterOpen) v=v | 1; //--- if(L1FilterClose) v=v | 2; //--- string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt"; return(filename); } //+------------------------------------------------------------------+ //| Save account statistics to file | //+------------------------------------------------------------------+ void SaveAccountStatistics() { //--- check file name if(ExtStrategyFileName=="") return; //--- int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI); if(file==INVALID_HANDLE) { Print("File open error: ",GetLastError()); return; } //--- append FileSeek(file,0,SEEK_END); //--- account data double balance = AccountInfoDouble(ACCOUNT_BALANCE); double equity = AccountInfoDouble(ACCOUNT_EQUITY); double margin = AccountInfoDouble(ACCOUNT_MARGIN); double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double margin_lvl = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); //--- volume double volume=0.0; if(PositionSelect(_Symbol)) volume=PositionGetDouble(POSITION_VOLUME); //--- time datetime t[1]; if(CopyTime(_Symbol,_Period,0,1,t)<=0) { FileClose(file); return; } double current_close[1]; if(CopyClose(_Symbol,_Period,0,1,current_close)<=0) { FileClose(file); return; } string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS), balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]); //--- FileWrite(file,line); //--- FileClose(file); } //+------------------------------------------------------------------+ //| Expert OnTick function | //+------------------------------------------------------------------+ void OnTick() { //--- trade only at new bar if(!IsNewBar()) return; //--- check trade conditions if(SelectPosition()) CheckForClose(); else CheckForOpen(); //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); } //+------------------------------------------------------------------+ //| Expert deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); //--- if(ExtHandle != INVALID_HANDLE) IndicatorRelease(ExtHandle); } //+------------------------------------------------------------------+
Test ayarları Şekil 38'de gösterilmiştir.

Şekil 38. MACDFilteredL1.mq5 Uzman Danışmanının Strateji Sınayıcı ayarları

Şekil 39. MACDFilteredL1.mq5 Uzman Danışmanının test parametreleri

Şekil 40. MACDFilteredL1.mq5 Uzman Danışmanının test sonuçları

Şekil 41. MACDFilteredL1.mq5 Uzman Danışmanı için sonuçları dosyalara kaydetme parametrelerini test etme
Strateji sınayıcıda farklı filtre yapılandırmalarıyla testler çalıştırdıktan sonra, x_MACD_EURUSD.txt dosyaları sınayıcı dizininde görünecektir.
Bunlar C:\Data\ klasörüne kopyalanmalı ve PlotData.py komut dosyası çalıştırılmalıdır.

Şekil 42. Farklı filtre modları için MACDFilteredL1.mq5 Uzman Danışmanının test sonuçlarını içeren dosyalar
3.5.2.1. L1 filtrelerini uygulama sonuçları (MACD stratejisi)
Sonuçlar Şekil 43-45'te gösterilmiştir.

Şekil 43. Farklı filtre modlarına göre MACDFilteredL1.mq5 Uzman Danışmanı için bakiye grafikleri

Şekil 44. Farklı filtre modlarına göre MACDFilteredL1.mq5 Uzman Danışmanı için varlık grafikleri

Şekil 45. Farklı filtre modlarına göre MACDFilteredL1.mq5 Uzman Danışmanı için birleşik Bakiye ve Varlık grafikleri
3.5.3. ADX Alım-Satım Stratejisi
Başka bir örnek olarak, Average Directional Movement Index (ADX) göstergesine dayalı bir trend takip stratejisi uygulayan ADXFilteredL1.mq5 Uzman Danışmanını ele alalım.
Ana işlem sinyalleri, +DI ve -DI çizgilerinin ve trend gücünü karakterize eden ADX seviyesinin analizine dayanmaktadır.
Sinyaller aşağıdaki gibi tanımlanır:
- ALIŞ: +DI, -DI'yı aşağıdan yukarıya doğru çaprazladığında;
- SATIŞ: +DI, -DI'yı yukarıdan aşağıya doğru çaprazladığında.
Ek olarak, ADX değeri de dikkate alınır. ADX, ADXTrendLevel eşiğinin altındaysa, piyasa zayıf trendli veya yatay seyirli olarak değerlendirilir ve sinyal göz ardı edilir.
Son iki kapanmış çubuktaki gösterge değerleri kullanılır, mevcut tamamlanmamış çubuğun etkisi hariç tutulur ve yanlış sinyaller azaltılır.
Ayrıca, L1 trendi ayarlarına göre (L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda) işlem giriş ve çıkış filtreleri uygulanır.
ADXFilteredL1.mq5 Uzman Danışmanının kodu aşağıda sunulmuştur.
//+------------------------------------------------------------------+ //| ADXFilteredL1.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- best ADX parameters for EURUSD,H1,2025 input int ADXPeriod = 65; // ADX Period input double ADXTrendLevel = 7; // ADX Trend Level //--- trade volume input double TradeLot = 0.1; // Lot size //--- L1 filter parameters input int L1TotalBars = 1000; // Total bars for L1 filter input bool L1FilterOpen = false; // Use filter for Open input bool L1FilterClose = false; // Use filter for Close input double L1CoefLambda = 0.2; // Lambda in lambda_max units //--- save statistics input bool SaveStatistics = false; // Save statistics to file //--- #define ADX_MAGIC 1234503 #include <Trade\Trade.mqh> CTrade ExtTrade; int ExtHandle = INVALID_HANDLE; bool ExtHedging = false; string ExtStrategyName="ADX"; string ExtStrategyFileName=""; //+------------------------------------------------------------------+ //| Check new bar | //+------------------------------------------------------------------+ bool IsNewBar() { static datetime last_time=0; datetime t[1]; //--- if(CopyTime(_Symbol,_Period,0,1,t)>0) { if(t[0]!=last_time) { last_time=t[0]; return(true); } } return(false); } //+------------------------------------------------------------------+ //| CheckTrendL1 | //+------------------------------------------------------------------+ double CheckTrendL1() { int max_bars=L1TotalBars; MqlRates rates_data[]; ArrayResize(rates_data,max_bars); ArraySetAsSeries(rates_data,false); if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars) { Print("CopyRates failed for L1Trend"); return(0); } //--- prepare data (close prices vector) int data_count=max_bars; vector<double> data_close; data_close.Resize(data_count); for(int i=0; i<data_count; i++) data_close[i] = rates_data[i].close; //--- calculate L1 filter vector<double> data_filtered; data_filtered.Resize(data_count); double dp=0.0; bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered); if(res) dp = data_filtered[data_count-1] - data_filtered[data_count-2]; //--- return(dp); } //+------------------------------------------------------------------+ //| GetTradeSignal (ADX) | //+------------------------------------------------------------------+ bool GetTradeSignal(ENUM_ORDER_TYPE &signal) { signal=WRONG_VALUE; double adx[],plusdi[],minusdi[]; ArrayResize(adx,2); ArrayResize(plusdi,2); ArrayResize(minusdi,2); //--- ArraySetAsSeries(adx,true); ArraySetAsSeries(plusdi,true); ArraySetAsSeries(minusdi,true); //--- buffer0 = ADX if(CopyBuffer(ExtHandle,0,1,2,adx)!=2) return(false); //--- buffer1 = +DI if(CopyBuffer(ExtHandle,1,1,2,plusdi)!=2) return(false); //--- buffer2 = -DI if(CopyBuffer(ExtHandle,2,1,2,minusdi)!=2) return(false); double adx_last=adx[0]; double plus_prev=plusdi[1]; double plus_last=plusdi[0]; double minus_prev=minusdi[1]; double minus_last=minusdi[0]; //--- strong trend required if(adx_last<ADXTrendLevel) return(true); //--- +DI cross -DI if(plus_prev<minus_prev && plus_last>minus_last) signal=ORDER_TYPE_BUY; else if(plus_prev>minus_prev && plus_last<minus_last) signal=ORDER_TYPE_SELL; //--- return(true); } //+------------------------------------------------------------------+ //| CheckForOpen | //+------------------------------------------------------------------+ void CheckForOpen() { ENUM_ORDER_TYPE signal; //--- if(!GetTradeSignal(signal) || signal==WRONG_VALUE) return; //--- if(L1FilterOpen) { double dp=CheckTrendL1(); if(signal==ORDER_TYPE_BUY && dp<0) return; if(signal==ORDER_TYPE_SELL && dp>0) return; } //--- if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol,_Period)<L1TotalBars) return; //--- double price=(signal==ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol,SYMBOL_ASK) : SymbolInfoDouble(_Symbol,SYMBOL_BID); //--- ExtTrade.PositionOpen(_Symbol,signal,TradeLot,price,0,0); } //+------------------------------------------------------------------+ //| CheckForClose | //+------------------------------------------------------------------+ void CheckForClose() { //--- check position if(!PositionSelect(_Symbol)) return; //--- check position magic if(PositionGetInteger(POSITION_MAGIC)!=ADX_MAGIC) return; //--- check trade signal ENUM_ORDER_TYPE signal; if(!GetTradeSignal(signal)) return; //--- long type=PositionGetInteger(POSITION_TYPE); bool close_signal=false; //--- if(type==POSITION_TYPE_BUY && signal==ORDER_TYPE_SELL) close_signal=true; //--- if(type==POSITION_TYPE_SELL && signal==ORDER_TYPE_BUY) close_signal=true; //--- check L1 filter //--- check L1 filter if(L1FilterClose) { double dp = CheckTrendL1(); if(type == POSITION_TYPE_BUY && dp > 0) { close_signal = false; PrintFormat("Close BUY signal cancelled by L1 trend dp=%.5f", dp); } if(type == POSITION_TYPE_SELL && dp < 0) { close_signal = false; PrintFormat("Close SELL signal cancelled by L1 trend dp=%.5f", dp); } } //--- if(close_signal) ExtTrade.PositionClose(_Symbol,3); } //+------------------------------------------------------------------+ //| SelectPosition | //+------------------------------------------------------------------+ bool SelectPosition() { bool res = false; if(ExtHedging) { uint total = PositionsTotal(); for(uint i=0; i<total; i++) { string sym = PositionGetSymbol(i); if(sym == _Symbol && PositionGetInteger(POSITION_MAGIC)==ADX_MAGIC) { res = true; break; } } } else { if(PositionSelect(_Symbol)) res = (PositionGetInteger(POSITION_MAGIC)==ADX_MAGIC); } return(res); } //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { ExtHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); ExtTrade.SetExpertMagicNumber(ADX_MAGIC); ExtTrade.SetMarginMode(); ExtTrade.SetTypeFillingBySymbol(_Symbol); //--- prepare indicator ExtHandle=iADX(_Symbol,_Period,ADXPeriod); if(ExtHandle==INVALID_HANDLE) { Print("ADX handle error"); return(INIT_FAILED); } //--- prepare filename ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName); //--- delete old file if exists if(FileIsExist(ExtStrategyFileName)) FileDelete(ExtStrategyFileName); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| PrepareStrategyFileName | //+------------------------------------------------------------------+ string PrepareStrategyFileName(string strategy_name) { int v=0; if(L1FilterOpen) v=v | 1; //--- if(L1FilterClose) v=v | 2; //--- string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt"; return(filename); } //+------------------------------------------------------------------+ //| Save account statistics to file | //+------------------------------------------------------------------+ void SaveAccountStatistics() { //--- check file name if(ExtStrategyFileName=="") return; //--- int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI); if(file==INVALID_HANDLE) { Print("File open error: ",GetLastError()); return; } //--- append FileSeek(file,0,SEEK_END); //--- account data double balance = AccountInfoDouble(ACCOUNT_BALANCE); double equity = AccountInfoDouble(ACCOUNT_EQUITY); double margin = AccountInfoDouble(ACCOUNT_MARGIN); double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double margin_lvl = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); //--- volume double volume=0.0; if(PositionSelect(_Symbol)) volume=PositionGetDouble(POSITION_VOLUME); //--- time datetime t[1]; if(CopyTime(_Symbol,_Period,0,1,t)<=0) { FileClose(file); return; } double current_close[1]; if(CopyClose(_Symbol,_Period,0,1,current_close)<=0) { FileClose(file); return; } string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS), balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]); //--- FileWrite(file,line); //--- FileClose(file); } //+------------------------------------------------------------------+ //| Expert OnTick function | //+------------------------------------------------------------------+ void OnTick() { //--- trade only at new bar if(!IsNewBar()) return; //--- check trade conditions if(SelectPosition()) CheckForClose(); else CheckForOpen(); //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); } //+------------------------------------------------------------------+ //| Expert deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); //--- release indicator handle if(ExtHandle!=INVALID_HANDLE) IndicatorRelease(ExtHandle); } //+------------------------------------------------------------------+
Test ayarları, parametreler ve sonuçlar Şekil 46-48'de gösterilmiştir.

Şekil 46. ADXFilteredL1.mq5 Uzman Danışmanı için sınayıcı ayarı

Şekil 47. ADXFilteredL1.mq5 Uzman Danışmanının test parametreleri

Şekil 48. ADXFilteredL1.mq5 Uzman Danışmanının test sonuçları
Test için, farklı filtre ayarlarıyla 4 çalıştırma gerçekleştirmeniz gerekir:

Şekil 49. ADXFilteredL1.mq5 Uzman Danışmanı için sonuçları dosyalara kaydetme parametrelerini test etme
Sonrasında, dosyalar sınayıcı dizininde görünecektir; C:\Data'ya kopyalanmalı ve PlotData.py ile işlenmelidir.

Şekil 50. Farklı filtre modları için ADXFilteredL1.mq5 Uzman Danışmanının test sonuçlarını içeren dosyalar
3.5.3.1. L1 filtrelerini uygulama sonuçları (ADX stratejisi)
Sonuçlar Şekil 51-53'te gösterilmiştir.

Şekil 51. Farklı filtre modlarına göre ADXFilteredL1.mq5 Uzman Danışmanı için bakiye grafikleri

Şekil 52. Farklı filtre modlarına göre ADXFilteredL1.mq5 Uzman Danışmanı için varlık grafikleri

Şekil 53. Farklı filtre modlarına göre ADXFilteredL1.mq5 Uzman Danışmanı için birleşik Bakiye ve Varlık grafikleri
3.5.4. EMA çaprazlamasına dayalı alım-satım stratejisi
İki Exponential Moving Average (EMA) göstergesinin çaprazlamasına dayalı bir trend takip alım-satım stratejisi uygulayan EMAFilteredL1.mq5 Uzman Danışmanını inceleyelim.
Strateji iki hareketli ortalama kullanır:
- FastEMA - hızlı EMA;
- SlowEMA - yavaş EMA.
İşlem sinyalleri aşağıdaki gibi oluşturulur:
- ALIŞ: Hızlı EMA, yavaş EMA'yı aşağıdan yukarıya doğru çaprazladığında;
- SATIŞ: Hızlı EMA, yavaş EMA'yı yukarıdan aşağıya doğru çaprazladığında.
Analiz için, son iki kapanmış çubuktaki göstergelerin değerleri kullanılır, bu da mevcut tamamlanmamış çubuğun etkisini ortadan kaldırır ve yanlış sinyallerin sayısını azaltır.
Ayrıca, L1 trendi ayarlarına göre (L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda) işlem giriş ve çıkış filtreleri kullanılır.
EMAFilteredL1.mq5 Uzman Danışmanının kodu aşağıda sunulmuştur.
//+------------------------------------------------------------------+ //| EMAFilteredL1.mq5 | //| Copyright 2000-2026, MetaQuotes Ltd. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2026, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //--- best EMA parameters for EURUSD,H1,2025 input int FastEMA = 29; // Fast EMA input int SlowEMA = 101; // Slow EMA //--- trade volume input double TradeLot = 0.1; // Lot size //--- L1 filter parameters input int L1TotalBars = 1000; // Total bars for L1 filter input bool L1FilterOpen = false; // Use filter for Open input bool L1FilterClose = false; // Use filter for Close input double L1CoefLambda = 0.2; // Lambda in lambda_max units //--- save statistics input bool SaveStatistics = false; // Save statistics to file //--- #define EMA_MAGIC 1234503 #include <Trade\Trade.mqh> CTrade ExtTrade; int ExtHandle = INVALID_HANDLE; bool ExtHedging = false; int FastHandle, SlowHandle; string ExtStrategyName="EMA"; string ExtStrategyFileName=""; //+------------------------------------------------------------------+ //| Check new bar | //+------------------------------------------------------------------+ bool IsNewBar() { static datetime last_time=0; datetime t[1]; //--- if(CopyTime(_Symbol,_Period,0,1,t)>0) { if(t[0]!=last_time) { last_time=t[0]; return(true); } } return(false); } //+------------------------------------------------------------------+ //| CheckTrendL1 | //+------------------------------------------------------------------+ double CheckTrendL1() { int max_bars=L1TotalBars; MqlRates rates_data[]; ArrayResize(rates_data,max_bars); ArraySetAsSeries(rates_data,false); if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars) { Print("CopyRates failed for L1Trend"); return(0); } //--- prepare data (close prices vector) int data_count=max_bars; vector<double> data_close; data_close.Resize(data_count); for(int i=0; i<data_count; i++) data_close[i] = rates_data[i].close; //--- calculate L1 filter vector<double> data_filtered; data_filtered.Resize(data_count); double dp=0.0; bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered); if(res) dp = data_filtered[data_count-1] - data_filtered[data_count-2]; //--- return(dp); } //+------------------------------------------------------------------+ //| GetTradeSignal (2EMA crossover) | //+------------------------------------------------------------------+ bool GetTradeSignal(ENUM_ORDER_TYPE &signal) { signal=WRONG_VALUE; //--- double fast[],slow[]; ArrayResize(fast,2); ArrayResize(slow,2); //--- ArraySetAsSeries(fast,true); ArraySetAsSeries(slow,true); //--- if(CopyBuffer(FastHandle,0,1,2,fast)!=2) return(false); //--- if(CopyBuffer(SlowHandle,0,1,2,slow)!=2) return(false); //--- if(fast[1]<slow[1] && fast[0]>slow[0]) signal=ORDER_TYPE_BUY; if(fast[1]>slow[1] && fast[0]<slow[0]) signal=ORDER_TYPE_SELL; //--- return(true); } //+------------------------------------------------------------------+ //| CheckForOpen | //+------------------------------------------------------------------+ void CheckForOpen() { ENUM_ORDER_TYPE signal; //--- if(!GetTradeSignal(signal) || signal==WRONG_VALUE) return; //--- if(L1FilterOpen) { double dp=CheckTrendL1(); //--- if(signal==ORDER_TYPE_BUY && dp<0) return; if(signal==ORDER_TYPE_SELL && dp>0) return; } //--- if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol,_Period)<L1TotalBars) return; //--- double price=(signal==ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol,SYMBOL_ASK) : SymbolInfoDouble(_Symbol,SYMBOL_BID); //--- ExtTrade.PositionOpen(_Symbol,signal,TradeLot,price,0,0); } //+------------------------------------------------------------------+ //| CheckForClose | //+------------------------------------------------------------------+ void CheckForClose() { //--- check position if(!PositionSelect(_Symbol)) return; //--- check position magic if(PositionGetInteger(POSITION_MAGIC)!=EMA_MAGIC) return; //--- check trade signal ENUM_ORDER_TYPE signal; if(!GetTradeSignal(signal)) return; //--- long type=PositionGetInteger(POSITION_TYPE); bool close_signal=false; //--- if(type==POSITION_TYPE_BUY && signal==ORDER_TYPE_SELL) close_signal=true; //--- if(type==POSITION_TYPE_SELL && signal==ORDER_TYPE_BUY) close_signal=true; //--- check L1 filter if(L1FilterClose) { double dp = CheckTrendL1(); if(type == POSITION_TYPE_BUY && dp > 0) { close_signal = false; PrintFormat("Close BUY signal cancelled by L1 trend dp=%.5f", dp); } if(type == POSITION_TYPE_SELL && dp < 0) { close_signal = false; PrintFormat("Close SELL signal cancelled by L1 trend dp=%.5f", dp); } } //--- if(close_signal) ExtTrade.PositionClose(_Symbol,3); } //+------------------------------------------------------------------+ //| SelectPosition | //+------------------------------------------------------------------+ bool SelectPosition() { bool res = false; if(ExtHedging) { uint total = PositionsTotal(); for(uint i=0; i<total; i++) { string sym = PositionGetSymbol(i); if(sym == _Symbol && PositionGetInteger(POSITION_MAGIC)==EMA_MAGIC) { res = true; break; } } } else { if(PositionSelect(_Symbol)) res = (PositionGetInteger(POSITION_MAGIC)==EMA_MAGIC); } return(res); } //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { ExtHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); ExtTrade.SetExpertMagicNumber(EMA_MAGIC); ExtTrade.SetMarginMode(); ExtTrade.SetTypeFillingBySymbol(_Symbol); //--- prepare indicators FastHandle=iMA(_Symbol,_Period,FastEMA,0,MODE_EMA,PRICE_CLOSE); SlowHandle=iMA(_Symbol,_Period,SlowEMA,0,MODE_EMA,PRICE_CLOSE); if(FastHandle==INVALID_HANDLE||SlowHandle==INVALID_HANDLE) return(INIT_FAILED); //--- prepare filename ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName); //--- delete old file if exists if(FileIsExist(ExtStrategyFileName)) FileDelete(ExtStrategyFileName); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| PrepareStrategyFileName | //+------------------------------------------------------------------+ string PrepareStrategyFileName(string strategy_name) { int v=0; if(L1FilterOpen) v=v | 1; //--- if(L1FilterClose) v=v | 2; //--- string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt"; return(filename); } //+------------------------------------------------------------------+ //| Save account statistics to file | //+------------------------------------------------------------------+ void SaveAccountStatistics() { //--- check file name if(ExtStrategyFileName=="") return; //--- int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI); if(file==INVALID_HANDLE) { Print("File open error: ",GetLastError()); return; } //--- append FileSeek(file,0,SEEK_END); //--- account data double balance = AccountInfoDouble(ACCOUNT_BALANCE); double equity = AccountInfoDouble(ACCOUNT_EQUITY); double margin = AccountInfoDouble(ACCOUNT_MARGIN); double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double margin_lvl = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); //--- volume double volume=0.0; if(PositionSelect(_Symbol)) volume=PositionGetDouble(POSITION_VOLUME); //--- time datetime t[1]; if(CopyTime(_Symbol,_Period,0,1,t)<=0) { FileClose(file); return; } double current_close[1]; if(CopyClose(_Symbol,_Period,0,1,current_close)<=0) { FileClose(file); return; } string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS), balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]); //--- FileWrite(file,line); //--- FileClose(file); } //+------------------------------------------------------------------+ //| Expert OnTick function | //+------------------------------------------------------------------+ void OnTick() { //--- trade only at new bar if(!IsNewBar()) return; //--- check trade conditions if(SelectPosition()) CheckForClose(); else CheckForOpen(); //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); } //+------------------------------------------------------------------+ //| Expert deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- save account statistics if(SaveStatistics) SaveAccountStatistics(); //--- release indicator handles if(FastHandle!=INVALID_HANDLE) IndicatorRelease(FastHandle); //--- if(SlowHandle!=INVALID_HANDLE) IndicatorRelease(SlowHandle); } //+------------------------------------------------------------------+
EMAFilteredL1.mq5 Uzman Danışmanının ayarları, parametreleri ve test sonuçları Şekil 54-56'da gösterilmiştir.

Şekil 54. EMAFilteredL1.mq5 Uzman Danışmanı için sınayıcı ayarı

Şekil 55. EMAFilteredL1.mq5 Uzman Danışmanının test parametreleri

Şekil 56. EMAFilteredL1.mq5 Uzman Danışmanının test sonuçları
L1 filtrelerinin etkinliğini analiz etmek için, Uzman Danışmanı sınayıcıda farklı filtre modlarıyla sırayla çalıştırmak gerekir:

Şekil 57. EMAFilteredL1.mq5 Uzman Danışmanı için sonuçları dosyalara kaydetme parametrelerini test etme
Sonuç olarak, sınayıcı klasöründe dosyalar oluşturulacaktır; bunlar Python komut dosyası PlotData.py'da belirtilen dizine kopyalanmalı ve şu ayarlarla çalıştırılmalıdır: symbol = "EURUSD", name_strategy = "EMA".

Şekil 58. Farklı filtre modları için EMAFilteredL1.mq5 Uzman Danışmanının test sonuçlarını içeren dosyalar
3.5.4.1. L1 filtrelerini uygulama sonuçları (EMA stratejisi)
Sonuçlar Şekil 59-61'de gösterilmiştir.

Şekil 59. Farklı filtre modlarına göre EMAFilteredL1.mq5 Uzman Danışmanı için bakiye grafikleri

Şekil 60. Farklı filtre modlarına göre EMAFilteredL1.mq5 Uzman Danışmanı için varlık grafikleri

Şekil 61. Farklı filtre modlarına göre EMAFilteredL1.mq5 Uzman Danışmanı için birleşik Bakiye ve Varlık grafikleri
3.5.5. Moving Average, MACD, ADX ve EMA alım-satım stratejilerinde L1 filtresinin kullanımına ilişkin özet
Ele alınan örneklerde, alım-satım stratejileri EURUSD döviz paritesinde, H1 zaman diliminde, 2025 yılı için test edilmiştir (Şekil 62).

Şekil 62. Test dönemi boyunca EURUSD fiyat grafiği (2025, H1, kapanış fiyatları)

Şekil 63. Farklı filtre modlarına göre Moving Average, MACD, ADX, ve EMA stratejileri için bakiye grafikleri
Moving Average, MACD, ADX ve EMA stratejilerinin analizi, en iyi sonuçların pozisyon kapatma aşamasında L1 filtreleme uygulandığında elde edildiğini göstermiştir (grafiklerde yeşil renkle vurgulanmıştır). Çıkışta filtrenin kullanılması, gürültü geri dönüşlerini ve yanlış sinyalleri etkili bir şekilde bastırarak pozisyonların istikrarlı bir trend yönünde tutulmasına olanak tanır. Bu, karın ve Kar Faktörünün artmasının yanı sıra maksimum düşüşün azalmasına yol açar.
Pozisyon açma aşamasında L1 filtrelemenin kullanılmasının (turuncu ile vurgulanmıştır) daha az etkili olduğu kanıtlanmıştır, çünkü ek filtreleme giriş sayısını sınırlar ve işlem kalitesinde orantılı bir iyileşme olmaksızın karlı hareketlerin bir kısmının kaçırılmasına neden olur.

Şekil 64. Farklı filtre modlarına göre Moving Average, MACD, ADX, ve EMA stratejileri için birleşik Bakiye ve Varlık grafikleri
Böylece, pozisyon kapanışında işlem sinyallerinin L1 filtrelemesi alım-satım sisteminin istikrarını artırır, kısa vadeli fiyat dalgalanmalarına duyarlılığı azaltır ve kar/risk oranını iyileştirir. Klasik hareketli ortalamalara kıyasla, L1 filtresi geçici düzeltmeleri gerçek trend dönüşlerinden daha iyi ayırt ederek trend takip stratejilerinin daha verimli kullanılmasını sağlar.
L1 trend filtresiyle uyumlu sinyallerle işlem yaparken Bakiye ve Varlık eğrilerinin davranışına dikkat edilmelidir (Şekil 64). Trend boyunca işlem yaparken, Varlık genellikle Bakiyenin üzerindedir, bu da risk ölçütlerini önemli ölçüde iyileştirir ve düşüşü azaltır. Bu nedenle, trendle uyumlu hareket etmek, alım-satım sistemlerinin özelliklerini de iyileştirir (düşüşü ve riskleri azaltır).
Ek olarak, işlem sinyalleri L1 trendiyle uyumlu hale getirildiğinde, işlem sayısı azalırken kaliteleri artar ve bu da alım-satım stratejilerinin genel istatistiksel özelliklerini olumlu yönde etkiler.
| № | Strateji | Toplam Net Kar, USD | % Al ve Tut |
|---|---|---|---|
| 1 | Al ve Tut | 1363.8 | %100 |
| 2 | Moving Average (filtre yok) | 1001.03 | %73.4 |
| 3 | Moving Average (L1 giriş filtresi) | 107.65 | %7.89 |
| 4 | Moving Average (L1 çıkış filtresi) | 1342.5 | %98.43 |
| 5 | Moving Average (L1 giriş + çıkış filtresi) | 986.16 | %72.31 |
| 6 | MACD (filtre yok) | 997.79 | %73.16 |
| 7 | MACD (L1 giriş filtresi) | 140.13 | %10.27 |
| 8 | MACD (L1 çıkış filtresi) | 1359.52 | %99.69 |
| 9 | MACD (L1 giriş + çıkış filtresi) | 697.54 | %51.15 |
| 10 | ADX (filtre yok) | 791.99 | %58.07 |
| 11 | ADX (L1 giriş filtresi) | -50.9 | %-3.73 |
| 12 | ADX (L1 çıkış filtresi) | 940.39 | %68.95 |
| 13 | ADX (L1 giriş + çıkış filtresi) | 430.05 | %31.53 |
| 14 | EMA (filtre yok) | 957.3 | %70.19 |
| 15 | EMA (L1 giriş filtresi) | -173.35 | %-12.71 |
| 16 | EMA (L1 çıkış filtresi) | 1258.99 | %92.31 |
| 17 | EMA (L1 giriş + çıkış filtresi) | -131.41 | %-9.64 |
Tablo 4. Moving Average, MACD, ADX ve EMA stratejilerinde L1 filtresi kullanımının Al ve Tut'a kıyasla toplam kar sonuçları
Tablo 4'e göre, pozisyon kapanışında L1 filtresinin kullanılması tüm stratejiler için karlılığı artırmıştır.
Al ve Tut stratejisinin sonucu (1363.8$) tüm trend hareketinin %100'ü olarak alınırsa, şunları elde ederiz:
- Moving Average karı %73.4'ten %98.43'e yükseldi;
- MACD karı %73.16'dan %99.69'a yükseldi;
- ADX karı %58.07'den %68.5'e yükseldi;
- EMA karı %70.19'dan %92.31'e yükseldi.
Gördüğümüz gibi, L1 filtresinin kullanımı Moving Average, MACD ve EMA stratejilerinin karını %22-26 oranında artırmasına, trend hareketinin çoğunu yakalamasına (%98.43, %99.69 ve %92.31) ve Al ve Tut sonucuna yaklaşmasına olanak sağladı. ADX stratejisi karı %10 oranında arttı.
Örneklerde, en yüksek bakiye değerlerini veren (yani, mümkün olan en iyi çözümler arasında yer alan) parametrelere sahip stratejiler dikkate alınmıştır. Bunlar mavi renkle vurgulanmıştır. Sonuçlar, bu en karlı çözümlerin bile L1 trendiyle uyumlu hale getirilerek işlem sinyallerinin ilave filtrelenmesiyle daha da iyileştirilebileceğini göstermektedir. Bazı stratejiler çok az iyileşme göstermiştir (ADX için yeşil eğriler mavilere yakındır, bu da optimum bakiye çözümüne yakınlığı ifade eder). Bir stratejinin işlem sinyallerinin kalitesi (seçilen parametrelerin idealliği), bu tür bir L1 filtreleme ile ne kadar iyileştiğine göre değerlendirilebilir.
Özellikle, bu durumda güçlü bir trend gösteren EURUSD piyasası dikkate alınmıştır (Şekil 62). Diğer piyasa rejimleri ve enstrümanlar için sonuçlar farklılık gösterecektir. Ayrıca, L1 trendi H1 zaman diliminde λ = 0.2·λmax düzenlileştirme parametresi ile oluşturulmuştur. Diğer enstrümanlar ve zaman dilimleri için, bu katsayının uygun değerleri L1 trendi göstergeleri kullanılarak hesaplanabilir.
Sonuç
L1 trend filtreleme, yerel gürültüyü gerçek trend değişikliklerinden ayırmak için bir araç olarak pratik yararlılığını kanıtlamıştır.
Yöntem, otomatik kırılma noktaları ve λmax aracılığıyla uygun bir ayar ölçeği ile parçalı doğrusal bir trend oluşturur ve bu da manuel parametre uyarlama sorununu ortadan kaldırır.
Pratik entegrasyon düzeyinde, eksiksiz bir araç seti sağlanmıştır: λmax ve L1 filtresini hesaplamak için fonksiyonlar, üç gösterge (L1Trend, L1TrendSlope, L1TrendSlopeSign), yedi L1 trendi volatilite göstergesi (L1Volatility, L1VolatilitySmoothed, L1VolatilityAbsolute, L1VolatilityNormalized, L1VolatilityNormalizedSmoothed, L1VolatilityRegime, L1VolatilityRegimeColor), Uzman Danışman şablonları ve tekrarlanabilir bir test protokolü (dört mod: filtre yok, giriş filtresi, çıkış filtresi, her iki filtre; sonuçları kaydetme ve Python görselleştirme komut dosyası).
L1 trend filtresinin makine öğreniminde veri etiketleme için kullanılabileceği de unutulmamalıdır. Özellikle, “Makine Öğrenimini Kullanarak Trend Alım-Satım Stratejileri Geliştirme” makalesinde, trend belirleme, Savitzky-Golay filtresi ile yumuşatılmış fiyatların türevleri kullanılarak gerçekleştirilir. Benzer bir yaklaşım, trendin parçalı doğrusal fonksiyonlarla yaklaştırıldığı ve her bir segmentteki trendin gücünün doğal olarak ilgili segmentin eğimiyle ilişkili olduğu L1 filtreleme kullanılarak uygulanabilir.
Pratik öneriler:
- Göreceli düzenlileştirme kullanın: λ = coef_lambda_max · λmax. Çoğu görev için 0.04-0.25 aralığında katsayı kullanın; daha ince ayrıntılar için ≈0.02-0.04; kaba yaklaşma ve rejim tespiti için ≈0.12-0.25.
- Çoğu durumda, L1 filtresi en çok pozisyon kapatmaya uygulandığında etkilidir (karlı trendleri korumak ve erken çıkışları azaltmak). Bunu girişe uygulamak, genellikle kalitede orantılı bir iyileşme olmaksızın işlem sayısını azaltır.
- Mevcut trend analizi için basit bir kural kullanın: delta = x_filtered[last] - x_filtered[last-1]. Deltanın işareti baskın L1 trendinin yönünü gösterir.
Sınırlama: etki enstrümana, zaman dilimine ve piyasa rejimine bağlıdır; seçilen ölçütlerle geçmiş veriler üzerinde doğrulama gereklidir.
Sunulan MQL5 modülleri ve test protokolü, belirli bir alım-satım sistemi için hızlı hipotez testi ve çalışma parametrelerinin seçilmesine olanak tanır.
Makaledeki tüm kodlar "MQL5\Shared Projects\L1Trend" herkese açık projesinde de mevcuttur.
Örnekler
| Tür | Dosya | Açıklama |
|---|---|---|
| Komut dosyası | MQL5\Scripts\TestL1Trend.mq5 | Model verilerinde (rastgele yürüyüş) L1 trendini hesaplamak için test komut dosyası |
| Komut dosyası | MQL5\Scripts\TestL1TrendFloatDouble.mq5 | Double ve float vektörler için model verilerinde (rastgele yürüyüş) L1 trendini hesaplamak için test komut dosyası |
| Komut dosyası | MQL5\Scripts\TestL1TrendFilterSP500.mq5 | SP500 endeksi fiyat verilerinde L1 trendini hesaplamak için test komut dosyası |
| Veri dosyası | MQL5\Files\snp500.txt | Test komut dosyası için veri dosyası (SP500 endeksi fiyat serisinin logaritması) |
| Komut dosyası | MQL5\Scripts\TestScalingBrownianMotion.mq5 | Brown hareketi için λmax'ın kuvvet yasası bağımlılığını hesaplayan komut dosyası |
| Komut dosyası | MQL5\Scripts\TestScalingSymbol.mq5 | Belirli bir sembolün fiyat serisi için λmax'ın kuvvet yasası bağımlılığını hesaplayan komut dosyası |
| Gösterge | MQL5\Indicators\L1TrendFilter.mq5 | L1 trendini hesaplamak için gösterge |
| Gösterge | MQL5\Indicators\L1TrendFilter_Slope.mq5 | L1 trendinin değişim oranını hesaplamak için gösterge |
| Gösterge | MQL5\Indicators\L1TrendFilter_SlopeSign.mq5 | L1 trendinin değişim işaretini hesaplamak için gösterge |
| Gösterge | MQL5\Indicators\L1Volatility.mq5 | Artık volatiliteyi (kapanış fiyatları ile L1 trend değeri arasındaki fark) hesaplamak için gösterge |
| Gösterge | MQL5\Indicators\L1VolatilitySmoothed.mq5 | Yumuşatılmış artık volatiliteyi hesaplamak için gösterge |
| Gösterge | MQL5\Indicators\L1VolatilityAbsolute.mq5 | Mutlak volatiliteyi hesaplamak için gösterge |
| Gösterge | MQL5\Indicators\L1VolatilityNormalized.mq5 | ATR (Average True Range) ve L1 trendini kullanarak normalleştirilmiş volatiliteyi hesaplamak için gösterge |
| Gösterge | MQL5\Indicators\L1VolatilityNormalizedSmoothed.mq5 | Yumuşatılmış normalleştirilmiş volatiliteyi hesaplamak için gösterge |
| Gösterge | MQL5\Indicators\L1VolatilityRegime.mq5 | Piyasa rejimi tespit göstergesi |
| Gösterge | MQL5\Indicators\L1VolatilityRegimeColor.mq5 | Piyasa rejimi tespit göstergesinin renkli versiyonu |
| Uzman Danışman | MQL5\Experts\MovingAverageFilteredL1.mq5 | L1 filtreli Moving Average stratejisine dayalı Uzman Danışman |
| Uzman Danışman | MQL5\Experts\MACDFilteredL1.mq5 | L1 filtreli MACD stratejisine dayalı Uzman Danışman |
| Uzman Danışman | MQL5\Experts\ADXFilteredL1.mq5 | L1 filtreli ADX stratejisine dayalı Uzman Danışman |
| Uzman Danışman | MQL5\Experts\EMAFilteredL1.mq5 | L1 filtreli iki EMA'nın çaprazlamasına dayalı Uzman Danışman |
| Python komut dosyası | MQL5\Scripts\PlotData.py | İşlem sinyallerine L1 filtresi uygulamanın etkinliğini analiz etmek için Python komut dosyası |
Tablo 5. Makalede kullanılan program kodlarının açıklaması
MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/21142
Uyarı: Bu materyallerin tüm hakları MetaQuotes Ltd'ye aittir. Bu materyallerin tamamen veya kısmen kopyalanması veya yeniden yazdırılması yasaktır.
Yeni Raylara Adım Atın: MQL5'te Özel Göstergeler
Forex'te portföy optimizasyonu: VaR ve Markowitz teorisinin birleştirilmesi
İşte Karışınızda Yeni MetaTrader 5 ve MQL5
3D geri dönüş formasyonlarına dayalı algoritmik alım-satım
- Ücretsiz alım-satım uygulamaları
- İşlem kopyalama için 8.000'den fazla sinyal
- Finansal piyasaları keşfetmek için ekonomik haberler
Web sitesi politikasını ve kullanım şartlarını kabul edersiniz
Şöyle bir şey:
Şöyle bir şey:
Trendlere ayırma işlemi, lambda düzenleme parametresine büyük ölçüde bağlıdır - lambda ne kadar küçükse, o kadar kısa trendleri yakalayabilir.
İncelenen örneklerde, lambda=0.2*lambda_max birimlerinde sabit lambda değerleri kullanılmıştır. Lambda_max birimlerinde hesaplama, verilere kısmen uyum sağlamaya izin verir. Lambda_max değerinin kendisi, serinin geometrisine (göreceli dalgalanmalara), yani oynaklığa bağlıdır.
Trendin farklı aşamaları ve kendi yaşam döngüsü olduğunu akılda tutmak gerekir. Bu nedenle , mevcut trende uyum sağlamak için bir tür mekanizma gereklidir, yani lambda'yı bir şekilde yönetmek ve optimal trend bölünmesini bulmak - bu görev henüz çözülmemiştir.
Stratejinin kendisi zaman aralığında kâr sağlamıyorsa, filtre de yardımcı olamaz.
En iyi sonuçlar ideal bir trend piyasasında elde edilmelidir, örnek şuydu: EURUSD, 2025, H1 (en iyi parametreler MovingAverage period=61).
L1 Close filtresi
Burada, çıkış filtresinin trendli bölümde kârı artırmaya yardımcı olduğunu görüyoruz.
Aynı stratejinin, düzeltmelerde pozisyonların eklenmesiyle birlikte bir varyantı:
eklemeler olmadan:
Eklemelerle:
Düz piyasa aralıkları, yerel küçük trendler içerir ve bunları doğru bir şekilde hesaba katmak için, lambda parametresinin daha küçük değerlerini kullanmak gerekir (çıkış filtresi olarak kullanıldığında).
Ayrıca, düz piyasa aralığında MovingAverage parametrelerinin en iyi değerleri de farklı olmalıdır. Yani, ikinci aralıktaki ortalama dönemleri değişmiştir (ancak test cihazında optimizasyon sırasında bulunan parametreler, tüm optimizasyon aralığı içinde diğer tüm parametreler arasında en yüksek kârı sağlar).
Farklı lambda değerleriyle flat aralığındaki sonuçları inceleyelim.
Filtreler olmadan:
Çıkış filtresi lambda=0.2*lambda_max ile
Filtre ile lambda=0.001 lambda_max (daha küçük trendler)
Böylece, lambda=0.001 lambda_max değerinde düz aralıkta, filtreler olmadan elde edilen sonucu iyileştirmek ve yerel küçük trendleri hesaba katmak mümkün oluyor.
Ancak, lambda=0,2*lambda_max filtreli seçenek, burada filtre kullanılmayan stratejiye göre daha düşük karlılık göstermiştir.
Yatay seyrin içindeki yerel trendlerde pozisyon ekleme seçeneği (farklı lambda değerleri)
Filtreler olmadan:
lambda=0.2*lambda_max filtresi ve düzeltmelerde ekleme ile:
lambda=0.001*lambda_max filtresi ve düzeltmelere ekleme ile:
lambda=0.2*lambda_max filtresi ve düzeltmelerde ekleme seçeneği, filtreler olmadan seçenekten daha iyi sonuç verdi.
Yatay aralığın içindeki yerel küçük trendlerin (lambda=0.001*lambda_max) düzeltmelerine ekleme yapılması, filtre içermeyen orijinal stratejinin kârını artırdı (ve lambda=0.2*lambda_max seçeneğinin kârını iyileştirdi).
Yatay hareket içindeki yerel trendlerde pozisyon ekleme seçeneği (farklı lambda)
Filtreler olmadan:
lambda=0.2*lambda_max filtresi ve düzeltmelerde ekleme ile:
lambda=0.001*lambda_max filtresiyle ve düzeltmelere eklenerek:
lambda=0.2*lambda_max filtreli ve düzeltmelere ekleme yapılan seçenek, filtresiz seçeneğe göre daha iyi sonuç verdi.
Flat aralığı içindeki küçük yerel trendlerin (lambda=0.001*lambda_max) düzeltmelere eklenmesi, filtre içermeyen orijinal stratejinin kârını artırdı (ve lambda=0.2*lambda_max seçeneğinin kârını iyileştirdi).
En azından demo hesabında işlem yapın
Deneyim kazandıkça anlayacaksınız, 10 ay boyunca sonuçsuz çalışmanın ardından