Forex'te portföy optimizasyonu: VaR ve Markowitz teorisinin birleştirilmesi
Giriş: Forex'te portföy optimizasyonu görevleri
Son üç yılımı Forex için alım-satım robotları geliştirerek geçirdim. Ve biliyor musunuz? Risk yönetimi gerçek bir baş ağrısıdır. İlk başta, birkaç bakiye kaybedene kadar sabit durma seviyeleri belirliyordum. Sonra bu konuyu daha derinlemesine araştırmaya başladım ve Markowitz'in portföy optimizasyon teorisiyle karşılaştım.
Güzel görünüyordu - korelasyonları hesaplıyor, ağırlıkları optimize ediyorsunuz... Ancak gerçekte bu Forex için çok iyi çalışmaz. Neden? Çünkü Forex'te tüm pariteler birbiriyle ilişkilidir! EURUSD ve EURGBP ile aynı anda işlem yapmayı deneyin ve ne demek istediğimi göreceksiniz. Keskin bir EUR hareketi ve her iki pozisyon eşzamanlı olarak bir araya gelir. Güzel bir teori, acımasız gerçeklik karşısında paramparça oluyor.
Bundan bıktığım için başka yaklaşımlar aramaya başladım. Sonunda Riske Maruz Değer (Value at Risk, VaR) metodolojisiyle karşılaştım. İlk başta ne olduğunu bile anlamadım - bir tür karmaşık denklemler. Ama sonra birden bire fark ettim - tam da ihtiyacım olan şey buydu! VaR, belirli bir olasılık için maksimum kaybı gösterir. Başka bir deyişle, bir günde/haftada/ayda ne kadar para kaybedebileceğimizi doğrudan hesaplayabiliriz.
Sonunda Markowitz ile VaR'ı birleştirmeye karar verdim. Çılgınca mı geliyor? Belki. Ama başka bir seçenek göremedim. Markowitz fonların optimum dağılımını sağlar ve VaR da teminat çağrısına girmeyi önler. Kağıt üzerinde harika görünüyordu.
Ardından bir araştırma programcısının zorlu günlük yaşamı başladı. Python, МetaТrader 5 terminali, tonlarca geçmiş veri... Kolay olmayacağını biliyordum ama gerçek tüm beklentileri aştı. Size anlatacağım şey bu - sadece bir geriye dönük testte güzel görünmekle kalmayıp gerçekten çalışan bir sistemi nasıl oluşturmaya çalıştığım.
Forex alım-satımını otomatikleştirmeyi denediyseniz, acımı anlayacaksınız. Eğer denemediyseniz, belki de benim deneyimlerim, karşılaşabileceğiniz zorlukların en azından bazılarından kaçınmanıza yardımcı olacaktır.
VaR ve Markowitz teorisinin teorik ve matematiksel temelleri
Teoriyle başlayalım. İlk ay sadece matematiğe alışmaya çalışıyordum. Markowitz'in teorisi karmaşık görünüyor - bir sürü denklem, matris, ikinci dereceden optimizasyon... Ancak gerçekte her şey basittir: varlık getirilerini alır, korelasyonları hesaplar ve belirli bir getiri için riskin minimum olacağı şekilde ağırlıkları bulursunuz.
İlk başta sevinmiştim! Ama sonra gerçek Forex verileri üzerinde test etmeye başladım ve her şey o zaman başladı... EURUSD'nin bir yıllık geçmişi kullanıldığında, getirilerin dağılımı hiç de normal değildi. GBPUSD için de durum aynıydı. Bu, Markowitz'in teorisindeki temel varsayımdır. Başka bir deyişle, tüm hesaplamalar boşa gidiyor.
Bir hafta boyunca bir çözüm aradım. Bilimsel makaleleri inceledim, Google'da araştırdım, forumları okudum. VaR - Riske Maruz Değer hakkındaki makaleme geri döndüm. Kulağa akıllıca geliyor ama aslında sadece %95 (ya da her neyse) olasılıkla ne kadar kaybedebileceğimizi hesaplıyoruz. Önce en basit seçeneği denedim - parametrik VaR. Denklem basittir: ortalama eksi kantil başına sigma. Çalışma kalitesi orta düzeydedir.
Sonra tarihsel VaR'a geçtim. Buradaki fikir, gerçek geçmişi ele almak ve durumların en kötü %5'inde kayıpların ne olduğuna bakmaktır. Bu gerçeğe çok daha yakındır, ancak çok fazla veriye ihtiyaç vardır. Son olarak Monte Carlo yöntemine baktım. Pariteler arasındaki korelasyonları dikkate alarak bir dizi rastgele senaryo üretiyoruz. İşte sonunda burada makul bir sonuç elde ettim.
En zor kısım, VaR ile Markowitz optimizasyonunun nasıl birleştirileceğini bulmaktı. Sonuç olarak şu şey ortaya çıktı: standart optimizasyonu alıyoruz, ancak bir VaR sınırlaması ekliyoruz. Belirli bir getiri için minimum riski arıyoruz, ancak VaR'ın belirli bir seviyeyi aşmaması şartıyla.
Kağıt üzerinde her şey harika, ama bunu programlamamız gerekiyor... İlerleyen bölümlerde, bu denklemleri nasıl çalışan bir Python koduna dönüştürdüğümü göstereceğim.
Python'dan MetaTrader 5'e bağlanma
Sistemimin uygulanması, işlem terminali ile istikrarlı bir bağlantı kurmakla başladı. Farklı yaklaşımları denedikten sonra, en güvenilir ve hızlı olduğunu gördüğüm Python için MetaTrader 5 kütüphanesi aracılığıyla doğrudan bir bağlantıya karar verdim.
import MetaTrader5 as mt5 import time def initialize_mt5(account=12345, server="MetaQuotes-Demo", password="abc123"): if not mt5.initialize(): print(f"initialize() failed, error code = {mt5.last_error()}") return False authorized = mt5.login(account, password=password, server=server) if not authorized: print(f"login failed, error code = {mt5.last_error()}") mt5.shutdown() return False return True
Aracı kurum sunucusu ile yerel sistem arasındaki zaman senkronizasyonu da ayrı bir baş ağrısıydı. Birkaç saniyelik bir fark, VaR hesaplanırken ciddi sorunlara yol açabiliyordu. Özel bir düzeltme mekanizmasının uygulanması gerekiyordu:
def get_time_correction(): server_time = mt5.symbol_info_tick("EURUSD").time local_time = int(time.time()) return server_time - local_time def get_corrected_time(): correction = get_time_correction() return int(time.time()) + correction
Verilerin toplanmasını optimize etmek için çok zaman harcadım. Başlangıçta her döviz paritesi için ayrı ayrı istekler gönderiyordum, ancak toplu işlemeyi uyguladıktan sonra hız birkaç kat arttı:
def fetch_data_batch(symbols, timeframe, start_pos, count): data = {} for symbol in symbols: rates = mt5.copy_rates_from_pos(symbol, timeframe, start_pos, count) if rates is not None and len(rates) > 0: data[symbol] = rates else: print(f"Failed to get data for {symbol}") return None return data
Programı doğru bir şekilde sonlandırmanın şaşırtıcı derecede zor olduğu ortaya çıktı. Özel bir 'zarif kapatma' prosedürü geliştirmek gerekiyordu:
def safe_shutdown(): try: positions = mt5.positions_get() if positions: for position in positions: close_position(position.ticket) orders = mt5.orders_get() if orders: for order in orders: mt5.order_send(request={"action": mt5.TRADE_ACTION_REMOVE, "order": order.ticket}) finally: mt5.shutdown()
Sonuçta, tüm sistem için günün her saati hatasız çalışabilen güvenilir bir temel oluşturuldu. Bu temel üzerine daha karmaşık bir portföy optimizasyon mantığı inşa etmek mümkündü. Ancak bu, bir sonraki bölümün konusu.
Geçmiş verilerin elde edilmesi ve ön işlemeden geçirilmesi
Piyasa verileriyle çalıştığım yıllar boyunca basit bir gerçeği öğrendim: geçmiş verilerin kalitesi her alım-satım sistemi için kritik önem taşır. Özellikle de veri hatalarının zincirleme etkiye yol açabileceği portföy optimizasyonu söz konusu olduğunda.
Güvenilir bir geçmiş yükleme sistemi oluşturarak başladım. İlk versiyon oldukça basitti, ancak pratikte kısa sürede eksiklikleri ortaya çıktı. Fiyatlar boşluklar, ani değişimler ve hatta bazen tamamen yanlış değerler içerebiliyordu. İşte temel bir doğrulamayla verileri yüklemek için nihai kodun görünümü:
def load_historical_data(symbols, timeframe, start_date, end_date): data_frames = {} for symbol in symbols: # Load with a reserve to compensate for gaps rates = mt5.copy_rates_range(symbol, timeframe, start_date - timedelta(days=30), end_date) if rates is None: print(f"Failed to load data for {symbol}") continue df = pd.DataFrame(rates) df['time'] = pd.to_datetime(df['time'], unit='s') df.set_index('time', inplace=True) # Basic anomaly check df = detect_and_remove_spikes(df) df = fill_gaps(df) data_frames[symbol] = df return data_frames
Ayrı bir sorun da hafta sonlarındaki fiyat boşluklarının yönetilmesiydi. İlk başta bu günleri basitçe kaldırdım, ancak bu volatilite hesaplamalarını bozdu. Uzun süren denemelerin ardından, her bir döviz paritesinin özelliklerini dikkate alan bir interpolasyon yöntemi geliştirdim:
def fill_gaps(df, method='time'): if df.empty: return df # Check the intervals between points time_delta = df.index.to_series().diff() gaps = time_delta[time_delta > pd.Timedelta(hours=2)].index for gap_start in gaps: gap_end = df.index[df.index.get_loc(gap_start) + 1] # Create new points with interpolated values new_points = pd.date_range(gap_start, gap_end, freq='1H')[1:-1] for point in new_points: df.loc[point] = df.asof(point) return df.sort_index()
Getirileri hesaplamak için çeşitli yaklaşımlar denedim. Basit yüzde değişikliklerinin çok gürültülü olduğu ortaya çıktı. Logaritmik getiriler VaR hesaplamasında en iyi performansı gösterdi:
def calculate_returns(df): df['returns'] = np.log(df['close'] / df['close'].shift(1)) df['rolling_std'] = df['returns'].rolling(window=20).std() df['rolling_mean'] = df['returns'].rolling(window=20).mean() # Clean out emissions using the 3-sigma rule mean = df['returns'].mean() std = df['returns'].std() df = df[abs(df['returns'] - mean) <= 3 * std] return df
Veri doğrulama sisteminin geliştirilmesi önemli bir kilometre taşı oldu. Her veri kümesi, hesaplamalarda kullanılmadan önce çok aşamalı bir kontrolden geçirilir:
def verify_data_quality(df, symbol): checks = { 'missing_values': df.isnull().sum().sum() == 0, 'price_continuity': (df['close'] > 0).all(), 'timestamp_uniqueness': df.index.is_unique, 'reasonable_returns': abs(df['returns']).max() < 0.1 } if not all(checks.values()): failed_checks = [k for k, v in checks.items() if not v] print(f"Data quality issues for {symbol}: {failed_checks}") return False return True
Piyasadaki olağandışı durumların işlenmesine özellikle özen gösterdim. Haberlere bağlı keskin hareketler veya ani çöküşler gibi çeşitli olaylar risk değerlendirmesini büyük ölçüde bozabilir. Bunları tanımlamak ve doğru bir şekilde işlemek için özel bir algoritma geliştirdim:
def detect_market_anomalies(df, window=20, threshold=3): volatility = df['returns'].rolling(window=window).std() typical_range = volatility.mean() + threshold * volatility.std() anomalies = df[abs(df['returns']) > typical_range].index if len(anomalies) > 0: print(f"Detected {len(anomalies)} market anomalies") return anomalies
Sonuçta, daha sonraki tüm hesaplamalar için temel oluşturan güvenilir bir veri işleme hattı ortaya çıktı. Kaliteli geçmiş veriler, verimli bir portföy yönetim sistemi oluşturmanın temelidir; bu tür veriler olmadan böyle bir sistem kurmak imkansızdır. Bir sonraki bölümde, bu verilerin VaR'ı hesaplamak için nasıl kullanıldığını göreceğiz.
Döviz pariteleri için VaR hesaplamasının uygulanması
Geçmiş verilerle uzun süre çalıştıktan sonra, VaR hesaplamasının uygulanmasına giriş yaptım. Başlangıçta, hazır denklemleri alıp bunları koda dönüştürmek yeterli gibi görünüyordu. Forex'in özellikleri standart yaklaşımlarda ciddi değişiklikler gerektirdiğinden, gerçeğin daha karmaşık olduğu ortaya çıktı.
Üç klasik VaR hesaplama yöntemini uygulayarak başladım. Parametrik yaklaşım şu şekildedir:
def parametric_var(returns, confidence_level=0.95, holding_period=1): mu = returns.mean() sigma = returns.std() z_score = norm.ppf(1 - confidence_level) daily_var = -(mu + z_score * sigma) return daily_var * np.sqrt(holding_period)
Ancak, Forex'te normal getiri dağılımı varsayımının genellikle geçerli olmadığı kısa sürede anlaşıldı. Tarihsel yaklaşımın daha güvenilir olduğu kanıtlandı:
def historical_var(returns, confidence_level=0.95, holding_period=1): sorted_returns = np.sort(returns) index = int((1 - confidence_level) * len(sorted_returns)) daily_var = -sorted_returns[index] return daily_var * np.sqrt(holding_period)
Ancak en ilginç sonuçlar Monte Carlo yöntemiyle elde edildi. Döviz piyasasının özelliklerini dikkate alacak şekilde değiştirdim:
def monte_carlo_var(returns, confidence_level=0.95, holding_period=1, simulations=10000): mu = returns.mean() sigma = returns.std() # Consider auto correlation of returns corr = returns.autocorr() simulated_returns = [] for _ in range(simulations): daily_returns = [] last_return = returns.iloc[-1] for _ in range(holding_period): # Generate the next value taking auto correlation into account innovation = np.random.normal(0, 1) next_return = mu + corr * (last_return - mu) + sigma * np.sqrt(1 - corr**2) * innovation daily_returns.append(next_return) last_return = next_return total_return = sum(daily_returns) simulated_returns.append(total_return) return -np.percentile(simulated_returns, (1 - confidence_level) * 100)
Sonuçların doğrulanmasına özellikle dikkat ettim. Ayrıca VaR doğruluğunu kontrol etmek için bir geriye dönük test sistemi geliştirdim:
def backtest_var(returns, var, confidence_level=0.95): violations = (returns < -var).sum() expected_violations = len(returns) * (1 - confidence_level) z_score = (violations - expected_violations) / np.sqrt(expected_violations) p_value = 1 - norm.cdf(abs(z_score)) return { 'violations': violations, 'expected': expected_violations, 'z_score': z_score, 'p_value': p_value }
Döviz pariteleri arasındaki ilişkileri dikkate almak için portföy VaR’ının hesaplanmasını uygulamak gerekiyordu:
def portfolio_var(returns_df, weights, confidence_level=0.95, method='historical'): if method == 'parametric': portfolio_returns = returns_df.dot(weights) return parametric_var(portfolio_returns, confidence_level) elif method == 'historical': portfolio_returns = returns_df.dot(weights) return historical_var(portfolio_returns, confidence_level) elif method == 'monte_carlo': # Use the covariance matrix to generate # correlated random variables cov_matrix = returns_df.cov() L = np.linalg.cholesky(cov_matrix) means = returns_df.mean().values simulated_returns = [] for _ in range(10000): Z = np.random.standard_normal(len(weights)) R = means + L @ Z portfolio_return = weights @ R simulated_returns.append(portfolio_return) return -np.percentile(simulated_returns, (1 - confidence_level) * 100)
Sonuç olarak, Forex'in özelliklerine uyarlanmış esnek bir VaR hesaplama sistemi ortaya çıktı. Bir sonraki bölümde, bu hesaplamaların portföy optimizasyonu için Markowitz teorisi ile nasıl bütünleştirileceğinden bahsedeceğiz.
Markowitz yöntemi kullanılarak portföy optimizasyonu
Güvenilir bir VaR hesaplaması uyguladıktan sonra portföy optimizasyonuna odaklanmaya başladım. Markowitz'in klasik teorisinin Forex'in gerçeklerine ciddi bir şekilde uyarlanması gerekiyordu. Aylar süren denemeler ve testler beni birkaç önemli keşfe götürdü.
İlk fark ettiğim şey, standart risk ve getiri ölçütlerinin Forex'te borsada olduğundan farklı işlediğiydi. Döviz pariteleri zaman içinde değişen karmaşık ilişkilere sahiptir. Uzun denemelerden sonra, değiştirilmiş bir beklenen getiri hesaplama fonksiyonu geliştirdim:
def calculate_expected_returns(returns_df, method='ewma', halflife=30): if method == 'ewma': # Exponentially weighted average gives more weight to recent data return returns_df.ewm(halflife=halflife).mean().iloc[-1] elif method == 'capm': # Modified CAPM for Forex risk_free_rate = 0.02 # annual risk-free rate market_returns = returns_df.mean(axis=1) # market returns proxy betas = calculate_currency_betas(returns_df, market_returns) return risk_free_rate + betas * (market_returns.mean() - risk_free_rate)
Kovaryans matrisinin hesaplanması da bazı revizyonlar gerektirdi. Basit tarihsel yaklaşım çok istikrarsız sonuçlar üretti. Optimizasyonun sağlamlığını önemli ölçüde artıran büzülme hesaplamasını uyguladım:
def shrinkage_covariance(returns_df, shrinkage_factor=None): sample_cov = returns_df.cov() n_assets = len(returns_df.columns) # The target matrix is diagonal with average variance target = np.diag(np.repeat(sample_cov.values.trace() / n_assets, n_assets)) if shrinkage_factor is None: # Estimation of the optimal 'shrinkage' ratio shrinkage_factor = estimate_optimal_shrinkage(returns_df, sample_cov, target) shrunk_cov = (1 - shrinkage_factor) * sample_cov + shrinkage_factor * target return pd.DataFrame(shrunk_cov, index=sample_cov.index, columns=sample_cov.columns)
En zor kısım portföy ağırlıklarını optimize etmekti. Birçok testten sonra, değiştirilmiş bir ikinci dereceden programlama algoritmasında karar kıldım:
def optimize_portfolio(returns_df, expected_returns, covariance, target_return=None, constraints=None): n_assets = len(returns_df.columns) # Risk minimization function def portfolio_volatility(weights): return np.sqrt(weights.T @ covariance @ weights) # Limitations constraints = [] # The sum of the weights is 1 constraints.append({'type': 'eq', 'fun': lambda x: np.sum(x) - 1}) if target_return is not None: # Target income limit constraints.append({ 'type': 'eq', 'fun': lambda x: x @ expected_returns - target_return }) # Add leverage restrictions for Forex constraints.append({ 'type': 'ineq', 'fun': lambda x: 20 - np.sum(np.abs(x)) # max leverage 20 }) # Initial approximation - equal weights initial_weights = np.repeat(1/n_assets, n_assets) # Optimization result = minimize( portfolio_volatility, initial_weights, method='SLSQP', constraints=constraints, bounds=tuple((0, 1) for _ in range(n_assets)) ) if not result.success: raise OptimizationError("Failed to optimize portfolio: " + result.message) return result.x
Çözüm istikrarı problemine özellikle dikkat ettim. Girdi verilerindeki küçük değişiklikler portföyde ciddi değişikliklere yol açmamalıdır. Bu amaçla, düzenlileştirme prosedürünü geliştirdim:
def regularized_optimization(returns_df, current_weights, lambda_reg=0.1): # Add a penalty for deviation from the current weights def objective(weights): volatility = portfolio_volatility(weights) turnover_penalty = lambda_reg * np.sum(np.abs(weights - current_weights)) return volatility + turnover_penalty
Sonuç olarak, Forex'in özelliklerini dikkate alan ve sık sık yeniden dengeleme gerektirmeyen güvenilir bir portföy optimize edici elde ettim. Ancak asıl önemli olan, bu yaklaşımı VaR tabanlı bir risk kontrol sistemiyle birleştirmekti.
VaR ve Markowitz'in tek bir modelde birleştirilmesi
Bu iki yaklaşımı birleştirmek en zorlu kısım oldu. Her iki yöntemin avantajlarını, aralarında çelişki yaratmadan kullanmanın bir yolunu bulmam gerekiyordu. Birkaç ay süren denemelerden sonra zarif bir çözüm buldum.
Ana fikir, Markowitz optimizasyon probleminde VaR'ı ek bir kısıtlama olarak kullanmaktı. Kodda şu şekilde görünüyor:
def integrated_portfolio_optimization(returns_df, target_return, max_var_limit, current_weights=None): n_assets = len(returns_df.columns) # Calculation of basic metrics exp_returns = calculate_expected_returns(returns_df) covariance = shrinkage_covariance(returns_df) def objective_function(weights): # Portfolio standard deviation (Markowitz) portfolio_std = np.sqrt(weights.T @ covariance @ weights) # component VaR portfolio_var = calculate_portfolio_var(returns_df, weights) var_penalty = max(0, portfolio_var - max_var_limit) return portfolio_std + 100 * var_penalty # Penalty for exceeding VaR
Piyasanın dinamik yapısını dikkate almak için parametreleri yeniden hesaplayan uyarlanabilir bir sistem geliştirdim:
def adaptive_risk_limits(returns_df, base_var_limit, window=60): # Adapting VaR limits to current volatility recent_vol = returns_df.tail(window).std() long_term_vol = returns_df.std() vol_ratio = recent_vol / long_term_vol adjusted_var_limit = base_var_limit * np.sqrt(vol_ratio) return min(adjusted_var_limit, base_var_limit * 1.5) # Limit growth
Çözüm istikrarı problemine özellikle dikkat edilmesi gerekiyordu. Portföy durumları arasında yumuşak geçiş için bir mekanizma uyguladım:
def smooth_rebalancing(old_weights, new_weights, max_change=0.1): weight_diff = new_weights - old_weights excess_change = np.abs(weight_diff) - max_change where_excess = excess_change > 0 if where_excess.any(): # Limit changes in weights adjustment = np.sign(weight_diff) * np.minimum( np.abs(weight_diff), np.where(where_excess, max_change, np.abs(weight_diff)) ) return old_weights + adjustment return new_weights
Birleşik yaklaşımın verimliliğini değerlendirmek için özel bir ölçüt geliştirdim:
def evaluate_integrated_model(returns_df, weights, var_limit): # Calculation of performance metrics portfolio_returns = returns_df.dot(weights) realized_var = historical_var(portfolio_returns) sharpe = calculate_sharpe_ratio(portfolio_returns) var_efficiency = abs(realized_var - var_limit) / var_limit return { 'sharpe_ratio': sharpe, 'var_efficiency': var_efficiency, 'max_drawdown': calculate_max_drawdown(portfolio_returns), 'turnover': calculate_turnover(weights) }
Test sırasında, modelin özellikle volatilitenin arttığı dönemlerde iyi çalıştığı ortaya çıktı. VaR bileşeni riskleri etkin bir şekilde sınırlarken, Markowitz optimizasyonu karlılık için fırsatlar aramaya devam eder.
Sistemin son versiyonu ayrıca otomatik parametre ayarlaması için bir mekanizma içermektedir:
def auto_tune_parameters(returns_df, initial_params, optimization_window=252): best_params = initial_params best_score = float('-inf') for var_limit in np.arange(0.01, 0.05, 0.005): for shrinkage in np.arange(0.2, 0.8, 0.1): params = {'var_limit': var_limit, 'shrinkage': shrinkage} score = backtest_model(returns_df, params, optimization_window) if score > best_score: best_score = score best_params = params return best_params
Bir sonraki bölümde, bu birleşik modelin gerçek alım-satımda dinamik pozisyon yönetimine nasıl uygulandığına bakacağız.
Dinamik pozisyon büyüklüğü yönetimi
Teorik modelin pratikte bir alım-satım sistemine dönüştürülmesi birçok teknik sorunun çözülmesini gerektirdi. Bunlardan en önemlisi, mevcut piyasa koşulları ve hesaplanan optimum portföy ağırlıkları dikkate alınarak pozisyon büyüklüklerinin dinamik olarak yönetilmesiydi.
Sistemin temeli, pozisyonları yönetmek için bir sınıftı:
class PositionManager: def __init__(self, account_balance, risk_limit=0.02): self.balance = account_balance self.risk_limit = risk_limit self.positions = {} def calculate_position_size(self, symbol, weight, var_estimate): symbol_info = mt5.symbol_info(symbol) pip_value = symbol_info.trade_tick_value * 10 # Calculate the position size taking into account VaR max_risk_amount = self.balance * self.risk_limit * abs(weight) position_size = max_risk_amount / (abs(var_estimate) * pip_value) # Round to minimum lot return round(position_size / symbol_info.volume_step) * symbol_info.volume_step
Pozisyonları sorunsuz bir şekilde değiştirmek için kısmi emirlerden oluşan bir mekanizma geliştirdim:
def adjust_positions(self, target_positions): for symbol, target_size in target_positions.items(): current_size = self.get_current_position(symbol) if abs(target_size - current_size) > self.min_adjustment: # Break big changes into pieces steps = min(5, int(abs(target_size - current_size) / self.min_adjustment)) step_size = (target_size - current_size) / steps for i in range(steps): next_size = current_size + step_size self.execute_order(symbol, next_size - current_size) current_size = next_size time.sleep(1) # Prevent order flooding
Pozisyonları değiştirirken risk kontrolüne özellikle dikkat ettim:
def execute_order(self, symbol, size_delta, max_slippage=10): if size_delta > 0: order_type = mt5.ORDER_TYPE_BUY else: order_type = mt5.ORDER_TYPE_SELL # Get current prices tick = mt5.symbol_info_tick(symbol) # Set VaR-based stop loss if order_type == mt5.ORDER_TYPE_BUY: stop_loss = tick.bid - (self.var_estimates[symbol] * tick.bid) take_profit = tick.bid + (self.var_estimates[symbol] * 2 * tick.bid) else: stop_loss = tick.ask + (self.var_estimates[symbol] * tick.ask) take_profit = tick.ask - (self.var_estimates[symbol] * 2 * tick.ask) request = { "action": mt5.TRADE_ACTION_DEAL, "symbol": symbol, "volume": abs(size_delta), "type": order_type, "price": tick.ask if order_type == mt5.ORDER_TYPE_BUY else tick.bid, "sl": stop_loss, "tp": take_profit, "deviation": max_slippage, "magic": 234000, "comment": "var_based_adjustment", "type_time": mt5.ORDER_TIME_GTC, "type_filling": mt5.ORDER_FILLING_IOC, } result = mt5.order_send(request) return self.handle_order_result(result)
Keskin piyasa hareketlerine karşı koruma sağlamak için bir volatilite izleme sistemi ekledim:
def monitor_volatility(self, returns_df, threshold=2.0): # Current volatility calculation current_vol = returns_df.tail(20).std() * np.sqrt(252) historical_vol = returns_df.std() * np.sqrt(252) if current_vol > historical_vol * threshold: # Reduce positions in case of increased volatility self.reduce_exposure(current_vol / historical_vol) return False return True
Sistem ayrıca kritik risk seviyelerine ulaşıldığında pozisyonların otomatik olarak kapatılması için bir mekanizma içermektedir:
def emergency_close(self, max_loss_percent=5.0): total_loss = sum(pos.profit for pos in mt5.positions_get()) if total_loss < -self.balance * max_loss_percent / 100: print("Emergency closure triggered!") for position in mt5.positions_get(): self.close_position(position.ticket)
Sonuç olarak, çeşitli piyasa koşullarında etkin bir şekilde çalışabilen sağlam bir pozisyon yönetim sistemi ortaya çıktı. Bir sonraki bölümde VaR tabanlı risk kontrol sistemine odaklanacağız.
Portföy risk kontrol sistemi
Dinamik pozisyon yönetimini uyguladıktan sonra, portföy düzeyinde kapsamlı bir risk kontrol sistemi oluşturma ihtiyacıyla karşı karşıya kaldım. Deneyimlerim, bireysel pozisyonların yerel risk kontrolünün yeterli olmadığını gösterdi - bütünsel bir yaklaşıma ihtiyaç vardır.
Portföy risklerini izlemek için bir sınıf oluşturarak başladım:
class PortfolioRiskManager: def __init__(self, max_portfolio_var=0.03, max_correlation=0.7, max_drawdown=0.1): self.max_portfolio_var = max_portfolio_var self.max_correlation = max_correlation self.max_drawdown = max_drawdown self.current_drawdown = 0 self.peak_balance = 0 def update_portfolio_metrics(self, positions, returns_df): # Calculation of current portfolio weights total_exposure = sum(abs(pos.volume) for pos in positions) weights = {pos.symbol: pos.volume/total_exposure for pos in positions} # Update portfolio VaR self.current_var = self.calculate_portfolio_var(returns_df, weights) # Check correlations self.check_correlations(returns_df, weights)
Enstrümanlar arasındaki korelasyonları izlemeye özellikle önem gösterdim:
def check_correlations(self, returns_df, weights): corr_matrix = returns_df.corr() high_corr_pairs = [] for i in returns_df.columns: for j in returns_df.columns: if i < j and abs(corr_matrix.loc[i,j]) > self.max_correlation: if weights.get(i, 0) > 0 and weights.get(j, 0) > 0: high_corr_pairs.append((i, j, corr_matrix.loc[i,j])) if high_corr_pairs: self.handle_high_correlations(high_corr_pairs, weights)
Piyasa koşullarına bağlı olarak dinamik risk yönetimi uyguladım:
def adjust_risk_limits(self, market_state): volatility_factor = market_state.get('volatility_ratio', 1.0) trend_strength = market_state.get('trend_strength', 0.5) # Adapt limits to market conditions self.max_portfolio_var *= np.sqrt(volatility_factor) if trend_strength > 0.7: # Strong trend self.max_drawdown *= 1.2 # Allow a big drawdown elif trend_strength < 0.3: # Weak trend self.max_drawdown *= 0.8 # Reduce the acceptable drawdown
Düşüş izleme sistemi özellikle ilginç oldu:
def monitor_drawdown(self, current_balance): if current_balance > self.peak_balance: self.peak_balance = current_balance self.current_drawdown = (self.peak_balance - current_balance) / self.peak_balance if self.current_drawdown > self.max_drawdown: return self.handle_excessive_drawdown() elif self.current_drawdown > self.max_drawdown * 0.8: return self.reduce_risk_exposure(0.8) return True
Olağandışı durumlara karşı koruma sağlamak için bir stres testi sistemi ekledim:
def stress_test_portfolio(self, returns_df, weights, scenarios=1000): results = [] for _ in range(scenarios): # Simulate extreme conditions stress_returns = returns_df.copy() # Increase volatility vol_multiplier = np.random.uniform(1.5, 3.0) stress_returns *= vol_multiplier # Add random shocks shock_magnitude = np.random.uniform(-0.05, 0.05) stress_returns += shock_magnitude # Calculate losses in a stress scenario portfolio_return = (stress_returns * weights).sum(axis=1) results.append(portfolio_return.min()) return np.percentile(results, 1) # 99% VaR in case of a stress
Sonuçta, aşırı riskleri etkili bir şekilde önleyen ve yüksek volatilite dönemlerine karşı dayanıklılığı artıran çok seviyeli bir sermaye koruma sistemi elde ettim. Bir sonraki bölümde, tüm bu bileşenlerin gerçek alım-satım nasıl birlikte çalıştığını ele alacağız.
Analiz sonuçlarını görselleştirme
Görselleştirme, araştırmamın önemli bir aşaması haline geldi. Tüm hesaplama modüllerini uyguladıktan sonra, sonuçların görsel bir temsilini oluşturmak gerekiyordu. Sistemin performansını gerçek zamanlı olarak izlemeye yardımcı olan birkaç önemli grafiksel bileşen geliştirdim.
Portföy yapısını ve zaman içindeki değişimini görselleştirmekle başladım:
def plot_portfolio_composition(weights_history): plt.figure(figsize=(15, 8)) ax = plt.gca() # Create a graph of weight changes over time dates = weights_history.index bottom = np.zeros(len(dates)) for symbol in weights_history.columns: plt.fill_between(dates, bottom, bottom + weights_history[symbol], label=symbol, alpha=0.6) bottom += weights_history[symbol] plt.title('Evolution of portfolio structure') plt.legend(bbox_to_anchor=(1.05, 1), loc='upper left') plt.grid(True, alpha=0.3)
Risklerin görselleştirilmesine özellikle dikkat ettim. Ayrıca farklı döviz pariteleri için bir VaR ısı haritası geliştirdim:
def plot_var_heatmap(var_matrix): plt.figure(figsize=(12, 8)) sns.heatmap(var_matrix, annot=True, cmap='RdYlBu_r', fmt='.2%', center=0) plt.title('Portfolio risk map (VaR)') # Add a timestamp plt.annotate(f'Last update: {datetime.now().strftime("%Y-%m-%d %H:%M")}', xy=(0.01, -0.1), xycoords='axes fraction')
Karlılığı analiz etmek için önemli olayların vurgulandığı etkileşimli bir grafik oluşturdum:
def plot_performance_analytics(returns_df, var_values, significant_events): fig = plt.figure(figsize=(15, 10)) gs = GridSpec(2, 1, height_ratios=[3, 1]) # Returns graph ax1 = plt.subplot(gs[0]) cumulative_returns = (1 + returns_df).cumprod() ax1.plot(cumulative_returns.index, cumulative_returns, label='Portfolio returns') # Mark important events for date, event in significant_events.items(): ax1.axvline(x=date, color='r', linestyle='--', alpha=0.3) ax1.annotate(event, xy=(date, ax1.get_ylim()[1]), xytext=(10, 10), textcoords='offset points', rotation=45) # VaR graph ax2 = plt.subplot(gs[1]) ax2.fill_between(var_values.index, -var_values, color='lightblue', alpha=0.5, label='Value at Risk')
Portföy durumunu izlemek için etkileşimli bir panel ekledim:
class PortfolioDashboard: def __init__(self): self.fig = plt.figure(figsize=(15, 10)) self.setup_subplots() def setup_subplots(self): gs = self.fig.add_gridspec(3, 2) self.ax_returns = self.fig.add_subplot(gs[0, :]) self.ax_weights = self.fig.add_subplot(gs[1, 0]) self.ax_risk = self.fig.add_subplot(gs[1, 1]) self.ax_metrics = self.fig.add_subplot(gs[2, :]) def update(self, portfolio_data): self._plot_returns(portfolio_data['returns']) self._plot_weights(portfolio_data['weights']) self._plot_risk_metrics(portfolio_data['risk']) self._update_metrics_table(portfolio_data['metrics']) plt.tight_layout() plt.show()
Korelasyonları analiz etmek için dinamik bir görselleştirme geliştirdim:
def plot_correlation_dynamics(returns_df, window=60): # Calculation of dynamic correlations correlations = returns_df.rolling(window=window).corr() # Create an animated graph fig, ax = plt.subplots(figsize=(10, 10)) def update(frame): ax.clear() sns.heatmap(correlations.loc[frame], vmin=-1, vmax=1, center=0, cmap='RdBu', ax=ax) ax.set_title(f'Correlations on {frame.strftime("%Y-%m-%d")}')
Tüm bu görselleştirmeler, portföyün durumunu hızlı bir şekilde değerlendirmeye ve işlem kararları vermeye yardımcı olur. Bir sonraki bölümde sistemi test edeceğiz.
Strateji geriye dönük testi
Sistemin tüm bileşenlerinin geliştirilmesini tamamladıktan sonra, kapsamlı bir şekilde test edilmesi ihtiyacı ortaya çıktı. Bu süreç, basitçe geçmiş verileri çalıştırmaktan çok daha karmaşık oldu. Birçok faktörü hesaba katmak gerekiyordu: kayma, komisyonlar ve farklı aracı kurumlarda emir gerçekleştirme özellikleri.
İlk geriye dönük test denemeleri, sabit makaslı klasik yaklaşımın aşırı iyimser sonuçlar verdiğini gösterdi. Volatiliteye ve günün saatine bağlı olarak makaslardaki değişimi dikkate alan daha gerçekçi bir model oluşturmak gerekiyordu.
Veri boşluklarının ve likidite sorunlarının modellenmesine özel önem verdim. Gerçek alım-satımda, bir emrin istenilen fiyattan gerçekleştirilemediği durumlar sıklıkla ortaya çıkar. Bu senaryolar test sırasında doğru şekilde ele alınmalıdır.
İşte geriye dönük test sisteminin tam uygulanması:
class PortfolioBacktester: def __init__(self, initial_capital=100000, commission=0.0001): self.initial_capital = initial_capital self.commission = commission self.positions = {} self.trades_history = [] self.balance_history = [] self.var_history = [] self.metrics = {} def run_backtest(self, returns_df, optimization_params): self.current_capital = self.initial_capital portfolio_returns = [] # Preparing sliding windows for calculations window = 252 # Trading yesr for i in range(window, len(returns_df)): # Receive historical data for calculation historical_returns = returns_df.iloc[i-window:i] # Optimize the portfolio weights = self.optimize_portfolio( historical_returns, optimization_params['target_return'], optimization_params['max_var'] ) # Calculate VaR for the current distribution current_var = self.calculate_portfolio_var( historical_returns, weights, optimization_params['confidence_level'] ) # Check the need for rebalancing if self.should_rebalance(weights, current_var): self.execute_rebalancing(weights, returns_df.iloc[i]) # Update positions and calculate profitability portfolio_return = self.update_positions(returns_df.iloc[i]) portfolio_returns.append(portfolio_return) # Update metrics self.update_metrics(portfolio_return, current_var) # Check stop losses triggering self.check_stop_losses(returns_df.iloc[i]) # Calculate the final metrics self.calculate_final_metrics(portfolio_returns) def optimize_portfolio(self, returns, target_return, max_var): # Using our hybrid optimization model opt = HybridOptimizer(returns, target_return, max_var) weights = opt.optimize() return self.apply_position_limits(weights) def execute_rebalancing(self, target_weights, current_prices): for symbol, target_weight in target_weights.items(): current_weight = self.get_position_weight(symbol) if abs(target_weight - current_weight) > self.REBALANCING_THRESHOLD: # Simulate execution with slippage slippage = self.simulate_slippage(symbol, current_prices[symbol]) trade_price = current_prices[symbol] * (1 + slippage) # Calculate the deal size trade_volume = self.calculate_trade_volume( symbol, current_weight, target_weight ) # Consider commissions commission = abs(trade_volume * trade_price * self.commission) self.current_capital -= commission # Set a deal to history self.record_trade(symbol, trade_volume, trade_price, commission) def update_metrics(self, portfolio_return, current_var): self.balance_history.append(self.current_capital) self.var_history.append(current_var) # Updating performance metrics self.metrics['max_drawdown'] = self.calculate_drawdown() self.metrics['sharpe_ratio'] = self.calculate_sharpe() self.metrics['var_efficiency'] = self.calculate_var_efficiency() def calculate_final_metrics(self, portfolio_returns): returns_series = pd.Series(portfolio_returns) self.metrics['total_return'] = (self.current_capital / self.initial_capital - 1) self.metrics['volatility'] = returns_series.std() * np.sqrt(252) self.metrics['sortino_ratio'] = self.calculate_sortino(returns_series) self.metrics['calmar_ratio'] = self.calculate_calmar() self.metrics['var_breaches'] = self.calculate_var_breaches() def simulate_slippage(self, symbol, price): # Simulate realistic slippage base_slippage = 0.0001 # Basic slippage time_factor = self.get_time_factor() # Time dependency volume_factor = self.get_volume_factor(symbol) # Volume dependency return base_slippage * time_factor * volume_factorTest sonuçları oldukça açıktı. Hibrit model, klasik yaklaşımlara kıyasla piyasa şoklarına karşı önemli ölçüde daha iyi dayanıklılık gösterdi. Bu durum, özellikle yüksek volatilite dönemlerinde, VaR limitinin portföyü aşırı risklerden etkili bir şekilde koruduğu zamanlarda belirgindi.
Son hata ayıklama
Aylarca süren geliştirme ve test çalışmalarının ardından nihayet sistemin son versiyonuna ulaştım. Dürüst olmak gerekirse, başlangıçta planladığımdan çok farklı hale geldi. Pratik süreç, bazıları oldukça beklenmedik olan birçok değişikliği zorunlu kıldı.
İlk büyük değişiklik verileri işleme biçimimde oldu. Sistemi sadece geçmiş veriler üzerinde test etmenin yeterli olmadığını fark ettim - çok çeşitli piyasa koşullarında davranışını kontrol etmek gerekiyordu. Bu yüzden sentetik veriler üretmek için bir sistem geliştirdim. Kulağa basit geliyor ama gerçekte birkaç hafta sürdü.
Tüm döviz paritelerini likiditeye göre gruplara ayırarak başladım. İlk grup EURUSD ve GBPUSD gibi büyük pariteleri içeriyordu. İkincisi AUDUSD ve USDCAD gibi emtia döviz paritelerini içeriyordu. Ardından çapraz pariteler geldi - EURJPY, GBPJPY ve diğerleri. Son olarak, CADJPY ve EURAUD gibi egzotik pariteler vardı. Her grup için, mümkün olduğunca gerçeğe yakın olacak şekilde kendi volatilite ve korelasyon parametrelerimi belirledim.
Ancak en ilginç şey, farklı piyasa modları eklediğimde başladı. Düşünün: zamanın üçte birinde piyasa sakindir, volatilite düşüktür. Diğer bir üçte birlik kısım normal alım-satımdır. Kalan zaman ise her şeyin çılgınlar gibi hareket ettiği, volatilitenin arttığı dönemdir. Ayrıca, uzun vadeli trendleri ve döngüsel dalgalanmaları da ekledim. Gerçek piyasaya çok benzer hale geldi.
Portföy optimizasyonu da biraz çaba gerektirdi. İlk başta pozisyonların ağırlıkları üzerinde basit kısıtlamalarla idare etmeyi düşündüm, ancak bunun yeterli olmadığını kısa sürede fark ettim. Bu yüzden dinamik risk primleri ekledim - paritenin volatilitesi ne kadar yüksekse, potansiyel getiri o kadar yüksek olmalıdır. Getirilen kısıtlamalar: pozisyon başına minimum - %4, maksimum - %25. Çok gibi görünüyor, ancak kaldıracınız varsa, bu normaldir.
Kaldıraçtan bahsetmişken. Bu ayrı bir hikaye. İlk başta, temkinli davrandım ve neredeyse kaldıraçsız çalıştım. Ancak analizler, 1'e 10 civarındaki ılımlı kaldıracın sonuçları önemli ölçüde iyileştirdiğini gösterdi. Önemli olan, tüm maliyetleri doğru bir şekilde dikkate almaktır. Ve bunlardan epeyce var: işlem komisyonları (iki baz puan), kaldıraç sürdürme faizi (günlük %0.01), gerçekleşim kayması. Tüm bunların optimize edicinin içine yerleştirilmesi gerekiyordu.
Ayrı bir baş ağrısı da teminat çağrılarına karşı korumadır. Birkaç başarısız denemeden sonra basit bir çözümde karar kıldım: düşüş %10'u aşarsa, tüm pozisyonları kapat ve bakiyenin en azından bir kısmını kurtar. Kulağa çok temkinli geliyor, ancak uzun vadede harika çalışıyor.
En zor şey raporlama yapmaktı. Sisteminiz düzinelerce döviz paritesiyle çalıştığında ve sürekli bir şeyler alıp sattığında, her şeyi takip etmek imkansızdır. Bütün bir izleme sistemi geliştirmem gerekti: bir sürü ölçüt içeren yıllık raporlar, portföyün basit değerinden ağırlık dağılımının ısı haritalarına kadar her türlü şeyin grafikleri.
Son testi 2000'den 2024'e kadar uzun bir dönem boyunca gerçekleştirdim. Başlangıç bakiyesi olarak bir milyon dolar seçtim ve üç ayda bir yeniden dengeledim. Sonuçlardan memnun kaldım. Sistem farklı piyasa koşullarına iyi uyum sağlıyor ve riskleri kontrol altında tutuyor. En ağır krizlerde bile, yönettiği bakiyenin büyük kısmını korumayı başarıyor.
Ancak hala yapılması gereken çok iş var. Volatiliteyi tahmin etmek için makine öğrenimini eklemek istiyorum. Şu anda sistem yalnızca geçmiş verilerle çalışmaktadır. Ayrıca, kaldıraç yönetimini nasıl daha esnek hale getirebileceğimi düşünüyorum. Yeniden dengeleme sıklığı da optimize edilebilir. Bazen üç aylık bir süre çok uzun olabilirken, bazen pozisyonları altı ay boyunca yalnız bırakabilirsiniz.
Genel olarak, başlangıçta planladığımdan tamamen farklı bir şey ortaya çıktı. Ama ne derler bilirsiniz, en iyisi, iyinin düşmanıdır. Sistem çalışıyor, riskleri kontrol ediyor ve para kazandırıyor. Ve asıl önemli olan da bu.

