English Русский Deutsch 日本語
preview
Python + MetaTrader 5: Veriler, Özellikler ve Prototipler için Hızlı Araştırma Çerçevesi

Python + MetaTrader 5: Veriler, Özellikler ve Prototipler için Hızlı Araştırma Çerçevesi

MetaTrader 5Entegrasyon |
16 2
MetaQuotes
MetaQuotes

Giriş

Python, verilerle çalışmak için en uygun araçlardan biri haline geldi. Zaman ve kaynak israfı olmadan hızlı bir şekilde istatistiksel analizler yapmamızı, hipotezleri test etmemizi ve sonuçları görsel olarak sunmamızı sağlayan çok çeşitli kütüphaneler sunar. Bu, finansal piyasalarla ilgili problemlerin çözümü için önemlidir: burada sadece veri işleme hızına değil, aynı zamanda analizden pratik sonuçlara hızlı bir şekilde geçme yeteneğine de değer verilir.

MetaTrader 5, Python ile doğrudan entegrasyona sahiptir ve bu, piyasa verileriyle uygulamalı çalışma olanaklarını önemli ölçüde genişletir. Bir araştırmacı veya geliştirici, işlem platformuyla bağlantısını kesmeden fiyat verilerini incelemek, istatistiksel modeller oluşturmak ve uygulamalı hipotezleri test etmek için tanıdık Python araç setini kullanabilir. Bu yaklaşım, iş akışını daha esnek hale getirir ve birleşik bir döngüyü destekler: veriden hipoteze, hipotezden modele ve modelden pratik uygulamaya.

MetaTrader 5 + Python

Bu makalede, şunları göreceğiz:

  • Python'ın MetaTrader 5 ile nasıl entegre edildiği;
  • Finansal verileri analiz etmek ve hipotezleri test etmek için nasıl kullanılacağı;
  • Küçük bir modelin nasıl oluşturulacağı ve eğitileceği ve ardından eğitilen sonucun ONNX kullanılarak bir Uzman Danışmana nasıl aktarılacağı.

Bu, bir araştırma deneyinden bir alım-satım sisteminde pratik uygulamaya geçmemizi sağlayacaktır.

1. Kurulum ve bağlantı

Veri analizine geçmeden önce, Python ve MetaTrader 5'i birlikte kullanmak için çalışma ortamını uygun şekilde hazırlamak gerekir. Görev basittir, ancak titizlik gerektirir. Başlangıçta doğru kurulum sizi daha sonra onlarca küçük sorundan kurtaracaktır.

Öncelikle resmi web sitesinden dağıtımı indirerek MetaTrader 5 terminalini yükleyelim.

Daha sonra mevcut Python sürümüne ihtiyacımız olacak. Bu makalenin yazıldığı sırada 3.14.3'tü. Yüklerken, PythonPATH ortam değişkenine ekleyen seçeneği etkinleştirdiğinizden emin olun. Bu, gereksiz manuel ayarlar olmadan doğrudan komut satırından yorumlayıcı ile çalışmaya izin verecektir.

Python yüklemesi

Kilit nokta, ortamı izole etmektir. Deneyimler, veriler ve modellerle çalışan projelerin hızla bağımlılıklarla aşırı yüklendiğini göstermektedir. Sonuçların düzenini ve tekrarlanabilirliğini korumak için her proje için ayrı bir sanal ortam oluşturulur. Python'da bu, yerleşik venv aracı ile çözülebilir.

İş akışı aşağıdaki gibidir.

  1. Komut yürütme ortamını açın. En kolay yol Win+R tuşlarına basmaktır. Görüntülenen pencerede cmd komutunu girin ve Enter tuşuna basın. Bu, Windows komut istemini açar. İsterseniz PowerShell kullanabilirsiniz - aynı prensip bu durumda da geçerlidir.
  2. Ortamın oluşturulacağı proje dizinine gidin. Bu, şu standart komutla yapılır:
  3. cd /path/to/your/project
  4. Sanal bir ortam oluşturun.
  5. python -m venv integration

    Ve etkinleştirin.

    integration\Scripts\activate

    Şu andan itibaren, yüklenen tüm paketler mevcut proje içinde izole edilecektir.

  6. MetaTrader 5 ile etkileşim için Python modülünü yükleyin.
  7. pip install MetaTrader5
  8. Finansal verilerin tam bir analizi için, temel ancak neredeyse tam teşekküllü bir eyleme hazır kütüphane yığınını hemen devreye almak mantıklıdır. Veri işleme, model oluşturma ve teknik analiz gibi temel görevleri kapsar.

Öncelikle, sayısal hesaplama için temel olan NumPy'ı yükleyin. Bu, sonraki tüm yığının dayandığı temeldir.

Daha sonra, tablo verileri ve zaman serileri ile çalışmak için ana araç olan Pandas'ı bağlıyoruz, bu olmadan fiyat analizi bir eziyet haline gelir.

Görselleştirme için Matplotlib ve Seaborn bağlantısını kullanıyoruz. İlki grafikleriniz üzerinde tam kontrol sağlarken, ikincisi istatistiksel olarak etkileyici görselleştirmelerin oluşturulmasını hızlandırır. Birlikte çalıştıklarında, piyasayı sadece saymak yerine görmemizi sağlarlar.

Makine öğrenimi görevleri için, modeller oluşturmak ve doğrulamak konusunda zaman içinde kendini kanıtlamış bir araç olan Scikit-Learn'ü ekliyoruz. Erken prototipler ve temel stratejiler için çok uygundur.

Uygulamalı piyasa analizi için teknik göstergeler kütüphanesi TA'yı bağlıyoruz. Bu, klasik formülleri manuel olarak uygulamadan verileri sinyallerle hızlı bir şekilde zenginleştirmenin uygun bir yoludur.

Kütüphanelerin yüklenmesi tek bir komutla gerçekleştirilir.

pip install numpy pandas matplotlib seaborn scikit-learn ta

pytz kütüphanesini eklemeye değer. İlk bakışta yardımcıdır, ancak pratikte saat dilimleriyle çalışmak için kritik öneme sahiptir.

pip install pytz

Finansal veriler kesinlikle zamana duyarlıdır. Borsalar farklı saat dilimlerinde çalışır.

Varsayılan olarak, Python datetime nesnesini oluştururken sistemin yerel saatini kullanır. Bu davranış günlük işler için uygundur, ancak finansal bağlamda sistemik hataların kaynağı haline gelir. MetaTrader 5, tik ve çubuk zamanlarını UTC formatında depolar - yer değiştirmeden ve yerel saat dilimine referans vermeden.

Bu da klasik bir uyumsuzluk yaratır. Model bir zaman referansında, veriler ise başka bir zaman referansında çalışır. Uygulamada bu durum hoş olmayan etkilere yol açar.

Dolayısıyla buradaki kural katıdır. Zamanla ilgili tüm işlemler UTC'de gerçekleştirilmelidir. datetime nesneleri açıkça UTC diliminde oluşturulmalı, yerel değerler ise tek bir standarda getirilmelidir. Bu, verileri ve modeli aynı zaman koordinat sisteminde hizalar.

Saat dilimlerinin net bir şekilde yönetilmesi için pytz kullanmak iyi bir uygulamadır. Bu, örtük dönüşümleri ortadan kaldırır ve sistem davranışını öngörülebilir hale getirir.

MetaTrader 5'ten elde edilen veriler zaten UTC'dedir. Düzeltilmemelidirler. Bunlar doğru yorumlanmalı ve model mantığı ile tutarlı olmalıdır. Finansal problemlerde zaman sadece bir işaretleyici değil, bir koordinat eksenidir. İçindeki herhangi bir hata tüm analiz geometrisini bozar.

Bu set sınırlı görünmektedir, ancak pratikte tipik görevlerin %80'ini kapsar. Bu klasik bir yaklaşımdır: daha az kalabalık, daha fazla verimlilik.

Mevcut ortamdan çıkmamız gerekiyorsa, şu standart komutu kullanırız:

deactivate

Bu aşamada altyapı tamamen hazırdır. Terminal, yorumlayıcı ve gerekli kütüphaneler yüklendi. Ortam izole edildi ve tekrarlanabilir durumda. Bu, daha ileri analizler yapmanın, hipotezleri test etmenin ve kademeli olarak alım-satım modelleri geliştirmeye geçmenin uygun ve güvenli olduğu temeldir.


2. Verileri yükleme

Altyapıyı kurduktan sonra, ilk pratik adıma geçiyoruz - programı yazmak. Burada katı bir sınırlama yoktur: herhangi bir tanıdık düzenleyici işinizi görecektir. Bununla birlikte, entegrasyon bağlamında, Python ile çalışmayı zaten destekleyen ve tüm süreci tek bir çerçeve içinde tutmanıza olanak tanıyan MetaEditor adlı yerleşik MetaTrader 5 düzenleyicisini kullanmak mantıklıdır.

