Files
manifoldbt/python/tests/test_resolution_1s.py
T

93 lines
3.5 KiB
Python

"""One-second bars: Pro simulates and returns them, Community stops at the minute.
The store holds a one-second tier since 0.24.0. Two rules sit on top of it:
* simulating on bars finer than one minute needs Pro (``resolution_1s``),
enforced by the engine core on every runner and answered early by the
bindings;
* the output floor is one second on Pro and one day on Community, so on Pro a
one-second run comes back with one point per bar.
The Pro cases run on a Pro machine and in the engine's own debug builds, which
is what the CI runs; the Community cases skip there, like ``test_combo_limit``.
"""
import os
import tempfile
import numpy as np
import pandas as pd
import pytest
import manifoldbt as bt
IS_PRO = bt.license_info()[0] == "Pro"
N_BARS = 3 * 3600 # three hours of one-second bars
@pytest.fixture(scope="module")
def one_second_store():
root = tempfile.mkdtemp()
idx = pd.date_range("2024-01-01", periods=N_BARS, freq="1s", tz="UTC")
close = 100.0 + np.cumsum(np.random.default_rng(1).normal(0, 0.01, N_BARS))
df = pd.DataFrame({
"timestamp": idx, "open": close, "high": close + 0.02,
"low": close - 0.02, "close": close, "volume": np.full(N_BARS, 10.0),
})
return bt.import_dataframe(
df, symbol="XS", symbol_id=1, interval="1s",
data_root=os.path.join(root, "data"),
metadata_db=os.path.join(root, "meta.sqlite"),
)
def _strategy():
return bt.Strategy(
name="always_long",
signals={"signal": bt.lit(1.0)},
position_sizing=bt.lit(1.0) * bt.param("size", default=1.0),
parameters={"size": bt.param("size", default=1.0)},
)
def _config(interval):
t0, t1 = bt.time_range("2024-01-01", "2024-01-02")
return bt.BacktestConfig(
universe=[1], time_range_start=t0, time_range_end=t1, bar_interval=interval,
)
def test_one_second_bars_land_in_their_own_tier(one_second_store):
root = one_second_store.data_root()
tiers = {
d for _, dirs, _ in os.walk(root) for d in dirs if d in ("1s", "1m", "bars_1s", "bars_1m")
}
assert "1s" in tiers or "bars_1s" in tiers, tiers
# Nothing was silently rewritten as one-minute data.
assert "1m" not in tiers and "bars_1m" not in tiers, tiers
@pytest.mark.skipif(not IS_PRO, reason="Pro (or the debug override): one-second simulation")
def test_pro_simulates_and_returns_every_second(one_second_store):
result = bt.run(_strategy(), _config({"Seconds": 1}), one_second_store)
# One point per bar: the output floor is one second, not one minute.
assert len(result.equity_curve) == N_BARS
@pytest.mark.skipif(not IS_PRO, reason="Pro (or the debug override): one-second simulation")
def test_one_minute_run_on_the_same_store_is_coarser(one_second_store):
result = bt.run(_strategy(), _config({"Minutes": 1}), one_second_store)
assert len(result.equity_curve) == N_BARS // 60 + 1
@pytest.mark.skipif(IS_PRO, reason="Community-only; needs a machine without a Pro licence")
def test_community_is_refused_below_one_minute(one_second_store):
with pytest.raises(PermissionError, match="Pro"):
bt.run(_strategy(), _config({"Seconds": 1}), one_second_store)
@pytest.mark.skipif(IS_PRO, reason="Community-only; needs a machine without a Pro licence")
def test_community_still_runs_at_one_minute_on_a_one_second_store(one_second_store):
result = bt.run(_strategy(), _config({"Minutes": 1}), one_second_store)
# Daily output floor: three hours collapse onto a single day.
assert len(result.equity_curve) <= 2