//+------------------------------------------------------------------+ //| US_Rates_WFO.mq5 | //| Beta-Driven Strategy with Walk-Forward Optimization | //| Copyright 2025, https://www.mql5.com/en/users/exceltrader | //+------------------------------------------------------------------+ #property copyright "exceltrader" #property link "https://www.mql5.com/en/users/exceltrader" #property version "1.0" #include #include //--- Inputs: Asset Proxies input group "1. Asset Proxies" input string Bond_Symbol = "TLT"; // Bond ETF (Yield Proxy) - Darwinex: "TLT" Ensure you Pick and US Bond Inde Proxy //--- Inputs: Strategy Parameters input group "2. Forecast Logic" input int Beta_Period = 5; // Default Lookback (Fallback if Opt off) input double Min_Correlation = 0.5; // Filter: Minimum correlation absolute value input bool Use_Theory_Filter = false; // Filter: Trade only if Stats match Econ Theory input int Open_Hour = 2; // Entry Hour (Server Time - usually NY Close) //--- Inputs: Self-Optimization (WFO) input group "3. Self-Optimization" input bool Do_Self_Optimize = true; // Enable Pre-Trade Optimization input int Opt_Lookback_Days= 40; // Optimization Window (Days to test) input int Opt_Beta_Start = 10; // Start of Beta Period range input int Opt_Beta_End = 40; // End of Beta Period range input int Opt_Beta_Step = 5; // Step size for optimization //--- Inputs: Risk Management input group "4. Risk Management" input double Risk_Percent = 1.0; // Risk per trade (% of Equity) input int ATR_Period = 20; // ATR Period for Volatility input double SL_ATR_Mult = 2; // Stop Loss distance in ATRs input double TP_ATR_Mult = 4.0; // Take Profit distance in ATRs //--- Inputs: Trade Management input group "5. Trade Management" input bool Use_Trailing = true; // Enable Trailing Stop input double Trail_Trigger_ATR= 2.0; // Start trailing after price moves X ATRs input double Trail_Step_ATR = 0.5; // Trail Step in ATRs //--- Global Variables CTrade Trader; ulong EA_MAGIC = 775599; datetime lastTradeDay = 0; //--- Symbol Enum (Major Liquid Pairs) enum ENUM_STRATEGY_SYMBOLS { EURUSD, USDJPY, GBPUSD, AUDUSD, USDCAD, USDCHF }; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { Trader.SetExpertMagicNumber(EA_MAGIC); Trader.SetTypeFilling(ORDER_FILLING_IOC); // Crucial for Darwinex Execution if(!SymbolSelect(Bond_Symbol, true)) { Print("Error: Symbol '", Bond_Symbol, "' not found in Market Watch."); return(INIT_FAILED); } EventSetTimer(60); // Run every minute return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { EventKillTimer(); } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { datetime currentTime = TimeCurrent(); MqlDateTime dt; TimeToStruct(currentTime, dt); // 1. Management: Run Trailing Stop logic constantly ManageExits(); // 2. Entry: Run once per day at Open_Hour MqlDateTime lastDt; TimeToStruct(lastTradeDay, lastDt); if(dt.hour == Open_Hour && dt.day_of_year != lastDt.day_of_year) { lastTradeDay = currentTime; RunDailyAnalysis(); } } //+------------------------------------------------------------------+ //| Main Strategy Logic (With Optimization) | //+------------------------------------------------------------------+ void RunDailyAnalysis() { for(int i = 0; i <= (int)USDCHF; i++) { ENUM_STRATEGY_SYMBOLS sym_enum = (ENUM_STRATEGY_SYMBOLS)i; string symbol = EnumToString(sym_enum); if(!SymbolSelect(symbol, true)) continue; // --- DYNAMIC PARAMETER SELECTION --- int current_beta_period = Beta_Period; // Default if(Do_Self_Optimize) { // Find the best performing period over the last X days int best_period = GetBestBetaPeriod(symbol, Bond_Symbol); if(best_period > 0) { current_beta_period = best_period; // Print(symbol, " Optimized Beta Period: ", current_beta_period); } } // ----------------------------------- // A. Get Statistical Metrics double beta = 0.0; double correlation = 0.0; double bond_ret_lag = 0.0; bool data_ok = CalculateMetrics(symbol, Bond_Symbol, current_beta_period, beta, correlation, bond_ret_lag); if(!data_ok) continue; // B. FILTER: Statistical Significance if(MathAbs(correlation) < Min_Correlation) continue; // C. FILTER: Theoretical Validity (Econ Theory Check) if(Use_Theory_Filter) { if(!IsTheoreticallyValid(symbol, beta)) continue; } // D. Forecast double forecast = beta * bond_ret_lag; // E. Execution ExecuteEntry(symbol, forecast, beta); } } //+------------------------------------------------------------------+ //| WFO: Find Best Beta Period based on recent history | //+------------------------------------------------------------------+ int GetBestBetaPeriod(string sym, string bond) { int best_p = -1; double max_profit = -99999.0; // 1. Bulk download history // Needed: Lookback Window + Max Beta Period + Buffers int req_bars = Opt_Lookback_Days + Opt_Beta_End + 10; double close_sym[], close_bond[]; // Use Series flag to make index 0 the most recent if(CopyClose(sym, PERIOD_D1, 1, req_bars, close_sym) < req_bars) return -1; if(CopyClose(bond, PERIOD_D1, 1, req_bars, close_bond) < req_bars) return -1; ArraySetAsSeries(close_sym, true); ArraySetAsSeries(close_bond, true); // 2. Iterate through parameters for(int p = Opt_Beta_Start; p <= Opt_Beta_End; p += Opt_Beta_Step) { double simulated_profit = 0.0; // 3. Simulate performance over the Lookback Window // Loop from 'Opt_Lookback_Days' ago up to yesterday (index 0) for(int day = Opt_Lookback_Days - 1; day >= 0; day--) { // day = 0 is "yesterday" (the most recent completed bar in this history slice) // We pretend we are at 'day' and need to calculate Beta using data BEFORE 'day' double mean_y=0, mean_x=0; double vec_y[], vec_x[]; ArrayResize(vec_y, p); ArrayResize(vec_x, p); bool math_error = false; // Fill vectors for Beta Calculation for(int k=0; k 0.00000001) ? cov/var_x : 0; // Predict 'day' returns // Bond move that precedes 'day' double bond_ret_predict = MathLog(close_bond[day+1] / close_bond[day+2]); double forecast = beta * bond_ret_predict; // Actual Result of 'day' double actual_sym_ret = MathLog(close_sym[day] / close_sym[day+1]); // Simulate PnL if(forecast > 0) simulated_profit += actual_sym_ret; // Long if(forecast < 0) simulated_profit -= actual_sym_ret; // Short } // 4. Store Best Result if(simulated_profit > max_profit) { max_profit = simulated_profit; best_p = p; } } return best_p; } //+------------------------------------------------------------------+ //| Helper: Check if Stats match Econ Theory | //+------------------------------------------------------------------+ bool IsTheoreticallyValid(string symbol, double beta) { // Theory: TLT Down (Yields Up) -> USD Strong if(StringFind(symbol, "USD") == 0) { // USDxxx if(beta < 0) return true; return false; } else { // xxxUSD if(beta > 0) return true; return false; } return true; } //+------------------------------------------------------------------+ //| Execute Entry with Risk Sizing | //+------------------------------------------------------------------+ void ExecuteEntry(string symbol, double forecast, double beta_val) { if(PositionSelect(symbol)) return; // One trade per pair if(forecast == 0.0) return; bool isBuy = (forecast > 0); ENUM_ORDER_TYPE type = isBuy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL; double atr = GetATR_Value(symbol); if(atr <= 0) return; double current_price = isBuy ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID); // SL/TP Calc double sl_dist = atr * SL_ATR_Mult; double tp_dist = atr * TP_ATR_Mult; double sl = isBuy ? current_price - sl_dist : current_price + sl_dist; double tp = isBuy ? current_price + tp_dist : current_price - tp_dist; int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); sl = NormalizeDouble(sl, digits); tp = NormalizeDouble(tp, digits); // Volume Calc double volume = CalculateRiskVolume(symbol, sl_dist, Risk_Percent); if(volume <= 0) return; string comment = StringFormat("B:%.2f", beta_val); Trader.PositionOpen(symbol, type, volume, current_price, sl, tp, comment); } //+------------------------------------------------------------------+ //| Math: Beta & Correlation Calculation | //+------------------------------------------------------------------+ bool CalculateMetrics(string sym_y, string sym_x, int period, double &out_beta, double &out_corr, double &out_x_ret) { int bars = period + 2; double close_y[], close_x[]; if(CopyClose(sym_y, PERIOD_D1, 1, bars, close_y) < bars) return false; if(CopyClose(sym_x, PERIOD_D1, 1, bars, close_x) < bars) return false; double vec_y[], vec_x_lag[]; ArrayResize(vec_y, period); ArrayResize(vec_x_lag, period); // Log Returns: Y(t) vs X(t-1) for(int i=0; i=0; i--) { if(PositionGetSymbol(i) == Bond_Symbol) continue; ulong ticket = PositionGetTicket(i); if(PositionGetInteger(POSITION_MAGIC) != EA_MAGIC) continue; string symbol = PositionGetSymbol(i); double atr = GetATR_Value(symbol); if(atr <= 0) continue; double sl_price = PositionGetDouble(POSITION_SL); double open_price = PositionGetDouble(POSITION_PRICE_OPEN); double current_price = PositionGetDouble(POSITION_PRICE_CURRENT); long type = PositionGetInteger(POSITION_TYPE); int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); double activation = atr * Trail_Trigger_ATR; double step = atr * Trail_Step_ATR; double sl_dist = atr * SL_ATR_Mult; if(type == POSITION_TYPE_BUY) { if(current_price > open_price + activation) { double new_sl = current_price - sl_dist; if(new_sl > sl_price + step || sl_price == 0) { if(new_sl < open_price) new_sl = open_price; // Secure Breakeven Trader.PositionModify(ticket, NormalizeDouble(new_sl, digits), PositionGetDouble(POSITION_TP)); } } } else if(type == POSITION_TYPE_SELL) { if(current_price < open_price - activation) { double new_sl = current_price + sl_dist; if(new_sl < sl_price - step || sl_price == 0) { if(new_sl > open_price) new_sl = open_price; Trader.PositionModify(ticket, NormalizeDouble(new_sl, digits), PositionGetDouble(POSITION_TP)); } } } } } //+------------------------------------------------------------------+ //| Risk: Calculate Lots based on % Equity | //+------------------------------------------------------------------+ double CalculateRiskVolume(string symbol, double sl_distance_price, double risk_pct) { if(sl_distance_price <= 0) return 0.0; double equity = AccountInfoDouble(ACCOUNT_EQUITY); double risk_amount = equity * (risk_pct / 100.0); double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); if(tick_value == 0 || tick_size == 0) return 0.0; double loss_per_lot = (sl_distance_price / tick_size) * tick_value; if(loss_per_lot == 0) return 0.0; double lots = risk_amount / loss_per_lot; double min_vol = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double max_vol = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double step_vol = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); lots = MathFloor(lots / step_vol) * step_vol; if(lots < min_vol) return 0.0; if(lots > max_vol) lots = max_vol; return lots; } //+------------------------------------------------------------------+ //| Utility: Get ATR | //+------------------------------------------------------------------+ double GetATR_Value(string symbol) { int handle = iATR(symbol, PERIOD_D1, ATR_Period); if(handle == INVALID_HANDLE) return 0.0; double val[1]; if(CopyBuffer(handle, 0, 1, 1, val) < 1) { IndicatorRelease(handle); return 0.0; } IndicatorRelease(handle); return val[0]; }