English Русский 中文 Español Deutsch 日本語 Português
preview
Borsada doğrusal olmayan regresyon modelleri

Borsada doğrusal olmayan regresyon modelleri

MetaTrader 5Alım-satım sistemleri |
20 16
Yevgeniy Koshtenko
Yevgeniy Koshtenko

Giriş

Dün bir kez daha regresyona dayalı alım-satım sistemimin raporlarına bakıyordum. Pencerenin dışında ıslak kar yağıyordu, kahve fincanda soğuyordu ama ben yine de takıntılı düşünceden kurtulamıyordum. Biliyorsunuz, bu bitmek bilmeyen RSI, Stochastic, MACD ve diğer göstergeler beni uzun zamandır rahatsız ediyor. Yaşayan ve dinamik bir piyasayı bu ilkel denklemlere nasıl sığdırmaya çalışabiliriz? Ne zaman YouTube'da "kutsal" göstergeleriyle bir başka Kase savunucusu görsem, sormak istiyorum - dostum, yetmişli yıllardan kalma bu hesap makinelerinin modern piyasanın karmaşık dinamiklerini yakalayabileceğine gerçekten inanıyor musun?

Son üç yılımı gerçekten işe yarayan bir şey yaratmaya çalışarak geçirdim. En basit regresyonlardan sofistike sinir ağlarına kadar pek çok şey denedim. Ve biliyor musunuz? Sınıflandırmada sonuç elde etmeyi başardım, ancak henüz regresyonda değil.

Her seferinde aynı hikaye vardı - geçmişte her şey saat gibi işliyordu, ancak gerçek piyasada çalıştırdığımda kayıplarla karşılaşıyordum. İlk konvolüsyonel ağım için ne kadar heyecanlandığımı hatırlıyorum. Eğitimde R² %1.00. Bunu iki haftalık işlem süreci ve bakiyenin %30'unun kaybı izledi. Klasik aşırı uyumun en güzel örneği. İleriye dönük görselleştirmeye devam ederek regresyona dayalı tahminin gerçek fiyatlardan nasıl gittikçe uzaklaştığını izledim...

Ama ben inatçı biriyim. Bir başka kayıptan sonra daha derine inmeye karar verdim ve bilimsel makaleleri incelemeye başladım. Ve tozlu arşivlerden ne buldum biliyor musunuz? Anlaşılan o ki, yaşlı Mandelbrot zaten piyasaların fraktal doğası hakkında atıp tutuyormuş. Ve hepimiz doğrusal modellerle işlem yapmaya çalışıyoruz! Bu, bir kıyı şeridinin uzunluğunu cetvelle ölçmeye çalışmak gibidir - ne kadar doğru ölçerseniz, o kadar uzar.

Bir noktada aklıma bir fikir geldi: klasik teknik analizi doğrusal olmayan dinamiklerle kesiştirmeye çalışırsam ne olur? Bu kaba göstergeler değil, daha ciddi bir şey - diferansiyel denklemler, uyarlanabilir katsayılar. Kulağa karmaşık gelebilir, ancak özünde piyasanın dilinden konuşmayı öğrenme çabasıdır.

Kısacası, Python'ı aldım, makine öğrenimi kütüphanelerini bağladım ve denemeye başladım. Hemen karar verdim - akademik ayrıntılar yok, sadece gerçekten kullanılabilecek şeyler. Süper bir bilgisayarım yoktu - sadece normal bir Acer dizüstü bilgisayar, süper güçlü VPS ve MetaTrader 5 terminali. Tüm bunlardan, size anlatmak istediğim model doğdu.

Hayır, bu bir Kase değil. Kase diye bir şey yok, bunu uzun zaman önce fark ettim. Ben sadece modern matematiği gerçek alım-satıma uygulama konusundaki deneyimlerimi paylaşıyorum. Gereksiz abartı yok, ama aynı zamanda "trend göstergelerinin" ilkelliği de yok. Sonuç ikisinin arasında bir şeydi: çalışacak kadar akıllı, ancak ilk siyah kuğu olayıyla karşılaştığında dağılacak kadar karmaşık değil.


Matematiksel model

Bu denklemi nasıl bulduğumu hatırlıyorum. Bu kod üzerinde 2022'den beri çalışıyorum, ancak sürekli değil: yaklaşımlar açısından şunu söyleyebilirim - birçok gelişme var, bu nedenle periyodik olarak (biraz kaotik bir şekilde) bunlardan geçiyorsunuz ve birbiri ardına sonuca ulaşıyorsunuz. EURUSD'de formasyonları yakalamaya çalışırken grafikleri birçok kez incelediğimi hatırlıyorum. Ve gözüme ne çarptı biliyor musunuz? Piyasa nefes alıyor gibi görünüyor - bazen trend boyunca yumuşak bir şekilde akıyor, bazen aniden keskin bir şekilde sarsılıyor, bazen bir tür büyülü ritme giriyor. Bunu matematiksel olarak nasıl tanımlayabiliriz? Bu yaşayan dinamiği denklemlerde nasıl yakalayabiliriz?

Daha sonra denklemin ilk versiyonunun taslağını çıkardım. İşte burada, tüm ihtişamıyla:

Ve işte kodda:

def equation(self, x_prev, coeffs):
    x_t1, x_t2 = x_prev[0], x_prev[1]
    return (coeffs[0] * x_t1 +          # trend
            coeffs[1] * x_t1**2 +       # acceleration
            coeffs[2] * x_t2 +          # market memory
            coeffs[3] * x_t2**2 +       # inertia
            coeffs[4] * (x_t1 - x_t2) + # impulse
            coeffs[5] * np.sin(x_t1) +  # market rhythm
            coeffs[6])                   # basic level

İlk iki terim, mevcut piyasa hareketini yakalamaya yönelik bir girişimdir. Bir arabanın nasıl hızlandığını biliyor musunuz? Önce yavaşça, sonra daha hızlı ve daha hızlı. Bu nedenle burada hem doğrusal hem de ikinci dereceden bir terim vardır. Fiyat sakin bir şekilde hareket ettiğinde, doğrusal kısım çalışır. Ancak piyasa hızlanır hızlanmaz, ikinci dereceden terim bu hareketi yakalar.

Şimdi en ilginç kısım geliyor. Üçüncü ve dördüncü terimler geçmişe biraz daha derinlemesine bakmaktadır. Bir piyasa hafızası gibi. Piyasanın seviyelerini hatırlamasıyla ilgili Dow Teorisini hatırlıyor musunuz? Burada da durum aynı. Ve yine keskin dönüşleri yakalamak için ikinci dereceden hızlanma özelliğine sahiptir.

Ardından, momentum bileşeni. Basitçe önceki fiyatı mevcut fiyattan çıkarırız. İlkel görünüyor. Ama trend hareketlerinde harika çalışıyor! Piyasa çılgına dönüp tek bir yöne doğru hareket ettiğinde, bu terim tahminin ana itici gücü haline gelir.

Sinüsü neredeyse tesadüfen ekledim. Grafiklere bakıyordum ve bir tür periyodiklik fark ettim. Özellikle H1'de. Hareketler ve sakin dönemler birbirini izliyordu... Sinüs dalgasına benziyor, değil mi? Sinüs dalgasını denkleme koydum ve model olayı kavradı ve bu ritimleri yakalamaya başladı.

Son katsayı bir tür güvenlik ağı, bir temel seviyedir. Bu terim, modelin tahminleriyle piyasayı büyük ölçüde şaşırtmasına izin vermez.

Bir sürü başka seçenek daha denedim. Üsleri, logaritmaları ve her türlü karmaşık trigonometrik fonksiyonu oraya tıkıştırdım. Pek bir anlamı olmuyor ama model bir canavara dönüşüyor. Occam'ın dediği gibi: bir şeyi açıklamak için ihtiyaç duyduğundan fazla unsur ekleme. Mevcut versiyon da aynen böyle oldu - basit ve çalışıyor.

