Spaces:
Running
Running
| import copy | |
| import sys | |
| import os | |
| # Bulletproof pathing: Force Python to look in both the current folder AND the parent folder | |
| _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 pandas as pd | |
| import numpy as np | |
| from analytics import backtest, build_macro | |
| from backtest import monte_carlo | |
| from config import DEFAULT_CONFIG | |
| from core_types import PortfolioState | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # 1. TIME-AGNOSTIC SCALING TESTS | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_trading_days_annualization_scaling(): | |
| """ | |
| Verifies that changing 'trading_days_per_year' correctly alters the | |
| mathematical annualization of returns and volatility in the backtester. | |
| """ | |
| # 100 days of synthetic returns at exactly 10 bps per day | |
| dates = pd.date_range("2023-01-01", periods=100, freq="D") | |
| returns_df = pd.DataFrame({'ASSET_A': np.repeat(0.001, 100)}, index=dates) | |
| spy_rets = pd.Series(np.repeat(0.001, 100), index=dates) | |
| weights = pd.Series({'ASSET_A': 1.0}) | |
| capital = 10000.0 | |
| rfr = 0.0 | |
| spread_map = {'ASSET_A': 0.0} | |
| state = PortfolioState.empty(['ASSET_A']) | |
| betas = pd.Series({'ASSET_A': 1.0}) | |
| # Run 1: US Equities (252 days) | |
| cfg_us = copy.deepcopy(DEFAULT_CONFIG) | |
| cfg_us['trading_days_per_year'] = 252 | |
| cfg_us['transaction_cost'] = 0.0 | |
| _, _, _, stats_us = backtest(returns_df, weights, capital, rfr, spy_rets, spread_map, cfg_us, state) | |
| # Run 2: Crypto (365 days) | |
| cfg_crypto = copy.deepcopy(DEFAULT_CONFIG) | |
| cfg_crypto['trading_days_per_year'] = 365 | |
| cfg_crypto['transaction_cost'] = 0.0 | |
| _, _, _, stats_crypto = backtest(returns_df, weights, capital, rfr, spy_rets, spread_map, cfg_crypto, state) | |
| # Assert: Because Crypto assumes a longer compounding year, its annualized return | |
| # extrapolated from the same 100-day sample MUST be mathematically higher. | |
| assert stats_crypto['ann_ret'] > stats_us['ann_ret'] | |
| def test_monte_carlo_path_horizon(): | |
| """ | |
| Verifies that the Monte Carlo engine dynamically extends its simulation | |
| paths based on the global trading days configuration. | |
| """ | |
| tickers = ["ASSET_A"] | |
| weights = pd.Series({'ASSET_A': 1.0}) | |
| exp_rets = pd.Series({'ASSET_A': 0.10}) | |
| cov_mat = pd.DataFrame([[0.04]], index=tickers, columns=tickers) | |
| # Run with Frankfurt calendar (256 days) for 2 years | |
| cfg_eu = copy.deepcopy(DEFAULT_CONFIG) | |
| cfg_eu['trading_days_per_year'] = 256 | |
| cfg_eu['monte_carlo_years'] = 2.0 | |
| _, stats = monte_carlo(weights, exp_rets, cov_mat, capital=10000.0, cfg=cfg_eu, seed=42) | |
| # Assert: 2 years * 256 days = 512 path points | |
| assert len(stats['dates']) == 512 | |
| # Verify the paths array matches the length | |
| assert len(stats[50]) == 512 | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # 2. DYNAMIC BENCHMARK TESTS | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| def test_build_macro_dynamic_benchmarks(): | |
| """ | |
| Verifies that the macro-economic context builder correctly reads | |
| dynamic benchmarks (like European VIX variants) instead of hardcoding US indexes. | |
| """ | |
| cfg = copy.deepcopy(DEFAULT_CONFIG) | |
| # Switch to European benchmarks | |
| cfg['benchmarks'] = { | |
| "equity": "DAX", | |
| "volatility": "^V2TX", # Euro Stoxx 50 Volatility | |
| "risk_free": "^DE10Y" | |
| } | |
| # Mock data dictionaries matching the European tickers | |
| prices = {"^DE10Y": 2.50} | |
| raw = { | |
| "DAX": pd.Series(np.linspace(10000, 15000, 250)), # Trending up | |
| "^V2TX": pd.Series([25.0, 30.0]) | |
| } | |
| vix_raw = pd.Series([25.0, 35.0]) # Emulate a high volatility regime | |
| macro = build_macro( | |
| prices=prices, | |
| raw=raw, | |
| rfr=0.025, | |
| display_df=pd.DataFrame(), | |
| w_arr=np.array([]), | |
| vix_raw=vix_raw, | |
| cfg=cfg | |
| ) | |
| # Assert: It should successfully read the ^V2TX proxy and flag it as high volatility (> 20) | |
| assert macro['vix_val'] == 35.0 | |
| assert macro['vix_high'] is True | |
| # Assert: It should successfully read the DAX trend (which we mocked as a straight line up) | |
| assert macro['spy_trend'] == "BULL" | |