Executive Summary
Energy ETFs trend and gap more aggressively, which makes them a useful stress test for <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="A tendency for prices that moved unusually far in one direction to snap back toward a more normal level." data-tooltip="A tendency for prices that moved unusually far in one direction to snap back toward a more normal level." aria-label="mean reversion: A tendency for prices that moved unusually far in one direction to snap back toward a more normal level.">mean reversion</span>. This run covers 2016-01-01 to 2025-01-01 using quantstrat in R, with initial equity of $100,000. I compare it with buy-and-hold, apply base friction, and run a first sequence stress test.
Research Card
| Field | Detail |
|---|---|
| Instrument | XLE |
| Strategy type | Mean reversion / band break |
| Publication archetype | empirical myth-busting research note |
| Test period | 2016-01-01 to 2025-01-01 |
| Engine | quantstrat in R |
| Starting equity | $100,000 |
| Strategy net P&L | $1,470 |
| Strategy <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="Compound annual growth rate. It converts the full-period return into an annualized growth pace." data-tooltip="Compound annual growth rate. It converts the full-period return into an annualized growth pace." aria-label="CAGR: Compound annual growth rate. It converts the full-period return into an annualized growth pace.">CAGR</span> | 0.16% |
| Strategy max <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="The fall from a prior equity high to a later low. It measures the pain a trader had to sit through." data-tooltip="The fall from a prior equity high to a later low. It measures the pain a trader had to sit through." aria-label="drawdown: The fall from a prior equity high to a later low. It measures the pain a trader had to sit through.">drawdown</span> | -0.42% |
| Trades | 50 |
| <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="The comparison hurdle. For a single ETF strategy, buy-and-hold of the same ETF is the first test to beat." data-tooltip="The comparison hurdle. For a single ETF strategy, buy-and-hold of the same ETF is the first test to beat." aria-label="Benchmark: The comparison hurdle. For a single ETF strategy, buy-and-hold of the same ETF is the first test to beat.">Benchmark</span> | Buy and hold XLE |
| Benchmark CAGR | 13.03% |
| CAGR spread | -12.87% |
| Costs/stress | Base friction and <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="A simulation method that repeatedly reshuffles the same historical trades to see how much the sequence alone changes the path." data-tooltip="A simulation method that repeatedly reshuffles the same historical trades to see how much the sequence alone changes the path." aria-label="Monte Carlo: A simulation method that repeatedly reshuffles the same historical trades to see how much the sequence alone changes the path.">Monte Carlo</span> sequence stress included |
| Verdict | Trailed buy-and-hold on CAGR before costs |
The Setup
I’m testing a simple but non-obvious version of mean reversion in XLE: when energy names get pushed below a short-term lower bound, do they tend to snap back rather than keep trending lower? The idea is that sector ETF flows, commodity-linked overreaction, and periodic macro shocks can create temporary dislocations that price eventually digests.
Results
| Metric | Strategy | Benchmark | Why it matters |
|---|---|---|---|
| Final Equity | $101,470 | $301,093 | Ending account value before full friction modeling. |
| CAGR | 0.16% | 13.03% | Annualized growth; the first hurdle is buy-and-hold. |
| Max Drawdown | -0.42% | -64.17% | Worst peak-to-trough decline. |
| <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="Return per unit of volatility. It asks whether the strategy was paid enough for the ride it took." data-tooltip="Return per unit of volatility. It asks whether the strategy was paid enough for the ride it took." aria-label="Sharpe Ratio: Return per unit of volatility. It asks whether the strategy was paid enough for the ride it took.">Sharpe Ratio</span> | 3.61 | 0.56 | Return earned per unit of volatility. |
| <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="Like Sharpe, but it only penalizes downside volatility, which is often what traders actually care about." data-tooltip="Like Sharpe, but it only penalizes downside volatility, which is often what traders actually care about." aria-label="Sortino Ratio: Like Sharpe, but it only penalizes downside volatility, which is often what traders actually care about.">Sortino Ratio</span> | 0.40 | 0.73 | Like Sharpe, but focused on downside volatility. |
| <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="Annualized return divided by maximum drawdown. It asks how much growth the strategy earned for the pain it caused." data-tooltip="Annualized return divided by maximum drawdown. It asks how much growth the strategy earned for the pain it caused." aria-label="Calmar Ratio: Annualized return divided by maximum drawdown. It asks how much growth the strategy earned for the pain it caused.">Calmar Ratio</span> | 0.39 | N/A | CAGR divided by max drawdown. |
| <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="Gross profits divided by gross losses. Above 1 means winners outweighed losers before considering other constraints." data-tooltip="Gross profits divided by gross losses. Above 1 means winners outweighed losers before considering other constraints." aria-label="Profit Factor: Gross profits divided by gross losses. Above 1 means winners outweighed losers before considering other constraints.">Profit Factor</span> | 1.92 | N/A | Gross profits divided by gross losses. |
| <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="Average winning trade divided by average losing trade. It shows whether wins are large enough to pay for losses." data-tooltip="Average winning trade divided by average losing trade. It shows whether wins are large enough to pay for losses." aria-label="Payoff Ratio: Average winning trade divided by average losing trade. It shows whether wins are large enough to pay for losses.">Payoff Ratio</span> | 1.28 | N/A | Average winner divided by average loser. |
| Median Trade | $14 | N/A | A reality check against a few outsized trades. |
| <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="The share of the test period where capital was actually in the market. Lower exposure can change how returns should be judged." data-tooltip="The share of the test period where capital was actually in the market. Lower exposure can change how returns should be judged." aria-label="Exposure: The share of the test period where capital was actually in the market. Lower exposure can change how returns should be judged.">Exposure</span> | 13.87% | 100.00% | Percentage of days with capital at risk. |