Elbette tüm bu katsayıların bir şekilde seçilmesi gerekiyor. İşte bu noktada eski güzel Nelder-Mead yöntemi imdada yetişiyor. Ancak bu, bir sonraki bölümde açıklayacağım tamamen farklı bir hikaye. İnanın bana, konuşacak çok şey var - sadece optimizasyon sırasında yaptığım hatalar bile ayrı bir makale için yeterli olacaktır.

Doğrusal bileşenler

Doğrusal kısımla başlayalım. Buradaki ana şeyin ne olduğunu biliyor musunuz? Model, önceki iki fiyat değerine bakar, ancak farklı şekillerde. İlk katsayı genellikle 0.3-0.4 civarında çıkar - bu son değişime verilen anlık bir tepkidir. Ancak ikincisi daha ilginçtir, genellikle 0.7'ye yaklaşır, bu da sondan bir önceki fiyatın daha güçlü bir etkisine işaret eder. İlginç, değil mi? Piyasa, son dalgalanmalara güvenmeyerek biraz daha eski seviyelere dayanıyor gibi görünüyor.

İkinci dereceden bileşenler

İkinci dereceden terimlerle ilgili ilginç bir hikaye yaşandı. Başlangıçta bunları sadece doğrusal olmayan durumu hesaba katmak için ekledim, ancak daha sonra şaşırtıcı bir şey fark ettim. Sakin bir piyasada bunların katkısı önemsizdir - katsayılar 0.01-0.02 civarında dalgalanır. Ancak güçlü bir hareket başlar başlamaz, bunlar uyanıyor gibi görünüyor. Bu durum özellikle günlük EURUSD grafiklerinde açıkça görülmektedir - trend güç kazandığında, ikinci dereceden terimler baskın olmaya başlıyor ve modelin fiyatla birlikte "hızlanmasına" olanak tanıyor.

Momentum bileşeni

Momentum bileşeninin gerçek bir keşif olduğu ortaya çıktı. Önemsiz bir fiyat farkı gibi görünebilir, ancak piyasanın ruh halini keskin bir doğrulukla yansıtıyor! Sakin dönemlerde bu katsayı 0.2-0.3 civarında kalır, ancak güçlü hareketlerden önce genellikle 0.5'e sıçrar. Bu benim için yaklaşan bir kırılmanın bir tür göstergesi oldu - optimize edici momentum ağırlığını yükseltmeye başladığında, hareket bekleyin.

Döngüsel bileşen

Döngüsel bileşenin biraz elden geçirilmesi gerekti. İlk başta sinüs dalgasının farklı periyotlarını denedim, ancak daha sonra piyasanın kendisinin ritmi belirlediğini fark ettim. Modelin genliği katsayı üzerinden ayarlamasına izin vermek yeterlidir ve frekans doğal olarak fiyatların kendisinden elde edilir. Bu katsayının Avrupa ve Amerika seansları arasında nasıl değiştiğini izlemek ilginçtir - sanki piyasa gerçekten farklı bir ritimde nefes alıyormuş gibi.

Son olarak, serbest terim. Rolünün başlangıçta düşündüğümden çok daha önemli olduğu ortaya çıktı. Yüksek volatilite dönemlerinde, bir çapa görevi görerek tahminlerin uzaya uçmasını önler. Ve sakin dönemlerde genel fiyat seviyesinin daha doğru bir şekilde dikkate alınmasına yardımcı olur. Çoğu zaman, değeri trendin gücü ile ilişkilidir - trend ne kadar güçlü olursa, serbest terim sıfıra o kadar yakın olur.

En ilginç olanı ne biliyor musunuz? Modeli her karmaşıklaştırmaya çalıştığımda - yeni terimler eklediğimde, daha karmaşık fonksiyonlar kullandığımda vb. sonuçlar daha da kötüye gitti. Sanki piyasa şöyle diyordu: "Evlat, ukalalık yapma, sen zaten asıl şeyi yakaladın". Denklemin mevcut versiyonu, karmaşıklık ve verimlilik arasında gerçekten de uygun bir orta noktada. Yedi katsayı vardır - ne daha fazla ne de daha az, her birinin genel tahmin mekanizmasında kendine özgü net bir rolü bulunur.

Bu arada, bu katsayıların optimizasyonu başlı başına büyüleyici bir hikayedir. Nelder-Mead yönteminin optimum değerleri nasıl aradığını gözlemlemeye başladığınızda, ister istemez kaos teorisini hatırlarsınız. Ancak bundan bir sonraki bölümde bahsedeceğiz - orada görülecek şeyler var, inanın bana.


Nelder-Mead algoritması kullanılarak model optimizasyonu

Burada en ilginç konuyu ele alacağız - modelimizi gerçek veriler üzerinde nasıl çalıştıracağımızı. Aylar süren optimizasyon denemeleri, onlarca uykusuz gece ve litrelerce kahvenin ardından nihayet işe yarayan bir yaklaşım buldum.

Her şey her zamanki gibi gradyan inişle başladı. Türünün bir klasiği, her veri bilimcinin aklına gelen ilk şey. Üç günümü uygulamaya, bir haftamı da hata ayıklamaya harcadım... Peki sonuçlar ne oldu? Model kategorik olarak yakınsamayı reddetti. Ya sonsuzluğa doğru uçuyor ya da yerel minimumlarda takılıp kalıyordu. Gradyanlar çılgınca sıçradı.

Sonra genetik algoritmalarla geçen bir hafta vardı. Fikir görünüşte zarif - bırakalım evrim en iyi katsayıları bulsun. Uyguladım, başlattım... sadece çalışma süresi karşısında şaşkına döndüm. Bilgisayar bir haftalık geçmiş verileri işlemek için bütün gece uğuldadı. Sonuçlar o kadar istikrarsızdı ki çay yapraklarından fal bakmak gibiydi.

Sonra Nelder-Mead yöntemiyle karşılaştım. 1965'te geliştirilen eski güzel simpleks yöntemi. Türev yok, yüksek matematik yok - sadece çözüm alanının akıllıca araştırılması. Onu çalıştırdım ve gözlerime inanamadım. Algoritma, optimum değerlere yumuşak bir şekilde yaklaşarak piyasa ile dans ediyor gibiydi.

İşte temel kayıp fonksiyonu. Son derece basit ama kusursuz çalışıyor:

def loss_function(self, coeffs, X_train, y_train):
    y_pred = np.array([self.equation(x, coeffs) for x in X_train])
    mse = np.mean((y_pred - y_train)**2)
    r2 = r2_score(y_train, y_pred)
    
    # Save progress for analysis
    self.optimization_progress.append({
        'mse': mse,
        'r2': r2,
        'coeffs': coeffs.copy()
    })
    return mse

İlk başta, kayıp fonksiyonunu karmaşıklaştırmaya çalıştım, büyük katsayılar için cezalar ekledim ve MAPE ve diğer metrikleri içine soktum. Klasik bir geliştirici hatası, bir şey çalışıyorsa, tamamen çalışmaz hale gelene kadar iyileştirilmesi gerektiğidir. Sonunda basit MSE'ye geri döndüm ve ne oldu biliyor musunuz? Basitliğin gerçekten de bir deha işareti olduğu ortaya çıktı.

Optimizasyonu gerçek zamanlı olarak izlemek özel bir heyecan. İlk yinelemeler - katsayılar çılgınca sıçrıyor, MSE sıçrıyor, R² sıfıra yakın. Ardından en ilginç kısım başlıyor - algoritma doğru yönü buluyor ve metrikler kademeli olarak iyileşiyor. Yüzüncü yinelemede, herhangi bir fayda olup olmayacağı zaten belli oluyor ve üç yüzüncü yinelemede sistem genellikle kararlı bir seviyeye ulaşıyor.

Bu arada, metrikler hakkında birkaç söz söyleyeyim. R² değerimiz genellikle 0.996'nın üzerindedir, bu da modelin fiyat varyansının %99.6'sından fazlasını açıkladığı anlamına gelir. MSE yaklaşık 0.0000007'dir - başka bir deyişle, tahmin hatası nadiren bir pip'in onda yedisini aşar. MAPE'ye gelince... MAPE genellikle tatmin edici seviyededir - genellikle %0.1'den azdır. Tüm bunların geçmiş verilere dayandığı açıktır, ancak ileri testte bile sonuçlar çok daha kötü değildir.

