You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
45 lines
1.7 KiB
Python
45 lines
1.7 KiB
Python
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
from pathlib import Path
|
|
import sys
|
|
|
|
import pandas as pd
|
|
|
|
_SCRIPT_PATH = Path(__file__).resolve().parents[2] / "scripts" / "ensemble_overlay_backtest.py"
|
|
_SPEC = importlib.util.spec_from_file_location("ensemble_overlay_backtest", _SCRIPT_PATH)
|
|
assert _SPEC and _SPEC.loader
|
|
_MODULE = importlib.util.module_from_spec(_SPEC)
|
|
sys.modules[_SPEC.name] = _MODULE
|
|
_SPEC.loader.exec_module(_MODULE)
|
|
build_ensemble_returns = _MODULE.build_ensemble_returns
|
|
summarize_ensemble = _MODULE.summarize_ensemble
|
|
|
|
|
|
def test_build_ensemble_returns_uses_lagged_rolling_sharpe() -> None:
|
|
dates = pd.date_range("2024-01-01", periods=6, freq="B")
|
|
returns = pd.DataFrame(
|
|
{
|
|
"date": dates,
|
|
"a": [0.0, 0.01, 0.015, 0.02, -0.02, -0.02],
|
|
"b": [0.0, -0.01, -0.015, -0.02, 0.02, 0.02],
|
|
}
|
|
)
|
|
ensemble, weights = build_ensemble_returns(returns, window=3)
|
|
# Before enough history, fallback is equal weight.
|
|
assert weights.loc[0, "a"] == 0.5
|
|
assert weights.loc[1, "b"] == 0.5
|
|
# Once trailing Sharpe is available, positive trailing performer gets all the weight.
|
|
assert weights.loc[4, "a"] == 1.0
|
|
assert weights.loc[4, "b"] == 0.0
|
|
assert ensemble.loc[4, "ensemble_return"] == returns.loc[4, "a"]
|
|
|
|
|
|
def test_summarize_ensemble_reports_yearly_returns() -> None:
|
|
dates = pd.to_datetime(["2024-12-30", "2024-12-31", "2025-01-02"])
|
|
ensemble = pd.DataFrame({"date": dates, "ensemble_return": [0.0, 0.01, 0.02]})
|
|
summary = summarize_ensemble(ensemble, initial_equity=100.0)
|
|
assert round(summary["total_return_pct"], 4) == 3.02
|
|
assert "2024" in summary["yearly_returns_pct"]
|
|
assert "2025" in summary["yearly_returns_pct"]
|