Python komut dosyalarını doğrudan MetaEditor veya terminalden başlatmak için yorumlayıcının yolunu platform ayarlarında bir kez belirtmek yeterlidir. Bu temel bir işlemdir, ancak burada önemli bir ayrıntı vardır. Önceden oluşturulmuş bir sanal ortam kullanıyorsanız, yol genel bir Python yüklemesine değil, özellikle yorumlayıcısına işaret etmelidir.

Bu yaklaşım proje izolasyonunu korur ve tüm bağımlılıkların kontrol altında tutulmasını sağlar. Aksi takdirde, aynı kodun çalışma ortamına bağlı olarak farklı davrandığı, fark edilmesi zor hatalar alma riskiyle karşı karşıya kalırsınız. Finansal konularda bu kabul edilemez bir lükstür.

İlk aşamada temel bir bağlantı kuruyoruz: PythonMetaTrader 5 → geçmiş veriler. Görev formülasyon açısından basit ve özünde kritiktir: bir komut dosyasından terminale bağlanma ve belirli bir enstrüman ve zaman dilimi için fiyat verilerini alma. İşte bu andan itibaren anlamlı bir analiz başlar.

Komut dosyası yapısını bloklar halinde inşa etmek mantıklıdır. İlk olarak, gerekli kütüphaneleri bağlarız - bu çalışma araç setini oluşturur.

from datetime import datetime
import MetaTrader5 as mt5
import pandas as pd
import numpy as np
import pytz
import seaborn as sns
import matplotlib.pyplot as plt
import ta

Ardından, terminale bağlantı MetaTrader5 modülü aracılığıyla başlatılır. Bu, sisteme giriş noktasıdır. Bağlantı kurulmazsa, devam etmenin bir anlamı yoktur. Bu nedenle, durum kontrolü derhal ve taviz verilmeden gerçekleştirilir.

# Display data on the MetaTrader 5 package
print("MetaTrader5 package author: ", mt5.__author__)
print("MetaTrader5 package version: ", mt5.__version__)

# Connection to MetaTrader 5 terminal
if not mt5.initialize():
    print("initialize() failed, error code =", mt5.last_error())
    quit()

Bir sonraki adım zaman aralığını ayarlamaktır. Burada UTC dilimini açıkça belirtmek için pytz kullanıyoruz. Bu teknik bir ayrıntı değil, zorunlu bir gerekliliktir. Terminal fiyat verilerini UTC olarak kaydeder ve Python tarafındaki herhangi bir tutarsızlık veri yanlılığına yol açar. Hata sessizdir, ancak sonuçları sistemiktir.

# Set time zone to UTC
timezone = pytz.timezone("Etc/UTC")
# Create 'datetime' objects in UTC time zone to avoid the implementation of a local time zone offset
utc_from = datetime(2020, 1, 1, tzinfo=timezone)
utc_to = datetime.now(timezone)  # Set to the current date and time

Bunun ardından geçmiş veriler için talepte bulunulur. Örnekte EURUSD için saatlik çubuklar kullanılmaktadır. Burada enstrümanın adına dikkat etmekte fayda var. Son ekler ve ön ekler de dahil olmak üzere terminaldeki yazımla kesinlikle eşleşmelidir.

# Get bars from EURUSD H1 (hourly timeframe) within the specified interval
rates = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H1, utc_from, utc_to)

Çıktı, fiyatlar ve zaman damgaları içeren bir yapı dizisidir - filtreler ve yorumlar olmadan ham piyasa. Bu tam da ilk aşamada ihtiyaç duyulan veri türüdür.

Sonrasında, terminale olan bağlantı doğru şekilde kapatılır. Bu, yürütme disiplinidir: zarif kapatma, sonraki başlatmalarda gizli arızaları önler ve sistem davranışını öngörülebilir hale getirir.

# Shut down connection to the MetaTrader 5 terminal
mt5.shutdown()

Bunu, sonucun temel bir kontrolü takip eder. Veri alınırsa, hızlı doğrulama için ilk kayıtlar görüntülenir. Aksi takdirde, komut dosyası sonlandırılır.

# Check if data was retrieved
if rates is None or len(rates) == 0:
    print("No data retrieved. Please check the symbol or date range.")
    quit()
# Print the first 10 raw records for a quick data sanity check
print("Display obtained data 'as is'")
for rate in rates[:10]:
    print(rate)

Ek olarak, görselleştirme gerçekleştirilir: kapanış fiyatları ve hacimlerin bir grafiği oluşturulur. Bu durumda, hacimlerin ayrı bir eksene yerleştirilmesi tavsiye edilir. Hacim ekseni boyunca maksimum değeri, gözlemlenen maksimum değerden beş kat daha büyük bir marjla ayarlayacağız. Bu yöntem basit görünüyor, ancak kusursuz çalışıyor. Histogram grafiğin alt kısmına doğru bastırılır ve dikkat çekmek için fiyatla rekabet etmeyi bırakır.

Sonuç olarak, kapanış fiyatı çizgisi temiz ve okunabilir kalır ve hacimler görsel sunuma aşırı yüklenmeden bilgilendirici olmaya devam eder. Bu, veri bütünlüğü ile anlaşılabilirlik arasındaki klasik bir dengedir. Bir grafik sadece bilgi içermemeli, aynı zamanda gereksiz stres olmadan hızlı bir şekilde yorumlanmasına da izin vermelidir.

# Create a DataFrame from the retrieved tick data
rates_frame = pd.DataFrame(rates)
# Convert the timestamp column from seconds since epoch to datetime
rates_frame['time'] = pd.to_datetime(rates_frame['time'], unit='s')

# Use datetime as the DataFrame index for time series plotting and analysis
rates_frame.set_index('time', inplace=True)

# Plot closing price and tick volume
fig, ax1 = plt.subplots(figsize=(12, 6))

# Close price on primary y-axis
ax1.set_xlabel('Date')
ax1.set_ylabel('Close Price', color='tab:blue')
ax1.plot(rates_frame.index, rates_frame['close'], color='tab:blue', label='Close Price')
ax1.tick_params(axis='y', labelcolor='tab:blue')

# Tick volume on secondary y-axis
ax2 = ax1.twinx()  
ax2.set_ylabel('Tick Volume', color='tab:green')
max_tick = rates_frame['tick_volume'].max()
ax2.set_ylim(0, max_tick * 5)
ax2.plot(rates_frame.index, rates_frame['tick_volume'], color='tab:green', label='Tick Volume')
ax2.tick_params(axis='y', labelcolor='tab:green')

# Show the plot
plt.title('Close Price and Tick Volume Over Time')
fig.tight_layout()
plt.show()
fig.savefig('close_price.png')

Kapanış fiyatı hareketleri

Pratik açıdan bakıldığında, prosedür basit görünmektedir. Ancak aslında bu, veri kalitesi kontrolünün önemli bir aşamasıdır. Modele neyin beslendiğini açıkça anlıyorsunuz: format, zaman damgaları, değerler. Bu tür bir ilk denetim, mühendislik yaklaşımının klasik bir örneğidir. Zamandan, sinirlerden ve özellikle alım-satım sistemlerinde önemli olan paradan tasarruf sağlar.


3. Hipotezi test etme ve özellikleri seçme

Artık elimizde geçmiş fiyat verileri olduğuna göre, ilk analize geçelim. Son derece basit, neredeyse ders kitabı niteliğinde bir hipotezle başlayalım: Piyasa, son çubuğun hareketini devam ettirme eğilimindedir. Kısa vadeli hareketlerde durağanlık olup olmadığını kontrol edelim.

Python araç seti, böyle bir hipotezi sadece birkaç satırda test etmemizi sağlar. Mantık şu şekildedir. Bir kapanış fiyatları serisi alırız ve çubuktan çubuğa fiyat farklarını hesaplarız. Analiz için temel olarak önemli olan fiyat dinamiklerini elde ederiz.

# Correlation analysis between adjacent bar moves
close = rates_frame['close'].to_numpy(dtype=float)
# last and next price move differences
diff = close[1:] - close[:-1]

Daha sonra iki zaman serisi oluşturuyoruz: son ve sonraki değişiklikler. Teknik olarak bu, dizinin bir eleman kaydırılmasıyla gerçekleştirilir. Sonuç, her gözlemin basit bir soruyu yanıtladığı değer çiftleridir: piyasa yukarı (veya aşağı) hareket ediyorsa, bir sonraki çubukta ne oluyor?

diff = np.column_stack((diff[:-1], diff[1:]))
data_matrix = pd.DataFrame(diff, columns=['last', 'next'])