Ancak en önemli şey sayılar bile değildir. En önemlisi sonuçların istikrarıdır. Optimizasyonu arka arkaya on kez çalıştırabilirsiniz ve her seferinde çok yakın katsayı değerleri elde edersiniz. Özellikle diğer optimizasyon yöntemleriyle mücadelelerim düşünüldüğünde bu çok değerlidir.

Başka ne harika biliyor musunuz? Optimizasyonu gözlemleyerek piyasanın kendisi hakkında çok şey anlayabilirsiniz. Örneğin, algoritma sürekli olarak momentum bileşeninin ağırlığını artırmaya çalıştığında, bu piyasada güçlü bir hareketin oluşmakta olduğu anlamına gelir. Ya da döngüsel bileşenle oynamaya başladığında - volatilitenin artacağı bir dönem bekleyin.

Bir sonraki bölümde, tüm bu matematiksel yapının nasıl gerçek bir alım-satım sistemine dönüştüğünü anlatacağım. İnanın bana, orada da düşünülmesi gereken çok şey var - MetaTrader 5 ile ilgili olası zorluklar tek başına ayrı bir makale için yeterli.


Eğitim süreci özellikleri

Eğitim için verileri hazırlamak ayrı bir hikayeydi. Sistemin ilk versiyonunda tüm veri setini sklearn.train_test_split'e nasıl mutlu bir şekilde beslediğimi hatırlıyorum... Ve ancak daha sonra, şüpheli derecede iyi sonuçlara baktığımda, gelecekteki verilerin geçmişe sızdığını fark ettim!

Sorunun ne olduğunu anlıyor musunuz? Finansal verileri normal bir Kaggle tablosu gibi ele alamazsınız. Burada her bir veri noktası zaman içinde bir andır ve bunları karıştırmak, yarınınkine dayanarak dünün hava durumunu tahmin etmeye çalışmak gibidir. Sonuç olarak bu basit ama etkili kod ortaya çıktı:

def prepare_training_data(prices, train_ratio=0.67):
    # Cut off a piece for training
    n_train = int(len(prices) * train_ratio)
    
    # Forming prediction windows
    X = np.array([[prices[i], prices[i-1]] for i in range(2, len(prices)-1)])
    y = prices[3:]  
    
    # Fair time sharing
    X_train, y_train = X[:n_train], y[:n_train]
    X_test, y_test = X[n_train:], y[n_train:]
    
    return X_train, y_train, X_test, y_test
Basit bir kod gibi görünüyor. Ancak bu sadeliğin ardında pek çok zorluk yatıyor. İlk başta farklı pencere boyutları ile denemeler yaptım. Ne kadar çok geçmiş nokta olursa o kadar iyi tahmin olur diye düşünmüştüm. Yanılmışım! Önceki iki değerin oldukça yeterli olduğu ortaya çıktı. Piyasa geçmişi uzun süre hatırlamaktan hoşlanmaz, biliyorsunuz.

Eğitim örnekleminin büyüklüğü ise ayrı bir konudur. Farklı seçenekler denedim - 50/50, 80/20, hatta 90/10. Sonunda, eğitim verilerinin yaklaşık %67'si olan altın oran üzerinde karar kıldım. Neden? En iyisi bu! Görünüşe göre yaşlı Fibonacci piyasaların doğası hakkında bir şeyler biliyordu...

Farklı veri parçalarından model eğitimini izlemek eğlencelidir. Sakin bir dönem geçirildiğinde, katsayılar yumuşak bir şekilde seçilir ve metrikler kademeli olarak iyileşir. Ve eğer eğitim örneklemi Brexit veya Federal Rezerv başkanının konuşması gibi bir şey içeriyorsa, ortalık karışır: katsayılar sıçrar, optimize edici çıldırır ve hata grafikleri adeta bir lunapark hız treni gibi dalgalanır.

Bu arada, metrikler hakkında tekrar birkaç söz söylemek istiyorum. Eğitim örneklemindeki R² değerinin 0.98'den yüksek olması durumunda verilerde bir tür hata olduğunun neredeyse kesin olduğunu fark ettim. Gerçek piyasa bu kadar öngörülebilir olamaz. Bu tıpkı çok başarılı öğrenci hikayesi gibidir - ya kopya çeker ya da bir dahidir. Bizim durumumuzda genellikle ilki geçerlidir.

Bir diğer önemli nokta ise veri ön işlemedir. İlk başta fiyatları normalleştirmeye, ölçeklendirmeye, aykırı değerleri kaldırmaya çalıştım... Genel olarak, makine öğrenimi kurslarında öğretilen her şeyi yaptım. Ancak yavaş yavaş ham veriye ne kadar az dokunursanız o kadar iyi olacağı sonucuna vardım. Piyasa kendi kendine normalleşecektir, sadece her şeyi doğru bir şekilde hazırlamanız gerekir.

Artık eğitim, neredeyse otomatik hale gelecek kadar sadeleştirildi. Haftada bir kez yeni verileri yüklüyor, eğitimi çalıştırıyor ve metrikleri geçmiş değerlerle karşılaştırıyoruz. Her şey normal sınırlar içindeyse, gerçek eylem sistemindeki katsayıları güncelliyoruz. Şüpheli bir şey varsa, daha derinlemesine araştırıyoruz. Neyse ki deneyimlerimiz, sorunu nerede aramamız gerektiğini anlamamızı sağlıyor.


Katsayıları optimize etme

def fit(self, prices):
    # Prepare data for training
    X_train, y_train = self.prepare_training_data(prices)
    
    # I found these initial values by trial and error
    initial_coeffs = np.array([0.5, 0.1, 0.3, 0.1, 0.2, 0.1, 0.0])
    
    result = minimize(
        self.loss_function,
        initial_coeffs,
        args=(X_train, y_train),
        method='Nelder-Mead',
        options={
            'maxiter': 1000,  # More iterations does not improve the result
            'xatol': 1e-8,   # Accuracy by ratios
            'fatol': 1e-8    # Accuracy by loss function
        }
    )
    
    self.coefficients = result.x
    return result

En zor olan ne oldu biliyor musunuz? Şu lanet olası başlangıç katsayılarını doğru ayarlamak. İlk başta rastgele değerler kullanmayı denedim - o kadar farklı sonuçlar elde ettim ki vazgeçmeye hazırdım. Sonra birlerle başlamayı denedim - optimize edici ilk yinelemeler sırasında bir yerlerde uzaya uçtu. Yerel minimumlara takıldığı için sıfırlarla da çalışmadı.

İlk katsayı olan 0.5 doğrusal bileşenin ağırlığıdır. Daha az olduğunda model trendini kaybeder, daha fazla olduğunda ise son fiyata çok fazla bağımlı hale gelir. İkinci dereceden terimler için 0.1'in mükemmel bir başlangıç olduğu ortaya çıktı - doğrusal olmayan davranışları yakalamak için yeterli, ancak modelin ani hareketlerde çıldırmaya başlamasına neden olacak kadar değil. Momentum için 0.2 değeri deneysel olarak elde edildi; sadece bu değerde sistem en istikrarlı sonuçları gösterdi.

Optimizasyon sırasında Nelder-Mead, yedi boyutlu bir katsayı uzayında bir simpleks oluşturur. Aynı anda yedi boyutta oynanan bir sıcak-soğuk oyunu gibi. Süreç sapmasını önlemek önemlidir, bu nedenle doğruluk için bu kadar katı gereklilikler vardır (1e-8). Daha az olursa kararsız sonuçlar elde ederiz, daha fazla olursa optimizasyon yerel minimumlara takılmaya başlar.

Bin yineleme aşırı görünebilir, ancak pratikte optimize edici genellikle 300-400 adımda yakınsar. Sadece bazen, özellikle yüksek volatilite dönemlerinde, optimum çözümü bulmak için daha fazla zamana ihtiyacı vardır. Ve ekstra yinelemeler performansı gerçekten etkilemez - tüm süreç genellikle modern donanımda bir dakikadan az sürer.

