portfolio-engine / tests /test_bsts.py
engineportf's picture
Initial Deployment from Local Engine
208fbf8 verified
Raw
History Blame Contribute Delete
2.99 kB
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'])