52 lines
2 KiB
Python
52 lines
2 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
||
|
|
"""RESEARCH HARD-STOP TEST: no-lookahead enforcement.
|
||
|
|
|
||
|
|
A failing test here is a HARD STOP. Both SAX and ARIMA forecasts must be
|
||
|
|
invariant to any change in bars AFTER the forecast origin, and must not carry
|
||
|
|
actual (future) outcome information.
|
||
|
|
"""
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
from src.forecasting.target import ForecastContext
|
||
|
|
from src.sax import SaxConfig, SaxAnalogForecaster
|
||
|
|
from src.arima import ArimaConfig, ArimaModel
|
||
|
|
from tests.helpers import make_series, clone_with_future
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def context():
|
||
|
|
return ForecastContext(symbol="XAUUSD", timeframe="H1", horizon=12, atr_period=20)
|
||
|
|
|
||
|
|
|
||
|
|
def test_sax_forecast_unchanged_by_future(context):
|
||
|
|
s = make_series(n=700, seed=3)
|
||
|
|
origin = 400
|
||
|
|
model = SaxAnalogForecaster(SaxConfig(window_length=24, word_length=8,
|
||
|
|
alphabet_size=5, min_analogs=5))
|
||
|
|
rec_a = model.forecast(s, origin, context)
|
||
|
|
s_future = clone_with_future(s, origin, mut=100000.0)
|
||
|
|
rec_b = model.forecast(s_future, origin, context)
|
||
|
|
assert rec_a.to_dict() == rec_b.to_dict(), (
|
||
|
|
"SAX forecast changed when a FUTURE bar changed -> lookahead leak"
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
def test_forecast_carries_no_actual_outcome(context):
|
||
|
|
s = make_series(700, seed=4)
|
||
|
|
model = SaxAnalogForecaster(SaxConfig(window_length=5, min_analogs=5))
|
||
|
|
rec = model.forecast(s, 400, context)
|
||
|
|
assert rec.outcome_boundary is None or rec.outcome_boundary >= rec.forecast_origin
|
||
|
|
# forecast must be frozen: no actual (future) outcome may be attached here
|
||
|
|
assert rec.actual_forward_return is None
|
||
|
|
assert rec.actual_forward_return_ATR is None
|
||
|
|
|
||
|
|
|
||
|
|
def test_arima_forecast_unchanged_by_future(context):
|
||
|
|
s = make_series(300, seed=5)
|
||
|
|
origin = 220
|
||
|
|
model = ArimaModel(ArimaConfig(p=1, d=0, q=0, fit_window=150))
|
||
|
|
s_future = clone_with_future(s, origin, mut=5000.0)
|
||
|
|
a = model.forecast(s, origin, context)
|
||
|
|
b = model.forecast(s_future, origin, context)
|
||
|
|
assert a.normalized_expected_return == b.normalized_expected_return
|
||
|
|
assert a.arima_state == b.arima_state
|