Bu arada, optimizasyon sürecinin görselleştirilmesi fikri bu kodun hata ayıklama sürecinde doğdu. Katsayıların gerçek zamanlı olarak değiştiğini gördüğünüzde, modelde neler olup bittiğini ve nereye gidebileceğini anlamak çok daha kolaydır.


Kalite metrikleri ve bunların yorumlanması

Bir tahmin modelinin kalitesini değerlendirmek, net olmayan nüanslarla dolu ayrı bir hikayedir. Algoritmik alım-satımla çalıştığım yıllar boyunca, bu konuda ayrı bir kitap yazacak kadar metriklerle uğraştım. Ama size asıl meseleyi anlatacağım.

İşte sonuçlar:

marketsolver

R-kare ile başlayalım. EURUSD'de 0.9'un üzerindeki değerleri ilk gördüğümde gözlerime inanamadım. Veri sızıntısı veya hesaplama hatası olmadığından emin olmak için kodu on kez kontrol ettim. Herhangi bir sorun yoktu - model fiyat varyansının %90'ından fazlasını açıklıyor. Ancak daha sonra bunun iki ucu keskin bir kılıç olduğunu fark ettim. Çok yüksek R² (0.95'ten büyük) genellikle aşırı uyumu gösterir. Gerçek piyasa bu kadar öngörülebilir olamaz.

MSE bizim iş arkadaşımızdır. İşte tipik bir değerlendirme kodu:

def evaluate_model(self, y_true, y_pred):
    results = {
        'R²': r2_score(y_true, y_pred),
        'MSE': mean_squared_error(y_true, y_pred),
        'MAPE': mean_absolute_percentage_error(y_true, y_pred) * 100
    }
    
    # Additional statistics that often save the day
    errors = y_pred - y_true
    results['max_error'] = np.max(np.abs(errors))
    results['error_std'] = np.std(errors)
    
    # Look separately at error distribution "tails"
    results['error_quantiles'] = np.percentile(np.abs(errors), [50, 90, 95, 99])
    
    return results

Lütfen ek istatistiklere dikkat edin. Hoş olmayan bir olaydan sonra max_error ve error_std ekledim - model mükemmel MSE gösterdi, ancak bazen tahminlerde öyle aykırı değerler verdi ki, denemeye bile gerek kalmadan hesabımı hemen kapatabilirdim. Şimdi ilk baktığım şey hata dağılımının "kuyrukları". Ancak, kuyruklar hala mevcuttur:

MAPE yatırımcılar için ev gibidir. Onlara R-kare değerinden bahsederseniz gözleri donup kalır, ancak "model ortalama %0.05 oranında yanlış" derseniz hemen anlarlar. Yine de bir sorun var - MAPE küçük fiyat hareketleri sırasında aldatıcı bir şekilde düşük olabilir ve keskin hareketler sırasında fırlayabilir.

Ancak anladığım en önemli şey, geçmiş verilere dayanan hiçbir ölçütün gerçek hayatta başarıyı garanti etmediğidir. Bu yüzden artık bir kontrol sistemim var:

def validate_model_performance(self):
    # Check metrics on different timeframes
    timeframes = ['H1', 'H4', 'D1']
    for tf in timeframes:
        metrics = self.evaluate_on_timeframe(tf)
        if not self._check_metrics_thresholds(metrics):
            return False
    
    # Look at behavior at important historical events
    stress_periods = self.get_stress_periods()
    stress_metrics = self.evaluate_on_periods(stress_periods)
    if not self._check_stress_performance(stress_metrics):
        return False
    
    # Check the stability of forecasts
    stability = self.check_prediction_stability()
    if stability < self.min_stability_threshold:
        return False
    
    return True

Model, gerçek alım-satıma başlamadan önce tüm bu testleri geçmelidir. Ve bundan sonra bile, ilk iki hafta boyunca minimum hacimle işlem yapıyorum - gerçek piyasada nasıl davrandığını kontrol ediyorum.

İnsanlar sıklıkla hangi metrik değerlerin iyi kabul edildiğini soruyor. Deneyimlerime göre, 0.9'dan yüksek R² mükemmel, 0.00001'den düşük MSE kabul edilebilir, %0.05'e kadar MAPE ise muhteşemdir. Ancak! Bu göstergelerin zaman içindeki istikrarına bakmak daha önemlidir. Biraz daha kötü ama istikrarlı ölçütlere sahip bir modele sahip olmak, süper doğru ama istikrarsız bir sisteme sahip olmaktan daha iyidir.


Teknik uygulama

Alım-satım sistemleri geliştirirken en zor şeyin ne olduğunu biliyor musunuz? Matematik değil, algoritmalar değil, çalışma güvenilirliği. Güzel bir denklem yazmak başka bir şeydir, gerçek parayla 7/24 çalışmasını sağlamak ise bambaşka bir şey. Gerçek bir hesapta yaptığım birkaç acı verici hatadan sonra şunu fark ettim: mimari sadece iyi değil, kusursuz olmalı.

Sistem çekirdeğini şu şekilde düzenledim:

class PriceEquationModel:
    def __init__(self):
        # Model status
        self.coefficients = None
        self.training_scores = []
        self.optimization_progress = []
        
        # Initializing the connection
        self._setup_logging()
        self._init_mt5()
        
    def _init_mt5(self):
        """Initializing connection to MT5"""
        try:
            if not mt5.initialize():
                raise ConnectionError(
                    "Unable to connect to MetaTrader 5. "
                    "Make sure the terminal is running"
                )
            self.log.info("MT5 connection established")
        except Exception as e:
            self.log.critical(f"Critical initialization error: {str(e)}")
            raise

Buradaki her satır üzücü bir deneyimin sonucudur. Örneğin, yeniden bağlanmaya çalışırken kilitlenme yaşadıktan sonra MetaTrader 5'i başlatmak için ayrı bir yöntem kullanmaya başladım. Ve sistem gecenin bir yarısı sessizce çöktüğünde ve sabah ne olduğunu tahmin etmek zorunda kaldığımda günlük kaydı ekledim.

Hata işleme tamamen başka bir konudur.  

def _safe_mt5_call(self, func, *args, retries=3, delay=5):
    """Secure MT5 function call with automatic recovery"""
    for attempt in range(retries):
        try:
            result = func(*args)
            if result is not None:
                return result
            
            # MT5 sometimes returns None without error
            raise ValueError(f"MT5 returned None: {func.__name__}")
            
        except Exception as e:
            self.log.warning(f"Attempt {attempt + 1}/{retries} failed: {str(e)}")
            if attempt < retries - 1:
                time.sleep(delay)
                # Trying to reinitialize the connection
                self._init_mt5()
            else:
                raise RuntimeError(f"Call attempts exhausted {func.__name__}")

Bu kod parçası, MetaTrader 5 deneyiminin özüdür. Bir şeyler ters giderse yeniden bağlanmaya çalışır, gecikmeli olarak tekrarlanan denemeler yapar ve en önemlisi sistemin belirsiz bir durumda çalışmaya devam etmesine izin vermez. Her ne kadar genel olarak MetaTrader 5 kütüphanesiyle ilgili herhangi bir sorun yaşanmasa da - bu kütüphane mükemmeldir!

Modeli çok basit bir halde tutuyorum. Yalnızca en gerekli unsurları içeriyor. Karmaşık veri yapıları yok, zor optimizasyonlar yok. Ancak her durum değişikliği kaydedilir ve kontrol edilir:

def _update_model_state(self, new_coefficients):
    """Safely updating model ratio"""
    if not self._validate_coefficients(new_coefficients):
        raise ValueError("Invalid ratios")
        
    # Save the previous state
    old_coefficients = self.coefficients
    try:
        self.coefficients = new_coefficients
        if not self._check_model_consistency():
            raise ValueError("Model consistency broken")
            
        self.log.info("Model successfully updated")
        
    except Exception as e:
        # Roll back to the previous state
        self.coefficients = old_coefficients
        self.log.error(f"Model update error: {str(e)}")
        raise

