# Article-24649-GrangerMTF-Causality-Detection This repository is an article-derived reference project based on the original MQL5 article. It does not claim to reproduce the full original source code unless files are explicitly attached. ## Overview This project documents a complete multi-timeframe causality detection pipeline for MetaTrader 5. Instead of assuming that a higher timeframe always informs the lower, the system tests that relationship statistically and uses the result to filter signals at runtime. The pipeline automatically: - Fetches historical EURUSD OHLC data for M5, M15, M30 and H1 directly from MetaTrader 5 - Converts prices to log returns and verifies stationarity with an ADF test - Builds look-ahead-free design matrices aligned so each lower-TF bar only sees already-closed higher-TF bars - Selects lag structure by BIC and fits restricted and unrestricted OLS models for each timeframe pair - Runs a Granger F-test, cross-checks it against statsmodels, and adds an HC3 robust Wald test - Scans rolling windows to measure how stable each relationship is over time - Corrects for multiple comparisons across all four pairs with Benjamini–Hochberg - Exports a calibration table to `Terminal\Common\Files` for the EA to read - Repeats the same F-test live on a rolling window inside MQL5 at runtime - Marks each pair VALID or INVALID based on both the offline table and the live result - Generates BUY/SELL signals from the votes of causally valid pairs only - Gates every trade through the live causality check before execution - Manages positions with ATR-based stop loss and take profit - Publishes pair statuses as terminal global variables for use by other EAs The project demonstrates how a statistical test developed in Python can be reproduced exactly in MQL5 so that the offline research and the live system stay faithful to the same rules. ## Original Article **Article ID:** 24649 **Author:** Hlomohang John Borotho **Category:** Experts **URL:** [https://www.mql5.com/en/articles/24649](https://www.mql5.com/en/articles/24649) ## Repository Purpose This repository should be treated as a reference or reconstruction project derived from the article content. Its purpose is to: - Test whether higher timeframes statistically Granger-cause lower timeframes before trading on that assumption - Demonstrate look-ahead-free multi-timeframe alignment in both Python and MQL5 - Reproduce an F-test and rolling causality monitor natively in MQL5 without external libraries - Provide a reusable causality-filtering framework that works on any symbol or timeframe pair - Serve as a foundation for regime-aware Expert Advisor development ## Key Concepts ### Timeframe Pairs The system tests four lead → lag relationships. The lead must always be the higher timeframe. | Pair | Lead | Lag | EURUSD Result | |------|------|-----|---------------| | H1 → M15 | H1 | M15 | INVALID (F ≈ 0, stab 9%) | | M30 → M15 | M30 | M15 | VALID (F = 9.64, stab 43%) | | M15 → M5 | M15 | M5 | INVALID (F = 4.84, stab 20%) | | H1 → M5 | H1 | M5 | INVALID (F ≈ 0, stab 6%) | ### Look-Ahead-Free Alignment For each lower-TF bar opening at time `t`, only higher-TF bars whose open time is at or before `t - lead_period` are used as regressors. This rule is identical in Python and MQL5. Any deviation between the two stages would make the offline coefficients meaningless at runtime. ### Granger F-Test Two OLS models are compared per pair: - **Restricted model** — constant plus `q` own lags of the lower-TF return - **Unrestricted model** — restricted model plus `p` lags of the completed higher-TF return If adding the higher-TF lags reduces the residual sum of squares by more than chance would allow, the F-statistic is large and the pair is a candidate for VALID status. ### Rolling Stability The F-test is repeated on every consecutive window of `WINDOW` bars, stepping forward by `STEP`. The fraction of windows where the pair is significant is its stability score. A pair passes only when both its adjusted p-value and its stability clear their thresholds. ### VALID / INVALID Decision | Condition | Required | |-----------|----------| | Adjusted p-value (BH) < ALPHA | Yes | | Rolling stability ≥ STABILITY_MIN | Yes | | OOS directional edge > 0 | Optional (REQUIRE_OOS_EDGE) | ## Algorithm / Architecture Summary ### 1. Data Fetch (Python) OHLCV data is pulled from MetaTrader 5 using `copy_rates_range` for all four timeframes. `copy_rates_from_pos` is not used because it fails with Invalid params at the bar counts required for M5 over 150 days. The last (forming) bar is always dropped before any calculation. ### 2. Feature Engineering (Python) Prices are converted to log returns in basis points (`SCALE * np.log(close / close.shift(1))`). ADF tests confirm prices are non-stationary and returns are stationary. The design matrix is built with `np.searchsorted` to enforce the look-ahead-free cutoff without any forward fill. ### 3. Label Generation (Python) The target for each lower-TF bar is its own log return, in basis points. This is a regression problem, not a classification problem. The "label" is the raw return the model is trying to forecast. Sign of the forecast becomes the vote direction; magnitude relative to return std controls the threshold via `InpMinEdgeSigma`. ### 4. Granger F-Test (Python) Lags are selected by BIC over a grid of `p` from 1 to `MAX_P` and `q` from 1 to `MAX_Q` on the training 70%. The hand-written F-test is cross-checked against `sm.OLS(...).compare_f_test(...)` with an assert. An HC3 robust Wald test on the higher-TF coefficients is run alongside to flag cases where heteroskedasticity inflates the classical F. ### 5. Rolling Stability and Correction (Python) The rolling scan uses the same `WINDOW` and `ALPHA` the EA will use live. Benjamini–Hochberg is applied across all four pairs to control the false discovery rate. ### 6. Export (Python) The calibration table is written to `Terminal\Common\Files` with `\r\n` line endings and plain ASCII so the EA can read it with `FILE_TXT | FILE_ANSI`. One row per pair: `lead_tf, lag_tf, p, q, window, alpha, f_stat, p_value, p_adj, stability, oos_hit, oos_edge_bps, status`. ### 7. EA Deployment (MQL5) The EA reads the CSV on `OnInit`. On each new signal bar it re-runs the F-test on the most recent `WINDOW` bars per pair (every `InpRetestBars` lower-TF bars), computes a return forecast from the fitted coefficients, and casts a vote. Votes from VALID pairs only are aggregated into the final signal. A trade opens only when the signal changes. ### 8. Position Management (MQL5) Stop loss and take profit are set as ATR multiples on the signal timeframe at entry. The opposite-signal exit is optional via `InpCloseOnOpposite`. ## Signal Generation **Buy signal** — at least `InpMinVotes` VALID pairs forecast a positive return, zero forecast a negative return **Sell signal** — at least `InpMinVotes` VALID pairs forecast a negative return, zero forecast a positive return ## Risk Management Stop loss distance: ``` SL = ATR(InpATRPeriod) × InpSL_ATR ``` Take profit distance: ``` TP = ATR(InpATRPeriod) × InpTP_ATR ``` ## A/B Testing Setting `InpUseCausalityFilter = false` disables the causality gate entirely. The EA then signals from all pairs regardless of VALID/INVALID status, acting as the unfiltered baseline. Running both configurations over the same tester period isolates the filter's contribution to performance. Note: because the filter changes the signal frequency significantly, the two modes may require separate optimisation passes. Applying filter-OFF optimised inputs with the filter ON often results in no trades. ## Granger Causality: Concept Mapping | Granger Term | Trading Interpretation | |---|---| | Lead series | Higher timeframe bar returns | | Lag series | Lower timeframe bar returns | | Restricted model | Forecast from own past returns only | | Unrestricted model | Forecast from own past returns plus completed higher-TF returns | | F-statistic | Evidence that the higher TF adds predictive information | | p-value (adjusted) | Probability of the observed F under the null of no causality | | Rolling stability | Fraction of time windows where the relationship is significant | | VALID | Pair cleared all thresholds — allowed to vote | | INVALID | Pair failed — blocked from voting when filter is on | | Vote | Sign of the forecast: +1 BUY, -1 SELL, 0 flat | ## Mentioned or Attached Files **Primary Components** - `GrangerMTF_Causality.ipynb` - `GrangerMTF.mq5` - `README.md` **Generated Artifacts** - `granger_mtf_causality.csv` — calibration table written to `Terminal\Common\Files` by the notebook ## Statistics **Pipeline Components** - Data Fetch Module: 1 - Stationarity Check Module: 1 - Alignment and Feature Engineering Module: 1 - Granger F-Test Module: 1 - Rolling Stability Module: 1 - Multiple Testing Correction Module: 1 - Export Module: 1 **EA Components** - CSV Loader: 1 - OLS Solver (Gaussian elimination): 1 - Live F-Test Engine: 1 - Forecast and Vote Engine: 1 - Signal Aggregator: 1 - Trade Execution Module: 1 - Panel and Visualisation Module: 1 - Global Variable Export Module: 1 **Timeframe Pairs** - Total pairs tested: 4 - Pairs marked VALID on EURUSD (150 days): 1 ## Tags `MQL5` `MetaTrader5` `GrangerCausality` `MultiTimeframe` `StatisticalFilter` `OLS` `F-Test` `TimeSeries` `Expert-Advisor` `Algorithmic-Trading` `Python` `Statsmodels` `Scipy` `Feature-Engineering` `Strategy-Testing` `RollingWindow` `BenjaminiHochberg` ## Difficulty Moderate to Advanced — requires familiarity with: - MQL5 Expert Advisor development - Python data science toolchain (pandas, numpy, scipy, statsmodels) - Linear regression and F-test mechanics - Time series stationarity concepts - Multi-timeframe bar alignment and look-ahead bias - Rolling window statistical analysis - Multiple hypothesis testing correction ## Limitations - Causality is tested on a single symbol — same-symbol timeframes share the same price process, so a VALID result means longer history adds predictive information beyond short own lags, not that an external driver exists. - Rolling stability at 43% for M30 → M15 means the relationship is absent in more windows than it is present. The live re-test mitigates this but does not eliminate it. - The 150-day calibration window overlaps the backtest period in the article. Offline VALID/INVALID labels are partly in-sample relative to what the EA traded. - Out-of-sample directional hit rate for the one VALID pair was 50.0%, below the restricted-model baseline. Statistical significance did not translate to bar-by-bar directional accuracy. - The OLS solver in MQL5 adds a small ridge term (1e-9) for numerical stability. This is not present in the Python version and introduces a negligible but non-zero difference in coefficients. ## Future Improvements Potential enhancements include: - Calibrating the notebook on data that ends before the backtest starts for a fully out-of-sample test - Regime-specific VALID/INVALID thresholds (trending vs. ranging conditions) - Cross-symbol Granger tests where lead and lag are different instruments - Adaptive `STABILITY_MIN` that tightens in low-volatility regimes - Confidence-based position sizing proportional to the forecast magnitude - Periodic notebook recalibration and automatic CSV refresh - Multi-symbol training to identify which pairs Granger-cause each other across instruments - Integration with the Part 10 AutoML pipeline to use Granger-filtered signals as ML features ## Reference Original MQL5 article: [https://www.mql5.com/en/articles/24649](https://www.mql5.com/en/articles/24649)