Files
manifoldbt/python/tests/test_sweep.py
T

122 lines
4.3 KiB
Python
Raw Normal View History

2026-03-17 16:13:34 +01:00
"""Tests for parameter sweep via Python."""
import json
import os
import time
2026-07-09 11:01:08 +00:00
import pytest
2026-03-17 16:13:34 +01:00
import manifoldbt as bt
from manifoldbt import run_sweep, run_with_parquet
2026-07-09 11:01:08 +00:00
# The golden fixtures are 1-minute bars; on a Community license the engine caps
# resolution to daily, so these runs produce zero trades and the assertions are
# meaningless. CI unlocks via BT_UNLOCKED=1 (debug builds); locally this needs
# an activated Pro license.
pytestmark = pytest.mark.skipif(
bt.license_info()[0] != "Pro",
reason="requires Pro (sub-daily resolution); activate a license or use a BT_UNLOCKED dev build",
)
2026-03-17 16:13:34 +01:00
def test_sweep_returns_one_result_per_combo(golden_buy_hold_dir):
"""Sweep with 2x2 grid returns 4 results."""
strategy = bt.Strategy(
name="sweep_test",
signals={"signal": bt.lit(1.0)},
position_sizing=bt.col("signal") * bt.param("size", default=1.0),
parameters={"size": bt.param("size", default=1.0)},
)
config = bt.BacktestConfig(
universe=[1],
time_range_start=0,
time_range_end=4_000_000_000,
2026-07-19 02:07:07 +00:00
# Fixture bars are 1-second spaced; Days(1) collapses them into a
# single bar and signal_delay=1 then never fills → zero trades.
bar_interval={"Seconds": 1},
2026-03-17 16:13:34 +01:00
execution=bt.ExecutionConfig(
signal_delay=1,
execution_price="AtClose",
max_position_pct=1.0,
allow_short=False,
allow_fractional=True,
skip_gap_bars=False,
position_sizing_mode="Units",
),
slippage={"FixedBps": {"bps": 0.0}},
data_version="golden_v1",
rng_seed=7,
)
parquet_path = os.path.join(golden_buy_hold_dir, "bars_1m.parquet")
# Use native run_with_parquet for the InMemoryStore — but sweep needs a
# DataStore. Since we can't easily build an InMemoryStore from Python for
# sweep, let's test via the low-level _native.run_sweep with parquet store.
# Instead, we test at the JSON level directly.
from manifoldbt._native import run_sweep as _native_sweep
from manifoldbt._serde import scalar_value_to_json
# We need a DataStore for sweep — create a temp one with the golden data.
# But DataStore needs a metadata DB. Let's use a workaround: test the
# sweep logic via run_with_parquet for each combo manually, and verify
# the native run_sweep works when a store is available.
#
# For now, verify the grid expansion and result count via a simpler
# approach: run two single runs with different params and ensure they
# produce different metrics.
results = []
for size_val in [0.5, 1.0]:
s = bt.Strategy(
name="sweep_test",
signals={"signal": bt.lit(1.0)},
position_sizing=bt.col("signal") * bt.lit(size_val),
)
r = run_with_parquet(
s.to_json(), config.to_json(), parquet_path, "golden_v1"
)
results.append(r)
# Size=0.5 should have lower total return than size=1.0
assert results[0].metrics["total_return"] != results[1].metrics["total_return"]
assert results[0].trade_count > 0
assert results[1].trade_count > 0
def test_sweep_golden_grid_deterministic_order(golden_buy_hold_dir):
"""Verify multiple runs with same params produce same equity."""
parquet_path = os.path.join(golden_buy_hold_dir, "bars_1m.parquet")
config = bt.BacktestConfig(
universe=[1],
time_range_start=0,
time_range_end=4_000_000_000,
2026-07-19 02:07:07 +00:00
bar_interval={"Seconds": 1},
2026-03-17 16:13:34 +01:00
execution=bt.ExecutionConfig(
signal_delay=1,
execution_price="AtClose",
position_sizing_mode="Units",
),
slippage={"FixedBps": {"bps": 0.0}},
data_version="golden_v1",
rng_seed=7,
)
# Run twice with same params — results must be identical
strategy = bt.Strategy(
name="deterministic",
signals={"signal": bt.lit(1.0)},
position_sizing=bt.col("signal"),
)
r1 = run_with_parquet(
strategy.to_json(), config.to_json(), parquet_path, "golden_v1"
)
r2 = run_with_parquet(
strategy.to_json(), config.to_json(), parquet_path, "golden_v1"
)
eq1 = r1.equity_curve.to_pylist()
eq2 = r2.equity_curve.to_pylist()
assert eq1 == eq2, "Deterministic runs must produce identical equity curves"