Files
bitcoin-model/btcmodel/models/baselines.py
T
sam cfc27a38de Rewrite as a probabilistic model with walk-forward evaluation.
Replace the 2024 model (model.py, ~2000 lines) with the btcmodel package, the
baseline for future work:

- Forecasts are quantiles of log price at each horizon, scored with CRPS in a
  walk-forward backtest (origins every 30 days from 2014, horizons 1 month to
  4 years). Skill is relative to a zero-drift random walk, with circular
  block-bootstrap intervals and a count of independent windows.
- Development data stops at 2024-11-26, the last day the 2024 model saw.
  Later outcomes are a holdout, scored only by `backtest --holdout`.
- Models: random_walk, drift_rw, and cycle (the 2024 model's cycle-position
  drift, now kernel-smoothed and recency-weighted). On development data
  nothing beats the random walk with confidence; cycle loses at every horizon.
- Prices: the Investing.com archive moves to data/ (cut at 2024-11-26; its
  last row was intraday) and is extended with Coinbase daily closes by
  `update`.

Also: Nix flake dev shell (Python 3.13, pandas 3), ruff in place of black,
pytest suite, and a rewritten README. NOTES.md is removed as inaccurate, and
poetry is dropped.
2026-09-24 02:19:02 -07:00

48 lines
1.5 KiB
Python

"""Reference forecasts every other model has to beat."""
from dataclasses import dataclass
from typing import ClassVar
import numpy as np
import pandas as pd
from ..data import log_returns
from ..forecast import Forecast
@dataclass(frozen=True)
class RandomWalk:
"""
Zero-drift random walk in log price: "it stays about here, give or take".
Volatility is the trailing standard deviation of daily log returns.
"""
name: ClassVar[str] = "random_walk"
vol_window: int = 365
def forecast(self, history: pd.DataFrame, horizons: np.ndarray) -> Forecast:
sigma = log_returns(history).iloc[-self.vol_window :].std()
mean = np.log(history["close"].iloc[-1])
return Forecast.normal(history.index[-1], horizons, mean, sigma * np.sqrt(horizons))
@dataclass(frozen=True)
class DriftRandomWalk:
"""
Random walk whose drift is the mean daily log return over the trailing
`drift_window` days (one halving cycle by default): "it keeps doing what it
did last cycle".
"""
name: ClassVar[str] = "drift_rw"
drift_window: int = 1460
vol_window: int = 365
def forecast(self, history: pd.DataFrame, horizons: np.ndarray) -> Forecast:
returns = log_returns(history)
mu = returns.iloc[-self.drift_window :].mean()
sigma = returns.iloc[-self.vol_window :].std()
mean = np.log(history["close"].iloc[-1]) + mu * horizons
return Forecast.normal(history.index[-1], horizons, mean, sigma * np.sqrt(horizons))