Modülerlik burada sadece güzel bir kelime değildir. Her bileşen ayrı ayrı test edilebilir, değiştirilebilir, modifiye edilebilir. Yeni bir metrik eklemek ister misiniz? Yeni bir metot oluşturun. Veri kaynağını değiştirmeniz mi gerekiyor? Aynı arayüze sahip başka bir bağlayıcıyı uygulamak yeterlidir.


Geçmiş verilerin işlenmesi

MetaTrader 5'ten verileri almak oldukça zorlu bir iş oldu. Basit bir kod gibi görünüyor, ancak şeytan her zaman olduğu gibi ayrıntıda gizlidir. Birkaç ay boyunca ani bağlantı kopmaları ve veri kayıplarıyla mücadele ettikten sonra, terminalle çalışmak için aşağıdaki yapı ortaya çıktı:

def fetch_data(self, symbol="EURUSD", timeframe=mt5.TIMEFRAME_H1, bars=10000):
    """Loading historical data with error handling"""
    try:
        # First of all, we check the symbol itself
        symbol_info = mt5.symbol_info(symbol)
        if symbol_info is None:
            raise ValueError(f"Symbol {symbol} unavailable")
            
        # MT5 sometimes "loses" MarketWatch symbols
        if not symbol_info.visible:
            mt5.symbol_select(symbol, True)
            
        # Collect data
        rates = mt5.copy_rates_from_pos(symbol, timeframe, 0, bars)
        if rates is None:
            raise ValueError("Unable to retrieve historical data")
            
        # Convert to pandas
        df = pd.DataFrame(rates)
        df['time'] = pd.to_datetime(df['time'], unit='s')
        
        return self._preprocess_data(df['close'].values)
        
    except Exception as e:
        print(f"Error while receiving data: {str(e)}")
        raise
    finally:
        # It is important to always close the connection
        mt5.shutdown()

Her şeyin nasıl organize edildiğine bir göz atalım. İlk olarak, sembolün varlığını kontrol ediyoruz. Apaçık bir durum gibi görünebilir, ancak yapılandırmadaki bir yazım hatası nedeniyle sistemin var olmayan bir parite üzerinde işlem yapmak için saatler harcadığı bir durum vardı. Bundan sonra, symbol_info aracılığıyla sıkı bir kontrol ekledim.

Ardından, 'visible' ile ilgili ilginç bir nokta var. Sembol orada görünüyor, ancak Piyasa Gözleminde yok. Ve symbol_select çağrısı yapmazsanız, herhangi bir veri alamazsınız. Dahası, terminal bir işlem seansının tam ortasında sembolü "unutabilir". Eğlenceli, değil mi?

Verileri elde etmek de kolay değildir. copy_rates_from_pos, bir düzine farklı nedenden dolayı None geri döndürebilir: sunucuya bağlantı yok, sunucu aşırı yüklü, yeterli geçmiş yok... Bu nedenle, sonucu hemen kontrol ediyoruz ve bir şeyler ters giderse bir hata mesajı gösteriyoruz.

Pandas'a dönüştürme ise ayrı bir konu. Zaman Unix formatında gelir, bu yüzden onu normal bir zaman damgasına dönüştürmemiz gerekir. Bu olmadan, nihai zaman serisi analizi çok daha zor hale gelir.

Ve en önemlisi, bağlantıyı 'finally' bloğunda kapatmaktır. Bunu yapmazsanız, MetaTrader 5 veri sızıntısı belirtileri göstermeye başlar: önce veri alma hızı düşer, ardından rastgele zaman aşımları ortaya çıkar ve sonunda terminal donabilir. İnanın bana, bunu kendi deneyimlerimden öğrendim.

Genel olarak, bu özellik verilerle çalışmak için bir İsviçre çakısı gibidir. Dışarıdan bakıldığında basittir, ancak içinde yanlış gidebilecek her şeye karşı çok sayıda koruyucu mekanizma vardır. Ve inanın bana, bu mekanizmaların her biri er ya da geç işe yarayacaktır.


Sonuçların analizi. İleri test sonuçlarının kalite metrikleri

Test sonuçlarını ilk gördüğüm anı hatırlıyorum. Bilgisayarın başında oturmuş, soğuk kahvemi yudumluyordum ve gözlerime inanamıyordum. Testleri beş kez tekrarladım, her kod satırını kontrol ettim - hayır, bu bir hata değildi. Model gerçekten fantezinin sınırlarında çalıştı.

Nelder-Mead algoritması saat gibi çalıştı - sadece 408 yineleme, normal bir dizüstü bilgisayarda bir dakikadan az. 0.9958 R-kare değeri sadece iyi değil, beklentilerin de ötesindeydi. %99.58 fiyat varyansı! Bu sayıları yatırımcı arkadaşlarıma gösterdiğimde önce bana inanmadılar, sonra bir yanlışlık aramaya başladılar. Onları anlıyorum - ilk başta ben de inanmamıştım.

MSE değeri mikroskobik seviyede çıktı - 0.00000094. Bu, ortalama tahmin hatasının bir pip'ten az olduğu anlamına geliyor. Herhangi bir yatırımcı size bunun en çılgın hayallerin ötesinde olduğunu söyleyecektir. MAPE'nin %0.06 olması inanılmaz doğruluğu teyit etmektedir. Çoğu ticari sistem %1-2'lik bir hatadan memnundur, ancak burada bu oran çok daha iyidir.

Model katsayıları bir araya gelerek güzel bir resim oluşturdu. Bir önceki fiyatta 0.5517 olması piyasanın güçlü bir kısa vadeli hafızaya sahip olduğunu gösteriyor. İkinci dereceden terimler küçüktür (0.0105 ve 0.0368), bu da hareketin çoğunlukla doğrusal olduğu anlamına geliyor. Döngüsel bileşen 0.1484 değeri ile tamamen farklı bir hikayedir. Yatırımcıların yıllardır söylediği şeyi doğruluyor: piyasa dalgalar halinde hareket ediyor.

Ancak en ilginç şey ileri test sırasında gerçekleşti. Tipik olarak, modeller yeni verilerle kötüleşir - bu klasik makine öğrenimidir. Peki ya burada? R² 0.9970'e yükseldi, MSE %19 daha düşerek 0.00000076 oldu, MAPE %0.05'e düştü. Dürüst olmak gerekirse, ilk başta kodun bir yerinde hata yaptığımı düşündüm, çünkü bu inanılmaz görünüyordu. Ancak, her şey doğruydu.

Sonuçlar için özel bir görselleştirici ekledim:

def plot_model_performance(self, predictions, actuals, window=100):
    fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(15, 12))
    
    # Forecast vs. real price chart 
    ax1.plot(actuals, 'b-', label='Real prices', alpha=0.7)
    ax1.plot(predictions, 'r--', label='Forecast', alpha=0.7)
    ax1.set_title('Comparing the forecast with the market')
    ax1.legend()
    
    # Error graph
    errors = predictions - actuals
    ax2.plot(errors, 'g-', alpha=0.5)
    ax2.axhline(y=0, color='k', linestyle=':')
    ax2.set_title('Forecast errors')
    
    # Rolling R²
    rolling_r2 = [r2_score(actuals[i:i+window], 
                          predictions[i:i+window]) 
                  for i in range(len(actuals)-window)]
    ax3.plot(rolling_r2, 'b-', alpha=0.7)
    ax3.set_title(f'Rolling R² (window {window})')
    
    plt.tight_layout()
    return fig

Grafikler ilginç bir resim ortaya koymaktadır. Sakin dönemlerde model bir İsviçre saati gibi çalışıyor. Ancak bazı tehlikeler de var - önemli haberler ve ani geri dönüşler sırasında doğruluğu düşüyor. Model temel faktörleri dikkate almadan sadece fiyatlarla çalıştığı için bu beklenen bir durumdur. Bir sonraki bölümde bunu da kesinlikle ekleyeceğiz.

