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.
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 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.
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.
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.
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.
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.
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.
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`.
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.
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.
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`.
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.
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.
- 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.