239 lines
No EOL
12 KiB
Markdown
239 lines
No EOL
12 KiB
Markdown
# 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) |