Composable models and A/B tests of the 2024 ideas; add powerlaw.

Models are now a Composite of drift, volatility and (optional) shape
components, so an experiment can swap one part against a fixed control.

btcmodel/experiments.py holds seven experiments built from the ideas in the
old branches (catalogued in docs/2024-ideas.md), each with its hypothesis
and source, and a verdict rule fixed before anything ran. `just ab` runs
them on development data. Results:

- Shrinking the cycle drift, and a power-law trend (plain or reverting),
  beat their controls. The power law beats the random walk by 53-63% at
  3-4 years with unbiased outcomes, so it is promoted to MODELS.
- Every alternative volatility estimate (EWMA blends, other windows,
  reversion to a level or trend) is worse than the trailing 365-day window.
  Cycle-dependent volatility, heavy tails and stretched cycle phase show no
  reliable effect.
This commit is contained in:
sam
2026-09-24 03:01:46 -07:00
parent cfc27a38de
commit b0243adf61
18 changed files with 795 additions and 155 deletions
+4 -4
View File
@@ -5,7 +5,7 @@ import pytest
from btcmodel import data
from btcmodel.evaluate import backtest
from btcmodel.halving import HALVINGS, cycle_position
from btcmodel.models import MODELS, CycleModel
from btcmodel.models import MODELS, CycleDrift
def synthetic_prices(daily_return, start="2011-01-01", end="2024-11-26", noise=0.0, seed=0):
@@ -31,7 +31,7 @@ def test_cycle_model_recovers_a_cycle_shaped_drift():
return np.where(day < 700, 0.002, -0.001)
prices = synthetic_prices(shape)
drift = CycleModel(prior_days=0).drift_by_cycle_day(prices)
drift = CycleDrift(prior_days=0).by_cycle_day(prices)
assert drift[300] == pytest.approx(0.002, abs=2e-4)
assert drift[1100] == pytest.approx(-0.001, abs=2e-4)
@@ -42,8 +42,8 @@ def test_cycle_model_weights_recent_cycles_more():
cycle, _ = cycle_position(prices.index)
r = 0.004 / 2.0**cycle
prices["close"] = 100 * np.exp(np.cumsum(r))
drift = CycleModel(recency_half_life=0.25).drift_by_cycle_day(prices)
equal = CycleModel(recency_half_life=1e9).drift_by_cycle_day(prices)
drift = CycleDrift(recency_half_life=0.25).by_cycle_day(prices)
equal = CycleDrift(recency_half_life=1e9).by_cycle_day(prices)
latest_complete = 0.004 / 2.0**3
assert abs(drift[900] - latest_complete) < abs(equal[900] - latest_complete)