Ardından, bu iki seri arasındaki Pearson korelasyon oranı hesaplanır. Pozitif bir korelasyon güçlü bir durağanlığı gösterirken, negatif bir korelasyon baskın bir geri çekilmeye işaret eder.

Netlik için, sonuç Seaborn kullanılarak görselleştirilir - bir korelasyon ısı haritası oluşturulur. Bu, sayısal ayrıntılara girmeden bağımlılığın yapısını görmenin hızlı bir yoludur.

correlation_matrix = data_matrix.corr('pearson')
plt.subplots(figsize=(3, 2))
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm')
plt.title('Correlation Bar to Bar') 
plt.savefig('bar_to_bar.png')
plt.show()

Buradaki kilit nokta stratejinin kendisi değildir - doğası gereği ilkeldir. Değer başka bir yerdedir. Temel bir hipotez testi döngüsü gördük. Bir hipotez formüle ettik, verileri dönüştürdük, bir hesaplama yaptık ve sonucu görselleştirdik. Bu yaklaşım, analizi disipline eder ve gözle sonuçlar çıkarmamızı engeller.

Son mumun bir sonraki mumla korelasyonu

Ancak yine de elde edilen sonuçları değerlendirelim. Gözlenen korelasyon -0.018'dir — sıfıra yakın bir değerdir, ancak negatif işaretlidir.

Bu, bitişik çubuklar arasında net bir ilişki olmadığı anlamına gelir. Ayrıca, zayıf negatif işaret, hafif bir ortalamaya dönüş etkisini göstermektedir. Bir yönde hareket ettikten sonra, bir sonraki çubuğun ters yönde hareket etmesi muhtemeldir. Bununla birlikte, etkinin büyüklüğü o kadar küçüktür ki, pratik açıdan istatistiksel gürültü sınırındadır.

Hareketin devam ettiği hipotezi doğrulanmamıştır. H1 zaman dilimindeki piyasa, durağan bir sistemden ziyade rastgele bir sürece daha yakın davranır. Bu önemli bir gözlemdir. Bu, acemice stratejilerin tüm bir sınıfını derhal ortadan kaldırır ve daha sonraki analizler için daha ölçülü bir başlangıç noktası belirler.

Bir mum çok küçük bir ölçektir. Bu tür verilere yapıdan ziyade gürültü hakimdir. Bu nedenle, bir sonraki mantıksal adımı atıyoruz: gözlemleri genişletiyoruz ve bireysel değişiklikleri değil, ortalama hareketleri kontrol ediyoruz.

Tek bir çubuk yerine, 1 ila 23 çubukluk bir geçmiş aralığındaki ortalama fiyat değişimini alıyoruz. Bu, rastgele dalgalanmaları yumuşatır ve hareketin daha istikrarlı bileşenini izole etmemizi sağlar. Benzer şekilde, ufukta 1 ila 9 çubukluk bir zaman aralığı için gelecekteki ortalama fiyat değişimini oluşturuyoruz. Böylece, noktasal gözlemlerden toplu sinyallere geçiyoruz.

Uygulama düzgün bir şekilde iki bloğa ayrılmıştır. İlki, geçmiş değişimler için hareketli ortalamaları hesaplar.

# Add rolling mean features for the previous and future moves
for period in range(2, 24, 1):
    data_matrix[f'last_mean_{period:02d}'] = data_matrix['last'].rolling(window=period).mean()

İkincisi ise gelecekteki değişimler için; gelecekten geçmişe bilgi sızmasını önlemek amacıyla zorunlu bir kaydırma içeriyor. Bu kritik bir noktadır: bu olmadan analiz anlamsız hale gelir.

for period in range(2, 10, 1):
    data_matrix[f'next_mean_{period}'] = data_matrix['next'].rolling(window=period).mean().shift(-(period-1))

Hesaplamadan sonra, pencereleme işlemlerinin kaçınılmaz bir sonucu olarak eksik değerlere sahip satırlar kaldırılır.

# Remove rows with missing values created by rolling calculations
data_matrix.dropna(inplace=True)

Ardından, Pearson korelasyon matrisi oluşturulur ve gerekli alt matris bundan alınır: geleceğin geçmişe bağımlılığı. İşte ana sorunun cevabı: ortalama hareketin tahmin gücü var mı?

correlation_matrix = data_matrix.corr('pearson')
# Match columns that begin with "next"
reg = r'^next.*$'
selected_cols = correlation_matrix.filter(regex=reg).columns
remaining_rows = correlation_matrix.index.difference(selected_cols)
correlation_matrix = correlation_matrix.loc[remaining_rows, selected_cols]

Seaborn aracılığıyla ısı haritası şeklinde görselleştirme burada özellikle uygundur. Tüm parametre ızgarası boyunca bağımlılıkların yapısını hızlı bir şekilde değerlendirmemizi sağlar.

plt.figure(figsize=(12, 7))
plt.subplots_adjust(left=0.15, right=1, bottom=0.16, top=0.95)
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm')
plt.title('Correlation Means Last to Next Bars') 
plt.savefig('mean_to_bar.png')
plt.show()

Metodolojik açıdan bakıldığında, bu zaten daha olgun bir deneydir. Acemice yapılan çubuktan çubuğa testlerden uzaklaşıyor ve toplu etkilerin analizine geçiyoruz. Eğer piyasa zayıf örüntüler içeriyorsa, bunlar bu seviyede ortaya çıkmaya başlar.

Bir dönemdeki ortalama fiyat değişiminin gelecekteki hareketle korelasyonu

Burada elde edilen sonuç daha ilginç görünüyor, ancak genel sonuç korunuyor. Hala negatif korelasyon değerleri görüyoruz. Ancak şimdi yapılandırılmış durumdalar. Matrisin orta bölgesinde (geçmişte 8-14 çubukluk ve ufukta 5-8 çubukluk pencereler), etki yoğunlaşıyor ve -0.02...-0.03 değerlerine ulaşıyor. Bu zayıf ama tutarlı bir ortalamaya dönüş sinyalidir.

Mantık oldukça açık bir şekilde okunuyor. Piyasa bir süredir tek yönde hareket ediyorsa, sonraki çubuklarda kısmi bir düzeltme görme olasılığımız daha yüksektir. Ancak bu etki doğrusal değildir:

  • Kısa pencerelerde - gürültüye boğuluyor;
  • Çok uzun olanlarda bulanıklaşıyor ve gücünü kaybediyor;
  • Maksimum etki orta aralıklarda ortaya çıkıyor.

Matrisin sağ alt köşesinden ayrıca bahsetmek gerekir. Burada korelasyon sıfıra yaklaşmakta ve hatta bazı yerlerde pozitif olmaktadır. Bu, aşırı yumuşatma ile tahmin gücü kaybının klasik bir işaretidir: sinyal, veri değişkenliği ile birlikte kaybolur.

Seaborn aracılığıyla görselleştirme, burada bağımlılık azalmasını oldukça iyi vurgulamaktadır. Resmin oldukça anlamlı olduğu ortaya çıkıyor.

Sonuç açıktır: piyasa güçlü bir durağanlık sergilememektedir, ancak zayıf bir ortalamaya dönüş etkisi göstermektedir. Bu hazır bir strateji sağlamaz, ancak yönü belirler.

Elde edilen korelasyon değerleri düşük olduğundan, böyle bir sinyal doğrusal bir ortamda doğrudan alım-satıma ölçeklenemez. Ancak işin daha ilginç kısmı da burada başlıyor. Basit bir ilişki zayıfsa, özelliklerin bir kombinasyonu yoluyla onu güçlendirmeyi deneyebiliriz - yani bir modele geçebiliriz.

Temel görev, bilgilendirici bir özellik uzayı oluşturmaktır. Tek bir fiyat serisi yeterli değildir. Bu nedenle, bir sonraki adım, piyasanın gizli yapısını yakalayabilecek ek özellikler aramaktır. Klasik teknik göstergeleri temel bir set olarak kullanmak mantıklıdır. Bu, basitliğine rağmen genellikle faydalı sinyaller sağlayan, zaman içinde kendini kanıtlamış bir araçtır.

Ardından, aynı disiplinli yaklaşımı uyguluyoruz - korelasyon yoluyla doğrulama. Gelecekteki fiyat hareketleri ile çeşitli göstergelerin değerleri arasındaki ilişkiyi değerlendiriyoruz. Bu durumda, göstergelerin parametreleri döngü içinde değişir. Bu, çok çeşitli konfigürasyonları hemen kapsamamıza ve sinyalin en güçlü olduğu yeri görmemize olanak tanır.

