MetaTrader 5'i kullanarak Python'da yüksek frekanslı arbitraj alım-satım sistemi
Giriş
Forex piyasası. Algoritmik stratejiler. Python ve MetaTrader 5. Bunlar bir arbitraj alım-satım sistemi üzerinde çalışmaya başladığımda bir araya geldi. Fikir basitti - fiyat dengesizliklerini bulmak için yüksek frekanslı bir sistem oluşturmak.
Bu süreçte en çok MetaTrader 5 API’sini kullandım. Sentetik çapraz kurları hesaplamaya karar verdim. Kendimi on ya da yüz taneyle sınırlamadım. Bu sayı bini aşmış durumda.
Risk yönetimi ayrı bir görevdi. Sistem mimarisi, algoritmalar, karar verme - burada her şeyi analiz edeceğiz. Geriye dönük test ve canlı alım-satım sonuçlarını göstereceğim. Ve elbette gelecek için fikirlerimi paylaşacağım. Kim bilir, belki içinizden biri bu konuyu daha da geliştirmek ister? Çalışmalarımın talep göreceğini umuyorum. Algoritmik alım-satımın gelişmesine katkıda bulunacağına inanıyorum. Belki birileri bunu temel alır ve yüksek frekanslı arbitraj dünyasında daha da etkili bir şey yaratır. Ne de olsa bilimin özü budur - öncekilerin deneyimlerine dayanarak ilerlemek. Doğrudan konuya girelim.
Forex arbitraj alım-satımına giriş
Gerçekte ne olduğunu anlayalım.
Döviz değişimi ile bir analoji kurulabilir. Diyelim ki bir yerden EUR karşılığında USD satın alabilir, hemen başka bir yerde bu USD'yi GBP karşılığında satabilir ve ardından GBP'yi tekrar EUR'ya çevirip kar elde edebilirsiniz. Bu en basit haliyle arbitrajdır.
Aslında durum biraz daha karmaşıktır. Forex devasa, merkezi olmayan bir piyasadır. Burada çok sayıda banka, aracı kurum ve fon vardır. Ve herkesin kendi döviz kurları vardır. Çoğu zaman eşleşmezler. İşte bu noktada arbitraj için bir fırsata sahibiz. Ama bunun kolay para olduğunu düşünmeyin. Genellikle bu fiyat farklılıkları sadece birkaç saniye sürer. Hatta milisaniyeler. Zamanında yetişmek neredeyse imkansız. Bunun için güçlü bilgisayarlar ve hızlı algoritmalar gerekir.
Farklı arbitraj türleri vardır. Basit olanı, farklı yerlerdeki kurlar arasındaki farktan kar elde etmektir. Karmaşık olanı ise çapraz kurları kullanmaktır. Örneğin, GBP'nin USD ve EUR cinsinden ne kadara satılacağını hesaplıyor ve bunu doğrudan GBP/EUR döviz kuru ile karşılaştırıyoruz.
Liste bununla da bitmiyor. Ayrıca zaman arbitrajı da vardır. Burada, zamanın farklı noktalarındaki fiyat farklarından kar elde ediyoruz. Şimdi almak, bir dakika içinde satmak. Elbette süreç basit görünüyor. Ancak asıl sorun, fiyatın bir dakika içinde nereye gideceğini bilmememiz. Bunlar ana risklerdir. Piyasa, istediğiniz emri etkinleştirebileceğiniz süreden daha hızlı bir şekilde tersine dönebilir. Ya da aracı kurumunuz emirleri gerçekleştirmeyi geciktirebilir. Genel olarak, oldukça fazla zorluk ve risk vardır. Tüm zorluklara rağmen, Forex arbitrajı oldukça popüler bir sistemdir. Burada ciddi finansal kaynaklar ve yalnızca bu tür alım-satımda uzmanlaşmış yeterli sayıda yatırımcı vardır.
Şimdi, kısa bir girişten sonra, stratejimize geçelim.Kullanılan teknolojilere genel bakış: Python ve MetaTrader 5
Python ve MetaTrader 5.
Python çok yönlü ve anlaşılması kolay bir programlama dilidir. Hem acemi hem de deneyimli geliştiriciler tarafından tercih edilmesi boşuna değildir. Ve veri analizi için en uygun olanıdır.
Öte yandan, MetaTrader 5. Bu, her Forex yatırımcısının aşina olduğu bir platformdur. Güvenilirdir ve karmaşık değildir. Ayrıca oldukça işlevseldir - gerçek zamanlı fiyatlar, alım-satım robotları ve teknik analiz. Hepsi bir arada uygulama. Pozitif sonuçlar elde etmek için tüm bunları birleştirmemiz gerekiyor.
Python, MetaTrader 5'ten verileri alır, kütüphanelerini kullanarak işler ve ardından alım-satım işlemlerini gerçekleştirmek için komutları MetaTrader 5'e geri gönderir. Elbette bazı zorluklar var. Ancak bu uygulamalar birlikte çok verimlidir.
Python'dan MetaTrader 5 ile çalışmak için geliştiricilerin özel bir kütüphanesi mevcuttur. Etkinleştirmek için yüklemeniz yeterlidir. Bunu yaptıktan sonra fiyatları alabilir, emirler gönderebilir ve pozisyonları yönetebiliriz. Her şey terminalin kendisinde olduğu gibi, sadece şimdi Python yetenekleri de kullanılıyor.
Şu anda hangi özellik ve kabiliyetlere sahibiz? Artık oldukça fazla sayıda. Örneğin, alım-satımı otomatikleştirebiliyor ve geçmiş verilerin karmaşık analizini yapabiliyoruz. Kendi işlem platformumuzu bile oluşturabiliriz. Bu ileri düzey kullanıcılar için bir görevdir, ancak bu da mümkündür.
Ortamın kurulması: Gerekli kütüphanelerin yüklenmesi ve MetaTrader 5'e bağlanma
İş akışımıza Python ile başlayacağız. Eğer henüz yüklemediyseniz, python.org adresine gidin. Ayrıca “Add Python to PATH” seçeneğini de işaretlemeniz gerekiyor.
Bir sonraki adımımız kütüphaneler. Birkaç tanesine ihtiyacımız olacak. Ana olan MetaTrader 5'tir. Kurulum herhangi bir özel beceri gerektirmez.
Komut satırını açın ve şunu yazın:
pip install MetaTrader5 pandas numpy
Enter'a basın ve gidip biraz kahve için. Ya da çay. Ya da neyi tercih ederseniz.
Her şey hazır mı? Şimdi MetaTrader 5'e bağlanma zamanı.
Yapmanız gereken ilk şey MetaTrader 5'in kendisini yüklemektir. Aracı kurumunuzdan indirin. Terminale giden yolu hatırladığınızdan emin olun. Tipik olarak şöyle görünür: "C:\ProgramFiles\MetaTrader 5\terminal64.exe".
Şimdi Python'ı açın ve şunları yazın:
import MetaTrader5 as mt5 if not mt5.initialize(path="C:/Program Files/MetaTrader 5/terminal64.exe"): print("Alas! Failed to connect :(") mt5.shutdown() else: print("Hooray! Connection successful!")
Her şey başladıysa, bir sonraki bölüme geçebilirsiniz.
Kod yapısı: Ana fonksiyonlar ve amaçları
İçe aktarmalarla başlıyoruz: MetaTrader5, pandas, datetime, pytz... Daha sonra, fonksiyonlar var:
- İlk fonksiyon remove_duplicate_indices'tır. Verilerimizde herhangi bir tekrar olmadığından emin olur.
- Ardından get_mt5_data gelir. MetaTrader 5 fonksiyonlarına erişir ve son 24 saat için gerekli verileri çıkarır.
- get_currency_data - çok ilginç bir fonksiyon. Bir grup döviz paritesi için get_mt5_data'yı çağırır. AUDUSD, EURUSD, GBPJPY ve daha birçok parite.
- Bir sonraki calculate_synthetic_prices’tır. Bu fonksiyon gerçek bir başarıdır. Döviz paritelerini işlerken yüzlerce sentetik fiyat üretir.
- analyze_arbitrage, gerçek fiyatları sentetik fiyatlarla karşılaştırarak arbitraj fırsatları arar. Tüm bulgular bir CSV dosyasına kaydedilir.
- open_test_limit_order - başka bir güçlü kod birimi. Bir arbitraj fırsatı bulunduğunda, bu fonksiyon bir test emri açar. Ancak aynı anda 10'dan fazla açık işlem olmamalıdır.
Ve son olarak, 'main' fonksiyonu. Fonksiyonları doğru sırada çağırarak tüm bu süreci yönetir.
Kodun sonunda sonsuz bir döngü vardır. Her 5 dakikada bir tüm döngüyü çalıştırır, ancak yalnızca çalışma saatleri içinde. Sahip olduğumuz yapı budur. Basit ama etkili.
MetaTrader 5'ten verileri alma: get_mt5_data fonksiyonu
İlk görev terminalden verileri almaktır.
if not mt5.initialize(path=terminal_path): print(f"Failed to connect to MetaTrader 5 terminal at {terminal_path}") return None timezone = pytz.timezone("Etc/UTC") utc_from = datetime.now(timezone) - timedelta(days=1)
UTC kullandığımızı unutmayın. Çünkü Forex dünyasında saat dilimi karmaşasına yer yoktur.
Şimdi en önemli şey tikleri elde etmek:
ticks = mt5.copy_ticks_from(symbol, utc_from, count, mt5.COPY_TICKS_ALL) Verileri aldınız mı? Harika! Şimdi bu verileri işlememiz gerekiyor. Bunu yapmak için pandas’ı kullanıyoruz:
ticks_frame = pd.DataFrame(ticks) ticks_frame['time'] = pd.to_datetime(ticks_frame['time'], unit='s')
İşte bu kadar! Artık verileri içeren kendi DataFrame'imiz var. Analiz için çoktan hazır durumda.
Peki ya bir şeyler ters giderse? Merak etmeyin! Fonksiyonumuz bunu da kapsamaktadır:
if ticks is None: print(f"Failed to fetch data for {symbol}") return None
Basitçe bir sorun bildirecek ve None geri döndürecektir.
Birden fazla döviz paritesini işleme: get_currency_data fonksiyonu
get_currency_data fonksiyonu ile sisteme daha fazla dalıyoruz. Koda bir göz atalım:
def get_currency_data(): # Define currency pairs and the amount of data symbols = ["AUDUSD", "AUDJPY", "CADJPY", "AUDCHF", "AUDNZD", "USDCAD", "USDCHF", "USDJPY", "NZDUSD", "GBPUSD", "EURUSD", "CADCHF", "CHFJPY", "NZDCAD", "NZDCHF", "NZDJPY", "GBPCAD", "GBPCHF", "GBPJPY", "GBPNZD", "EURCAD", "EURCHF", "EURGBP", "EURJPY", "EURNZD"] count = 1000 # number of data points for each currency pair data = {} for symbol in symbols: df = get_mt5_data(symbol, count, terminal_path) if df is not None: data[symbol] = df[['time', 'bid', 'ask']].set_index('time') return data
Her şey döviz paritelerini tanımlamakla başlıyor. Liste AUDUSD, EURUSD, GBPJPY ve bizim için iyi bilinen diğer enstrümanları içeriyor.
Ardından bir sonraki adıma geçiyoruz. Fonksiyon boş bir 'data' sözlüğü oluşturur. Daha sonra gerekli verilerle doldurulacaktır.
Şimdi fonksiyonumuz çalışmaya başlıyor. Döviz pariteleri listesinin üzerinden geçecektir. Her parite için get_mt5_data çağrısı yapar. Eğer get_mt5_data veri geri döndürürse (None değil), fonksiyonumuz sadece en önemlilerini alır: zaman, Bid fiyatı ve Ask fiyatı.
Ve işte, nihayet, büyük final. Fonksiyon, verilerle dolu bir sözlük geri döndürür.
Böylece fonksiyon tamamlanır. Küçük, güçlü, basit ama etkilidir.
2000 sentetik fiyatın hesaplanması: Strateji ve uygulama
Sistemimizin temellerine dalıyoruz - calculate_synthetic_prices fonksiyonu. Sentezlenmiş verilerimizi elde etmemizi sağlar.
Koda bir göz atalım:
def calculate_synthetic_prices(data):
synthetic_prices = {}
# Remove duplicate indices from all DataFrames in the data dictionary
for key in data:
data[key] = remove_duplicate_indices(data[key])
# Calculate synthetic prices for all pairs using multiple methods
pairs = [('AUDUSD', 'USDCHF'), ('AUDUSD', 'NZDUSD'), ('AUDUSD', 'USDJPY'),
('USDCHF', 'USDCAD'), ('USDCHF', 'NZDCHF'), ('USDCHF', 'CHFJPY'),
('USDJPY', 'USDCAD'), ('USDJPY', 'NZDJPY'), ('USDJPY', 'GBPJPY'),
('NZDUSD', 'NZDCAD'), ('NZDUSD', 'NZDCHF'), ('NZDUSD', 'NZDJPY'),
('GBPUSD', 'GBPCAD'), ('GBPUSD', 'GBPCHF'), ('GBPUSD', 'GBPJPY'),
('EURUSD', 'EURCAD'), ('EURUSD', 'EURCHF'), ('EURUSD', 'EURJPY'),
('CADCHF', 'CADJPY'), ('CADCHF', 'GBPCAD'), ('CADCHF', 'EURCAD'),
('CHFJPY', 'GBPCHF'), ('CHFJPY', 'EURCHF'), ('CHFJPY', 'NZDCHF'),
('NZDCAD', 'NZDJPY'), ('NZDCAD', 'GBPNZD'), ('NZDCAD', 'EURNZD'),
('NZDCHF', 'NZDJPY'), ('NZDCHF', 'GBPNZD'), ('NZDCHF', 'EURNZD'),
('NZDJPY', 'GBPNZD'), ('NZDJPY', 'EURNZD')]
method_count = 1
for pair1, pair2 in pairs:
print(f"Calculating synthetic price for {pair1} and {pair2} using method {method_count}")
synthetic_prices[f'{pair1}_{method_count}'] = data[pair1]['bid'] / data[pair2]['ask']
method_count += 1
print(f"Calculating synthetic price for {pair1} and {pair2} using method {method_count}")
synthetic_prices[f'{pair1}_{method_count}'] = data[pair1]['bid'] / data[pair2]['bid']
method_count += 1
return pd.DataFrame(synthetic_prices)
Arbitraj fırsatlarını analiz etme: analyze_arbitrage fonksiyonu
İlk olarak, boş bir synthetic_prices sözlüğü oluşturuyoruz. Daha sonra onu verilerle dolduracağız. Ardından tüm verilerin üzerinden geçiyor ve gelecekte hataları önlemek için tekrar eden indeksleri kaldırıyoruz.
Bir sonraki adım 'pairs' listesidir. Bunlar sentez için kullanacağımız döviz paritelerimizdir. Ardından başka bir süreç başlıyor. Tüm pariteler üzerinde bir döngü çalıştırıyoruz. Her bir parite için sentetik fiyatı iki şekilde hesaplıyoruz:
- İlk paritenin Bid fiyatını ikinci paritenin Ask fiyatına bölüyoruz.
- İlk paritenin Bid fiyatını ikinci paritenin Bid fiyatına bölüyoruz.
Her seferinde method_count değerini artırıyoruz. Sonuç olarak, 2000 sentetik parite elde ediyoruz!
calculate_synthetic_prices fonksiyonu bu şekilde çalışıyor. Sadece fiyatları hesaplamakla kalmaz, aslında yeni fırsatlar yaratır. Bu fonksiyon arbitraj fırsatları şeklinde harika sonuçlar verir!
Sonuçları görselleştirme: Verileri CSV'ye kaydetme
analyze_arbitrage fonksiyonuna bakalım. Sadece verileri analiz etmekle kalmaz, ihtiyaç duyulan şeyi bir sayı akışı içinde arar. Fonksiyona göz atalım:
def analyze_arbitrage(data, synthetic_prices, method_count): # Calculate spreads for each pair spreads = {} for pair in data.keys(): for i in range(1, method_count + 1): synthetic_pair = f'{pair}_{i}' if synthetic_pair in synthetic_prices.columns: print(f"Analyzing arbitrage opportunity for {synthetic_pair}") spreads[synthetic_pair] = data[pair]['bid'] - synthetic_prices[synthetic_pair] # Identify arbitrage opportunities arbitrage_opportunities = pd.DataFrame(spreads) > 0.00008 print("Arbitrage opportunities:") print(arbitrage_opportunities) # Save the full table of arbitrage opportunities to a CSV file arbitrage_opportunities.to_csv('arbitrage_opportunities.csv') return arbitrage_opportunities
İlk olarak, fonksiyonumuz boş bir 'spreads' sözlüğü oluşturur. Daha sonra onu verilerle dolduracağız.
Bir sonraki adıma geçelim. Fonksiyon, tüm döviz pariteleri ve bunların sentetik analogları üzerinden geçer. Her bir parite için fiyat farkını hesaplar - gerçek Bid fiyatı ile sentetik fiyat arasındaki fark.
spreads[synthetic_pair] = data[pair]['bid'] - synthetic_prices[synthetic_pair]
Bu satır oldukça önemli bir rol oynar. Gerçek ve sentetik fiyat arasındaki farkı bulur. Bu fark pozitifse, bir arbitraj fırsatımız var demektir.
Daha ciddi sonuçlar elde etmek için 0.00008 sayısını kullanıyoruz:
arbitrage_opportunities = pd.DataFrame(spreads) > 0.00008 Bu satır 8 puanın altındaki tüm fırsatları filtreler. Bu şekilde daha yüksek kar olasılığı olan fırsatlar elde edeceğiz.
İşte bir sonraki adım:
arbitrage_opportunities.to_csv('arbitrage_opportunities.csv') Ardından tüm verilerimizi bir CSV dosyasına kaydediyoruz. Artık bunları inceleyebilir, analiz edebilir, grafikler çizebilir - genel olarak verimli çalışmalar yapabiliriz. Tüm bunlar bu fonksiyon sayesinde mümkün olmaktadır - analyze_arbitrage. Sadece analiz etmekle kalmaz, arbitraj fırsatlarını arar, bulur ve kaydeder.
Test emirlerini açma: open_test_limit_order fonksiyonu
Şimdi open_test_limit_order fonksiyonunu ele alalım. Bizim için emirlerimizi açar.
Fonksiyona göz atalım:
def open_test_limit_order(symbol, order_type, price, volume, take_profit, stop_loss, terminal_path): if not mt5.initialize(path=terminal_path): print(f"Failed to connect to MetaTrader 5 terminal at {terminal_path}") return None symbol_info = mt5.symbol_info(symbol) positions_total = mt5.positions_total() if symbol_info is None: print(f"Instrument not found: {symbol}") return None if positions_total >= MAX_OPEN_TRADES: print("MAX POSITIONS TOTAL!") return None # Check if symbol_info is None before accessing its attributes if symbol_info is not None: request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": volume, "type": order_type, "price": price, "deviation": 30, "magic": 123456, "comment": "Stochastic Stupi Sustem", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_IOC, "tp": price + take_profit * symbol_info.point if order_type == mt5.ORDER_TYPE_BUY else price - take_profit * symbol_info.point, "sl": price - stop_loss * symbol_info.point if order_type == mt5.ORDER_TYPE_BUY else price + stop_loss * symbol_info.point, } result = mt5.order_send(request) if result is not None and result.retcode == mt5.TRADE_RETCODE_DONE: print(f"Test limit order placed for {symbol}") return result.order else: print(f"Error: Test limit order not placed for {symbol}, retcode={result.retcode if result is not None else 'None'}") return None else: print(f"Error: Symbol info not found for {symbol}") return None
Fonksiyonumuzun yaptığı ilk şey MetaTrader 5 terminaline bağlanmaya çalışmaktır. Daha sonra işlem yapmak istediğimiz enstrümanın var olup olmadığını kontrol eder.
Aşağıdaki koda bakalım:
if positions_total >= MAX_OPEN_TRADES: print("MAX POSITIONS TOTAL!") return None
Bu kontrol, çok fazla pozisyon açmamamızı sağlar.
Bir sonraki adım, bir emir açmak için bir talep oluşturmaktır. Burada oldukça fazla parametre var. Emir türü, hacim, fiyat, sapma, sihirli sayı, yorum... Her şey yolunda giderse, fonksiyon bize bunu söyler. Başarısız olursa, bir hata mesajı yazdırır.
open_test_limit_order fonksiyonu bu şekilde çalışıyor. Bu bizim piyasayla olan bağlantımız. Bir bakıma, bir aracı kurumun işlevlerini yerine getirir.
Geçici alım-satım kısıtlamaları: Belirli saatlerde çalışma
Şimdi de işlem saatleri hakkında konuşalım.
if current_time >= datetime.strptime("23:30", "%H:%M").time() or current_time <= datetime.strptime("05:00", "%H:%M").time(): print("Current time is between 23:30 and 05:00. Skipping execution.") time.sleep(300) # Wait for 5 minutes before checking again continue
Burada neler oluyor? Sistemimiz saati kontrol ediyor. Saat 11:30 ile 05:00 arasını gösteriyorsa, bu saatlerin işlem saatleri olmadığını görür ve 5 dakika boyunca bekleme moduna geçer. Ardından etkinleşir, saati tekrar kontrol eder ve hala erken ise tekrar bekleme moduna geçer.
Buna neden ihtiyacımız var? Bunun için bazı sebepler var. İlk olarak, likidite. Geceleri genellikle daha az olur. İkincisi, makas. Geceleri genişler. Üçüncüsü, haberler. En önemlileri genellikle çalışma saatleri içinde yayınlanır.
Çalışma zamanı döngüsü ve hata işleme
Şimdi 'main' fonksiyonuna bir göz atalım. Bir gemi kaptanı gibidir, ancak dümen yerine bir klavye vardır. Ne yapıyor? Her şey çok basit:
- Verileri toplama
- Sentetik fiyatları hesaplama
- Arbitraj fırsatlarını arama
- Emirler açma
Ayrıca küçük bir hata işleme de vardır.
def main():
data = get_currency_data()
synthetic_prices = calculate_synthetic_prices(data)
method_count = 2000 # Define the method_count variable here
arbitrage_opportunities = analyze_arbitrage(data, synthetic_prices, method_count)
# Trade based on arbitrage opportunities
for symbol in arbitrage_opportunities.columns:
if arbitrage_opportunities[symbol].any():
direction = "BUY" if arbitrage_opportunities[symbol].iloc[0] else "SELL"
symbol = symbol.split('_')[0] # Remove the index from the symbol
symbol_info = mt5.symbol_info_tick(symbol)
if symbol_info is not None:
price = symbol_info.bid if direction == "BUY" else symbol_info.ask
take_profit = 450
stop_loss = 200
order = open_test_limit_order(symbol, mt5.ORDER_TYPE_BUY if direction == "BUY" else mt5.ORDER_TYPE_SELL, price, 0.50, take_profit, stop_loss, terminal_path)
else:
print(f"Error: Symbol info tick not found for {symbol}")
Sistem ölçeklenebilirliği: Yeni döviz pariteleri ve yöntemler ekleme
Yeni bir döviz paritesi mi eklemek istiyorsunuz? Bu listeye eklemeniz yeterlidir:
symbols = ["EURUSD", "GBPUSD", "USDJPY", ... , "YOURPAIR"]
Sistem artık yeni pariteyi biliyor. Peki yeni hesaplama yöntemleri?
def calculate_synthetic_prices(data):
# ... existing code ...
# Add a new method
synthetic_prices[f'{pair1}_{method_count}'] = data[pair1]['ask'] / data[pair2]['bid']
method_count += 1
Arbitraj sisteminin geriye dönük test edilmesi
Geriye dönük test hakkında konuşalım. Bu, herhangi bir alım-satım sistemi için gerçekten önemli bir noktadır. Arbitraj sistemimiz de bir istisna değildir.
Biz ne yaptık? Stratejimizi geçmiş veriler üzerinde yürüttük. Neden? Ne kadar verimli olduğunu anlamak için. Kodumuz get_historical_data ile başlar. Bu fonksiyon, MetaTrader 5'ten eski verileri alır. Bu veriler olmadan verimli bir şekilde çalışmamız mümkün olmayacaktır.
Ardından calculate_synthetic_prices gelir. Burada sentetik döviz kurlarını hesaplıyoruz. Bu, arbitraj stratejimizin önemli bir parçasıdır. analyze_arbitrage fonksiyonu bizim fırsat dedektörümüzdür. Gerçek fiyatları sentetik olanlarla karşılaştırır ve farkı bulur, böylece potansiyel kar elde edebiliriz. simulate_trade neredeyse bir alım-satım sürecidir. Ancak, test modunda çalışır. Bu çok önemli bir süreçtir: gerçek para kaybetmektense simülasyonda hata yapmak daha iyidir.
Son olarak, backtest_arbitrage_system hepsini bir araya getirir ve stratejimizi geçmiş veriler üzerinde çalıştırır. Gün be gün, işlem üstüne işlem.
import MetaTrader5 as mt5 import pandas as pd import numpy as np import matplotlib.pyplot as plt from datetime import datetime, timedelta import pytz # Path to MetaTrader 5 terminal terminal_path = "C:/Program Files/ForexBroker - MetaTrader 5/Arima/terminal64.exe" def remove_duplicate_indices(df): """Removes duplicate indices, keeping only the first row with a unique index.""" return df[~df.index.duplicated(keep='first')] def get_historical_data(start_date, end_date, terminal_path): if not mt5.initialize(path=terminal_path): print(f"Failed to connect to MetaTrader 5 terminal at {terminal_path}") return None symbols = ["AUDUSD", "AUDJPY", "CADJPY", "AUDCHF", "AUDNZD", "USDCAD", "USDCHF", "USDJPY", "NZDUSD", "GBPUSD", "EURUSD", "CADCHF", "CHFJPY", "NZDCAD", "NZDCHF", "NZDJPY", "GBPCAD", "GBPCHF", "GBPJPY", "GBPNZD", "EURCAD", "EURCHF", "EURGBP", "EURJPY", "EURNZD"] historical_data = {} for symbol in symbols: timeframe = mt5.TIMEFRAME_M1 rates = mt5.copy_rates_range(symbol, timeframe, start_date, end_date) if rates is not None and len(rates) > 0: df = pd.DataFrame(rates) df['time'] = pd.to_datetime(df['time'], unit='s') df.set_index('time', inplace=True) df = df[['open', 'high', 'low', 'close']] df['bid'] = df['close'] # Simplification: use 'close' as 'bid' df['ask'] = df['close'] + 0.000001 # Simplification: add spread historical_data[symbol] = df mt5.shutdown() return historical_data def calculate_synthetic_prices(data): synthetic_prices = {} pairs = [('AUDUSD', 'USDCHF'), ('AUDUSD', 'NZDUSD'), ('AUDUSD', 'USDJPY'), ('USDCHF', 'USDCAD'), ('USDCHF', 'NZDCHF'), ('USDCHF', 'CHFJPY'), ('USDJPY', 'USDCAD'), ('USDJPY', 'NZDJPY'), ('USDJPY', 'GBPJPY'), ('NZDUSD', 'NZDCAD'), ('NZDUSD', 'NZDCHF'), ('NZDUSD', 'NZDJPY'), ('GBPUSD', 'GBPCAD'), ('GBPUSD', 'GBPCHF'), ('GBPUSD', 'GBPJPY'), ('EURUSD', 'EURCAD'), ('EURUSD', 'EURCHF'), ('EURUSD', 'EURJPY'), ('CADCHF', 'CADJPY'), ('CADCHF', 'GBPCAD'), ('CADCHF', 'EURCAD'), ('CHFJPY', 'GBPCHF'), ('CHFJPY', 'EURCHF'), ('CHFJPY', 'NZDCHF'), ('NZDCAD', 'NZDJPY'), ('NZDCAD', 'GBPNZD'), ('NZDCAD', 'EURNZD'), ('NZDCHF', 'NZDJPY'), ('NZDCHF', 'GBPNZD'), ('NZDCHF', 'EURNZD'), ('NZDJPY', 'GBPNZD'), ('NZDJPY', 'EURNZD')] for pair1, pair2 in pairs: if pair1 in data and pair2 in data: synthetic_prices[f'{pair1}_{pair2}_1'] = data[pair1]['bid'] / data[pair2]['ask'] synthetic_prices[f'{pair1}_{pair2}_2'] = data[pair1]['bid'] / data[pair2]['bid'] return pd.DataFrame(synthetic_prices) def analyze_arbitrage(data, synthetic_prices): spreads = {} for pair in data.keys(): for synth_pair in synthetic_prices.columns: if pair in synth_pair: spreads[synth_pair] = data[pair]['bid'] - synthetic_prices[synth_pair] arbitrage_opportunities = pd.DataFrame(spreads) > 0.00008 return arbitrage_opportunities def simulate_trade(data, direction, entry_price, take_profit, stop_loss): for i, row in data.iterrows(): current_price = row['bid'] if direction == "BUY" else row['ask'] if direction == "BUY": if current_price >= entry_price + take_profit: return {'profit': take_profit * 800, 'duration': i} elif current_price <= entry_price - stop_loss: return {'profit': -stop_loss * 400, 'duration': i} else: # SELL if current_price <= entry_price - take_profit: return {'profit': take_profit * 800, 'duration': i} elif current_price >= entry_price + stop_loss: return {'profit': -stop_loss * 400, 'duration': i} # If the loop completes without hitting TP or SL, close at the last price last_price = data['bid'].iloc[-1] if direction == "BUY" else data['ask'].iloc[-1] profit = (last_price - entry_price) * 100000 if direction == "BUY" else (entry_price - last_price) * 100000 return {'profit': profit, 'duration': len(data)} def backtest_arbitrage_system(historical_data, start_date, end_date): equity_curve = [10000] # Starting with $10,000 trades = [] dates = pd.date_range(start=start_date, end=end_date, freq='D') for current_date in dates: print(f"Backtesting for date: {current_date.date()}") # Get data for the current day data = {symbol: df[df.index.date == current_date.date()] for symbol, df in historical_data.items()} # Skip if no data for the current day if all(df.empty for df in data.values()): continue synthetic_prices = calculate_synthetic_prices(data) arbitrage_opportunities = analyze_arbitrage(data, synthetic_prices) # Simulate trades based on arbitrage opportunities for symbol in arbitrage_opportunities.columns: if arbitrage_opportunities[symbol].any(): direction = "BUY" if arbitrage_opportunities[symbol].iloc[0] else "SELL" base_symbol = symbol.split('_')[0] if base_symbol in data and not data[base_symbol].empty: price = data[base_symbol]['bid'].iloc[-1] if direction == "BUY" else data[base_symbol]['ask'].iloc[-1] take_profit = 800 * 0.00001 # Convert to price stop_loss = 400 * 0.00001 # Convert to price # Simulate trade trade_result = simulate_trade(data[base_symbol], direction, price, take_profit, stop_loss) trades.append(trade_result) # Update equity curve equity_curve.append(equity_curve[-1] + trade_result['profit']) return equity_curve, trades def main(): start_date = datetime(2024, 1, 1, tzinfo=pytz.UTC) end_date = datetime(2024, 8, 31, tzinfo=pytz.UTC) # Backtest for January-August 2024 print("Fetching historical data...") historical_data = get_historical_data(start_date, end_date, terminal_path) if historical_data is None: print("Failed to fetch historical data. Exiting.") return print("Starting backtest...") equity_curve, trades = backtest_arbitrage_system(historical_data, start_date, end_date) total_profit = sum(trade['profit'] for trade in trades) win_rate = sum(1 for trade in trades if trade['profit'] > 0) / len(trades) if trades else 0 print(f"Backtest completed. Results:") print(f"Total Profit: ${total_profit:.2f}") print(f"Win Rate: {win_rate:.2%}") print(f"Final Equity: ${equity_curve[-1]:.2f}") # Plot equity curve plt.figure(figsize=(15, 10)) plt.plot(equity_curve) plt.title('Equity Curve: Backtest Results') plt.xlabel('Trade Number') plt.ylabel('Account Balance ($)') plt.savefig('equity_curve.png') plt.close() print("Equity curve saved as 'equity_curve.png'.") if __name__ == "__main__": main()
Bu neden önemli? Çünkü geriye dönük test, sistemimizin ne kadar verimli olduğunu gösterir. Karlı mı yoksa bakiyemizi tüketiyor mu? Düşüş ne kadar? Kazançlı işlemlerin yüzdesi ne kadar? Tüm bunları geriye dönük testten öğreniyoruz.
Elbette geçmişteki sonuçlar gelecekteki sonuçları garanti etmez. Piyasa değişiyor. Ancak geriye dönük test olmadan herhangi bir sonuç edinemeyiz. Sonucu bildiğimiz için kabaca ne bekleyeceğimizi de biliyoruz. Bir başka önemli nokta - geriye dönük test, sistemi optimize etmeye yardımcı olur. Parametreleri değiştiriyoruz ve sonuca tekrar tekrar bakıyoruz. Böylece, adım adım sistemimizi daha iyi hale getiriyoruz.
İşte sistemimizin geriye dönük testinin sonucu:

İşte MetaTrader 5'te sistemin bir testi:

Ve işte sistem için MQL5 Uzman Danışmanının kodu:
//+------------------------------------------------------------------+ //| TrissBotDemo.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" // Input parameters input int MAX_OPEN_TRADES = 10; input double VOLUME = 0.50; input int TAKE_PROFIT = 450; input int STOP_LOSS = 200; input double MIN_SPREAD = 0.00008; // Global variables string symbols[] = {"AUDUSD", "AUDJPY", "CADJPY", "AUDCHF", "AUDNZD", "USDCAD", "USDCHF", "USDJPY", "NZDUSD", "GBPUSD", "EURUSD", "CADCHF", "CHFJPY", "NZDCAD", "NZDCHF", "NZDJPY", "GBPCAD", "GBPCHF", "GBPJPY", "GBPNZD", "EURCAD", "EURCHF", "EURGBP", "EURJPY", "EURNZD"}; int symbolsTotal; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { symbolsTotal = ArraySize(symbols); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { // Cleanup code here } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { if(!IsTradeAllowed()) return; datetime currentTime = TimeGMT(); if(currentTime >= StringToTime("23:30:00") || currentTime <= StringToTime("05:00:00")) { Print("Current time is between 23:30 and 05:00. Skipping execution."); return; } AnalyzeAndTrade(); } //+------------------------------------------------------------------+ //| Analyze arbitrage opportunities and trade | //+------------------------------------------------------------------+ void AnalyzeAndTrade() { double synthetic_prices[]; ArrayResize(synthetic_prices, symbolsTotal); for(int i = 0; i < symbolsTotal; i++) { synthetic_prices[i] = CalculateSyntheticPrice(symbols[i]); double currentPrice = SymbolInfoDouble(symbols[i], SYMBOL_BID); if(MathAbs(currentPrice - synthetic_prices[i]) > MIN_SPREAD) { if(currentPrice > synthetic_prices[i]) { OpenOrder(symbols[i], ORDER_TYPE_SELL); } else { OpenOrder(symbols[i], ORDER_TYPE_BUY); } } } } //+------------------------------------------------------------------+ //| Calculate synthetic price for a symbol | //+------------------------------------------------------------------+ double CalculateSyntheticPrice(string symbol) { // This is a simplified version. You need to implement the logic // to calculate synthetic prices based on your specific method return SymbolInfoDouble(symbol, SYMBOL_ASK); } //+------------------------------------------------------------------+ //| Open a new order | //+------------------------------------------------------------------+ void OpenOrder(string symbol, ENUM_ORDER_TYPE orderType) { if(PositionsTotal() >= MAX_OPEN_TRADES) { Print("MAX POSITIONS TOTAL!"); return; } double price = (orderType == ORDER_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID); double point = SymbolInfoDouble(symbol, SYMBOL_POINT); double tp = (orderType == ORDER_TYPE_BUY) ? price + TAKE_PROFIT * point : price - TAKE_PROFIT * point; double sl = (orderType == ORDER_TYPE_BUY) ? price - STOP_LOSS * point : price + STOP_LOSS * point; MqlTradeRequest request = {}; MqlTradeResult result = {}; request.action = TRADE_ACTION_DEAL; request.symbol = symbol; request.volume = VOLUME; request.type = orderType; request.price = price; request.deviation = 30; request.magic = 123456; request.comment = "ArbitrageAdvisor"; request.type_time = ORDER_TIME_GTC; request.type_filling = ORDER_FILLING_IOC; request.tp = tp; request.sl = sl; if(!OrderSend(request, result)) { Print("OrderSend error ", GetLastError()); return; } if(result.retcode == TRADE_RETCODE_DONE) { Print("Order placed successfully"); } else { Print("Order failed with retcode ", result.retcode); } } //+------------------------------------------------------------------+ //| Check if trading is allowed | //+------------------------------------------------------------------+ bool IsTradeAllowed() { if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { Print("Trade is not allowed in the terminal"); return false; } if(!MQLInfoInteger(MQL_TRADE_ALLOWED)) { Print("Trade is not allowed in the Expert Advisor"); return false; } return true; }
Aracı kurumlar için sistemin olası iyileştirmeleri ve yasallığı; limit emirlerini kullanarak likidite sağlayıcısını nasıl etkilememe
Sistemimizin başka potansiyel zorlukları da vardır. Aracı kurumlar ve likidite sağlayıcılar genellikle bu tür sistemlere sıcak bakmazlar. Neden? Çünkü esasen piyasadan gerekli likiditeyi alıyoruz. Hatta bunun için özel bir terim bile ürettiler: Toksik Emir Akışı (Toxic Order Flow).
Bu gerçek bir sorun. Piyasa emirlerimizle sistemdeki likiditeyi tam anlamıyla emiyoruz. Herkesin bu likiditeye ihtiyacı var: hem büyük oyuncular hem de küçük yatırımcılar. Elbette bunun bazı sonuçları olacaktır.
Bu durumda ne yapmalı? Bir ara çözüm var - limit emirleri.
Ancak bu tüm sorunları çözmez: Toksik Emir Akışı etiketi, mevcut likiditenin piyasadan emilmesi nedeniyle değil, bu tür bir emir akışına hizmet etmenin getirdiği yüksek yükler nedeniyle konulur. Bu sorunu henüz çözemedim. Örneğin, büyük bir arbitraj işlemi akışına hizmet vermek için 100 USD harcamak ve bundan 50 USD komisyon almak karlı değildir. Belki de buradaki kilit nokta yüksek hacimdir. O zaman aracı kurumlar da iskonto ödemeye hazır olabilir.
Şimdi koda geçelim. Onu nasıl iyileştirebiliriz? İlk olarak, limit emirlerini işlemek için bir fonksiyon ekleyebiliriz. Burada da yapılacak çok iş var - gerçekleştirilmemiş emirleri bekletme ve iptal etme mantığını düşünmemiz gerekiyor.
Makine öğrenimi, sistemi iyileştirmek için ilginç bir fikir olabilir. Sistemimizi hangi arbitraj fırsatlarının işe yarama olasılığının daha yüksek olduğunu tahmin edecek şekilde eğitmenin mümkün olabileceğini düşünüyorum.
Sonuç
Özetleyelim. Arbitraj fırsatları arayan bir sistem oluşturduk. Sistemin tüm finansal sorunlarınızı çözmediğini unutmayın.
Geriye dönük test yapmayı ele aldık. Zamana dayalı verilerle çalışır ve daha da iyisi, sistemimizin geçmişte nasıl çalışmış olabileceğini görmemizi sağlar. Ancak unutmayın - geçmiş sonuçlar gelecekteki sonuçları garanti etmez. Piyasa sürekli değişen karmaşık bir mekanizmadır.
Ama en önemlisi ne biliyor musunuz? Kod değil. Algoritma değil. Sizsiniz. Öğrenme, deneme, hata yapma ve tekrar deneme isteğiniz. Bu gerçekten paha biçilemez.
Bu yüzden burada durmayın. Bu sistem, algoritmik alım-satım dünyasındaki yolculuğunuzun sadece başlangıcıdır. Bunu yeni fikirler ve yeni stratejiler için bir başlangıç noktası olarak kullanın. Tıpkı hayatta olduğu gibi, alım-satımda da asıl önemli olan dengedir. Risk ve temkin, açgözlülük ve rasyonellik, karmaşıklık ve basitlik arasındaki denge.
Bu heyecan verici yolculukta iyi şanslar ve algoritmalarınız her zaman piyasanın bir adım önünde olsun!
MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/15964
Uyarı: Bu materyallerin tüm hakları MetaQuotes Ltd.'a aittir. Bu materyallerin tamamen veya kısmen kopyalanması veya yeniden yazdırılması yasaktır.
Bu makale sitenin bir kullanıcısı tarafından yazılmıştır ve kendi kişisel görüşlerini yansıtmaktadır. MetaQuotes Ltd, sunulan bilgilerin doğruluğundan veya açıklanan çözümlerin, stratejilerin veya tavsiyelerin kullanımından kaynaklanan herhangi bir sonuçtan sorumlu değildir.
Yeni Raylara Adım Atın: MQL5'te Özel Göstergeler
William Gann yöntemleri (Bölüm III): Astroloji işe yarıyor mu?
İşte Karışınızda Yeni MetaTrader 5 ve MQL5
William Gann yöntemleri (Bölüm II): Gann Karesi göstergesi oluşturma
- Ücretsiz alım-satım uygulamaları
- İşlem kopyalama için 8.000'den fazla sinyal
- Finansal piyasaları keşfetmek için ekonomik haberler
Gizlilik ve Veri Koruma Politikasını ve MQL5.com Kullanım Şartlarını kabul edersiniz
Lütfen bunun ne hakkında olduğunu açıklayın:
İşte çiftler:
pairs = [('AUDUSD', 'USDCHF'), ('AUDUSD', 'NZDUSD'), ('AUDUSD', 'USDJPY'), ('USDCHF', 'USDCAD'), ('USDCHF', 'NZDCHF'), ('USDCHF', 'CHFJPY'), ('USDJPY', 'USDCAD'), ('USDJPY', 'NZDJPY'), ('USDJPY', 'GBPJPY'), ('NZDUSD', 'NZDCAD'), ('NZDUSD', 'NZDCHF'), ('NZDUSD', 'NZDJPY'), ('GBPUSD', 'GBPCAD'), ('GBPUSD', 'GBPCHF'), ('GBPUSD', 'GBPJPY'), ('EURUSD', 'EURCAD'), ('EURUSD', 'EURCHF'), ('EURUSD', 'EURJPY'), ('CADCHF', 'CADJPY'), ('CADCHF', 'GBPCAD'), ('CADCHF', 'EURCAD'), ('CHFJPY', 'GBPCHF'), ('CHFJPY', 'EURCHF'), ('CHFJPY', 'NZDCHF'), ('NZDCAD', 'NZDJPY'), ('NZDCAD', 'GBPNZD'), ('NZDCAD', 'EURNZD'), ('NZDCHF', 'NZDJPY'), ('NZDCHF', 'GBPNZD'), ('NZDCHF', 'EURNZD'), ('NZDJPY', 'GBPNZD'), ('NZDJPY', 'EURNZD')]İlk çiftin Teklifi nedir? İlk çift:
İlk çiftin Teklifi nedir? İlk çift:
AUDUSD aynı zamanda bir çifttir. AUD - USD.
Lütfen bunun neyle ilgili olduğunu açıklayın:
İşte çiftler:
İlk çiftin Teklifi nedir? İlk çift:
ticks = mt5.copy_ticks_from(symbol, utc_from, count, mt5.COPY_TICKS_ALL)Hepsi yüklendi. Bu, tiklerde ortaya çıkan şeydir:
array([b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'',
...
b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'',
b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'',
b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b'', b''],
dtype='|V0')
Ve burada zaten zamanında bir çıkış alıyoruz:
https://www.mql5.com/tr/docs/python_metatrader5/mt5copyticksfrom_py örneğindeki kod da çalışmıyor
Her neyse, python nasıl bir şey? Nasıl hazırlanır? Belli değil...