import sys import os import pandas as pd import numpy as np # Bulletproof pathing: Force Python to look in both the current folder AND the parent folder # This ensures it finds models.py regardless of whether this file is run from /tests or the root. _this_dir = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, _this_dir) sys.path.insert(0, os.path.dirname(_this_dir)) # Import the specific function from the engine from models import model_bsts # ───────────────────────────────────────────── # 1. FREQUENCY GATING TESTS # ───────────────────────────────────────────── def test_bsts_frequency_gate_aborts_on_daily(): """ BSTS overfits on daily stock returns (which are essentially white noise). This test verifies that the engine completely aborts if 'periods' > 52 (e.g., daily data with 252 periods). """ rng = np.random.default_rng(42) # Synthetic daily data returns_df = pd.DataFrame({'AAPL': rng.normal(0.0005, 0.01, size=100)}) # Act: Request a BSTS fit expecting 252 trading periods in a year res = model_bsts(returns_df, periods=252, silent=True) # Assert: The model should instantly reject the request to prevent noise-fitting assert res.expected_returns is None or res.expected_returns.empty def test_bsts_short_series_fallback(): """ BSTS requires a minimum amount of data to establish stationary momentum. If the series is too short (e.g., < 12 months), it must output massive uncertainty so the Black-Litterman bridge safely ignores it. """ rng = np.random.default_rng(42) # Only 10 months of data returns_df = pd.DataFrame({'TSLA': rng.normal(0.01, 0.05, size=10)}) res = model_bsts(returns_df, periods=12, silent=True) # Assert: Should output 0 expected return and massive (1000.0) uncertainty assert np.isclose(res.expected_returns['TSLA'], 0.0) assert np.isclose(res.uncertainties['TSLA'], 1000.0) def test_bsts_valid_signal(): """ If a monthly time series exhibits momentum, the BSTS model should correctly output a genuine variance (uncertainty < 1000.0). """ rng = np.random.default_rng(42) n = 60 # Create a synthetic AR(1) process with strong positive momentum series = np.zeros(n) ar_coef = 0.8 for i in range(1, n): series[i] = ar_coef * series[i-1] + rng.normal(0, 0.005) returns_df = pd.DataFrame({'TRENDY_ASSET': series}) # Act res = model_bsts(returns_df, periods=12, silent=True) # Assert: The model must recognize the signal. The uncertainty should be the # legitimate standard error of the forecast, NOT the 1000.0 rejection flag. assert res.uncertainties['TRENDY_ASSET'] < 1000.0 assert not pd.isna(res.expected_returns['TRENDY_ASSET'])