Pratik açıdan bakıldığında, bu süreç parametrelerin numaralandırılmasına ve ardından filtrelemeye benzemektedir. Ancak özünde bu, bir özellik uzayının oluşturulması ve seçilmesidir. Korelasyon burada bir teşhis aracı olarak işlev görür. Hangi veri dönüşümlerinin gelecekteki hareketle ilişkili olduğunu ölçüyoruz. Bu, temelde farklı bir düşünce düzeyidir: önce yapıyı anlama, sonra kar elde etme.

Kodda bu yaklaşım oldukça sistematik bir şekilde uygulanmaktadır. Bir yandan, fiyatın basit türevleri kullanılır - hareketli ortalamalar, bunların birinci ve ikinci farkları, mevcut değerden sapmalar. Bu, yerel dinamikleri ve piyasa ivmesini yakalamaya yönelik bir girişimdir.

# Recreate the base matrix for indicator engineering
data_matrix = pd.DataFrame(diff, columns=['last', 'next'])
# Add 11-period previous move averages and derived momentum features
data_matrix[f'last_mean_11'] = data_matrix['last'].rolling(window=11).mean()
data_matrix[f'Dlast_mean_11'] = data_matrix[f'last_mean_11'].diff()
data_matrix[f'DDlast_mean_11'] = data_matrix[f'Dlast_mean_11'].diff()
# Feature representing the gap between the rolling mean and the current move
data_matrix[f'last_last_11'] = data_matrix[f'last_mean_11'] - data_matrix['last']
data_matrix[f'Dlast_last_11'] = data_matrix[f'last_last_11'].diff()
data_matrix[f'DDlast_last_11'] = data_matrix[f'Dlast_last_11'].diff()
# Add short-term future sum targets for the next bars
for period in range(2, 10, 1):
    data_matrix[f'next_{period}'] = data_matrix['next'].rolling(window=period).sum().shift(-(period-1))

Öte yandan, TA kütüphanesinden klasik göstergeler eklenir: SMA, RSI, MACD, dahası, aynı anda birkaç parametrelendirmede. Bu kapsam, doğru dönemi tahmin etmemizi engeller, bunun yerine tüm aralıktaki davranışı görmemizi sağlar.

# Build additional technical indicators using the close price series
close = pd.DataFrame(close[:-1], columns=['close'])
indicator_cols = {}
for period in [4, 8, 12, 24, 36, 48]:
    sma = ta.trend.sma_indicator(close['close'], window=period, fillna=True)
    dsma = sma.diff()
    ddsma = dsma.diff()
    rsi = ta.momentum.rsi(close['close'], window=period, fillna=True)
    drsi = rsi.diff()
    ddrsi = drsi.diff()
    macd = ta.trend.MACD(
        close['close'],
        window_slow=2 * period,
        window_fast=period,
        window_sign=period * 3 // 4,
        fillna=True,
    )
    macd_main = macd.macd()
    dmacd = macd_main.diff()
    ddmacd = dmacd.diff()
    macd_diff = macd.macd_diff()
    dmacd_diff = macd_diff.diff()
    ddmacd_diff = dmacd_diff.diff()
    macd_signal = macd.macd_signal()
    dmacd_signal = macd_signal.diff()
    ddmacd_signal = dmacd_signal.diff()
    macd_sig_main = macd_signal - macd_main
    dmacd_sig_main = macd_sig_main.diff()
    ddmacd_sig_main = dmacd_sig_main.diff()

    indicator_cols[f'SMA_{period:02d}'] = sma
    indicator_cols[f'DSMA_{period:02d}'] = dsma
    indicator_cols[f'DDSMA_{period:02d}'] = ddsma
    indicator_cols[f'RSI_{period:02d}'] = rsi
    indicator_cols[f'DRSI_{period:02d}'] = drsi
    indicator_cols[f'DDRSI_{period:02d}'] = ddrsi
    indicator_cols[f'MACD_{period:02d},{2*period:02d},{period*3//4:02d}'] = macd_main
    indicator_cols[f'DMACD_{period:02d},{2*period:02d},{period*3//4:02d}'] = dmacd
    indicator_cols[f'DDMACD_{period:02d},{2*period:02d},{period*3//4:02d}'] = ddmacd
    indicator_cols[f'MACD_DIFF_{period:02d},{2*period:02d},{period*3//4:02d}'] = macd_diff
    indicator_cols[f'DMACD_DIFF_{period:02d},{2*period:02d},{period*3//4:02d}'] = dmacd_diff
    indicator_cols[f'DDMACD_DIFF_{period:02d},{2*period:02d},{period*3//4:02d}'] = ddmacd_diff
    indicator_cols[f'MACD_SIGNAL_{period:02d},{2*period:02d},{period*3//4:02d}'] = macd_signal
    indicator_cols[f'DMACD_SIGNAL_{period:02d},{2*period:02d},{period*3//4:02d}'] = dmacd_signal
    indicator_cols[f'DDMACD_SIGNAL_{period:02d},{2*period:02d},{period*3//4:02d}'] = ddmacd_signal
    indicator_cols[f'MACD_Sig_Main{period:02d},{2*period:02d},{period*3//4:02d}'] = macd_sig_main
    indicator_cols[f'DMACD_Sig_Main{period:02d},{2*period:02d},{period*3//4:02d}'] = dmacd_sig_main
    indicator_cols[f'DDMACD_Sig_Main{period:02d},{2*period:02d},{period*3//4:02d}'] = ddmacd_sig_main

# Append all indicator columns to the feature matrix in one operation
# This avoids repeated DataFrame assignment and keeps the DataFrame compact
data_matrix = pd.concat([data_matrix, pd.DataFrame(indicator_cols)], axis=1)
# Remove any rows with NaN values created by indicator calculations
data_matrix.dropna(inplace=True)