I do not think the backtest supports the original mean-reversion hypothesis in a strong way. Final equity was 101,469.67 from 100,000, for net P&L of 1,469.67 and CAGR of 0.162%. That is economically small, and the statistical limit matters here: the closed-trade bootstrap puts the 95% CI for mean P&L at -1.03 to 61.08, the Wilcoxon p-value is 0.2548, and the win-rate binomial test is 60.8% with p…
Statistical Limits And Regime Dependence
The key question is whether the evidence survives sampling uncertainty, clustered trades, and regime shifts. P-values are diagnostics, not trading signals.
| Statistical Check | Evidence | Why it matters |
|---|---|---|
| Bootstrap mean trade P&L | $-1.03 to $61.08 95% interval | Tests whether the <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="The typical profit or loss per closed trade. Small values are fragile because costs can erase them quickly." data-tooltip="The typical profit or loss per closed trade. Small values are fragile because costs can erase them quickly." aria-label="average trade: The typical profit or loss per closed trade. Small values are fragile because costs can erase them quickly.">average trade</span> edge survives resampling. |
| Bootstrap mean positive rate | 97.0% | Useful for direction, but not a substitute for <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="The later portion of the historical test, used here to check whether the result held up after the initial sample." data-tooltip="The later portion of the historical test, used here to check whether the result held up after the initial sample." aria-label="out-of-sample: The later portion of the historical test, used here to check whether the result held up after the initial sample.">out-of-sample</span> proof. |
| Binomial win-rate diagnostic | p=0.1608 | Checks whether the hit rate is unusual versus a neutral 50% baseline. |
| Wilcoxon trade-P&L diagnostic | p=0.2548 | Non-parametric check of whether trade P&L is centered above zero. |
| Equity-return serial dependence | p=0.0000 | Flags whether daily strategy returns are path-dependent. |
| Closed-trade serial dependence | p=0.0007 | Checks whether trade outcomes cluster. |
| Analysis Area | Status | Artifact |
|---|---|---|
| trade_edge | ready | statistical_diagnostics.rds/per_trade_stats.rds |
| win_rate | ready | statistical_diagnostics.rds/per_trade_stats.rds |
| path_risk | ready | stress_summary.rds |
| cost_sensitivity | ready | friction_summary.rds |
| parameter_robustness | ready | parameter_sensitivity.rds |
| sample_split | ready | stress_summary.rds |
| walk_forward | ready | walk_forward_summary.rds |
| serial_dependence | ready | statistical_diagnostics.rds |
Regime dependency: this setup can degrade when volatility expands, gaps dominate, or the sector enters a persistent trend. The forward-degradation rows test stability, not parameter-optimization skill.
Costs And Stress Test
I now run a simple friction pass before drafting the article. For this strategy test, the base case assumes 2.00 bps of <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="The difference between the expected execution price and the actual fill. It usually gets worse in fast or illiquid markets." data-tooltip="The difference between the expected execution price and the actual fill. It usually gets worse in fast or illiquid markets." aria-label="slippage: The difference between the expected execution price and the actual fill. It usually gets worse in fast or illiquid markets.">slippage</span> per side and 1.00 bps of spread cost per side, or 6.00 bps per round trip. That is still a model, but it is a better starting point than pretending fills are free.
| Friction Check | Value |
|---|---|
| Gross net P&L | $1,470 |
| Base <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="The full entry-plus-exit trading cost for one completed trade, expressed here in basis points." data-tooltip="The full entry-plus-exit trading cost for one completed trade, expressed here in basis points." aria-label="round-trip cost: The full entry-plus-exit trading cost for one completed trade, expressed here in basis points.">round-trip cost</span> | $70 |
| <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="Strategy profit after the friction model subtracts estimated spread, slippage, and commission costs." data-tooltip="Strategy profit after the friction model subtracts estimated spread, slippage, and commission costs." aria-label="Cost-adjusted net P&L: Strategy profit after the friction model subtracts estimated spread, slippage, and commission costs.">Cost-adjusted net P&L</span> | $1,400 |
| Gross profit factor | 1.92 |
| Cost-adjusted profit factor | 1.98 |
| <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="The estimated round-trip cost level where the historical net profit would be fully consumed by trading costs." data-tooltip="The estimated round-trip cost level where the historical net profit would be fully consumed by trading costs." aria-label="Break-even round-trip cost: The estimated round-trip cost level where the historical net profit would be fully consumed by trading costs.">Break-even round-trip cost</span> | 125.71 bps |
| Stress Check | Value |
|---|---|
| Monte Carlo simulations | 2000 |
| Observed trade-order max drawdown | -0.42% |
| <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="A conservative drawdown estimate: only about 5% of reshuffled paths were worse than this level." data-tooltip="A conservative drawdown estimate: only about 5% of reshuffled paths were worse than this level." aria-label="95th percentile drawdown: A conservative drawdown estimate: only about 5% of reshuffled paths were worse than this level.">95th percentile drawdown</span> | -0.43% |
| Median simulated ending P&L | $1,469 |
| <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="The low-end bootstrapped outcome. It asks what the strategy looked like when the sampled trades were less favorable than history." data-tooltip="The low-end bootstrapped outcome. It asks what the strategy looked like when the sampled trades were less favorable than history." aria-label="Weak-path ending P&L: The low-end bootstrapped outcome. It asks what the strategy looked like when the sampled trades were less favorable than history.">Weak-path ending P&L</span> | $147 |
| High-cost net P&L | $1,329 |
| <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="The earlier portion of the historical test, used here as a rough development-period diagnostic." data-tooltip="The earlier portion of the historical test, used here as a rough development-period diagnostic." aria-label="In-sample: The earlier portion of the historical test, used here as a rough development-period diagnostic.">In-sample</span> net P&L | $-148 over 35 trades |
| Out-of-sample net P&L | $1,617 over 16 trades |
| Out-of-sample profit factor | 4.62 |
| Forward average-trade degradation | 2493.96% |
| Forward profit-factor degradation | 446.94% |
| Walk-forward windows | 3 anchored test windows |
| Walk-forward positive test windows | 100.00% |
| Worst walk-forward test P&L | $3 |
| Median walk-forward avg-trade degradation | 557.35% |
| Parameter sensitivity | 27 <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="Percentage Bands, a band-style indicator that marks stretched prices relative to recent behavior." data-tooltip="Percentage Bands, a band-style indicator that marks stretched prices relative to recent behavior." aria-label="PBands: Percentage Bands, a band-style indicator that marks stretched prices relative to recent behavior.">PBands</span> variants tested; best net P&L $2,084 at n=25, sd=1.5, stop=7.00% |
| Baseline grid result | $1,744 net P&L; profit factor 2.51 |

