A purged walk-forward study with nested-model tests, transaction costs, and multiple-testing control
I test whether daily cryptocurrency returns can be forecast out of sample well enough to survive trading costs. Three assets (BTC, ETH, SOL), two horizons (1 and 7 days), and six forecasters (random walk, historical mean, AR(1), ridge, elastic net, gradient-boosted trees) are evaluated by purged and embargoed walk-forward cross-validation over 2020-07-23 to 2026-07-17, giving 18 machine-learning settings against a martingale null.
The headline result depends entirely on which test is used, and that is the point of the paper. Diebold–Mariano rejects in favour of the random walk in 7 of 18 settings and never in favour of a model. But the random walk is nested inside every model considered here, and under nesting the Diebold–Mariano statistic is biased toward the smaller model. Applying the Clark–West correction, 5 of 18 settings reject the no-predictability null at an uncorrected 5% level, against 0.9 expected from a search that size. After controlling the false discovery rate, 2 survive; after controlling the family-wise error rate, none do.
The two survivors are not usable forecasts. Their out-of-sample
Every high-Sharpe result in the study is long-only exposure in disguise. The historical-mean forecaster is always long, and its profit and loss is numerically identical to buy-and-hold on the same schedule and cost model.
Conclusion. Over this sample, with this feature set, there is a faint and fragile statistical signal in BTC at the one-day horizon and no tradeable one anywhere.
Retail-facing "crypto price prediction" projects report high accuracy with striking regularity. The reported accuracy is usually an artifact of one of four errors:
- Target leakage. Predicting a quantity that is a function of contemporaneous or future information, so the model is fitting an identity rather than forecasting.
- Preprocessing leakage. Fitting a scaler, an imputer, or a feature selector on the whole sample before splitting, which leaks the test distribution into training.
-
No benchmark. Reporting
$R^2$ or MAPE on a price series, where a naive random-walk forecast attains near-perfect scores because prices are close to a martingale. A model that appears to explain 99% of price variance may explain none of the return variance, which is the only part anyone can trade. - Silent multiple testing. Trying many model, asset, and horizon combinations and reporting the best one without adjusting the significance threshold.
This study is constructed so that each of these is either impossible by design or measured and reported. The contribution is not a model. It is a falsifiable evaluation protocol, applied honestly, that returns an uncomfortable answer and reports it.
A secondary contribution is methodological. The most common test for comparing forecast accuracy, Diebold–Mariano, is invalid for the comparison this literature actually makes: a conditional model against a random walk that the model nests. Section 3.6.2 sets out why, and Section 4.2 shows that in this data the correction changes the conclusion from "nothing predicts, several are significantly worse" to "five settings reject, two survive false-discovery control, none is tradeable." Both statements are defensible. Only the second is correct.
Daily OHLCV bars from Yahoo Finance, adjusted, for the USD pairs of three assets.
| Asset | Bars | First | Last | Labeled rows ( |
Folds |
|---|---|---|---|---|---|
| BTC | 2,756 | 2019-01-01 | 2026-07-18 | 2,696 | 35 |
| ETH | 2,756 | 2019-01-01 | 2026-07-18 | 2,696 | 35 |
| SOL | 2,291 | 2020-04-10 | 2026-07-18 | 2,231 | 28 |
Cryptocurrency markets trade every calendar day, so the bar index is contiguous with no weekend or holiday gaps; this was verified rather than assumed, and the split logic in Section 3.3 is proved conservative under gaps regardless.
The evaluated out-of-sample window is 2020-07-23 to 2026-07-17, shorter than the data
window because the first fold consumes 504 bars of training history. Per-setting
out-of-sample counts are 2,186 forecasts for BTC and ETH and 1,721 for SOL at
Loading is cached to Parquet keyed by request parameters, so a rerun is offline and the network is touched once per asset.
Asset selection is not survivorship-free. BTC, ETH, and SOL were chosen in 2026 with the knowledge that they survived. Hundreds of 2019-era tokens did not. This biases the study toward finding profitable long exposure, which strengthens rather than weakens a negative result on predictability, but would invalidate any cross-sectional claim. Section 6 returns to this.
Let
Cumulative log returns over
Realized volatility over
Wilder's RSI over 14 bars, recentred to roughly
MACD histogram, normalized by price so it is comparable across assets, where
Moving-average ratio and distance, with
Mean normalized range over 14 bars, and a 21-bar volume z-score on
The longest warm-up is 50 bars, and rows with any undefined feature are dropped.
Verification. tests/test_features.py::test_no_lookahead perturbs bars strictly after
The label attached to a decision made at
The final
Ordinary
Test blocks tile forward without overlap. The study uses an expanding window with
Figure 1. The design, drawn from the splits the study actually runs rather than illustrated separately. Panel (b) zooms on one boundary, where the purge and the embargo are individually visible.
Purging is applied by row position, while the leak it prevents is a calendar one. If
bars were missing, would position-based purging still be enough? Yes, and conservatively
so: gaps only stretch the timeline, so tests/test_splits.py punches 25% of bars out of a synthetic index and asserts the
calendar guarantee directly.
Two further leakage controls are worth naming because they are commonly missed:
- Scalers are fit inside the fold. Standardization is a pipeline step, so it sees training rows only. Fitting a scaler on the full sample is the single most common preprocessing leak in this genre.
-
The booster purges its own validation slice. Gradient boosting early-stops on a
temporal holdout carved from the end of the training window. Without a gap, the last
$h$ rows of the fitting slice carry labels realized inside that holdout, so early stopping is tuned on partly observed outcomes. The same$h$ -bar purge is applied one level down. The test for this corrupts exactly those rows and asserts the fitted model is unchanged, which a leaky implementation would fail.
Three benchmarks and three machine-learning models, all refit from scratch on every fold.
| Name | Definition |
|---|---|
random_walk |
|
historical_mean |
$\hat{y}t = \bar{y}{\mathcal{T}}$, the training-window mean. Unconditional drift. |
ar1 |
OLS of |
ridge |
|
elastic_net |
|
gbm |
XGBoost, depth 3, |
Ridge solves
and elastic net
The models are deliberately shrunk. The signal-to-noise ratio in return prediction is small enough that an unregularized learner will fit noise, and a study whose null result came from an overfit model would be uninformative.
Note that setting all slopes to zero recovers the random walk from any of the three machine-learning models. They are nested extensions of the benchmark, which Section 3.6.2 shows is not a detail.
Forecasts become unit positions on a non-overlapping schedule: one decision every
Costs are charged on turnover, so reversing a position pays for two sides:
with
Because decisions are taken every
The schedule has
An always-long reference is computed on the same schedule with the same costs, so that a forecaster whose only achievement is holding the asset can be recognized as such.
Reported in the Campbell–Thompson (2008) form, against a genuine ex-ante benchmark forecast rather than the realized mean of the evaluation window:
where r2_vs_sample_mean, but it is not the
number quoted.
For loss differential
where
Note the
The Harvey–Leybourne–Newbold (1997) small-sample correction and a
A negative statistic favours the model. The test is two-sided, because the null is equal accuracy and a model can be significantly worse.
The difficulty is that Diebold–Mariano assumes the two forecasts are non-nested. Here they are not: the random walk is the point in the model's parameter space where every slope is zero. Under the null of no predictability the larger model must still estimate those zero coefficients, and the resulting estimation noise inflates its sample mean squared prediction error even though its population MSPE is identical. Diebold–Mariano reads that noise as evidence for the benchmark and is undersized as a test of predictability.
Clark and West (2007) subtract the estimation-noise term explicitly. With
and the statistic
The bias this corrects is demonstrated rather than asserted:
tests/test_stats.py::test_dm_is_biased_against_a_useless_nested_model_and_clark_west_is_not
simulates 200 replications in which the larger model forecasts pure noise, so the null is
true by construction. The mean Diebold–Mariano statistic is positive and it rejects in
favour of the benchmark far above the nominal rate, while Clark–West stays centred on zero
and holds its size.
A model can be poor at magnitude and useful at direction, which is all a sign strategy
needs. Pesaran and Timmermann (1992) test independence between the sign of the forecast
and the sign of the outcome. With
A positive statistic means sign-timing skill; a negative one means the forecast is reliably wrong-way. The statistic is undefined when the forecast never changes sign, which is precisely the case for the always-long drift models, and it is reported as missing rather than as a number.
Sharpe ratios are noisy in short samples and inflated by selection. The Probabilistic
Sharpe Ratio (Bailey and López de Prado, 2014) gives the probability that the true Sharpe
exceeds a benchmark
The Deflated Sharpe Ratio sets
with
Strategy returns are serially dependent, so intervals come from a circular block bootstrap
(Politis and Romano, 1994) with block length
Eighteen settings tested at the 5% level produce
and Benjamini–Hochberg controls the false discovery rate,
Benchmarks are excluded from the family. They are the null being tested against, not candidates in the search.
A typeset write-up of this study is in
paper/paper.pdf (16 pages, NeurIPS preprint format). Its
result tables are emitted from reports/results.csv rather than transcribed, so the
manuscript cannot quote a number the study does not produce.
Full tables for every asset, horizon, and model are in
reports/results.md, regenerated by make backtest. The machine
readable version is reports/results.csv. Every number quoted in
this document is checked against that file programmatically.
Figure 2. Out-of-sample
| model | RMSE | Dir. acc | Rank IC | DM (p) | CW (p) | CW p Holm / BH | |
|---|---|---|---|---|---|---|---|
| random_walk | 0.0301 | +0.0005 | — | — | — | — | — |
| historical_mean | 0.0301 | 0.0000 | 0.506 | 0.002 | +0.18 (0.860) | +1.03 (0.152) | — |
| ar1 | 0.0301 | −0.0013 | 0.513 | 0.033 | +0.34 (0.737) | +1.24 (0.107) | — |
| ridge | 0.0302 | −0.0079 | 0.506 | 0.049 | +0.76 (0.447) | +2.62 (0.004) | 0.079 / 0.046 |
| elastic_net | 0.0301 | +0.0031 | 0.505 | 0.046 | −0.46 (0.644) | +2.57 (0.005) | 0.087 / 0.046 |
| gbm | 0.0301 | +0.0007 | 0.501 | 0.019 | −0.04 (0.970) | +2.02 (0.022) | 0.350 / 0.131 |
BTC, $h=1$. Each test is shown as statistic (p-value). DM negative favours the model, CW positive favours the model.
That ridge posts
Figure 4. Every setting placed by its Diebold–Mariano statistic against its Clark–West statistic. The red band is where DM calls the model significantly worse; the green band is where Clark–West rejects no-predictability.
| Test | Rejects for the model | Rejects for the benchmark |
|---|---|---|
| Diebold–Mariano ( |
0 of 18 | 7 of 18 |
| Clark–West ( |
5 of 18 | — |
The disagreement is the finding. Under Diebold–Mariano the conclusion would be that machine learning is actively harmful for this problem: no setting beats the random walk and seven lose to it significantly. Under the test that is valid for nested models, five settings reject the null of no predictability. Both numbers come from the same forecasts.
The five, ordered by p-value, are BTC ridge (
Figure 5. The 18 Clark–West p-values ranked, against the Holm–Bonferroni and Benjamini–Hochberg thresholds. A bar must fall below a line to be rejected by that procedure.
| Threshold | Surviving |
|---|---|
| Uncorrected, |
5 |
| Benjamini–Hochberg, FDR 5% | 2 |
| Holm–Bonferroni, FWER 5% | 0 |
Five raw rejections against 0.9 expected by chance is more than noise would comfortably
produce, but it is not many. Controlling the false discovery rate leaves BTC ridge and BTC
elastic net at
The honest summary is that the evidence for predictability is real but marginal, and would not survive a stricter reader.
Figure 3. Directional accuracy per setting against the band a fair coin would occupy.
The band uses the effective sample size
Directional accuracy spans 0.471 to 0.528 and every setting lies inside its coin-flip
band. Zero of 18 settings show significant sign-timing skill under
Pesaran–Timmermann. Two are significantly anti-predictive: ETH ridge and ETH elastic net
at
Those two are worth dwelling on, because they are the reason this report never prints a bare p-value. Reported as "$p = 0.001$" alone, they look like the strongest findings in the study. They are among the worst.
Figure 7. Net Sharpe with 95% circular-block-bootstrap intervals. The dashed line is buy-and-hold on the same schedule and cost model.
Two of 18 settings have a net Sharpe whose interval excludes zero, both gradient boosting on ETH:
| Setting | Sharpe | 95% CI | PSR | DSR | Max DD | Position changes |
|---|---|---|---|---|---|---|
| ETH, |
0.91 | [0.00, 1.74] | 0.99 | 0.74 | −80.4% | 163 |
| ETH, |
0.92 | [0.02, 1.80] | 0.99 | 0.76 | −84.5% | 43 |
Both intervals begin essentially at zero. Neither setting appears among the two that survive Benjamini–Hochberg, and neither of those two has an interval excluding zero.
The statistical winners and the economic winners are disjoint sets. Two unrelated groups of survivors drawn from one search is the signature of noise. If a genuine short-horizon effect existed and were tradeable, the settings that detect it and the settings that profit from it should overlap.
Figure 6. Out-of-sample equity net of costs, against the always-long reference.
Six of 18 settings out-Sharpe buy-and-hold on the point estimate, which is the weakest
form of that claim given that every interval contains zero. Meanwhile the
historical_mean forecaster attains Sharpe ratios of 0.84, 0.80, and 0.25 across the
three assets, which looks impressive until one notices it makes exactly one position
change. It predicts a positive drift, so it is always long. Its net returns are
numerically identical to buy-and-hold, asset by asset and horizon by horizon, and the
report carries buy-and-hold as an explicit row so this cannot be mistaken for model
performance.
Maximum drawdowns run from −55% to −99% across the machine-learning settings. This is a property of the position sizing rather than of any model: unit leverage flipping direction on roughly 70% annualized volatility carries a large variance drag, so the geometric return sits well below the arithmetic one. Buy-and-hold itself draws down 76% to 96% over the same window.
Figure 8. Net Sharpe on each of the
Sampling every
| Setting | Reported Sharpe | Range across 7 offsets | Span |
|---|---|---|---|
| SOL, ridge | 0.45 | [−0.83, 0.57] | 1.40 |
| SOL, elastic_net | 0.60 | [−0.55, 0.60] | 1.15 |
| ETH, gbm | 0.92 | [0.49, 1.05] | 0.56 |
| BTC, gbm | 0.82 | [0.50, 0.82] | 0.32 |
SOL ridge reports a Sharpe of 0.45 from a distribution that runs from −0.83 to 0.57. The headline number is a property of where the sampling happened to begin. Any study that reports a single-offset Sharpe at a multi-day horizon without this check is reporting one draw and calling it an estimate.
Three things are worth taking from this.
The choice of test changed the answer. Diebold–Mariano is the default in applied forecast comparison and it is the wrong default when the benchmark is nested, which it is in almost every return-predictability study that compares a model to a random walk. Here the difference is not cosmetic: "0 of 18, and 7 significantly worse" versus "5 of 18 reject." A referee shown only the first would conclude that the models are harmful. A referee shown only the second, without Section 4.3, would conclude that something was found. Neither is the result.
Statistical significance and economic value came apart cleanly. The settings that
reject no-predictability have negative or near-zero out-of-sample
Most of the apparent performance was exposure. The highest Sharpe ratios in the study belong to a forecaster that never changes its mind. Any evaluation that had omitted a buy-and-hold reference would have credited the drift model with skill it does not have.
The result is consistent with weak-form efficiency at daily frequency for large-cap cryptocurrencies over this period, net of realistic costs. It does not say that no crypto signal exists. It says that this feature set, at these horizons, on these assets, does not produce one that survives its own error bars.
- Asset selection is not survivorship-free. BTC, ETH, and SOL were chosen knowing they survived. This biases toward profitable long exposure and would invalidate a cross-sectional claim, though it strengthens a negative result on predictability.
- Daily spot bars only. No order book, no funding rates, no intraday structure, no cross-exchange information. Short-horizon predictability, if it exists, is more likely to live at frequencies this data cannot see.
- A compact feature set and three model families. Richer features or sequence models might change the picture. The leakage discipline would not need to change with them.
-
Shorts are modelled as fully collateralized, returning
$-r$ with no borrow cost, margin requirement, or liquidation. This is reasonable for assessing signal quality and optimistic as an execution model. - Costs are a flat per-side estimate, not a queue-level execution model, and do not scale with size or volatility.
- Intervals are percentile bootstrap, not BCa. Adequate for a reject / do-not-reject reading, mildly biased for a statistic as asymmetric as the Sharpe ratio.
- The deflated Sharpe understates the true search. It deflates for the models raced within one asset-horizon, not for the whole grid, nor for the feature set and horizons fixed before the grid ran.
-
The sample end date is unpinned, so re-running extends the window and the numbers
drift. The evaluated window is stamped into
reports/results.mdon every run so any table can be tied to the data that produced it.
Across 18 model, asset, and horizon combinations evaluated by purged walk-forward cross-validation from 2020 to 2026, there is a faint statistical signal in BTC at the one-day horizon that survives false-discovery control and does not survive family-wise control. It does not convert into a forecast that beats an ex-ante drift estimate, into sign-timing skill, or into a Sharpe ratio distinguishable from zero after costs. The strategies with the highest Sharpe ratios in the study are long-only exposure.
The negative result is the deliverable. It is reported with the tests that would have detected a positive one, and with the corrections that would have removed a spurious one.
make setup # install the package and dev extras into ./venv
make backtest # download data, run the study, regenerate reports/ and figures
make test # 154 tests, including the no-lookahead and purge guarantees
make check # lint, type-check, test (the CI gate)
make app # a small local viewer on http://127.0.0.1:8000
make paper # typeset paper/paper.pdf (needs tectonic)make backtest runs in roughly 15 seconds and rewrites reports/results.md,
reports/results.csv, and all eight figures. Market data is cached under data/cache/,
so re-runs are offline. reports/results.md is generated, never hand-edited: its headline
counts, its interpretation section, and its figure captions are all computed from the
results table, which is why they cannot drift away from the numbers they describe.
The CI gate runs ruff, black, mypy with disallow_untyped_defs, and the full test suite
with an 80% coverage floor, on Python 3.12 and 3.13.
src/cryptoforecast/
config.py frozen study configuration
data/ cached Yahoo Finance OHLCV loader
features.py leak-free, scale-free features
targets.py forward-return targets
dataset.py aligned (features, target) builder
splits.py purged and embargoed walk-forward
models/ benchmarks, ridge/elastic-net, gradient boosting
backtest/ walk-forward engine, cost model, trading rule
evaluate/ metrics, statistical tests, report renderer
plots/ the eight figures, one module per topic
cli.py cryptoforecast {data,backtest,report}
app/ a small Flask viewer over the same evaluation
paper/ NeurIPS-format preprint; tables generated from the results
reports/ generated results.md, results.csv, figures (PNG and PDF)
tests/ 154 tests, including the leakage guarantees
- Bailey, D. and López de Prado, M. (2014). The Deflated Sharpe Ratio: Correcting for Selection Bias, Backtest Overfitting, and Non-Normality. Journal of Portfolio Management 40(5), 94–107.
- Benjamini, Y. and Hochberg, Y. (1995). Controlling the False Discovery Rate: A Practical and Powerful Approach to Multiple Testing. JRSS-B 57(1), 289–300.
- Campbell, J. Y. and Thompson, S. B. (2008). Predicting Excess Stock Returns Out of Sample: Can Anything Beat the Historical Average? Review of Financial Studies 21(4), 1509–1531.
- Chen, T. and Guestrin, C. (2016). XGBoost: A Scalable Tree Boosting System. KDD, 785–794.
- Clark, T. E. and West, K. D. (2007). Approximately Normal Tests for Equal Predictive Accuracy in Nested Models. Journal of Econometrics 138(1), 291–311.
- Diebold, F. X. and Mariano, R. S. (1995). Comparing Predictive Accuracy. Journal of Business and Economic Statistics 13(3), 253–263.
- Fama, E. F. (1970). Efficient Capital Markets: A Review of Theory and Empirical Work. Journal of Finance 25(2), 383–417.
- Harvey, D., Leybourne, S. and Newbold, P. (1997). Testing the Equality of Prediction Mean Squared Errors. International Journal of Forecasting 13(2), 281–291.
- Holm, S. (1979). A Simple Sequentially Rejective Multiple Test Procedure. Scandinavian Journal of Statistics 6(2), 65–70.
- López de Prado, M. (2018). Advances in Financial Machine Learning. Wiley. Chapter 7, purged and embargoed cross-validation.
- Newey, W. K. and West, K. D. (1987). A Simple, Positive Semi-Definite, Heteroskedasticity and Autocorrelation Consistent Covariance Matrix. Econometrica 55(3), 703–708.
- Pesaran, M. H. and Timmermann, A. (1992). A Simple Nonparametric Test of Predictive Performance. Journal of Business and Economic Statistics 10(4), 461–465.
- Politis, D. N. and Romano, J. P. (1994). The Stationary Bootstrap. Journal of the American Statistical Association 89(428), 1303–1313.
MIT, see LICENSE. This is a research exercise. It is not investment advice, and the result is that the models do not work.