İkinci dereceden türevlerin eklenmesi özellikle belirleyicidir (diff ve diff'ten diff). Bu zaten sinyalin kendisindeki değişimi yakalamaya yönelik bir girişimdir - piyasa hareketinin hızlanması ve yavaşlamasının analizine bir geçiştir. Finansal serilerde, bu tür etkiler genellikle seviyelerin kendisinden daha bilgilendirici olmaktadır.

Ardından filtreleme uygulanır. Tüm korelasyon matrisinden, yalnızca hedef değişkenle maksimum ilişkisi belirlenen eşiği (bu durumda 0.02) aşan özellikler kalır. Bu önemli bir noktadır. Zayıf ve istikrarsız bağımlılıkları bilinçli bir şekilde keserek, yalnızca mümkün olan en az derecede veri içinde kalanları bırakıyoruz.

correlation_matrix = data_matrix.corr('pearson')
selected_cols = correlation_matrix.filter(regex=reg).columns
remaining_rows = correlation_matrix.index.difference(selected_cols)
correlation_matrix = correlation_matrix.loc[remaining_rows, selected_cols]
# Delete rows with low correlations
correlation_matrix = correlation_matrix[correlation_matrix.abs().max(axis=1) >= 0.02]

Seaborn aracılığıyla görselleştirme süreci tamamlar.

plt.figure(figsize=(12, 7))
plt.subplots_adjust(left=0.2, right=1, bottom=0.05, top=0.95)
sns.heatmap(correlation_matrix, annot=True, cmap='coolwarm')
plt.title('Correlation Indicators to Next Bars') 
plt.savefig('trend_to_bar.png')
plt.show()

Isı haritası artık kaotik gürültü gibi görünmüyor, bir sinyal haritasına dönüşüyor. Hangi gösterge gruplarının gelecekteki hareketlere tepki vermeye başladığı, bu tepkinin hangi parametrelerde arttığı ve nerede tamamen kaybolduğu açıktır.

Göstergelerin gelecekteki fiyat hareketleriyle korelasyonu

Bu, hipotezler kümesinin yapı kazanmaya başladığı yerdir. Göstergelerin rastgele seçiminden özelliklerin bilinçli oluşumuna geçiyoruz. Bu henüz bir model değil, daha ziyade onun çerçevesi. Bir sonraki görev, bu zayıf ve birbirinden farklı sinyalleri, tek tek bakıldığında neredeyse görünmez olan istikrarlı bir bağımlılığı ortaya çıkarabilecek tek bir sistemde bir araya getirmektir.


4. Modelin oluşturulması ve eğitimi

Bir önceki adımda, bir korelasyon haritası elde ettik ve doğrudan ve ters korelasyonun maksimum değerlerine sahip özellikleri seçtik. Bu temel bir noktadır: negatif korelasyon aynı sinyaldir, sadece ters işaretlidir. Model açısından bu bir sınırlama değil, ek bilgidir.

Daha sonra eğitime geçiyoruz. Scikit-Learn ekosistemi, doğrusal modellerden topluluklara kadar geniş bir algoritma yelpazesi sunar. "Scikit-learn kütüphanesinin regresyon modelleri ve bunların ONNX'e aktarılması" makalesi 55 regresyon modelinin karşılaştırmasını sunmaktadır. Ancak pratik bir problemi çözmek için sağlam ve kanıtlanmış algoritmalara odaklanmak mantıklıdır. Bu durumda RandomForestRegressor kullanacağız.

Klasik orman, sadelik ve ifade gücü arasında bir dengedir. Doğrusal olmayan bağımlılıklarla iyi çalışır, gürültüye karşı dayanıklıdır ve agresif veri normalizasyonu gerektirmez. Modellemenin ilk aşamasında ihtiyaç duyulan şey tam olarak budur.

Bir sonraki önemli adım hiperparametrelerin seçilmesidir. İki parametreye göre doğrudan numaralandırma kullanalım: ağaç sayısı (n_estimators) ve ağaç derinliği (max_depth). Bu makul bir seçimdir: ilk parametre topluluğun gücünden sorumluyken, ikincisi tek bir ağacın veriye daldırılma derecesine ve aşırı uyumun kontrolüne odaklanır.

Bu amaçla yeni bir komut dosyası oluşturuyoruz. MetaTrader 5'e bağlanma ve geçmiş fiyat verilerini yükleme algoritması aynı kalıyor, bu nedenle açıklamasını atlayacağız.

Geçmiş veriler alındıktan sonra özellik uzayının oluşturulmasına başlanır. Temel bir matris oluşturulur: fiyat farkları, bunların yumuşatılmış versiyonları ve türetilmiş özellikler.

macd_settings = [(8,16,6),(12,24,9),(36,72,27),(48,96,36)]
features = []

# Build the base feature matrix from close price changes
close = pd.DataFrame(rates_frame['close'][:-1].to_numpy(dtype=float), columns=['close'])
diff = rates_frame['close'].diff().to_numpy(dtype=float)
# Pair consecutive differences into 'last' and 'next' columns
diff = np.column_stack((diff[:-1], diff[1:]))
data_matrix = pd.DataFrame(diff, columns=['last', 'next'])
features.append('last') # Add to features list for later use
# Add a 11-period rolling mean of the previous bar move
data_matrix['last_11'] = data_matrix['last'].rolling(window=11).mean()
features.append('last_11') # Add to features list for later use 
# Add the difference between the rolling mean and current bar move
data_matrix['last_last_11'] = data_matrix['last_11'] - data_matrix['last']
features.append('last_last_11') # Add to features list for later use

Ardından, teknik göstergeler eklenir: SMA ve bir dizi MACD konfigürasyonu.

# Add a 12-period simple moving average as a technical feature
data_matrix['SMA_12'] = ta.trend.sma_indicator(close['close'], window=12, fillna=True)
features.append('SMA_12') # Add to features list for later use

# Add MACD-based technical indicators for the selected parameter sets
for fast, slow, sign in macd_settings:
    macd = ta.trend.MACD(
        close['close'],
        window_slow=slow,
        window_fast=fast,
        window_sign=sign,
        fillna=True,
    )
    macd_main = macd.macd()
    dmacd = macd_main.diff()
    macd_signal = macd.macd_signal()
    dmacd_signal = macd_signal.diff()
    macd_sig_main = macd_signal - macd_main

    sufix = f"{fast:02d},{slow:02d},{sign:02d}"
    data_matrix[f'MACD_MAIN_{sufix}'] = macd_main
    features.append(f'MACD_MAIN_{sufix}') # Add to features list for later use
    data_matrix[f'DMACD_MAIN_{sufix}'] = dmacd
    features.append(f'DMACD_MAIN_{sufix}') # Add to features list for later use     
    data_matrix[f'MACD_SIGNAL_{sufix}'] = macd_signal
    features.append(f'MACD_SIGNAL_{sufix}') # Add to features list for later use
    data_matrix[f'DMACD_SIGNAL_{sufix}'] = dmacd_signal
    features.append(f'DMACD_SIGNAL_{sufix}') # Add to features list for later use
    data_matrix[f'MACD_Sig_Main_{sufix}'] = macd_sig_main
    features.append(f'MACD_Sig_Main_{sufix}') # Add to features list for later use
    data_matrix[f'DMACD_Sig_Main_{sufix}'] = macd_sig_main.diff()
    features.append(f'DMACD_Sig_Main_{sufix}') # Add to features list for later use

Tüm özellikler sırayla tek bir veri yapısında toplanırken, adları ayrı bir features listesinde ayarlanır. Bu, ilerideki çalışmaları basitleştirir ve değişkenlerin kazara kaybolmasını önler.

Aynı adımda, hedef değişken oluşturulur - belirli bir ufuktaki (next_9) toplam fiyat hareketi.

# Add a 9-period future return target for the next bars
data_matrix['next_9'] = data_matrix['next'].rolling(window=9).sum().shift(-8)

Böylece, problem bir regresyon olarak formüle edilir: mevcut göstergelere dayanarak gelecekteki fiyat değişiklikleri tahmin edilir.

Bir sonraki adım veri temizliğidir. Kayan pencereler ve farklılaştırma uygulandıktan sonra, eksik değerler kaçınılmaz olarak ortaya çıkar. Doğru model eğitimini sağlamak için çıkarılırlar.

data_matrix.dropna(inplace=True)

Ardından, veriler zamana göre bölünür: ilk %90 eğitim için, kalan %10 ise test için kullanılır. Bu temelde önemli bir noktadır. Klasik makine öğrenimi problemlerinin aksine, burada veri karıştırmaya izin verilmez. Gerçek bir süreci simüle ederek kronolojiyi sıkı bir şekilde koruyoruz: önce geçmiş, sonra gelecek.

# ===== 1) Data preparation =====
# Copy the raw feature matrix (preserves original data for later reference)
df = data_matrix.copy()

# Keep only features that are actually present in the DataFrame
features = [c for c in features if c in data_matrix.columns]

df = df[features + ["next_9"]]
X = df[features]
y = df["next_9"]

# ===== 2) Time-based split =====
split_idx = int(len(X) * 0.9)

X_train = X.iloc[:split_idx]
X_test = X.iloc[split_idx:]
y_train = y.iloc[:split_idx]
y_test = y.iloc[split_idx:]

Bundan sonra, RandomForestRegressor model hiperparametrelerinin bir numaralandırma döngüsü başlatılır. Her bir parametre kombinasyonu için tam bir döngü gerçekleştirilir:

  • Model bir eğitim örneklemi üzerinde eğitilir;
  • # ===== 3) Model =====
    results = []
    for est in range(60, 111, 5):
        for dep in range(2, 14, 1):
            print(f"\n=== Estimators: {est}, Max Depth: {dep} ===")
            model = RandomForestRegressor(
                n_estimators = est,
                max_depth = dep,
                max_leaf_nodes = None,
                min_samples_split = 6,
                min_samples_leaf = 3,
                bootstrap = True,
                random_state = 42,
                n_jobs = -1
                )
    
            model.fit(X_train, y_train)
  • Eğitim ve test bölümleri için tahminler oluşturulur, sonuçlar kararlı bir forma getirilir (NaN ve sonsuzlukları işleme);
  •         # ===== 4) Evaluation =====
            pred_train=np.nan_to_num(model.predict(X_train), nan=0.0, posinf=0.0, neginf=0.0)
            pred_test = np.nan_to_num(model.predict(X_test), nan=0.0, posinf=0.0, neginf=0.0)
  • Kalite metrikleri hesaplanır.
  •         pt_corr = np.corrcoef(pred_test, y_test)[0, 1]
            results.append((est, dep, pt_corr))
            print("Train R2:", round(r2_score(y_train, pred_train), 6))
            print("Test  R2:", round(r2_score(y_test, pred_test), 6))
            print("Test MAE:", round(mean_absolute_error(y_test, pred_test), 8))
            print("Pred/Target corr:", round(pt_corr, 6))
    

Anahtar metrik, tahmin ile test örneklemindeki gerçek değer arasındaki korelasyondur. Bu, modelin hareket yönünü yakalama kabiliyetini yansıtır. ve MAE ayrıca yaklaşmanın genel kalitesini ve hata seviyesini kontrol etmek için hesaplanır.

Tüm sonuçlar, her satırı belirli bir hiperparametre kombinasyonuna karşılık gelen bir tabloda saklanır. Daha sonra bu tablo, model kalitesinin parametrelere olan bağımlılığını görselleştirmemizi sağlayan bir matris formuna dönüştürülür.

# --- results -> DataFrame ---
df_results = pd.DataFrame(
    results,
    columns=["Estimators", "Max Depth", "Test Correlation"]
)

# --- pivot table ---
heatmap_data = df_results.pivot(
    index="Estimators",
    columns="Max Depth",
    values="Test Correlation"
).sort_index()

Son adım ise Seaborn aracılığıyla görselleştirmedir. Isı haritası, hangi parametre alanında en iyi sonucun elde edildiğini açıkça gösterir. Bu, sadece en uygun konfigürasyonu seçmemize değil, aynı zamanda modelin kararlılığını - parametrelerdeki küçük değişikliklerle kalitenin ne kadar değiştiğini - değerlendirmemize de olanak tanır.

# --- heatmap ---
plt.figure(figsize=(14, 10))
sns.heatmap(
    heatmap_data,
    annot=True,
    fmt=".4f",
    cmap="coolwarm",
    linewidths=0.5,
    cbar_kws={"label": "Test Correlation"}
)

plt.title("Heatmap of Test Correlation by n_estimators and max_leaf_nodes")
plt.xlabel("Max Nodes")
plt.ylabel("Estimators")
plt.tight_layout()
plt.show()

Ağaç sayısının maksimum derinliğe en uygun oranının bulunması

Burada önemli bir nokta ortaya çıkmaktadır: model yoktan bir sinyal yaratmaz. Yalnızca daha önce bulunan zayıf bağımlılıkları toplar. Analiz aşamasında yapıya dair en ufak bir ipucu bile yoksa, hiçbir model durumu kurtarmayacaktır. Ancak zayıf da olsa bir sinyal varsa, topluluk bunu güçlendirebilir ve pratik kullanıma uygun hale getirebilir.

Hiperparametre seçiminin ilk yinelemesinin grafiğinde, max_depth = 10 olan bölge açıkça görülebilir. Bu değer, modelin bağımlılıkları yakalama ve aşırı uyumu kontrol etme yeteneği arasındaki en istikrarlı dengeyi gösterir. Aslında bu, model artık ilkel olmadığında, ancak henüz gürültüye uymaya başlamadığında çalışma modunun elde edildiği yerdir.

Daha sonra mantık doğal olarak gelişir. max_depth tanımlandıktan sonra ikinci aşamaya geçilir - max_leaf_nodes aracılığıyla ağaç yapısı kurulur. Buna paralel olarak, aralığı ağaç sayısına (n_estimators) göre daraltıyoruz ve yalnızca daha önce istikrarlı sonuçların gözlemlendiği alanı bırakıyoruz. Bu, arama çözünürlüğünü artırmamızı sağlar: arama adımı azaltılır ve dikkat gerçekten önemli olan parametre alanına yoğunlaştırılır.

Bu yaklaşım klasik yerel optimizasyon prosedürünü anımsatmaktadır. İlk olarak, maksimumun kaba bir alanı belirlenir, ardından bunun içinde ince bir ayar yapılır. Sonuç olarak, açıkça zayıf olan konfigürasyonlarda hesaplama kaynaklarını boşa harcamaktan kaçınıyoruz ve hızlı bir şekilde kararlı bir parametre kombinasyonuna ulaşıyoruz.

Koddaki değişiklikler hedeflenen niteliktedir: arama aralığı belirtilir ve ikinci parametre değiştirilir. Komut dosyası mimarisi aynı kalır.

Orman büyüklüğünün düğüm sayısına en uygun oranının bulunması

Seçimden sonra, optimum hiperparametreleri sabitliyoruz ve modelin son eğitimine geçiyoruz. Yapısal olarak hiçbir şey değişmez: yineleme döngüleri komut dosyasından kaldırılır ve RandomForestRegressor başlatılırken parametre değerleri doğrudan belirtilir. Diğer tüm mantık - verileri hazırlama, örneklem bölme, eğitim ve temel doğrulama - değişmeden kalır.

Ardından, daha göze çarpmayan ancak temel bir nokta ortaya çıkmaktadır. Model, tahminleri arasında eşit derecede emin değildir. Bazı durumlarda güçlü bir sinyal verirken, diğerlerinde değerler sıfıra yakındır ve aslında belirsizlik bölgesindedir. Tüm tahminler eşit olarak ele alınırsa, strateji kaçınılmaz olarak gürültüye dayalı alım-satım yapmaya başlar.

Bu da doğal bir hipoteze yol açmaktadır: zayıf sinyalleri görmezden gel ve yalnızca modelin yeterli güveni gösterdiği veya beklenen hareketin maliyetleri karşıladığı durumlarda çalış. Bu zaten fonksiyon modelinden alım-satım filtresi modeline bir geçiştir.

Kod bu fikri düzgün bir şekilde ve gereksiz karmaşıklık olmadan uygulamaktadır. Eğitim örneklemine dayalı olarak, mutlak tahminlerin eşik değerleri hesaplanır.

# ===== 5) Simple PnL prototype =====
# Calculate strategy metrics for a vector of thresholds without an explicit loop
percentiles = np.arange(10, 100, 5)
thresholds = np.percentile(np.abs(pred_train), percentiles)

Bu önemli bir noktadır: eşikler eğitimde belirlenir ve teste uygulanır, bu da deneyin geçerliliğini korur.

Ardından, bir tahminler matrisi ve buna karşılık gelen eşikler oluşturulur. Her seviye için bir maske hesaplanır - hangi sinyallerin filtreyi geçtiği. Pozisyon, genel korelasyon dikkate alınarak tahminin işaretine göre belirlenir ve bu da işlem yönünün modelin doğasıyla tutarlı olmasını sağlar.

# Build a matrix where each column repeats the test predictions
pred_matrix = np.tile(pred_test[:, None], (1, thresholds.size))
threshold_matrix = thresholds[None, :]

# Generate a mask per threshold and compute sign positions
mask = np.abs(pred_matrix) >= threshold_matrix
position = np.sign(pred_matrix) * np.sign(pt_corr) * mask.astype(float)

Bundan sonra karlılık, pozisyon ve gerçek hareketin ürünü olarak oluşur. Sabit maliyetler (makas/komisyon) çıkarılır ve kümülatif sermaye eğrisi oluşturulur.

# Broadcast y_check to match the threshold matrix shape
y_check_matrix = np.tile(y_check.values[:, None], (1, thresholds.size))
# Subtracting swap cost from the target to get a more realistic PnL estimate
strategy_ret = position * y_check_matrix - np.abs(position)*(0.00021)

# Compute equity curves for each threshold column
equity = np.cumsum(strategy_ret, axis=0)

Sonuçlar bir tabloda toplanır:

  • final_equity - nihai karlılık;
  • mean_return - ortalama işlem sonucu;
  • win_rate - karlı girişlerin payı.
# Aggregate results into a DataFrame
results = pd.DataFrame({
    'percentile': percentiles,
    'threshold': thresholds,
    'final_equity': equity[-1, :],
    'mean_return': np.sum(strategy_ret, axis=0)/(np.sum(strategy_ret != 0, axis=0)+1e-9),
    'win_rate': np.sum(strategy_ret > 0, axis=0)/(np.sum(strategy_ret != 0, axis=0)+1e-9)
})

print(results.to_string(index=False, float_format='%.8f'))

Pratik bir bakış açısıyla, bu artık sadece bir modelin değerlendirilmesi değil, bir alım-satım sisteminin başlangıcıdır. Sadece tahmin doğruluğunu kontrol etmekle kalmıyor, aynı zamanda farklı sınıflandırma seviyelerinde nasıl para kazandırdığını da hemen değerlendiriyoruz.


5. ONNX'e dönüşüm

Modeli eğittik. Bir sonraki mantıklı adım, kişiyi karar verme döngüsünden çıkarmaktır. Model sinyallerine dayalı manuel alım-satım neredeyse her zaman otomatik alım-satımdan daha yetersizdir: süreklilik yoktur, tepki hızı kaybolur ve psikolojik faktör eklenir. Gerçek hayatta bu durum sistemik çarpıklıklara yol açar - kaçırılan girişler, erken çıkışlar, kişinin kendi modeline güvensizliği.

MetaTrader 5 platformu iki otomasyon yolu sunar. Birincisi, işlemlerin doğrudan gerçekleştirilmesiyle Python komut dosyasını çalıştırmaktır. İkincisi, modelin ONNX formatına aktarılması ve daha sonra MQL5 Uzman Danışmanında kullanılmasıdır. Pratikte ikinci seçenek daha olgun görünmektedir.

ONNX formatı birkaç sorunu aynı anda çözer. Model, kompakt ve bağımsız bir biçimde sabitlenmiştir. Bilgisayarlar arasında kolayca aktarılabilir - ihtiyacınız olan tek şey terminalin kendisidir. Strateji sınayıcıda tam teşekküllü test yapmak mümkün hale gelir. Üstelik performans kaybı da yoktur: Terminal, GPU (CUDA) ile çalışmak da dahil olmak üzere donanım hızlandırmayı destekliyor, bu da özellikle topluluk modelleri kullanılırken önemlidir.

Dönüşüm oldukça basittir. İlk olarak, modelin girdisi tanımlanır - özellik uzayının boyutu.

# Number of features used for model input
n_features = X_train.shape[1]

# Describe the model input shape for ONNX conversion
initial_type = [("float_input", FloatTensorType([None, n_features]))]

Ardından, Scikit-Learn'den eğitilen model uygun dönüştürücü aracılığıyla ONNX'e dönüştürülür ve diske kaydedilir.

# Convert the trained sklearn model to ONNX format
onnx_model = convert_sklearn(model, initial_types=initial_type)

# Save the ONNX model to disk
with open(onnx_model_path, "wb") as f:
    f.write(onnx_model.SerializeToString())

Bunu zorunlu doğrulama aşaması takip eder. Model ONNX Runtime aracılığıyla yüklenir ve tahmin edilen değerleri hesaplamak için aynı veriler kullanılır.

# Load the ONNX model for inference
sess = rt.InferenceSession(onnx_model_path)

input_name = sess.get_inputs()[0].name

# ONNX runtime expects float32 input arrays
X_test_np = X_test.astype(np.float32).values

onnx_preds = sess.run(None, {
    input_name: X_test_np
})[0].ravel()

Daha sonra bunlar Sklearn modelinin orijinal sonuçlarıyla karşılaştırılır.

# Compare ONNX predictions with sklearn predictions
sk_preds = model.predict(X_test)

print("Correlation:", np.corrcoef(sk_preds, onnx_preds)[0, 1])
print("Max diff:", np.max(np.abs(sk_preds - onnx_preds)))

Burada iki temel kriter vardır:

  • Tahminler arasındaki korelasyon 1'e doğru eğilim göstermelidir;
  • Maksimum tutarsızlık ihmal edilebilir olmalıdır.

Bu koşullar karşılanırsa, aktarımın başarılı olduğunu ve modelin alım-satım sistemine entegrasyon için hazır olduğunu varsayabiliriz.

Pratik açıdan bakıldığında bu, Python araştırma ortamından pratik kullanıma son geçiştir. Model bir deney olmaktan çıkar ve altyapının bir parçası haline gelir - otonom, tekrarlanabilir ve testler ve gerçek alım-satım için uygun.


6. Strateji sınayıcıda test etme

Python tarafındaki çalışma tamamlandıktan sonra mantık MetaTrader 5 çalışma zamanı ortamına aktarılır. Burada model bir araştırma aracı olmaktan çıkar ve bir alım-satım algoritmasının parçası haline gelir. Kod yapısının zaten bilinen işlem akışını takip etmesi önemlidir: başlatma → verileri hazırlama → tahmin → işlem kararı.

Başlatma işlemi OnInit metodunda gerçekleştirilir. Bu aşamada, kaynaktan ONNX modeli yüklenir ve OnnxCreateFromBuffer aracılığıyla çalışma zamanı ortamı oluşturulur.

int OnInit()
  {
//---
   if(!Symb.Name("EURUSD_i"))
      return INIT_FAILED;
   Symb.Refresh();
//---
   if(!Trade.SetTypeFillingBySymbol(Symb.Name()))
      return INIT_FAILED;
//--- load models
   onnx = OnnxCreateFromBuffer(model, ONNX_DEFAULT);
   if(onnx == INVALID_HANDLE)
     {
      Print("OnnxCreateFromBuffer error ", GetLastError());
      return INIT_FAILED;
     }
   const ulong input_state[] = {1, Inputs.Size()};
   if(!OnnxSetInputShape(onnx, 0, input_state))
     {
      Print("OnnxSetInputShape error ", GetLastError());
      OnnxRelease(onnx);
      return INIT_FAILED;
     }
   const ulong output_forecast[] = {1, Forecast.Size()};
   if(!OnnxSetOutputShape(onnx, 0, output_forecast))
     {
      Print("OnnxSetOutputShape error ", GetLastError());
      OnnxRelease(onnx);
      return INIT_FAILED;
     }

Daha sonra, girdi ve çıktı şekilleri açıkça ayarlanır - model kesinlikle sabit sayıda özellik beklediği için bu kritiktir. Bu aşamada yapılacak bir hata yanlış çıkarımla sonuçlanacaktır.

Eğitim sırasında kullanılan aynı parametrelere sahip SMA göstergesi ve MACD seti paralel olarak başlatılır.

//--- Indicators
   if(!ciSMA.Create(Symb.Name(), TimeFrame, 12, 0, MODE_SMA, PRICE_CLOSE))
     {
      Print("SMA create error ", GetLastError());
      OnnxRelease(onnx);
      return INIT_FAILED;
     }
   ciSMA.BufferResize(2);
   for(uint i = 0; i < ciMACD.Size(); i++)
     {
      if(!ciMACD[i].Create(Symb.Name(), TimeFrame, int(macd_set[i, 0]),
                int(macd_set[i, 1]), int(macd_set[i, 2]), PRICE_CLOSE))
        {
         PrintFormat("MACD %d create error %d", i, GetLastError());
         OnnxRelease(onnx);
         return INIT_FAILED;
        }
      ciMACD[i].BufferResize(4);
     }
//---
   return(INIT_SUCCEEDED);
  }

Bu temel bir noktadır: MQL5'teki özellikler, eğitim sırasında modele beslenen özelliklerle aynı olmalıdır. Herhangi bir tutarsızlık tahmin yeteneğini yok eder.

Ana mantık OnTick metodunda yoğunlaşmıştır, ancak yeni çubuk açılış olayı için IsNewBar filtresi ile. Bu, modelin her tikte yeniden hesaplanmasını önler ve hesaplamaları zaman dilimiyle senkronize eder.

void OnTick()
  {
//---
   if(!IsNewBar())
      return;

Ardından, mevcut pozisyonların muhasebeleştirilmesi için blok gelir - hacimlerin ve karların yöne göre basit bir şekilde toplanması. Bu, halihazırda açık olan işlemleri kontrol etmek için gereklidir.

   double buy_value = 0, sell_value = 0, buy_profit = 0, sell_profit = 0;
   int total = PositionsTotal();
   for(int i = 0; i < total; i++)
     {
      if(PositionGetSymbol(i) != Symb.Name())
         continue;
      double profit = PositionGetDouble(POSITION_PROFIT);
      switch((int)PositionGetInteger(POSITION_TYPE))
        {
         case POSITION_TYPE_BUY:
            buy_value += PositionGetDouble(POSITION_VOLUME);
            buy_profit += profit;
            break;
         case POSITION_TYPE_SELL:
            sell_value += PositionGetDouble(POSITION_VOLUME);
            sell_profit += profit;
            break;
        }
     }

Inputs başlangıç veri vektörü daha sonra oluşturulur. Esasen, Python'dan Özellik Mühendisliği burada manuel olarak yeniden üretilir:

  • Temel özellikler;
  • //--- prepare input data
       ciSMA.Refresh();
       for(uint i = 0; i < ciMACD.Size(); i++)
          ciMACD[i].Refresh();
       if(!Rates.CopyRates(Symb.Name(), TimeFrame, COPY_RATES_CLOSE, 1, 12))
         {
          Print("CopyRates error ", GetLastError());
          return;
         }
       Inputs[0] = float(Rates[11] - Rates[10]);
       Inputs[1] = float(Rates[11] - Rates[0]) / 11;
       Inputs[2] = float(Inputs[1] - Inputs[0]);
    
  • SMA;
  •    Inputs[3] = float(ciSMA.Main(1));
  • MACD blokları ve türevleri.
  •    for(uint i = 0; i < ciMACD.Size(); i++)
         {
          Inputs[4 + i * 6] = float(ciMACD[i].Main(1));
          Inputs[5 + i * 6] = float(Inputs[4 + i * 6] - ciMACD[i].Main(2));
          Inputs[6 + i * 6] = float(ciMACD[i].Signal(1));
          Inputs[7 + i * 6] = float(Inputs[6 + i * 6] - ciMACD[i].Signal(2));
          Inputs[8 + i * 6] = Inputs[6 + i * 6] - Inputs[4 + i * 6];
          Inputs[9 + i * 6] = Inputs[7 + i * 6] - Inputs[5 + i * 6];
         }
    

Lütfen indekslemeye dikkat edin: her özellik kesin olarak tanımlanmış bir yer kaplar. Bu, model ile yürütme arasında bir sözleşmedir. Düzen ihlal edilirse, model bozuk girdilerle çalışmaya başlar.

Özellikler hazırlandıktan sonra OnnxRun çağrılır.

//--- run the inference
   if(!OnnxRun(onnx, ONNX_LOGLEVEL_INFO, Inputs, Forecast))
     {
      Print("OnnxRun error ", GetLastError());
      return;
     }

Çıktı bir tahmindir - beklenen fiyat hareketi. Sırada uygulamalı kısım geliyor - sinyal yorumlama.

Kod basit ama etkili bir mantık kullanır:

  • Zayıf sinyalleri kesmek için bir eşik değeri getirildi. Bunu eğitim sonuçlarından alıyoruz.
  • Korelasyonun yönü dikkate alınır ve gerekirse modelin tersine çevrilmesine izin verilir.
  • Tahmin eşiği aşarsa, bir pozisyon açılır. Sinyal kaybolursa, pozisyon kapatılır.
   Symb.Refresh();
   Symb.RefreshRates();
   double min_lot = Symb.LotsMin();
   double step_lot = Symb.LotsStep();
   double stops = (MathMax(Symb.StopsLevel(), 1) + Symb.Spread()) * Symb.Point();
//--- buy control
   if(Forecast[0]*direction >= threshold)
     {
      double buy_lot = min_lot;
      if(buy_value <= 0)
         Trade.Buy(buy_lot, Symb.Name(), Symb.Ask(), 0, 0);
     }
   else
     {
      if(buy_value > 0)
         CloseByDirection(POSITION_TYPE_BUY);
     }
//--- sell control
   if(Forecast[0]*direction <= -threshold)
     {
      double sell_lot = min_lot;
      if(sell_value <= 0)
         Trade.Sell(sell_lot, Symb.Name(), Symb.Bid(), 0, 0);
     }
   else
     {
      if(sell_value > 0)
         CloseByDirection(POSITION_TYPE_SELL);
     }
  }

Böylece model, yönlü hareket filtresi olarak kullanılır. Bu önemli bir ayrımdır: tahmin edilen her değerde değil, yalnızca sinyal gücünü geçenlerde işlem yaparız.

Ortaya çıkan Uzman Danışman daha sonra önemli bir kontrolden geçer - MetaTrader 5 strateji sınayıcıda 2026'nın ilk çeyreği için geçmiş veriler üzerinde test edilir. Bu artık modelin soyut bir değerlendirmesi değil, gerçeğe yakın bir uygulama senaryosudur.

Bu test formatı temelde önemlidir. Python aşamasında modeli metrikler aracılığıyla değerlendirirken, burada özellik üretiminden pozisyon açma ve kapama mantığına kadar tüm sistem test edilmektedir. Aslında, strateji ilk sınavını mümkün olan en yakın ölçüde gerçek koşullara benzeyen koşullarda verir.

Test aşaması geliştirme döngüsünü tamamlar: hipotez ve veri analizinden bir modele, ardından otomasyona ve geçmiş veriler üzerinde doğrulamaya kadar. Bu noktada, zayıf istatistiksel ilişkilerin başarılı bir şekilde pratik bir alım-satım aracına dönüştürülüp dönüştürülmediği netleşir.

Ancak, ONNX modelinin bir Uzman Danışmana entegrasyonu tek kullanım alanı değildir. Manuel alım-satım hayranları için MetaTrader 5, bir modeli doğrudan özel bir göstergeye yerleştirme olanağı sağlar. Bu durumda, model yatırımcı için karar vermez, ancak kullanıcının bağımsız olarak yorumladığı sinyaller üreten analitik bir araç olarak hareket eder.

Mühendislik açısından bakıldığında, pratikte hiçbir fark yoktur. ONNX model bağlantı mekaniği, başlangıç verilerinin hazırlanması ve çıkarım çağrısı Uzman Danışmandaki uygulama ile tamamen aynıdır. Sadece uygulama noktası değişir: otomatik olarak pozisyon açmak yerine, model sonucu bir grafik üzerinde görselleştirilir veya karar verme sürecinde ek bir filtre olarak kullanılır.

RandomForest ile gösterge

Bu yaklaşımın avantajları vardır. Model sinyallerinin klasik analizle esnek bir şekilde birleştirilmesine olanak tanır ve algoritma güvenilirliği için gereksinimleri azaltır - model, çözümlerin tek kaynağı değil, bir yardımcı haline gelir.


Sonuç

Python ve MetaTrader 5'in entegrasyonu, konseptten pratik uygulamaya kadar alım-satım çözümleri geliştirmek için mühendislik açısından test edilmiş eksiksiz bir çerçeve oluşturur. Bu makalede, veri toplama ve analizinden, hipotez testi ve model inşasına, gerçek dünya yürütme ortamında modelin uygulanması ve test edilmesine kadar bu yolu sırayla yürüdük.

Bu yaklaşımın en önemli avantajı rollerin ayrılmasıdır. Python araştırma kısmını üstlenir: veri işleme, özellik oluşturma, istatistiksel analiz ve model eğitimi. MetaTrader 5 ise yürütme sağlar: piyasa verilerine erişim, strateji testi ve alım-satım altyapısı. Bu, her ortamın kendi amacına uygun olarak kullanıldığı klasik bir laboratuvar - üretim devresi kombinasyonudur.

ONNX formatının kullanılması ek değer sağlar. Model, geliştirme ortamından bağımsız olarak taşınabilir hale gelir ve yüklü bir terminali olan herhangi bir cihazda çalıştırılmaya hazırdır. Bu, ölçeklendirmeyi basitleştirir, testleri hızlandırır ve ortamlar arasındaki uyumsuzlukla ilişkili riskleri azaltır.

Makalede kullanılan programlar

# Ad Tür Açıklama
1 Experts\Integration\Integration.mq5 Uzman Danışman Modeli terminalde test etmek için Uzman Danışman
2 Indicators\Integration\Integration.mq5 Gösterge Bir grafik üzerinde sinyalleri görüntülemek için gösterge
3 Scripts\Integration\load_data.py Komut dosyası Veri yükleme komut dosyası
4 Scripts\Integration\look_model_param_rf.py Komut dosyası Hiperparametre numaralandırma komut dosyası
5 Scripts\Integration\create_model_rf.py Komut dosyası Modeli eğitmek ve ONNX'e aktarmak için komut dosyası

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

Ekli dosyalar |
MQL5.zip (13.99 KB)
Son yorumlar | Tartışmaya git (2)
Denis Kirichenko
Denis Kirichenko | 17 Nis 2026 saat 11:17

Arşivdeki load_data.py komut dosyasında şu satırlar yer almaktadır:

#  Get bars from EURUSD H1 (hourly timeframe) within the specified interval
rates = mt5.copy_rates_range("EURUSD_i", mt5.TIMEFRAME_H1, utc_from, utc_to)

oysa makalenin kendisinde:

#  Get bars from EURUSD H1 (hourly timeframe) within the specified interval
rates = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H1, utc_from, utc_to)

