2026-09-07 09:11:20 +00:00 | | | # PDV
|
| | |
|
2026-09-07 14:12:22 +05:00 | | | Path-dependent volatility in native MQL5: the Guyon-Lekeufack model written as
|
| | | four exponential accumulators, so the whole thing costs four multiplies and
|
| | | four adds per bar and stores no history at all.
|
| | |
|
| | | Companion code for the MQL5 article: https://www.mql5.com/en/articles/24607
|
| | |
|
| | | ## What it does
|
| | |
|
| | | Square a return and you throw away its sign. Every GARCH-family model does this
|
| | | in its first step, which is why a market that has fallen five percent and a
|
| | | market that has risen five percent look identical to it. The econometric
|
| | | patches, GJR and TARCH and EGARCH, add back one bit of that discarded
|
| | | information: the sign of yesterday's return, times a single fixed multiplier.
|
| | |
|
| | | Guyon and Lekeufack keep the whole path instead. Volatility becomes
|
| | |
|
| | | ```
|
| | | sigma = b0 + b1 * R1 + b2 * sqrt(R2)
|
| | | ```
|
| | |
|
| | | where `R1` is a kernel-weighted sum of past signed returns and `R2` the same
|
| | | construction on squared returns. `R1` carries where the path has been going,
|
| | | `R2` how hard it has been moving, and the claim is that those two numbers are
|
| | | most of what there is to know about today's volatility.
|
| | |
|
| | | The reason this belongs in an indicator rather than a research report is the
|
| | | kernel. Both kernels are convex mixtures of two exponentials, and an
|
| | | exponential kernel has a recursive update, so a sum over unbounded history
|
| | | collapses to one running scalar. Two exponentials per feature and two features
|
| | | make four scalars. `CPdvState` is that arithmetic and nothing else: no lookback
|
| | | array, no matrix, nothing that grows with the length of the history, and no
|
| | | knowledge of bars, so the same object serves a calibration sweep over ten years
|
| | | and an indicator updating on every tick.
|
| | |
|
| | | Calibration splits nine parameters by how they are found rather than solving
|
| | | all nine the same way. Hold the kernels fixed and the model is a plain linear
|
| | | regression of realised volatility on two features, so the three betas come out
|
| | | of a closed form with no search. Only the six kernel parameters need an
|
| | | optimiser, and they get a Nelder-Mead simplex with the regression nested inside
|
| | | its objective. Six dimensions searched over three solved exactly is what makes
|
| | | this calibrate in seconds rather than minutes.
|
| | |
|
| | | Two choices in `CPdvModel` are about honesty rather than accuracy. The features
|
| | | at bar `i` use returns up to and including bar `i` while the target is measured
|
| | | from bar `i+1` onward, and the train/test cut is chronological, because a
|
| | | shuffled split on data this autocorrelated will manufacture an R-squared out of
|
| | | nothing. The evaluation window is also fixed before the search starts rather
|
| | | than derived per candidate: a long-memoried kernel needs more warm-up than a
|
| | | short one, so letting the window follow the candidate would score different
|
| | | models on different samples and quietly reward whichever got the easiest bars.
|
| | |
|
| | | `PDV_Evidence.mq5` is where the method has to justify itself. It scores the
|
| | | model out of sample against a constant, an EWMA volatility and GARCH(1,1),
|
| | | every baseline given the same intercept and slope the model gets, and then runs
|
| | | the ablation that matters: the identical machinery with `b1` pinned at zero.
|
| | | Same data, same target, same split, same two-exponential kernel on squared
|
| | | returns, and the only difference is whether the sign of the path is allowed to
|
| | | count. Whatever R-squared the trend term adds over that is the entire
|
| | | measurable value of path dependence.
|
| | |
|
| | | On H1 equity indices it is worth a lot. The trend term adds +0.171 out-of-sample
|
| | | R-squared on SPX500 and +0.15 and +0.11 on US30 and NDX100. On FX it adds
|
| | | roughly nothing, and that null is the useful part of the result rather than a
|
| | | failure: the leverage effect is an equity phenomenon, a currency pair has no
|
| | | issuer whose equity can be geared, and a `b1` near zero there is the model
|
| | | reporting the truth. Gold's `b1` comes out positive, which is a different
|
| | | market telling a different story about its own path.
|
| | |
|
| | | Two things are worth knowing before reading any of those numbers.
|
| | |
|
| | | A realised-volatility target caps the attainable R-squared by its own sampling
|
| | | noise, because the target is an estimate and not the thing itself. On synthetic
|
| | | data with a known generating process the fit scored 0.1156 against a
|
| | | close-to-close target while a forecaster that *knew* the true volatility scored
|
| | | 0.1114. The fit was not weak; the target was noisy. Against the true generating
|
| | | sigma the same fit correlated 0.976. Compute that ceiling before calling an
|
| | | R-squared disappointing.
|
| | |
|
| | | The model is linear in `R1` and `b1` is negative wherever leverage exists, so a
|
| | | long enough rally can drive fitted volatility through zero. That is a property
|
| | | of the specification, which the paper is explicit about, not a bug in the port.
|
| | | Both values are exposed: `Sigma()` is floored at a share of the activity term
|
| | | and is what a stop distance should be built on, while `SigmaRaw()` is what tells
|
| | | you the model has been pushed outside the range it was fitted in.
|
| | | `PDV_Calibrate.mq5` reports the count of negative raw bars for exactly this
|
| | | reason. A handful is the model working as specified; thousands means the fit is
|
| | | not usable.
|
| | |
|
| | | The decomposition, not the R-squared, is the deliverable. Splitting volatility
|
| | | into a direction component and an activity component answers a question a
|
| | | single conditional-variance number cannot express, and it costs nothing extra
|
| | | to compute: a market falling steadily and a market thrashing sideways at the
|
| | | same volatility become distinguishable, before the fact, from the state alone.
|
| | |
|
| | | ## Layout
|
| | |
|
| | | ```
|
| | | Include/PDV/PdvTypes.mqh structs, buffer map, half-life and decay conversions
|
| | | Include/PDV/PdvState.mqh CPdvState: the four accumulators, O(1) per bar
|
| | | Include/PDV/PdvModel.mqh CPdvModel: forward target, closed-form betas, Nelder-Mead, ablation
|
| | | Include/PDV/PdvForecast.mqh CPdvForecast: decomposition, floor, trend share, multi-step projection
|
| | | Indicators/PDV/PDV_Decomposition.mq5 total volatility against its trend and activity components
|
| | | Scripts/PDV/PDV_Calibrate.mq5 fits one symbol, reports parameters, split scores, decomposition summary
|
| | | Scripts/PDV/PDV_Evidence.mq5 out-of-sample comparison against constant, EWMA and GARCH(1,1)
|
| | | ```
|
| | |
|
| | | Run `PDV_Calibrate.mq5` first on the symbol you care about. Its last line
|
| | | prints the nine fitted parameters in input order, which paste straight into
|
| | | `PDV_Decomposition.mq5`; the defaults shipped in the indicator are the SPX500
|
| | | H1 fit. Then run `PDV_Evidence.mq5` for the comparison table and the ablation.
|
| | | Pick the instrument deliberately, because it is load-bearing: an FX-only run
|
| | | will show a gain near zero and no story at all. Broker symbol names vary, and
|
| | | the indices are not always called what you expect.
|
| | |
|
| | | Do not reconcile a figure from one script against another. Each rebuilds its
|
| | | own scored window, so a different warm-up starts the scored range at a
|
| | | different bar and the numbers legitimately differ: SPX500 comes out at 0.4480
|
| | | under `PDV_Calibrate` at a 750-bar warm-up and 0.4464 inside the `PDV_Evidence`
|
| | | loop, and both are correct.
|
| | |
|
| | | Substituting your own target is a single edit. `BuildTarget()` in
|
| | | `PdvModel.mqh` is the only place that decides what the model is being asked to
|
| | | predict, and nothing downstream knows where the target came from. The two
|
| | | supplied forms are close-to-close and Parkinson, which are on the same scale
|
| | | and interchangeable; Parkinson reads the whole bar range and is several times
|
| | | more efficient, at the cost of ignoring gaps.
|
| | |
|
| | | ## Disclaimer
|
| | |
|
| | | Educational code. This is a volatility measurement and forecasting library, not
|
| | | a trading system: there is no Expert Advisor here and nothing in this repository
|
| | | demonstrates a trading edge. A better volatility forecast is an input to
|
| | | position sizing and stop placement, which is a different thing from money.
|
| | | The out-of-sample gains reported above are R-squared against a realised
|
| | | volatility target on H1 bars from one broker's history, and they are strongly
|
| | | instrument-dependent, close to zero on FX by construction. Test on your own
|
| | | data and broker conditions before drawing conclusions.
|