Round 2: reverting power law and cycle-on-powerlaw tests; one-time holdout run.

Add TrendReversionVol: deviations from the power-law trend follow a daily
AR(1), so uncertainty levels off, optionally plus trend-parameter
uncertainty with an autocorrelation-adjusted effective sample size.

Two experiments, run under the unchanged verdict rule:
- powerlaw-ou: +21% to +45% vs powerlaw at 2-4 years, but slightly negative
  point estimates at 1 month make it inconclusive.
- cycle-on-powerlaw: inconclusive (+18% at 2 years, negative elsewhere).

The holdout (outcomes after 2024-11-26) was scored once, for the four
candidates fixed beforehand. powerlaw is the best long-horizon forecast
(+45% and +58% vs the random walk at 2 and 3 years); nothing beats the
random walk inside a year; cycle fails badly. Results are in the README.
This commit is contained in:
sam
2026-09-24 03:06:21 -07:00
parent b0243adf61
commit 082bcfbbbc
4 changed files with 116 additions and 6 deletions
+20 -1
View File
@@ -6,7 +6,7 @@ from btcmodel.experiments import EXPERIMENTS
from btcmodel.halving import GENESIS
from btcmodel.models.drift import CycleDrift, PowerLawDrift, ShrunkDrift
from btcmodel.models.shape import StudentT
from btcmodel.models.volatility import ReversionVol, TrailingVol
from btcmodel.models.volatility import ReversionVol, TrailingVol, TrendReversionVol
from .test_models import synthetic_prices
@@ -64,3 +64,22 @@ def test_student_t_shape_has_unit_variance_and_fatter_tails():
q = StudentT(4).standard_quantiles(None, np.array([1]))[0]
assert q[-1] > 2.576 # beyond the normal 99.5% quantile
assert np.interp(0.8413, np.linspace(0.005, 0.995, 100), q) < 1.0 # thinner shoulders
def test_trend_reversion_vol_levels_off():
# A power-law trend plus AR(1) deviations with a ~70-day half-life.
rng = np.random.default_rng(0)
dates = pd.date_range("2011-01-01", "2024-11-26", freq="D", name="date")
t = (dates - GENESIS).days.to_numpy()
gap = np.zeros(len(t))
for i in range(1, len(t)):
gap[i] = 0.99 * gap[i - 1] + 0.03 * rng.standard_normal()
prices = pd.DataFrame({"close": np.exp(-40 + 5.8 * np.log(t) + gap)}, index=dates)
horizons = np.array([30, 365, 1460, 5000])
plain = TrendReversionVol().sd(prices, horizons)
with_params = TrendReversionVol(parameter_uncertainty=True).sd(prices, horizons)
assert np.all(np.diff(plain) >= 0)
stationary = 0.03 / np.sqrt(1 - 0.99**2)
assert plain[-1] == pytest.approx(stationary, rel=0.15)
assert np.all(with_params >= plain)