Sonuç
Harika bir yolculuktu. Markowitz'in teorisiyle ilk uğraşmaya başladığımda, bunun neye dönüşeceğini hayal bile edemezdim. Sadece Forex'e geleneksel bir yaklaşım uygulamak istedim ama sonunda risk yönetimine farklı yaklaşımlardan bir tür Frankenstein canavarı icat etmek zorunda kaldım.
En güzel şey ise Markowitz ile VaR'ı birleştirmeyi başarmış olmam ve bu şey gerçekten işe yarıyor! İlginç olan şu ki, her iki yöntem de tek başına kullanıldığında orta düzeyde sonuçlar verdi, ancak bir araya geldiklerinde mükemmel sonuçlar ortaya çıkarıyorlar. Piyasa dalgalandığında sistemin nasıl sağlam durduğunu görmek beni özellikle memnun etti. VaR, optimizasyonda bir sınırlayıcı olarak tek kelimeyle muhteşemdir.
Tabii ki teknik kısımla ilgili çok sorun yaşadım. Ancak artık her şey dikkate alınıyor: kaymalar, komisyonlar ve gerçekleşim özellikleri.
Sistemi 2000'den 2024'e kadar olan geçmiş veriler üzerinde test ettim. Sonuçlar oldukça iyiydi. Farklı piyasa koşullarına iyi uyum sağlıyor ve krizler sırasında bile yıkılmıyor. 10'a 1 kaldıraçla saat gibi çalışıyor. Önemli olan riskleri sıkı bir şekilde kontrol etmektir.
Ancak hala yapılması gereken tonlarca iş var. Şunları yapmak istiyorum:
- Volatilite tahminlerine makine öğrenimi ekleme (bu bir sonraki makalenin konusu olacak);
- Yeniden dengeleme sıklığını ayarlama - belki optimize edilebilir;
- Kaldıraç yönetimini daha akıllı hale getirme (dinamik kaldıraç ve dinamik "akıllı" mevduat yükü ayarlama özellikleri de gelecek makalelerde uygulanacaktır);
- Sistemi farklı piyasa rejimlerine daha iyi uyum sağlayacak şekilde eğitme.
Genel olarak, ana sonuç şudur: harika bir alım-satım sistemi sadece bir ders kitabındaki denklemler değildir. Burada piyasayı anlamanız, teknoloji konusunda bilgi sahibi olmanız ve riskleri kontrol altında tutabilmeniz özellikle önemlidir. Tüm bu gelişmeler artık sadece Forex'e değil, diğer piyasalara da uygulanabilir. Her ne kadar hala geliştirmeye açık alanlar olsa da, temeller çoktan atılmış durumda ve çalışıyor.
MetaQuotes Ltd tarafından Rusçadan çevrilmiştir.
Orijinal makale: https://www.mql5.com/ru/articles/16604
Uyarı: Bu materyallerin tüm hakları MetaQuotes Ltd'ye 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
3D geri dönüş formasyonlarına dayalı algoritmik alım-satım
İşte Karışınızda Yeni MetaTrader 5 ve MQL5
Zaman, fiyat ve hacme göre 3D çubuklar 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
Web sitesi politikasını ve kullanım şartlarını kabul edersiniz