The panels now say what each prediction answers (where the price will be
in 24 hours; whether the next 24 hours will be more volatile than normal)
instead of naming the models. The computed hours are saved, so wording
changes redraw in seconds with --redraw.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3 days ago
Redraw the regime-vs-range figure with both live DVOL models
5 Apr - 25 Sep 2026, inside the DVOL regime model's hold-out: the 24h 80%
range held 80.0%, the regime call was right 77.4% of hours, and the two
agree (Spearman 0.59; median width 5.3% when HIGH vs 4.0% when LOW;
realised move 1.5% vs 0.9%).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add Deribit DVOL to the live price range as a fourth HAR input
scripts/backtest_range_dvol.py: with implied hourly variance (DVOL/100)^2 /
8760 in the log-HAR regression, the interval score improved 0.3/0.2/0.4/0.7%
at 1/6/24/72h (significant at 1, 24 and 72h) and the 50% range narrowed about
6%, with coverage slightly below nominal (48/79/95%). DVOL takes the largest
weight in the fit (0.67-0.71).
The fit now produces two HAR sets, with and without DVOL, each with its own
quantiles; forecasts use the DVOL set when the frame carries DVOL and the
plain set otherwise, and report which (volatility_inputs). The quarterly
refit downloads two years of DVOL with the candles. The raw index rides on
the volatility frame as "dvol", excluded from model features.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 days ago
Add Deribit DVOL implied-volatility features as a training option
DVOL is the options market's 30-day expectation of BTC volatility, which
the price series does not contain. scripts/fetch_dvol.py downloads the full
hourly history (48,832 hours from March 2021, no gaps).
src/volatility.add_implied_features derives five scale-free features: DVOL
against its 90-day median, against realised 24h volatility (variance risk
premium) and against the regime threshold, and its 6h and 24h changes.
In a quick logistic check on the regime target, adding them raised hold-out
accuracy from 72.6% to 73.9% and ROC-AUC from 0.786 to 0.799. The
VAE+Transformer is retrained with --dvol on Colab to test it properly; the
existing 33 features and the live model are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Methodology figure: the three chronological splitting schemes
Dates computed as the experiments compute them: the 31,234-hour contiguous
run behind the regime ladder and hold-out (Mar 2023 - Oct 2026), and the 15
quarterly refits of the price range.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Drop 460 duplicate headlines before scoring; rerun news-fusion ablation
The same title at the same timestamp appeared twice in 460 rows (1.8%),
inflating that hour's news volume. The scored file now holds 25,553 unique
headlines and the scoring script drops duplicates itself. The news-fusion
ablation and the Fear & Greed timing study were rerun on the cleaned file;
no conclusion changed (fusion still ties or trails the no-news model).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Test options expiry and US market holidays as known-in-advance events
Monthly and quarterly Deribit expiries (last Friday, 08:00 UTC) showed no
effect beyond an ordinary Friday 08:00 (quarterly +0.06, 95% CI -0.58 to
+0.87). Weekdays the NYSE is closed were clearly calmer: -0.62 at the US
session hour and -0.28 over the rest of the day.
A holiday variance factor (0.74 on average, refitted quarterly) on the live
HAR range brought holiday-window 80% coverage from 86-90% down towards 80%
and improved the holiday-window interval score by 1-3%; overall the gain is
0.02-0.09% (significant at 6h and 24h only).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
Plot the regime model against the price range out of sample; methodology draft
Apr-Sep 2026, every hour, out of sample for both live models: the 24h 80%
range held 82.9%, the VAE+Transformer regime call was right 75.3% of hours,
and the two agree (rank correlation 0.56; median range width 5.7% when the
regime says HIGH vs 4.3% when LOW; realised move 1.4% vs 0.9%).
Also the first methodology draft (LaTeX and plain text).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ablation ladder: validation-tuned cut-offs and a significance test
Accuracy and MCC were read at a fixed 0.5, which penalised a model whose
probabilities rank well but sit off-centre: the VAE+Transformer had the best
ROC-AUC (0.762) and one of the lowest accuracies. Every rung now takes the
cut-off with the best MCC on its own validation block (the last fifth of
training, never the test fold); the 0.5 figures are kept beside them.
Test predictions are pooled across folds and the VAE+Transformer's ROC-AUC is
compared with every other rung by a moving-block bootstrap (one-week blocks),
giving a 95% interval and p-value per comparison. Per-fold scores are saved.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Ablation ladder on the deployed regime target, with HAR and GARCH rungs
--target regime asks the live question (next 24h above the trailing 90-day
median), with persistence as the current regime and two econometric rungs on
the same purged folds: HAR (Corsi 2009) and GARCH(1,1) by maximum likelihood,
each turned into a probability by logistic calibration on the training block.
GARCH forecasts mean variance against a median threshold, so an uncalibrated
cut scored 43% where the calibrated one scores 61%.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Test the VAE+Transformer on top of the live HAR range: no gain
With HAR as the base, every network variant (Transformer, VAE+Transformer,
and VAE+Transformer fed HAR's own day/week/month levels) scored within 0.1%
of HAR at every horizon, none better at p < 0.01. The +0.4-0.7% the hybrid
found over the one-week range was the multi-scale volatility HAR now models
explicitly.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Size the live price range with a HAR blend of day, week and month
scripts/backtest_range_upgrades.py tested four upgrades walk-forward on
2023 - Sep 2026. Only the HAR blend (Corsi 2009) won: a log-linear mix of the
last day, week and month of seasonally adjusted variance, fitted per horizon,
improved the 80% interval score by 1.9/1.3/1.4/2.2% at 1/6/24/72h (all
p < 0.001, Diebold-Mariano) with 50/80/95% coverage kept. A high/low range
estimator, asymmetric quantiles and adaptive conformal widths did not help.
The live fit now learns the HAR weights each quarter; forecasts fall back to
the one-week volatility for older params or a frame shorter than a month.
The card's backtest line now reports the HAR record.
Also the Fear & Greed timing study (the index follows price: r = 0.59 one
way, 0.01 the other) and a position-sizing backtest (volatility-managed
exposure did not beat buy-and-hold over 2023-2026).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Test whale transfers and labelled exchange flows against BTC volatility
- whale_event_study.py: every >= 1,000 BTC transaction since Feb 2022
(BigQuery crypto_bitcoin, 268,446 rows) against 5-minute BTC bars, by
largest-recipient tier, with a vectorised event study and an hourly
lead-lag test that removes the hour-of-week pattern first.
- exchange_flow_study.py: the same tests on transactions touching 22
publicly labelled exchange wallets, split into inflow, outflow and
internal moves. Inflows follow volatility (t = 7.5) far more than they
lead it (t = 2.3, +0.02 pp R2).
- Colab cells that pull both datasets from BigQuery, and the checked
exchange address list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add Fed/CPI calendar features to the volatility regime model; new studies
- src/volatility.py: optional release-timing features (next release within
24h/6h, hours to next, hours since last), from the published calendar the
live price-range forecaster already uses. Off by default; serving computes
them so a model trained with --calendar can be deployed, while the live
model keeps selecting its own 33 columns.
- train_vae_transformer_artifacts.py: --calendar and --tag, so the experiment
never overwrites the live artifact, plus the persistence baseline on the
held-out rows in the metadata.
- Hybrid ablation of the price range (Transformer, VAE, self- vs
cross-attention news fusion) walk-forward against the live model.
- Jobs report and PCE added to the macro event study; the jobs report tested
as a calendar candidate for the price range (no gain, kept out).
- Trump Truth Social posts timed to the minute against 5-minute BTC bars.
- Side-by-side method diagram of the two live forecasts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Put the validated price-range forecast live, with its own forward test
"Where will the price be?" now leads the Forecast tab: a range for 1h,
6h, 24h and 3 days, centred on the current price and stated at 50, 80 and
95%. The card says outright that direction is 50/50, because six
directional configurations measured chance and a centred number should not
look like a call.
The rules are exactly the ones validated walk-forward on 32,700 hourly
forecasts per horizon (scripts/backtest_improvements.py): fat-tailed
quantiles, an hour-of-week variance factor, and a Fed/CPI calendar factor,
fitted on the two years ending 72h before the fit. The calendar runs to
December 2027 from the primary sources; CPI dates come from bls.gov
release filenames and the published 2026-27 schedule.
The Space refits the rules itself every quarter from Binance, in a
background thread, keeping the newer of the stored refit and the shipped
artifact. A stale fit is used while the refit downloads: last quarter's
rules beat no forecast.
Forward test: every horizon's bounds are recorded…
Fix the price range three ways, validated walk-forward
Three fixes to the range forecast, each targeting one failure the plain
backtest found, scored on every hour of 2023 to September 2026:
fat tails empirical quantiles of standardised outcomes, replacing
the normal: 50/80/95% ranges now hold 49-50/79-80/95%
hour of week a variance factor per hour of the week, summed over the
hours a range actually covers
Fed/CPI hours a further factor for release hours and the two after;
the dates are published ahead, so this is not look-ahead
Walk-forward, not a single split: every quarter the fixes are refitted on
the previous two years, ending 72h before the quarter so no training
outcome overlaps it. The frozen 2019-2022 fit is kept for comparison, and
walk-forward wins where it matters. The weekend factor fell from 0.65 in
early 2023 to 0.42-0.44 through 2024-25: weekends got much quieter relative
to weekdays after the ETFs, which a frozen fit cannot follow. At 1h the
frozen fi…
Backtest the live price forecast against the real price
The forecast issued live -- centre at the current price, range from the
previous week's hourly volatility -- reissued at every hour from 2023 to
September 2026 and scored against the price that followed: 32,700
forecasts per horizon, none of which sees its own future.
The 80% range holds: 86.5% at 1h, 84.6% at 6h, 81.9% at 24h, 80.2% at 72h,
and every year from 2023 to 2026 lands within two points of its average.
Two systematic errors, both fixable:
The ranges are the wrong shape. 50% ranges hold 56-66% of the time and 95%
ranges only 92-94%: too wide in the middle, too narrow in the tails. That is
a normal distribution fitted to a fat-tailed market, and a Student-t fixes
it.
Weekends are mis-sized. At 1h the 80% range holds 94.2% on weekends against
83.4% on weekdays, because one week of volatility mixes busy weekdays into
quiet Saturdays.
The centre misses by 0.20% at 1h and 2.18% at 72h (median), and the price
rose 50.7-52.9% of the time: direction is still the coin flip it was…
Add fig10: blockchain activity against the Fed, on one daily scale
Spike days (top 5% of weekday-adjusted daily jumps) in active addresses,
transaction count and hash rate, against Fed and CPI release days. Each
day's move is scaled by the prior 30 days and compared with the same
weekday in other weeks.
On-chain activity does not lead price. The day after a spike moves 0.97x
to 0.99x a normal day, and lead-lag correlations before any move stay
inside the 95% band for all three measures. Active-address spikes do
coincide with 1.60x days, but that is the same day, before the figures
are even complete: people transact because the price is moving.
This also qualifies fig9. Over a whole UTC day, Fed decision days run at
0.98x and CPI days 1.16x, neither distinguishable from 1. The release hour
doubles, but one hour is too small a share of a day to show at daily
resolution, so the calendar is an intraday effect and belongs in the 1-6h
model rather than a daily one.
Market cap and MVRV are excluded: both are computed from the price.
Co-Authored-By: Claud…
Add two event studies: which global news moves Bitcoin
Topic study (fig8): 26,013 Bitcoin headlines tagged into twelve topics,
one burst of coverage counted as one event, hourly BTC traced 24h either
side against matched same-hour-of-week controls. No topic's excess after
its headline clears zero except exchange failures (+0.17 normal hours),
and that topic's excess BEFORE its headline is larger (+0.30): the news
reported the move. War +0.05, tariffs +0.05, both indistinguishable from
zero. A headline is written after its event, so this design cannot time
macro news; it is kept to show that.
Scheduled releases (fig9) remove the timing problem: the minute a Fed
decision or CPI report lands is fixed weeks ahead. Over 61 FOMC decisions
and 80 CPI reports the release hour runs at 2.0x and 2.2x the same hour in
other weeks (excess +1.31 [0.81, 1.84] and +1.54 [0.78, 2.41] normal
hours), with nothing beforehand (-0.16, -0.08). Direction is a coin flip:
57% and 54% of releases are followed by a rise.
CPI dates come from the archived release fil…
Add news to fig7, and test whether it comes before or after price
26,013 headlines with "Bitcoin" in the title, 2019 to June 2025, from
edaschau/bitcoin_news (Hugging Face, Open RAIL), scored by FinBERT as
P(positive) - P(negative). FinBERT rather than a lexicon because "Bitcoin
slips below $23K" carries no negative word.
News sentiment trails price like Fear & Greed does: 0.24 with the previous
day's return, 0.27 the same day, 0.03 with any later day.
Two results needed correcting before they could be shown:
News volume against move size first read 0.144 at exactly +7 days. That is
the calendar: about 14 headlines a day on weekdays and 3 at weekends, and
smaller weekend moves, so the series align at every multiple of seven.
Weekday-adjusted on both sides it is 0.08 before vs 0.13 after.
The most negative news quintile is followed by a +2.0% week, the one group
of ten whose interval clears the all-days line. But negative-news days are
mostly down days, so this could be price recovering. Regressed on the same
day's return and news sentiment togeth…
Add fig7: does sentiment move before the market, or after it
BTC and the Fear & Greed Index on one time axis in separate panels, since
a dual-axis overlay lets the apparent co-movement be set by the choice of
axis limits. Then the two tests the overlay cannot answer by eye.
Lead-lag correlation, 2,826 days: the index correlates +0.21 with the
previous day's return and decays slowly over two weeks, but at most 0.04
with any future day's return, at the edge of the 95% band. Sentiment
records the move; it does not anticipate it.
Forward 7-day return by zone, with a block bootstrap because overlapping
weekly returns understate uncertainty if resampled day by day. Greed and
extreme greed show positive means, but BTC rose about twentyfold over the
span and the average week returned +0.77%, so the panel draws that line:
every zone's interval contains it, and no mood is followed by a week that
differs from an ordinary one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
16 days ago
Drop the baseline and margin columns from the directional table
Six columns instead of eight, at the author's request. The baselines move
into the caption rather than out of the chapter: the table's claim is that
every configuration loses to a guess, and that has to stay checkable from
the same page -- it is also the exact criticism the chapter makes of a
paper that reports 79.50% with no baseline beside it.
ROC-AUC carries the result unaided, since 0.5000 is chance by
construction, so the narrower table still shows the finding: six rows on
0.50 and a control at 0.83.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Put the source's own reported accuracy beside the measurement
The source column named who each row answers to without showing the
disagreement, so a reader had to turn to the related-work table to find
that the 24h row's source reports 79.50% where this measures 50.3%. Both
numbers now sit on one line.
The three triple-barrier rows read "method, no figure": Lopez de Prado
supplies the labelling protocol and publishes no accuracy to compare, and
leaving the cell blank would have implied one was omitted.
Jaquart's figure carries a footnote rather than a bare 56%: it is measured
at 1-60 minute horizons, which this project never tested, and its own
authors state the 60-minute signal does not survive transaction costs. A
number that agrees with this result should not be presented as if it
contradicted it.
Also renders \textsuperscript in the Markdown copy, which was leaking as
literal LaTeX.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Label the project's own row instead of leaving a dash
The key column held an em dash on the one row that is this project's
measurement, which renders as an empty cell to anyone skimming. "Ours"
says what the row is at a glance, which is the row that most needs to be
findable.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
17 days ago
Add the Group D table, and render Markdown as Markdown
The directional result is the one an examiner is most likely to challenge,
so it gets what the ablation already had: a source column naming what each
row answers to, plus a second table setting the five studies against this
project's measurement with the protocol difference stated in the row.
The rows are authored once, in LaTeX, because that is the output nobody can
fix up by hand afterwards. That left the Markdown copy showing 30{,}969,
\% and \textbf{} to a reader. demath() translates on the way out instead,
so neither copy is maintained separately and they cannot drift.
Document renamed to related_work_four_comparisons.md; nothing referenced
the old name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A plain "\&" in a Python string is a SyntaxWarning now and an error in a
future version. The rendered output was already correct; this stops the
warning on every run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Name the source of every ablation rung, and fix a transcribed digit
The ablation table now carries a source column: each rung cites the study or
methodology section it answers to, which makes it checkable and makes
visible that exactly one rung in the ladder is this project's own.
Also fixes a wrong value. Verifying related_work_three_comparisons.md
against ablation_study.json found CNN+Transformer on BTC written as +22.26
where the measurement is +22.25, with CNN+VAE+Transformer likewise +22.60
against +22.65. Both came from hand-transcription into prose, not from the
measurements, which match the user's independent re-run on all 36 rungs to
within 0.005 points. The document now points at report_tables.md as
authoritative where the two disagree.
That is the whole argument for generating tables rather than typing them:
two digits drifted within hours of the numbers being measured.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Six tables in Markdown and LaTeX, read from the same files the figures
read. Nothing is transcribed, so re-running the experiments and this script
keeps the tables and the measurements in step.
That is the point rather than the convenience. Reformatting results by hand,
or by pasting them into a language model and asking for a table, is a step
where a digit changes and nobody notices, and a thesis is a bad place for
that. A model asked to restyle these should be given this output, so it is
only ever changing layout.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Record the twelve-rung ablation results and redraw the ladder figure
The results JSON from the Colab run, with its provenance recorded in the
file: A100, 12 rungs, 3 assets, 5 purged folds, 15 epochs, seq_len 48,
30,969 rows, repo commit 7b0fceb. run_ablation_study.py regenerates it.
fig2 now draws all twelve rungs across all three assets, as margin over the
majority class rather than accuracy -- accuracy alone puts the naive rungs
at a respectable-looking 50% and hides that they are the floor. A shaded
band marks the range the nine non-linear architectures occupy, 20.6 to 24.2
points, which is the figure's argument: they are indistinguishable from one
another, and the step that matters is the one to their left.
Two small fixes found while drawing it: the figure read the first key of the
results file as an asset, which a leading provenance note broke, and the
target-screen figure needed the same guard.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Complete the ablation ladder against the thesis methodology
Five rungs were missing. The ladder now covers every model named in
sections 3.1.5 and 3.1.9:
added CNN, LSTM baseline 4
added gradient boosting baseline 3, previously quoted from a
separate screen rather than measured
on these folds
added CNN+Transformer ablation (b)
added CNN+VAE+Transformer ablation (d), baseline 6
The last is the architecture Contribution 1 claims as novel. No rung in
this study contained a CNN at all, so the proposed model had never been
measured -- the thesis claimed an architecture the code does not implement
and the ablation could not speak to.
The three CNN rungs share one CNNEncoder, so they differ only in what
follows the convolution, which is what isolating the CNN's contribution
requires.
Gradient boosting skips with a note where scikit-learn is unavailable --
Windows Application Control blocks it…
Add the ablation, HAR benchmark, figures and three related-work comparisons
Fifteen studies in three groups -- volatility benchmarking, architecture
ablation, historic versus sentiment -- kept separate because a paper
benchmarking against HAR-RV is not evidence about whether a VAE earns its
place, and mixing them is how a related-work chapter ends up comparing
numbers that were never comparable.
run_har_benchmark.py regresses log realised volatility against HAR-RV on
identical folds and reports QLIKE as well as R2, which is what makes the
comparison like-for-like. Nine cells, three assets, three horizons: the
neural network wins two, both on BTC, and loses QLIKE in both. On ETH and
ADA ordinary least squares on the same 33 features wins both metrics at
every horizon. Improvement over HAR is 1.1-1.4x against the 5.4x claimed by
arXiv 2511.20105, whose HAR scores 0.128 where this one scores 0.32-0.36.
It also corrects an earlier figure in sota_volatility_comparison.md: the
2.1x reported there used a weak HAR specification taken from stored
metadata. Fitted pro…
19 days ago
Serve jump risk and volatility-rises on the volatility panel
Both ride on the volatility payload rather than taking endpoints of their
own: they read the same frame and the same 33 features, so each costs one
forward pass instead of another Binance page. A missing artifact yields
None and the row is omitted, rather than rendering dashes that look like a
failure.
Live, per asset, with each using its own horizon, sequence length and
threshold:
BTC rising 90.65% (12h) jump 65.21% (24h)
ETH rising 93.56% (12h) jump 49.95% (24h)
ADA rising 33.40% (1h) jump 42.46% (24h)
Each row prints its own baseline name and value beside its accuracy. The
first version of predict_sequence_regime took whichever baseline appeared
first and preferred the majority class, which reported jump risk at +19.37
points when its real margin over persistence is +14.76 -- a four-point
overstatement in the model's favour. It now takes the harder of the two,
which is correct for both targets automatically: volatility-rises binds on
majority (persistence is 34%, …
Train "will volatility rise", and close out sentiment with a null
The training script covers the best target found in this project. Across
sixteen candidates in two screens it won on all three assets, and its
persistence baseline is BELOW chance (39.0-39.5%) because volatility
mean-reverts, so the model supplies all of the skill rather than inheriting
it from autocorrelation:
BTC 0.8128 73.39% vs majority 51.5% +21.88
ETH 0.7921 71.06% vs majority 51.7% +19.32
ADA 0.7699 68.82% vs majority 51.4% +17.40
Horizons are per asset from the horizon sweep -- BTC 12h, ETH 12h, ADA 1h
-- rather than the 24h every deployed model inherited and none of them
should use. The label is imported from the screening script, so what trains
is what was screened. Artifacts store both baselines and a beats_majority
flag, and training warns when the majority class is not cleared.
Sentiment ablation: fifteen comparisons across three assets and three
working targets, with both daily sources lagged a full day so nothing
describes the hour it scores…
Both were inherited from the first volatility model and never tested.
Horizon: 24h, which every deployed model uses, is the worst or
second-worst choice on all three assets.
asset best margin 24h margin gain
BTC 12h +24.77 +21.90 +2.87
ETH 12h +23.30 +19.56 +3.74
ADA 1h +22.51 +17.79 +4.72
ADA degrades monotonically with horizon; BTC and ETH peak at 12h. The 1h
numbers are also the most trustworthy in the whole screen, because at a
one-hour horizon consecutive labels barely overlap, so the dependence that
inflates confidence elsewhere is largely absent.
Spillover: adding BTC's trailing volatility, momentum and volume z-score to
ETH and ADA does nothing. Ten comparisons, none clears +0.005 of ROC-AUC,
six are negative, and the largest is +0.0038. Volatility spillover between
crypto assets is widely assumed in this literature; once an asset's own
history is present it adds nothing measurable here.
BTC feature…
Screen change and transition targets; framing beats architecture
The first screen framed every candidate as a level -- does statistic X
exceed its trailing median. Six of eight lost to persistence, all for the
same reason: levels are autocorrelated, so carrying yesterday forward is
already 60-72% accurate and the model has nothing to add.
This screen asks about changes, transitions and cross-asset structure
instead, where "it stays the same" is not an available answer. Four clear
both baselines on all three assets:
volatility rises AUC 0.770-0.813 worst margin +17.40
illiquidity rises AUC 0.644-0.679 worst margin +8.91
volume surges 1.5x AUC 0.717-0.819 worst margin +1.06
volatility expands 1.5x AUC 0.760-0.822 worst margin +0.44
"Will volatility be higher than it is right now" dominates everything
deployed. Its persistence baseline is 39.0-39.5% -- worse than chance,
because volatility mean-reverts -- so the model supplies all of the skill,
and it is the more useful question: the deployed model says "…
Add jump-risk training, the one target worth deploying after volatility
Target: over the next 24h, does the largest single-hour absolute move
exceed its trailing median in sigmas -- will one hour be violent enough
to gap through a stop.
Selected by screen_prediction_targets.py on the margin over persistence,
not on ROC-AUC. Jumps do not cluster the way volatility does, so their
persistence baseline is only 51-57% and the model supplies nearly all the
skill: +9.39 / +7.62 / +4.80 points on BTC / ETH / ADA. Volume scored a
higher AUC and was discarded for adding nothing over persistence.
VAE+Transformer, no GRU, seed-averaged, same shape as the volatility
artifacts. The label is imported from the screening script rather than
reimplemented, so the deployed target cannot drift from the screened one.
Configuration is inherited from the volatility sweep and recorded as such
in the metadata: the inputs are the same 33 features but the label is
different, so a sweep against this target remains a real follow-up.
Every artifact stores persistence_baseline_accura…
Same framing as the deployed volatility label -- does a statistic exceed its
own causal trailing median over the next 24h -- so the numbers compare
directly. Purged walk-forward, 5 folds, and both baselines reported.
Only two targets beat majority AND persistence on all three assets:
largest jump (sigmas) ROC-AUC 0.675-0.714 +4.80 to +9.39 pts
max drawdown ROC-AUC 0.678-0.692 +1.27 to +4.04 pts
Traded volume is the cautionary row. Its ROC-AUC is the highest in the
screen at 0.750-0.796, and it still loses to persistence on ETH (-0.76)
and ADA (-4.65): volume is autocorrelated enough that carrying yesterday's
regime forward is already 70-72% accurate. Reporting the AUC alone would
have made the weakest candidate look like the strongest.
That reframes the earlier conclusion. It is not that magnitudes are
predictable -- it is that magnitudes are persistent, and persistence is
free. The targets worth modelling are the ones that resist extrapolation,
which is why ju…
Record the directional-predictability negative result
Four tests of whether hourly OHLCV features predict direction on BTC, ETH
and ADA at 1-48 hour horizons. All say no:
deployed direction models ROC-AUC 0.503 / 0.474 / 0.490
volatility conditioning no edge even with perfect foresight
triple-barrier labels 9 of 9 configurations below majority class
volatility regime ROC-AUC 0.776-0.815 (positive control)
The barrier test was the substantive one: it changed the target from
fixed-horizon return sign to a path-dependent, executable label with
intrabar touches, dropped unresolved rows rather than coercing them, and
purged the 48-hour hold. It changed nothing, so the earlier failures are
not an artefact of how the question was framed.
The volatility result on the same pipeline is what makes this a finding
rather than an inconclusive experiment: the apparatus detects signal when
signal is present.
Two scratchpad scripts are promoted into scripts/ so the tables in the
document can be regenerated.
Co-Authored-…
Serve the VAE+Transformer volatility models, and show seed disagreement
All three assets now score their next-24h volatility regime with the
VAE+Transformer artifacts trained on Colab, replacing the per-row models
including BTC's gradient-boosted one. Held-out ROC-AUC over 4,639 rows:
BTC 0.7761 (was 0.7560) seq_len 168 threshold 0.52
ETH 0.7729 (was 0.7560) seq_len 48 threshold 0.56
ADA 0.8145 (was 0.7874) seq_len 168 threshold 0.70
Each asset carries its own sequence length, feature order, scaler and
threshold, so all four come from the metadata rather than constants.
Predictions are the mean over five seeds. That mean can cross the
threshold while most seeds sit on the other side of it, so the panel now
reports how many seeds agree with the call and marks a split ensemble as
low confidence rather than letting the averaged score read as settled.
BTC's current candle is exactly this case: 2 of 5 seeds, scores spanning
38.3% to 92.0%.
The frame is deepened to MIN_HISTORY_HOURS + 192 because a 168-hour
window would otherwi…
Fetch klines from Binance's public archive, and stop retrying for ever
A Colab run sat silent for over twenty minutes. The cause was in this function:
every non-200 response rotated host, slept a second and continued, with no
budget, so a request that could never succeed was retried indefinitely and
looked exactly like slow progress. Binance answers HTTP 451 to many cloud IP
ranges, which is the response it was retrying.
Two changes.
Consecutive failures are now budgeted at four passes over the host list, after
which it raises and names the last status, with an explicit hint when that
status is 451. A successful page clears the budget, so a long download is not
penalised for transient failures.
More usefully, data.binance.vision is tried first. The public archive carries no
geo-restriction, needs one request per month instead of sixty-six paged
requests, and returned cleaner data in testing: 3,000 ETH candles with every
interval exactly one hour, against the twenty maintenance gaps the REST route
produced. Monthly files are combined with daily files fo…
Add artifact training at the tuned VAE+Transformer configurations
The sweep measured and saved nothing, so there was still nothing deployable.
This trains at each asset chosen configuration -- BTC seq 168 d_model 64, ETH
seq 48 d_model 128, ADA seq 168 d_model 128, all kl 1e-4 -- and writes weights
plus plain-JSON metadata.
Seeds are averaged rather than trusted singly. The selection was visibly noisy:
one BTC configuration swung 0.0384 of validation AUC on a hyperparameter that
moved nothing elsewhere, purely because early stopping fired at epoch 8 rather
than epoch 1. Every state_dict is stored so the ensemble is reproducible rather
than one lucky initialisation.
The decision threshold is tuned by MCC and stored with the model, because a 0.5
cut-off underperforms the persistence baseline on this target.
Two notebook cells added: the training run, and an export that copies both files
per asset to Drive before the runtime is recycled.
Smoke-tested locally on ETH at one seed and two epochs: trains, asserts no GRU is
present, and writes both files.…
20 days ago
Add a Colab notebook for the volatility tuning sweep
The training path in src/pipeline.py already moves the model and every batch to
CUDA when it is present, but the machine running these experiments has
torch 2.11.0+cpu, so every VAE-Transformer fit this session has been on CPU.
The sweep was measured there at roughly 54 hours, which made the long-sequence
arm unaffordable and forced the grid down.
On a T4 the same code should take hours, so the arm that matters can actually be
tested. Sequence length is the hypothesis: 48 is inherited from the direction
models, volatility has long memory, and 48 hours cannot see a weekly cycle.
The notebook clones the Space, checks a GPU is attached before committing time to
it, prefers the kline caches from Drive because Binance returns HTTP 451 to many
Colab IP ranges, runs a smoke test before the full sweep, and copies the results
out before the runtime is recycled.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bring ETH and ADA online with their own volatility models
run_multi_asset_volatility.py showed the result holds on all three coins. This
turns that into live models, closing the gap where the thesis covers BTC, ETH
and ADA while /assets reported two of them as having nothing.
Portability decided the format. The Space pins scikit-learn 1.6.1 and this host
runs 1.9.1, so a pickled estimator cannot cross, and BTC's classifier was
pickled elsewhere at 1.6.1 and is not reproducible here. ETH and ADA are
therefore torch. Measured per asset rather than assumed, and the framing in the
training script is now wrong in the asset's favour: there is no cost.
asset boosted torch accuracy persistence margin beats
BTC 0.7875 0.7933 71.34% 65.61% +5.73pp 5/5
ETH 0.7560 0.7724 69.64% 64.93% +4.71pp 5/5
ADA 0.7874 0.7963 71.39% 67.43% +3.96pp 5/5
Torch beat the gradient-boosted incumbent on all three. BTC is deliberately left
on its original artifact regardless, because that is…
Train the GRU-free 6h model, and measure autoencoder features: no gain
Two results.
The 6h horizon was the only genuine GRU in the stack -- a standalone GRU model,
where 1h is already market_only VAE+Transformer and 24h's only GRU is the
sentiment branch that disappears when trained market-only. It now has a
VAE+Transformer trained on the 27 market features recovered from the shipped
scaler, over the longest contiguous run of Binance history. The checkpoint is
verified GRU-free: 46 tensors, none matching gru or sent, with mu, logvar and
decoder present for the autoencoder and 24 tensors for the transformer.
6h CV folds 0.5140 / 0.4953 / 0.5257, 11 epochs chosen
holdout ROC-AUC 0.5195, accuracy 51.89% against a 51.72% majority
Marginally above the existing 6h model's 0.4975 but still chance. This was an
architecture-consistency change, never an accuracy one, and it delivers that.
Second, the arrangement Badar et al. (Mathematics 13(12):1908) and CMS-VAE
(ICAIF '25) actually use: the autoencoder as an unsupervised feature extractor
feeding th…
VAE+Transformer loses to gradient-boosted trees on the volatility target
Tests whether the whole deployment stack can be moved to VAE + Transformer. The
answer for the one model that beats its baseline is no, and the margin is small
but consistent.
Both architectures were fitted on identical purged folds with the same features
and the same scaling, and scored on identical rows -- the incumbent is trimmed to
the rows the sequence model can score, so the delta is not an artefact of
different row sets. sent_cols is empty throughout, so VAETransformer never builds
its sentiment GRU and the candidate is genuinely VAE + Transformer.
fold 1 2024-12-24 -> 2025-07-26 HistGB 0.7532 VAE+Transformer 0.7321 -0.0211
fold 2 2025-07-26 -> 2026-02-24 HistGB 0.8092 VAE+Transformer 0.7902 -0.0189
fold 3 2026-02-24 -> 2026-09-26 HistGB 0.7793 VAE+Transformer 0.7658 -0.0135
Mean -0.0178, 95% CI [-0.0276, -0.0081], paired p = 0.0157, worse in 3 of 3
folds. Small, but the interval excludes zero.
One number invites misreading: the VAE arm shows 70.28…
Serve a predicted volatility magnitude, and chart it against recent history
The panel said whether the next 24 hours clear the trailing median but never how
far price is expected to move, which is what position sizing needs. It also had
no chart.
The best regressor found, a boosted tree at R2 0.4285, cannot be deployed: the
Space pins scikit-learn 1.6.1 while models are trained on a host running 1.9.1,
and a pickle survives neither direction. So the candidates were restricted to
portable formats and compared honestly:
Boosted trees (cannot ship) 0.4037 -> R2 0.4285 4/5 folds best
Torch MLP (portable) R2 0.4037 1/5
Ridge + HAR (portable) R2 0.3373 0/5
HAR-RV only R2 0.1898 0/5
The torch MLP ships. Portability costs 0.0248 of R2, and it still beats HAR-RV
by +0.2139 at p = 0.015 in 5 of 5 folds, so it earns its place against the
benchmark the field actually uses.
/volatility now carries `magnitude` (predicted 24h volatility, the ratio to
normal, and the walk-forward R2 beside H…
Volatility regression: R2 0.4285 out of sample, and it beats HAR-RV
Predicts log realised volatility over the next 24h rather than only whether it
clears the trailing median. Unlike regression on price, which this project
already found useless (58-73% MAPE, negative R2, direction at 0.504), realised
volatility is persistent and mean-reverting and has a standing literature.
Five purged chronological folds, 39,335 test hours, on log realised volatility:
model R2 RMSE QLIKE Spearman derived call
Boosted trees + HAR 0.4285 0.4035 0.4380 0.6150 71.98%
Boosted trees (33 feat) 0.4268 0.4033 0.3952 0.6059 71.78%
Ridge (33 features) 0.2822 0.4522 0.4332 0.5384 68.09%
HAR-RV 0.1898 0.4779 0.5342 0.4300 67.38%
Persistence (vol now) -0.0955 0.5549 0.8841 0.4270 65.61%
Train mean -0.3273 0.6156 0.5652 n/a 50.87%
R2 0.4285 sits in the range the volatility literature reports f…
Volatility regime reproduces on ETH and ADA, not just BTC
The thesis covers three coins but /assets reported ETH and ADA as
deployed: false, and the only working model was Bitcoin's. The volatility model
needs nothing but OHLCV, so closing that gap costs a Binance download. It also
tests something worth more than the gap: whether 0.79 is a property of crypto
volatility or a fluke of one asset's history.
Identical across assets -- the same 33 causal features, the same
HistGradientBoosting configuration as the deployed artifact, the same purged
expanding folds, the same threshold tuned on a training tail, and both baselines.
asset test hours accuracy persistence margin ROC-AUC MCC beats
BTC 39,335 70.75% 65.61% +5.14pp 0.7875 +0.4192 5/5
ETH 39,425 68.29% 64.93% +3.36pp 0.7560 +0.3745 5/5
ADA 39,425 70.82% 67.43% +3.39pp 0.7874 +0.4233 5/5
All three beat persistence in every fold, over roughly 118,000 test hours in
total. ADA matches BTC almost …
Selective forecasting on the volatility model: 78.46% at 57.8% coverage
Implements the method specified in the thesis notebook
BTC_1Y_Regime_MetaLabel_Selective_Forecasting.ipynb, which had never been
executed. Abstain where the score sits near the decision boundary, report
accuracy only on the hours the model qualifies.
Two rules keep it honest. Persistence is rescored on exactly the hours the model
keeps, because abstaining on hard cases flatters any rule on what remains, so
comparing reduced coverage against the full-sample 65.61% would prove nothing.
And the operating point is fixed on a validation slice carved from each training
fold before the test fold is touched.
Accuracy against coverage, five purged folds, persistence on identical hours:
100% 70.75% vs 65.61% +5.14pp 5/5 folds
80% 74.83% vs 67.81% +7.03pp 5/5
60% 78.65% vs 70.67% +7.98pp 5/5
40% 81.54% vs 72.89% +8.66pp 5/5
20% 85.35% vs 75.10% +10.25pp 5/5
The margin widens as coverage falls. That is the result: accuracy alone would
rise for any abs…
Replace the brief's direction metrics with measured ones; mark unusable horizons
The dashboard published ROC-AUCs of 0.5483/0.5243/0.5188 taken from section 15
of the integration brief, which describes them as approximate. The holdout each
checkpoint defines had never been scored, so no measured figure existed.
scripts/score_direction_holdout.py now scores it and commits the result. It
reuses the deployed inference path and asserts the batched path reproduces the
single-row path bit for bit before reporting anything. Measured over ~4,600
purged hours per horizon:
1h ROC-AUC 0.5358 accuracy 52.55% vs 50.16% majority says UP 57.1%
6h ROC-AUC 0.4975 accuracy 48.59% vs 51.82% majority says UP 15.5%
24h ROC-AUC 0.5049 accuracy 52.22% vs 52.94% majority says UP 83.8%
The brief's figures are not supported: 6h is below chance and below its own
majority baseline.
Serving now reads outputs/direction_holdout_metrics.json rather than hard-coded
constants, and a missing file yields no performance claim instead of a fabricated
one. /predictions carries per-h…