Watch the break-even round-trip cost: above that level, the historical edge is gone. Monte Carlo separates edge from path luck; the in/out row is a closed-trade split, not walk-forward optimization.
What Breaks It

The trade P&L distribution is right-skewed: there are a lot of small-to-moderate gains, a meaningful cluster of losses below zero, and a few larger winners that pull the average up. That matters because the edge is not coming from a clean, symmetric payoff; it depends on occasional outsized snapbacks to offset many mediocre trades.
Verdict
I set out to test whether a simple lower-band PBands mean-reversion rule in XLE could capture temporary overshoots and turn them into a repeatable long-only edge.
What would change my mind: the same rule needs to survive costs while closing the gap versus buy-and-hold, or prove useful as a timing overlay rather than a standalone strategy.
R Notes
The reproducibility contract is the saved artifact set plus the R scripts that created it. The key JSON/RDS files are recorded in the frontmatter.
# Rebuild the evidence layer for one saved strategy output
source('backtesting_blog_posts/statistical_diagnostics.R')
source('backtesting_blog_posts/walk_forward_diagnostics.R')
source('backtesting_blog_posts/analysis_test_selector.R')
out_dir <- file.path('backtesting_blog_posts', 'outputs', 'strat_pbands_mean_reversion_long_only_xle')
per_trade <- readRDS(file.path(out_dir, 'per_trade_stats.rds'))
portfolio <- readRDS(file.path(out_dir, 'portfolio_summary.rds'))
diagnostics <- build_statistical_diagnostics(per_trade, portfolio)
walk_forward <- build_walk_forward_summary(per_trade)
test_plan <- build_analysis_test_plan(out_dir)For the current PBands series, parameter_sensitivity uses a deterministic next-bar rerun helper because native quantstrat parameter clones can fail to propagate osMaxPos limits safely. Future strategy families should call register_parameter_sensitivity_builder() with their own sensitivity builder.
Important: Backtests are simulations. Past performance of a simulated strategy does not guarantee future results. Results do not yet fully account for real-world factors including <span class="lf0-tooltip" tabindex="0" style="border-bottom: 1px dotted currentColor; cursor: help;" title="The gap between the price buyers bid and sellers ask. Crossing that gap is a real trading cost." data-tooltip="The gap between the price buyers bid and sellers ask. Crossing that gap is a real trading cost." aria-label="bid-ask spread: The gap between the price buyers bid and sellers ask. Crossing that gap is a real trading cost.">bid-ask spread</span>, market impact, borrow costs, or taxes. This is for educational purposes only and is not investment advice.
Leave a Comment