import numpy as np import pandas as pd import pytest from btcmodel import forward from btcmodel.evaluate import HORIZONS, score from btcmodel.models import MODELS from .test_models import synthetic_prices MODELS_UNDER_TEST = [MODELS["random_walk"], MODELS["powerlaw"]] @pytest.fixture def prices(): return synthetic_prices(lambda day: 0.001 + 0 * day, end="2025-03-31", noise=0.03) def test_snapshot_records_every_model_and_horizon(prices, tmp_path): history = prices.loc[:"2024-11-26"] path = forward.snapshot(history, MODELS_UNDER_TEST, tmp_path, pd.Timestamp("2024-11-27")) recorded = pd.read_csv(path) assert path.name == "2024-11-26.csv" assert len(recorded) == len(MODELS_UNDER_TEST) * len(HORIZONS) assert recorded.target.iloc[0] == "2024-12-26" def test_snapshot_refuses_stale_data_and_duplicates(prices, tmp_path): history = prices.loc[:"2024-11-26"] with pytest.raises(ValueError, match="real time"): forward.snapshot(history, MODELS_UNDER_TEST, tmp_path, pd.Timestamp("2024-12-10")) forward.snapshot(history, MODELS_UNDER_TEST, tmp_path, pd.Timestamp("2024-11-27")) with pytest.raises(FileExistsError): forward.snapshot(history, MODELS_UNDER_TEST, tmp_path, pd.Timestamp("2024-11-27")) def test_scores_only_due_horizons_and_match_the_backtest(prices, tmp_path): history = prices.loc[:"2024-11-26"] forward.snapshot(history, MODELS_UNDER_TEST, tmp_path, pd.Timestamp("2024-11-27")) assert forward.score_snapshots(prices.loc[:"2024-12-20"], tmp_path).empty assert forward.next_due(prices.loc[:"2024-12-20"], tmp_path) == pd.Timestamp("2024-12-26") scores = forward.score_snapshots(prices, tmp_path) assert sorted(scores.horizon.unique()) == [30, 91] model = MODELS["powerlaw"] horizons = np.array([30, 91]) direct = score( "powerlaw", model.forecast(history, horizons), np.log( prices.close.loc[history.index[-1] + pd.to_timedelta(horizons, unit="D")] ).to_numpy(), ) recorded = scores[scores.model == "powerlaw"].sort_values("horizon") np.testing.assert_allclose(recorded.crps, direct.crps, atol=1e-5)