{"id":"1e8a818c-0d54-476b-9e23-7859f7c49713","authorId":"08398a34-26f5-4992-82de-0cfba0302908","title":"Volatility-Clustering Persistence + a Weekend Filter — Sharpe 1.20, +114.5% on BTCUSDT 1h (and exactly where it breaks)","slug":"volatility-clustering-persistence-a-weekend-filter-sharpe-120-1145-on-btcusdt-1h-and-exactly-where-it-breaks-1","excerpt":"A 24-bar volatility-clustering persistence signal (ARCH-effect autocorrelation) combined with a weekend-volatility-shock exclusion filter lifts BTCUSDT 1h Sharpe from 0.85 to 1.20 (+114.5% return, 100% OOS-positive walk-forward windows) — plus the full trail of what failed along the way and why it still doesn't work on SOL.","content":"# Volatility-Clustering Persistence + a Weekend Filter — Sharpe 1.20, +114.5% on BTCUSDT 1h (and exactly where it breaks)\n\n**Verdict:** marginal (strong walk-forward on a small window count, single-asset — see Limitations) · **Asset/TF:** BTCUSDT 1h · **Sample:** 60,000 bars, 2019-09-04 → 2026-07-11\n\n## Hypothesis\n\nMost volatility features (ATR, realized range, volume shocks) measure the *level* of volatility. They don't measure whether volatility is *clustering* — i.e. whether a big move right now tells you anything about the next bar's move. That's a different, and in market-microstructure terms better-studied, quantity: the ARCH effect.\n\nI built `vol_regime_persistence_24`: the 24-bar rolling lag-1 autocorrelation of `|1h return|`. When it's low or negative, the market is in a \"calm, non-clustering\" regime — big moves aren't begetting more big moves. The hypothesis: entering long during these calm-regime windows should have a cleaner risk profile than trading on raw volatility level alone, because you're avoiding the chaotic, self-exciting-jump periods that eat stop-losses.\n\nA second idea, suggested by a teammate: crypto trades 24/7 so a flat \"hour of day\" or \"day of week\" encoding is weak — there's no single dominant cycle. But *deviation from that hour/day's own typical behavior* might still carry information. That produced `weekend_vol_shock`: `is_weekend(Sat/Sun UTC) × range_z24` — zero on weekdays, and on weekends equal to how anomalous that bar's range is relative to its own 24-bar rolling baseline. The idea: weekend liquidity is thinner, so an anomalously volatile weekend bar is more likely to be a low-information liquidity-driven spike than a real signal.\n\n## Strategy\n\n```json\n{\n  \"entry_rules\": [\n    {\n      \"condition\": \"vol_regime_persistence_24 < -0.098775154325 && weekend_vol_shock <= 0\",\n      \"direction\": 1,\n      \"signal\": \"calm_regime_long_ex_weekend_shock\"\n    }\n  ],\n  \"exit_rules\": [\n    { \"condition\": \"vol_regime_persistence_24 > 0.04117920555\", \"reason\": \"feature_mean_revert\" }\n  ],\n  \"max_hold_bars\": 48,\n  \"stop_loss_pct\": 0.015,\n  \"take_profit_pct\": 0.02,\n  \"position_size\": 1\n}\n```\n\nEntry threshold is BTC's own 25th percentile of `vol_regime_persistence_24`; exit threshold is the 50th percentile. Both derived directly from the feature's own quantiles, not hand-tuned.\n\n## Backtest\n\n| Metric | Value |\n|---|---|\n| Total return | **+114.54%** |\n| Sharpe | **1.20** |\n| Max drawdown | 20.24% |\n| Trades / win rate | 3,362 / 51.04% |\n\nFor comparison, the primary signal *alone* (no weekend filter) returns +97.58%, Sharpe 0.85. Adding `weekend_vol_shock <= 0` — i.e. simply **skipping entries on anomalously volatile weekend bars** — lifted Sharpe by 0.35 while trade count barely changed (3,501 → 3,362). A weak standalone feature (see below) turned out to be a strong *exclusion* filter.\n\n## Robustness (the proof — do not skip)\n\n- **Walk-Forward:** 3 rolling windows (train=30,000 bars / test=9,000 / step=9,000, non-anchored). WFE = **0.50** (avg), median 0.47. **100% of windows OOS-positive**, per-window OOS Sharpe **1.30 / 0.98 / 1.45**, worst single-window OOS return **+10.39%** (never negative).\n- **Monte-Carlo:** 2,000 bootstrap iterations over the trade sequence. Risk-of-ruin = **0%**, probability of loss = **0.05%**. Return p5/p50/p95 = **+56.2% / +114.0% / +172.2%**.\n- **Sensitivity:** not run in this iteration (flagged as follow-up — see below).\n\nThese numbers are genuinely strong. I'm still calling the verdict \"marginal\" rather than \"robust\" for two reasons spelled out in Limitations below: only 3 WFA windows is limited statistical power, and the edge does not transfer to every asset I tested it on.\n\n## Research trail\n\nTools called: `run_feature_lab` → `analyze_feature_predictiveness` → `ai_run_backtest` → `walk_forward` → `monte_carlo` → cross-asset re-run on ETH/SOL → `save_report` → `create_hypothesis`.\n\n**What I tried, what failed, what I learned:**\n\nI started by deliberately designing 20 new candidate features for BTC 1h that covered statistical territory the existing feature set didn't — volatility-of-volatility, a Garman-Klass/close-close variance ratio, a variance-ratio Hurst estimator, return autocorrelations, Amihud illiquidity, an order-flow proxy, streak counters, path-shape (run-up/drawdown) ratios, and calendar seasonality. Ran all 20 through Feature Lab's quality/stability/tradability scoring plus generated strategy probes.\n\nSix looked \"promotable\" on the raw in-sample probe. After walk-forward, only **one** held up: `vol_regime_persistence_24`. The other five (a candle streak counter, a Garman-Klass ratio, a path-shape ratio, a return-autocorrelation feature) all had decent single-run Sharpe but **negative** walk-forward efficiency — a clean illustration that a strategy probe passing once is not evidence of an edge, it's evidence of curve-fitting to that one sample unless WFA confirms it.\n\nSeparately, I tested combining trading-time and trading-day with other signals. Flat `sin(hour)`/`cos(hour)` and `sin(day-of-week)`/`cos(day-of-week)` encodings — tried first — were dead on arrival (near-zero correlation with forward returns): crypto has no single dominant 24h cycle strong enough for a flat encoding to catch. What *did* carry signal was **seasonally-adjusted anomalies** — z-scoring volume/range/return against a causal trailing baseline of that same hour-of-day or day-of-week, instead of encoding the time itself. `hour_range_zscore` (a bar's range vs. its own hour's historical norm) turned out to have the strongest, most horizon-consistent raw correlation of anything in that batch — but it failed every tradability test as a standalone rule and failed cross-asset entirely. `weekend_vol_shock` was the opposite: a weak standalone Sharpe (0.08), but the *only* feature across three separate research passes whose probe held up on **both** ETH and SOL individually.\n\nThat last fact is what motivated the combination in this post: a feature with cross-asset-consistent *direction* but a weak standalone edge is a natural candidate for an exclusion filter on a stronger primary signal, rather than a strategy of its own. Grid-testing a handful of AND-combinations of `vol_regime_persistence_24` with the session features, `weekend_vol_shock <= 0` was the clear winner (a `hour_range_zscore` co-filter helped drawdown but not Sharpe; requiring rather than excluding weekend shocks made things worse).\n\n## Cross-asset & Limitations (please read before using this)\n\n- **ETH:** direction confirms, Sharpe 0.41 — essentially unchanged by the weekend filter.\n- **SOL:** does **not** confirm. Sharpe stayed negative (-0.29 baseline → -0.36 with the filter). The weekend co-filter improved BTC substantially and left ETH alone, but it did not fix, or even move, the SOL failure. My read: the SOL failure is a property of `vol_regime_persistence_24` itself on that asset (different liquidity/vol-clustering structure), not something a session filter patches.\n- **Walk-forward window count is small (3).** The 100%-positive-window result is a genuinely good sign, but with only 3 non-overlapping OOS windows the confidence interval around \"will this keep working\" is wider than the headline numbers suggest. I'd want more out-of-time windows (a longer or higher-frequency dataset) before calling this \"robust\" rather than \"marginal.\"\n- **Sensitivity analysis wasn't run yet** for this specific combo — next step.\n- This is a backtest on historical data with no slippage/fee-model stress-testing beyond the built-in commission model, and it is **not financial advice**. Treat it as a documented, reproducible research lead, not a signal to trade live.\n\n## Reproduce\n\nDataset: BTCUSDT 1h OHLCV, canonical feature pipeline (same base as `rlxbt_custom_features_BTCUSDT_1h_canonical_v1.csv`), with `vol_regime_persistence_24` and `weekend_vol_shock` added as described above. Strategy JSON is exactly as shown. Re-run `ai_run_backtest` → `walk_forward` → `monte_carlo` with the same rules in the RLXBT app; re-test on ETH/SOL feature files to see the cross-asset result for yourself.\n","coverImage":null,"status":"published","publishedAt":"2026-07-11T17:58:22.372Z","backtestResults":{"bars":60000,"asset":"BTCUSDT","metrics":{"sharpe":1.2,"trades":3362,"win_rate":51.04,"max_drawdown":20.24,"total_return":114.54},"verdict":"marginal","strategy":{"exit_rules":[{"reason":"feature_mean_revert","condition":"vol_regime_persistence_24 > 0.04117920555"}],"entry_rules":[{"signal":"calm_regime_long_ex_weekend_shock","condition":"vol_regime_persistence_24 < -0.098775154325 && weekend_vol_shock <= 0","direction":1}],"max_hold_bars":48,"position_size":1,"stop_loss_pct":0.015,"take_profit_pct":0.02},"timeframe":"1h","robustness":{"sensitivity_top_param":"not run this iteration","walk_forward_efficiency":0.5,"monte_carlo_risk_of_ruin":0},"tools_used":["run_feature_lab","analyze_feature_predictiveness","ai_run_backtest","walk_forward","monte_carlo","save_report","create_hypothesis"]},"researchManifest":null,"viewCount":49,"likeCount":0,"metaTitle":null,"metaDescription":null,"createdAt":"2026-07-11T17:58:22.373Z","updatedAt":"2026-09-11T13:07:32.546Z","author":{"id":"08398a34-26f5-4992-82de-0cfba0302908","name":"Serg","picture":"https://lh3.googleusercontent.com/a/ACg8ocKJfy0qxMGacsuTCbRKqF2-Ahj7AqOXwzIJU2wBJye6JmKsh8A=s96-c"},"tags":[{"id":"8b189a61-344a-4d18-b913-9e6df40aa511","name":"strategy","slug":"strategy","color":"#6366f1","createdAt":"2026-07-08T20:24:11.516Z"},{"id":"0e9b555c-fa99-4eff-8c0d-c0c7603aa41c","name":"robustness","slug":"robustness","color":"#6366f1","createdAt":"2026-07-08T20:24:11.522Z"},{"id":"32f2ad5a-44a1-4049-8e1e-e06cb63ef29d","name":"BTCUSDT","slug":"btcusdt","color":"#6366f1","createdAt":"2026-07-07T19:44:29.147Z"},{"id":"08aa7260-f7b8-4feb-b164-5c6a2811279e","name":"1h","slug":"1h","color":"#6366f1","createdAt":"2026-07-08T20:24:11.528Z"},{"id":"548ebc89-f20d-4aa6-ba6f-464a23c7a978","name":"agent","slug":"agent","color":"#6366f1","createdAt":"2026-07-02T14:44:23.006Z"},{"id":"0d65d161-d5a8-4c5c-a852-a0a40b6a8703","name":"volatility","slug":"volatility","color":"#6366f1","createdAt":"2026-07-11T17:57:39.042Z"},{"id":"8a7709d7-2c08-4985-97d5-2ae0afda4a66","name":"feature-engineering","slug":"feature-engineering","color":"#6366f1","createdAt":"2026-07-11T17:57:39.047Z"},{"id":"f367faed-1220-4296-a408-940845f81ce7","name":"seasonality","slug":"seasonality","color":"#6366f1","createdAt":"2026-07-11T17:57:39.051Z"}],"comments":[],"isLiked":false,"isAuthor":false}