Files
manifoldbt/python/tests/test_lookahead_sans_trade.py
T

80 lines
3.1 KiB
Python

"""Un sous-test de re-run qui n'a rien compare ne rend pas PASS.
detect_lookahead rendait PASS sur une strategie qui ne trade jamais (un
param() sans valeur suffit), ou quand le run tronque echouait : un verdict
vide lu comme un feu vert. Il rend maintenant INCONCLUSIVE, `.passed` vaut
False et `assert_clean()` leve, en disant pourquoi.
"""
import os
import pytest
import manifoldbt as bt
# Safety diagnostics are a Pro feature.
pytestmark = pytest.mark.skipif(
bt.license_info()[0] != "Pro",
reason="requires Pro: skipped on a Community wheel",
)
pd = pytest.importorskip("pandas")
np = pytest.importorskip("numpy")
def _monte(tmp_path):
n = 2400
idx = pd.date_range("2021-01-01", periods=n, freq="1h", tz="UTC", name="timestamp")
pas = np.sin(np.arange(n) / 37.0) * 2.0 + np.cos(np.arange(n) / 11.0)
px = 100.0 + np.cumsum(pas) * 0.05
df = pd.DataFrame({"open": px, "high": px * 1.004, "low": px * 0.996,
"close": px, "volume": np.full(n, 1000.0)}, index=idx)
racine = str(tmp_path / "data")
os.makedirs(racine, exist_ok=True)
store = bt.import_dataframe(df.reset_index(), symbol="ZLSV", symbol_id=1,
interval="1h", asset_class="equity",
exchange="TEST", data_root=racine,
metadata_db=str(tmp_path / "meta.sqlite"))
tr0, tr1 = bt.time_range("2021-01-01", "2021-04-11")
cfg = bt.BacktestConfig(universe=[1], time_range_start=tr0, time_range_end=tr1,
initial_capital=1000.0, provider="TEST",
bar_interval=bt.Interval.hours(1), symbol_names={"ZLSV": 1})
cfg.warmup_bars = 0
return store, cfg
def _jamais():
from manifoldbt.indicators import close
return (bt.Strategy.create("jamais")
.signal("c", close)
.size(bt.lit(0.0)))
def _croise():
from manifoldbt.indicators import close, sma
m = sma(close, 24)
return bt.Strategy.create("croise").signal("m", m).size(bt.when(close > m, 1.0, 0.0))
def test_une_strategie_qui_ne_trade_pas_est_inconclusive(tmp_path):
store, cfg = _monte(tmp_path)
rapport = bt.diagnostics.detect_lookahead(_jamais(), cfg, store, mode="all")
assert not rapport.passed
re_runs = [r for r in rapport.reports if r.method != "static"]
assert {r.method for r in re_runs} == {"extension", "truncation"}
for r in re_runs:
assert r.inconclusive and "made no trade" in r.inconclusive
assert "INCONCLUSIVE" in str(r)
statique = [r for r in rapport.reports if r.method == "static"]
assert statique and statique[0].passed
assert str(rapport).startswith("Lookahead diagnostics: INCONCLUSIVE")
with pytest.raises(AssertionError, match="inconclusive"):
rapport.assert_clean()
def test_une_strategie_causale_qui_trade_reste_pass(tmp_path):
store, cfg = _monte(tmp_path)
rapport = bt.diagnostics.detect_lookahead(_croise(), cfg, store, mode="all")
assert rapport.passed, rapport
assert all(r.inconclusive is None for r in rapport.reports)
assert str(rapport).startswith("Lookahead diagnostics: PASS")