Küçük bir ayrıntı, ama test ederken hemen fark etmedim...

Sonra Python 3.14.3 sürümünden vazgeçmek zorunda kaldım. VS'de Python ile çalışıyorum. Orada hata ayıklama işlemi sadece 3.11 sürümünde yapılabiliyor.
Gloria Diana
Gloria Diana | 4 May 2026 saat 16:55
Bu kaynağa minnettarız
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.
Rutin İşler Olmadan Algoritmik Alım-Satım: SQLite ile MetaTrader 5'te Hızlı İşlem Analizi Rutin İşler Olmadan Algoritmik Alım-Satım: SQLite ile MetaTrader 5'te Hızlı İşlem Analizi
Makale, SQLite kullanılarak MQL5'te bir işlem günlüğü tutmak için temel bir çalışma seti sunmaktadır: işlemler, sinyaller ve olaylar için bir tablo yapısı, indeksler, hazırlanmış ifadeler ve işlemler ve standart analitik SQL sorguları. MetaTrader 5'te istatistik paneliyle entegrasyon ve MetaEditor aracılığıyla veritabanıyla çalışma gösterilmektedir. Bu yaklaşım, Uzman Danışman kodunu karmaşıklaştırmadan günlüğün otomatikleştirilmesine, hesaplamaların hızlandırılmasına ve analiz yapılmasına olanak tanı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.
MetaTrader 5'te L1 Trend Filtrelemeyi Uygulama MetaTrader 5'te L1 Trend Filtrelemeyi Uygulama
Bu makale, MetaTrader 5'te L1 trend filtrelemenin pratik uygulamasını ele almakta ve hem matematiksel temellerini hem de MQL5 programlarındaki kullanımını kapsamaktadır. L1 filtresi, fiyat gürültüsünü azaltırken temel piyasa yapısını koruyan parçalı doğrusal trendlerin çıkarılmasını sağlar. Çalışma, parametre ölçeklendirmesini, trend hesaplama davranışını ve yöntemin algoritmik alım-satım stratejilerine entegrasyonunu analiz etmektedir. Deneysel sonuçlar, L1 trend filtrelemenin sinyal kararlılığını, işlem zamanlamasını ve alım-satım sistemlerinin genel sağlamlığını nasıl iyileştirebileceğini göstermektedir.