Software companion
Spectral Signatures in SPY and BTC Intraday Returns
Overview
The study asks how intraday return variance distributes across timescales in an equity-index proxy and in bitcoin, so the software is built around one requirement: the two assets and the two decompositions must be treated identically. Loading, session alignment, return construction, band aggregation, and resampling are shared code. The only things that vary between arms are which series enters and which transform is applied to it.
That symmetry is what allowed the headline result — that the two decompositions disagree about the low-frequency band by more than the cross-asset difference being measured — to register as a methodological finding rather than a suspected bug. src/crosscheck.py exists to quantify the disagreement directly, and src/headline_method_choice.md records the resulting decision: publish the STFT 30-minute band share as the headline, carry the symmetric-mode wavelet series beside it, and never average the two into a single number.
Implementation
- Core libraries
- NumPy, pandas, SciPy, PyWavelets, pandas-market-calendars, Matplotlib
- Data sources
- yfinance 1-minute SPY bars, the Binance public klines endpoint for 1-minute BTCUSDT, and FRED
VIXCLSfor the stress split - Decomposition settings
- 30-minute Hann STFT with 15/30/60-minute sensitivity windows; a
db4discrete wavelet to five levels with symmetric, periodic and zero boundary sensitivity - Bands
- Four edges in minutes — 1–5, 5–15, 15–60, and 60 and above
- Configuration
- A single frozen
Configdataclass carrying dates, symbols, session hours, transform settings, band edges, stress cutoffs, the KS alpha and its Bonferroni divisor of four, and the run seed - Tests
- 18 pytest modules, roughly one per source module
Components
src/loaders/- SPY, BTC and VIX acquisition, each writing a hashed parquet snapshot through an atomic temp-file rename
src/alignment.py- Regular-trading-hours calendar construction, matched-session alignment, and the separate 24-hour BTC series
src/returns.py- Log returns and the sparse-session rule that drops days with too few observations
src/stft.py, src/dwt.py- The two decompositions, each returning per-band energy through a common interface
src/aggregation.py- Band shares from raw energies, pooled and rolling
src/bootstrap.py- Date-block resampling, so intraday dependence is not broken by the resampler
src/ks.py- Two-sample Kolmogorov–Smirnov tests at the Bonferroni-corrected alpha
src/stress.py- The stress overlay: a VIX median split for SPY, a realized-volatility quantile for BTC
src/crosscheck.py- STFT-versus-wavelet agreement, reported rather than reconciled
src/hashing.py, src/runtime_metadata.py- SHA-256 file digests and the run manifest recording environment and inputs
src/figures/- Nine figure builders, each reading only from processed artifacts
Pipeline
src/loaders/*Fetch and snapshot raw SPY, BTC and VIX, recording a digest for each file.build_processed.pyAlign to the trading calendar, build log returns, and drop sparse sessions.stft.py / dwt.pyDecompose both series under both transforms with the configured sensitivity settings.aggregation.pyReduce coefficient energies to band shares, pooled and rolling.build_ks_results.pyBootstrap the band-share distributions and run the corrected KS comparisons.crosscheck.pyQuantify STFT-versus-wavelet disagreement on the same input.figures/Render the nine report figures from processed artifacts only.build_report.pyAssemble the report against the manifest, so a stale input cannot be published silently.
Reproducibility and validation
- Every raw and processed artifact is hashed into a run manifest, and the report builder reads figures and tables through that manifest rather than from the filesystem directly.
- Sensitivity settings are declared in configuration rather than run ad hoc: three STFT window lengths and three wavelet boundary modes are part of the standing result, not a follow-up.
- Parquet writes go to a temporary file and are renamed into place, so an interrupted run cannot leave a half-written input behind.
- The multiple-comparison correction lives in configuration —
ks_alphaover a Bonferroni divisor of four, one per band — rather than being applied by hand at reporting time.
Availability
Not publicly released. Both price sources are public, but neither redistributes cleanly, and the repository carries hashed snapshots rather than the vendor data itself. A release would need the loaders repointed at a source whose terms allow the cached bars to be republished.