İyileştirme için birkaç yol görüyorum. Bunlardan ilki uyarlanabilir katsayılardır. Model, piyasa koşullarına göre kendini ayarlasın. İkincisi, hacimler ve emir defterine ilişkin verilerin eklenmesidir. Üçüncü ve en iddialı olanı ise yaklaşımımızın diğer algoritmalarla birlikte çalışacağı bir modeller topluluğu oluşturmaktır.

Ancak mevcut haliyle bile sonuçlar etkileyici. Şu anda asıl önemli olan, iyileştirmelere kapılmamak ve zaten işe yarayan şeyleri bozmamaktır.


Pratikte kullanım

Geçen hafta yaşadığım komik bir olayı hatırlıyorum. En sevdiğim kafede dizüstü bilgisayarımla oturmuş, lattemi yudumluyor ve sistemin çalışmasını izliyordum. Gün sakindi, EURUSD yumuşak bir şekilde yükseliyordu, aniden modelden bir bildirim geldi - bir satış pozisyonu açmaya hazırlanmak için. İlk düşüncem - bu ne saçmalık, trend açıkça yukarı yönlü! Ancak algoritmik alım-satımla iki yıl çalıştıktan sonra şu ana kuralı öğrendim: asla sistemle tartışma. 40 dakika sonra EUR 35 pip düştü. Model, fiyat yapısında benim insani görüşümle fark edemeyeceğim mikro değişikliklere yanıt verdi.

Bildirimlerden bahsetmişken. Birkaç kaçırdığım işlemden sonra, bu basit ama etkili uyarı modülünü oluşturdum:

def notify_signal(self, signal_type, message):
    try:
        # Format the message
        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        formatted_msg = f"[{timestamp}] {signal_type}: {message}"
        
        # Send to Telegram
        if self.use_telegram and self.telegram_token:
            self.telegram_bot.send_message(
                chat_id=self.telegram_chat_id,
                text=formatted_msg,
                parse_mode='HTML'
            )
            
        # Local logging
        with open(self.log_file, 'a', encoding='utf-8') as f:
            f.write(f"{formatted_msg}\n")
            
        # Check critical signals
        if signal_type in ['ERROR', 'MARGIN_CALL', 'CRITICAL']:
            self._emergency_notification(formatted_msg)
            
    except Exception as e:
        # If the notification failed, send the message to the console at the very least
        print(f"Error sending notification: {str(e)}\n{formatted_msg}")

_emergency_notification metoduna dikkat edin. Sistemde bir tür bellek hatası meydana gelip pozisyonlar arka arkaya açılmaya başladığı "komik" bir olayın ardından bunu ekledim. Şimdi kritik durumlarda bir SMS geliyor ve bot benim müdahaleme kadar otomatik olarak alım-satımı durduruyor. 

Pozisyonların büyüklüğü konusunda da çok sorun yaşadım. İlk başta sabit bir hacim kullandım - 0.1 lot. Ama yavaş yavaş bunun bale ayakkabılarıyla ip üzerinde yürümek gibi bir şey olduğunu anladım. Mümkün gibi görünüyor, ama neden? Sonunda, aşağıdaki uyarlanabilir hacim hesaplama sistemini geliştirdim:

def calculate_position_size(self):
    """Calculating the position size taking into account volatility and drawdown"""
    try:
        # Take the total balance and the current drawdown
        account_info = mt5.account_info()
        current_balance = account_info.balance
        drawdown = (account_info.equity / account_info.balance - 1) * 100
        
        # Basic risk - 1% of the deposit
        base_risk = current_balance * 0.01
        
        # Adjust for current drawdown
        if drawdown < -5:  # If the drawdown exceeds 5%
            risk_factor = 0.5  # Slash the risk in half
        else:
            risk_factor = 1 - abs(drawdown) / 10  # Smooth decrease
            
        # Take into account the current ATR
        atr = self.calculate_atr()
        pip_value = self.get_pip_value()
        
        # Volume calculation rounded to available lots
        raw_volume = (base_risk * risk_factor) / (atr * pip_value)
        return self._normalize_volume(raw_volume)
        
    except Exception as e:
        self.notify_signal('ERROR', f"Volume calculation error: {str(e)}")
        return 0.1  # Minimum safety volume

_normalize_volume metodu tam bir baş ağrısıydı. Farklı aracı kurumların farklı minimum hacim değişimi adımlarına sahip olduğu ortaya çıktı. Bazılarında 0.01 lot, bazılarında ise sadece yuvarlanmış sayılarla işlem yapabilirsiniz. Her bir aracı kurum için ayrı bir yapılandırma eklemem gerekti.

Yüksek volatilite dönemlerinde çalışmak ayrı bir konu. Bilirsiniz, piyasanın çıldırdığı günler olur. Fed başkanının bir konuşması, beklenmedik siyasi haberler ya da sadece "13. Cuma" - fiyat sarhoş bir denizci gibi oradan oraya savrulmaya başlar. Önceleri böyle anlarda sistemi kapatıyordum ama sonra daha şık bir çözüm buldum:

def check_market_conditions(self):
    """Checking the market status before a deal"""
    # Check the calendar of events
    if self._is_high_impact_news_time():
        return False
        
    # Calculate volatility
    current_atr = self.calculate_atr(period=5)  # Short period
    normal_atr = self.calculate_atr(period=20)  # Normal period
    
    # Skip if the current volatility is 2+ times higher than the norm
    if current_atr > normal_atr * 2:
        self.notify_signal(
            'INFO', 
            f"Increased volatility: ATR(5)={current_atr:.5f}, "
            f"ATR(20)={normal_atr:.5f}"
        )
        return False
        
    # Check the spread 
    current_spread = mt5.symbol_info(self.symbol).spread
    if current_spread > self.max_allowed_spread:
        return False
        
    return True

Bu fonksiyon, bakiyenin gerçek bir koruyucusu haline geldi. Özellikle haber kontrolünden memnun kaldım - ekonomik takvim API'sini bağladıktan sonra, sistem önemli olaylardan 30 dakika önce otomatik olarak "bekleme moduna" geçiyor ve 30 dakika sonra tekrar devreye giriyor. Aynı fikir MQL5 robotlarımın çoğunda da kullanılıyor. Harika!


Değişken durma seviyeleri

Gerçek alım-satım algoritmaları üzerinde çalışmak bana birkaç komik ders öğretti. Test sürecinin ilk ayında arkadaşlarıma sabit durma seviyelerine sahip bir sistemi nasıl gururla gösterdiğimi hatırlıyorum. "Bakın, her şey basit ve şeffaf!" dedim. Her zamanki gibi, piyasa beni hızla yere serdi - tam anlamıyla bir hafta sonra öyle bir volatilite yaşandı ki, durma seviyelerimin yarısı piyasa gürültüsü nedeniyle uçup gitti.

Çözüm yaşlı Gerchik tarafından önerildi - o sırada kitabını yeniden okuyordum. ATR hakkındaki düşüncelerine rastladım ve sanki bir ampul yandı: işte burada! Sistemi mevcut piyasa koşullarına uyarlamanın basit ve zarif bir yolu. Güçlü hareketler sırasında, fiyata dalgalanması için daha fazla yer veriyoruz; sakin dönemlerde, durma seviyelerini daha yakın tutuyoruz.

İşte piyasaya girmenin temel mantığı - fazladan hiçbir şey yok, sadece en gerekli şeyler:

def open_position(self):
    try:
        atr = self.calculate_atr()
        predicted_price = self.get_model_prediction()
        current_price = mt5.symbol_info_tick(self.symbol).ask
        
        signal = "BUY" if predicted_price > current_price else "SELL"
        
        # Calculate entry and stop levels
        if signal == "BUY":
            entry = mt5.symbol_info_tick(self.symbol).ask
            sl_level = entry - atr  
            tp_level = entry + (atr / 3)
        else:
            entry = mt5.symbol_info_tick(self.symbol).bid
            sl_level = entry + atr
            tp_level = entry - (atr / 3)

        # Send an order
        request = {
            "action": mt5.TRADE_ACTION_DEAL,
            "symbol": self.symbol,
            "volume": self.lot_size,
            "type": mt5.ORDER_TYPE_BUY if signal == "BUY" else mt5.ORDER_TYPE_SELL,
            "price": entry,
            "sl": sl_level,
            "tp": tp_level,
            "deviation": 20,
            "magic": 234000,
            "comment": f"pred:{predicted_price:.6f}",
            "type_filling": mt5.ORDER_FILLING_FOK,
        }

        result = mt5.order_send(request)
        if result.retcode != mt5.TRADE_RETCODE_DONE:
            raise ValueError(f"Error opening position: {result.retcode}")
            
        print(f"Position opened {signal}: price={entry:.5f}, SL={sl_level:.5f}, "
              f"TP={tp_level:.5f}, ATR={atr:.5f}")
        
        return result.order

    except Exception as e:
        print(f"Position opening failed: {str(e)}")
        return None
Hata ayıklama işlemi sırasında bazı komik anlar yaşandı. Örneğin, sistem her birkaç dakikada bir birbiriyle çelişen bir dizi sinyal üretmeye başladı. Alış, satış, tekrar alış... Acemi bir algoritmik yatırımcının klasik bir hatası, piyasaya çok sık girmektir. Çözümün son derece basit olduğu ortaya çıktı - işlemler arasına 15 dakikalık bir bekleme süresi ve bir açık pozisyon filtresi ekledim.

Risk yönetimi konusunda da çok sorun yaşadım. Bir sürü farklı yaklaşım denedim ama sonunda her şey basit bir kurala bağlandı: işlem başına bakiyenin %1'inden fazlasını asla riske atma. Kulağa önemsiz gelebilir, ama kusursuz bir şekilde işliyor. 50 puanlık ATR ile bu, maksimum 0.2 lotluk bir hacim verir - alım-satım için oldukça rahat sayılar.

Sistem en iyi performansı, EURUSD'nin sadece bir aralıkta dolaşmak yerine gerçekten işlem gördüğü Avrupa seansı sırasında gösterdi. Ama önemli haberler sırasında... Alım-satıma ara vermek daha uygundur. En gelişmiş model bile haber kaosuna ayak uyduramaz.

Şu anda pozisyon yönetim sistemini iyileştirmek için çalışıyorum - giriş büyüklüğünü tahmindeki model güvenilirliğine bağlamak istiyorum. Kabaca konuşmak gerekirse, güçlü bir sinyal tam hacimde işlem yapacağımız anlamına gelir, zayıf bir sinyal ise sadece bir kısmında işlem yapacağımız anlamına gelir. Kelly kriterine benzer bir şey, ancak bizim modelimizin özelliklerine uyarlanmış hali.

Bu projeden çıkardığım ana ders, mükemmeliyetçiliğin algoritmik alım-satımda işe yaramadığıdır. Sistem ne kadar karmaşıksa, o kadar çok zayıf noktası vardır. Basit çözümler, özellikle uzun vadede, genellikle karmaşık algoritmalardan çok daha verimli olurlar.


MetaTrader 5 için MQL5 sürümü

Bilirsiniz, bazen en basit çözümler en etkili olanlardır. Birkaç gün boyunca tüm matematiksel yapıyı MQL5'e doğru bir şekilde aktarmaya çalıştıktan sonra, aniden bunun klasik bir sorumluluk paylaşımı sorunu olduğunu fark ettim.

Kabul edelim ki, Python bilimsel kütüphaneleriyle veri analizi ve katsayı optimizasyonu için idealdir. Ve MQL5, alım-satım mantığını yürütmek için harika bir araçtır. Öyleyse neden tornavidayı çekiç olarak kullanmaya çalışalım ki?

Sonuç olarak, basit ve zarif bir çözüm doğdu - katsayıları seçmek için Python'ı ve alım-satım için MQL5'i kullanıyorum. Nasıl çalıştığını görelim:

double g_coeffs[7] = {0.2752466, 0.01058082, 0.55162082, 0.03687016, 
                      0.27721318, 0.1483476, 0.0008025};

Bu yedi sayı tüm matematiksel modelimizin özüdür. Haftalarca süren optimizasyon, binlerce Nelder-Mead algoritması yinelemesi ve saatlerce süren geçmiş veri analizi içerirler. En önemlisi, çalışıyorlar!

double GetPrediction(double price_t1, double price_t2)
{
   return g_coeffs[0] * price_t1 +                    // Linear t-1
          g_coeffs[1] * MathPow(price_t1, 2) +       // Quadratic t-1
          g_coeffs[2] * price_t2 +                    // Linear t-2
          g_coeffs[3] * MathPow(price_t2, 2) +       // Quadratic t-2
          g_coeffs[4] * (price_t1 - price_t2) +      // Price change
          g_coeffs[5] * MathSin(price_t1) +          // Cyclic
          g_coeffs[6];                               // Constant
}

Tahmin denkleminin kendisi neredeyse hiç değiştirilmeden MQL5'e aktarıldı.

Piyasaya giriş mekanizması özel bir ilgiyi hak etmektedir. Test Python versiyonundan farklı olarak, burada daha gelişmiş pozisyon yönetimi mantığı uyguladım. Sistem aynı anda birkaç pozisyonu tutabilir ve sinyal onaylandığında hami artırabilir:

void OpenPosition(bool buy_signal, double lot)
{
   MqlTradeRequest request;
   MqlTradeResult result;
   ZeroMemory(request);
   
   request.action = TRADE_ACTION_DEAL;
   request.symbol = Symbol();
   request.volume = lot;
   request.type = buy_signal ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
   request.price = buy_signal ? SymbolInfoDouble(Symbol(), SYMBOL_ASK) : 
                               SymbolInfoDouble(Symbol(), SYMBOL_BID);
   // ... other parameters
}

İşte hedef kara ulaşıldığında tüm pozisyonların otomatik olarak kapatılması. 

if(total_profit >= ProfitTarget)
{
   CloseAllPositions();
   return;
}

Yeni çubukların işlenmesine özellikle dikkat ettim - her tikte anlamsız bir seğirme yok:

bool isNewBar() {
   datetime lastbar_time = datetime(SeriesInfoInteger(Symbol(), 
                                   PERIOD_CURRENT, SERIES_LASTBAR_DATE));
   if(last_time == 0) {
      last_time = lastbar_time;
      return(false);
   }
   if(last_time != lastbar_time) {
      last_time = lastbar_time;
      return(true);
   }
   return(false);
}

Sonuç, kompakt ama işlevsel bir alım-satım robotudur. Gereksiz ayrıntılar yok - sadece işi yapmak için gerçekten ihtiyacınız olan şeyler var. Kodun tamamı 300 satırdan daha az yer kaplıyor ve gerekli tüm kontrol ve korumaları içeriyor.

En iyi kısmı ne biliyor musunuz? Python ile MQL5 arasındaki işlevleri ayıran bu yaklaşımın inanılmaz derecede esnek olduğu kanıtlandı. Yeni katsayılar denemek ister misiniz? Bunları Python'da yeniden hesaplayın ve diziyi MQL5'te güncelleyin. Yeni işlem koşulları eklemeniz mi gerekiyor? MQL5'teki işlem mantığı, matematiksel kısmı yeniden yazmaya gerek kalmadan kolayca genişletilebilir.

İşte robot testi:

Netting hesabında test, 2015'ten bu yana %40 kar (katsayı optimizasyonu geçen yıl yapıldı). Düşüş %0.82'dir ve aylık kar %4'ün üzerindedir. Ancak böyle bir makineyi kaldıraçsız çalıştırmak daha iyidir - bırakın tahvillerden ve USD mevduatlarından biraz daha iyi bir oranda kar etsin. Ayrıca, test sırasında 7800 lot işlem gördü. Bu da en az yüzde bir buçukluk ek bir karlılık anlamına gelmektedir.

Genel olarak, katsayıların aktarılması fikrinin iyi bir fikir olduğunu düşünüyorum. Sonuç olarak, algoritmik alım-satımda asıl önemli olan sistemin karmaşıklığı değil, güvenilirliği ve öngörülebilirliğidir. Bazen, modern matematiğin yardımıyla doğru seçilmiş yedi sayı bunun için yeterlidir.

