12 KiB
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\Filesfor 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
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
qown lags of the lower-TF return - Unrestricted model — restricted model plus
plags 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.ipynbGrangerMTF.mq5README.md
Generated Artifacts
granger_mtf_causality.csv— calibration table written toTerminal\Common\Filesby 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_MINthat 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