Önemli! Uzman Danışman, DCA pozisyon ortalama yöntemini kullanır (diğer bir deyişle, zaman ortalama), bu nedenle çok risklidir. Netting hesabında bazı temkinli ayarlarla yapılan testler olağanüstü sonuçlar gösterse de, pozisyon ortalama yönteminin tehlikesini ve böyle bir Uzman Danışmanın bakiyenizi tek seferde sıfıra indirebileceğini her zaman unutmayın!


İyileştirme için fikirler

Şu anda gece yarısı. Makaleyi bitiriyorum, kahve içiyorum, monitördeki grafiklere bakıyorum ve bu sistemle daha ne kadar çok şey yapılabileceğini düşünüyorum. Bilirsiniz, algoritmik alım-satımda genellikle şöyle olur: tam her şey hazır gibi göründüğünde, iyileştirme için bir düzine yeni fikir ortaya çıkar.

Ve en ilginç olanı ne biliyor musunuz? Tüm bu iyileştirmeler tek bir organizma olarak çalışmalıdır. Sadece bir dizi harika özellik eklemek yeterli değildir - birbirlerini uyumlu bir şekilde tamamlamaları ve gerçekten güvenilir bir alım-satım sistemi oluşturmaları gerekir.

Nihayetinde, amacımız mükemmel bir sistem yaratmak değildir - böyle bir sistem mevcut değildir. Amaç, sistemi para kazandıracak kadar akıllı, ancak mümkün olan en kötü anda dağılmayacak kadar basit hale getirmektir. Ne demişler, en iyisi, iyinin düşmanıdır.

Dahil edilecek dosya Dosya açıklaması
MarketSolver.py Katsayıları seçmek için kod ve ayrıca gerekirse Python aracılığıyla çevrimiçi alım-satım
MarketSolver.mql5 Seçilen katsayıları kullanarak alım-satım için MQL5 Uzman Danışman kodu

MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/16473

Ekli dosyalar |
MarketSolver.py (12.61 KB)
MarketSolver.mq5 (16.62 KB)
Son yorumlar | Tartışmaya git (16)
Vitaly Muzichenko
Vitaly Muzichenko | 5 Ara 2024 saat 02:55
Andrey Khatimlianskii #:

Yayınlanan Uzman Danışman'da herhangi bir eksiklik yoktur. Bunun gerçek bir hesaba ait bir kod olmadığı açıktır, burada herhangi bir filtreden bahsedilmemektedir.

Sadece fikrin bir gösterimi, ki bu da fena değil.

Katılıyorum.


Михалыч Трейдинг
Михалыч Трейдинг | 22 Şub 2025 saat 09:35

Sadece kapets (üzgünüm)! Materyallerinizi 10. kez incelediğim birkaç saat boyunca aynı yollarda (düşüncelerde) yürüdüğümüzü görüyorum.

Formüllerinizin halihazırda gördüğüm/kullandığım şeyleri matematiksel olarak formüle etmeme yardımcı olacağını umuyorum. Bu sadece bir durumda gerçekleşecek - eğer onları anlarsam. Annem hep "Çalış oğlum" derdi. Matematikte acı gözyaşları döküyorum. Birçok şeyin basit olduğunu görüyorum ama NASIL olduğunu bilmiyorum. Parabollere.... regresyonlara.... sapmalara girmeye çalışıyorum. 65'inde 6. sınıfa gitmek zor.

// Sadece bir sürü harika özellik eklemek yeterli değildir - bunların birbirini uyumlu bir şekilde tamamlaması ve gerçekten güvenilir bir ticaret sistemi oluşturması gerekir.

Evet. Hem özelliklerin seçimi hem de sonraki optimizasyon, bir bisiklet tekerleğinin sekiz rakamını düzeltmeye benzer. Bazı jant telleri gevşetilmeli, diğerleri sıkılaştırılmalı ve bu işlemin yasalarına harfiyen uyulmalıdır. O zaman tekerlek düzleşecektir, ancak yanlış bir yaklaşım benimsenirse, jant telleri yanlış şekilde sıkılırsa, normal bir tekerlekten "on" yapmak mümkündür.

Bizim işimizde "jant telleri" birbirlerine yardımcı olmalı, diğer "jant tellerinin" zararına olacak şekilde örtüyü kendi üzerlerine çekmemelidir.

Mai Abboud
Mai Abboud | 12 Tem 2025 saat 10:36

Sadece son iki veri noktasına dayanarak fiyat tahmini yapmanın etkili olduğunu düşünmüyorum.

Siz katılıyor musunuz?

HeAic
HeAic | 13 Eki 2025 saat 19:37
Denemek için, orijinal MarketSolver.py'ye Qt5 üzerinde gui benzeri bir arayüz ve bir dizi döviz çifti ekledim (bu deneyler içindir). katsayılar IPython konsolunda
HeAic
HeAic | 13 Eki 2025 saat 19:41
Mai Abboud #:

Sadece son iki veri noktasına dayanarak fiyat tahmini yapmanın etkili olduğunu düşünmüyorum.

Sence de öyle değil mi?

Kişisel ticaret deneyimime göre, analiz etmenin en iyi yolu sıfır çubuk - keneler ve ilk çubuktur. Bu biraz yeterli :)
Yeni Raylara Adım Atın: MQL5'te Özel Göstergeler Yeni Raylara Adım Atın: MQL5'te Özel Göstergeler
Yeni terminalin ve dilin tüm yeni olanaklarını ve özelliklerini listelemeyeceğim. Bunlar sayısızdır ve bazı yenilikler ayrı bir makalede tartışılmaya değerdir. Ayrıca burada nesne yönelimli programlama ile yazılmış bir kod yoktur, geliştiriciler için ek avantajlar olarak bir bağlamda basitçe bahsedilemeyecek kadar ciddi bir konudur. Bu makalede, MQL4'e kıyasla göstergeleri, yapılarını, çizimlerini, türlerini ve programlama ayrıntılarını ele alacağız. Umarım bu makale hem yeni başlayanlar hem de deneyimli geliştiriciler için faydalı olacaktır, belki bazıları yeni bir şeyler bulacaktır.
Forex veri analizinde ilişkilendirme kurallarını kullanma Forex veri analizinde ilişkilendirme kurallarını kullanma
Süpermarket perakendeciliği analitiğinin tahmin edici kuralları gerçek Forex piyasasına nasıl uygulanır? Kurabiye, süt ve ekmek satın alımlarının borsa işlemleriyle nasıl bir bağlantısı var? Makale, ilişkilendirme kurallarının kullanımına dayanan algoritmik alım-satıma yönelik yenilikçi bir yaklaşımı tartışmaktadır.
İşte Karışınızda Yeni MetaTrader 5 ve MQL5 İşte Karışınızda Yeni MetaTrader 5 ve MQL5
Bu MetaTrader 5 ile ilgili sadece kısa bir inceleme. Sistemin tüm yeni özelliklerini bu kadar kısa sürede açıklayamam, test süreci 09.09.2009’da başladı. Bu sembolik bir tarihtir ve şanslı sayı olacağına eminim. MetaTrader 5 terminalinin ve MQL5’in beta sürümünü beş gün önce aldım. Tüm özelliklerini deneme şansım olmadı ama şimdiden etkilendim.
Gelecekteki trendlerin anahtarı olarak hacimsel sinir ağı analizi Gelecekteki trendlerin anahtarı olarak hacimsel sinir ağı analizi
Makale, teknik analiz ilkelerini LSTM sinir ağı mimarisiyle entegre ederek işlem hacmi analizine dayalı fiyat tahminini iyileştirme olasılığını araştırmaktadır. Anormal hacimlerin tespiti ve yorumlanması, kümeleme kullanımı ve hacimlere dayalı özelliklerin oluşturulması ve bunların makine öğrenimi bağlamında tanımlanmasına özellikle dikkat edilmektedir.