Skip to content

Commit 127115d

Browse files
committed
test(pipeline): convert phase 2/3 pipeline tests from pytest to unittest
Same fix as the previous commit — CI uses 'unittest discover' which doesn't have pytest available. Converts test_vector_pipeline_engine, test_factor_arithmetic_and_lazy, and test_vector_backtest_pipeline_injection to native unittest.TestCase, with pytest.approx -> assertAlmostEqual, pytest.raises -> assertRaises, parametrize -> subTest, monkeypatch -> unittest.mock.patch.object, and caplog -> assertLogs.
1 parent 93f3a19 commit 127115d

3 files changed

Lines changed: 498 additions & 500 deletions

File tree

tests/infrastructure/services/backtesting/test_vector_backtest_pipeline_injection.py

Lines changed: 95 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,12 @@
88
"""
99
from __future__ import annotations
1010

11+
import logging
12+
import unittest
1113
from datetime import datetime, timedelta
1214
from types import SimpleNamespace
1315

1416
import polars as pl
15-
import pytest
1617

1718
from investing_algorithm_framework import (
1819
AverageDollarVolume,
@@ -77,120 +78,121 @@ def _make_data_sources_and_data():
7778
return sources, data
7879

7980

80-
def test_inject_pipelines_no_pipelines_is_noop():
81-
sources, data = _make_data_sources_and_data()
82-
strategy = _StubStrategy(data_sources=sources, pipelines=None)
83-
before = dict(data)
84-
85-
VectorBacktestService._inject_pipelines(
86-
strategy=strategy,
87-
data=data,
88-
backtest_date_range=BacktestDateRange(
89-
start_date=datetime(2024, 1, 2),
90-
end_date=datetime(2024, 1, 4),
91-
),
92-
)
93-
assert data == before
94-
95-
96-
def test_inject_pipelines_adds_long_frame_keyed_by_class_name():
97-
sources, data = _make_data_sources_and_data()
98-
strategy = _StubStrategy(data_sources=sources, pipelines=[_Screener])
99-
100-
VectorBacktestService._inject_pipelines(
101-
strategy=strategy,
102-
data=data,
103-
backtest_date_range=BacktestDateRange(
104-
start_date=datetime(2024, 1, 2),
105-
end_date=datetime(2024, 1, 4),
106-
),
107-
)
108-
109-
assert "_Screener" in data
110-
out = data["_Screener"]
111-
assert isinstance(out, pl.DataFrame)
112-
# Long format: datetime + symbol + factor columns (universe dropped)
113-
assert set(out.columns) == {"datetime", "symbol", "adv", "momentum"}
114-
# Universe restricts to top-2 by ADV every bar; BBB always loses
115-
# (volume=1 vs 100/200) so it must never appear in the output.
116-
assert "BBB/EUR" not in out["symbol"].to_list()
117-
118-
119-
def test_inject_pipelines_respects_end_date_no_lookahead():
120-
sources, data = _make_data_sources_and_data()
121-
strategy = _StubStrategy(data_sources=sources, pipelines=[_Screener])
122-
123-
VectorBacktestService._inject_pipelines(
124-
strategy=strategy,
125-
data=data,
126-
backtest_date_range=BacktestDateRange(
127-
start_date=datetime(2024, 1, 2),
128-
end_date=datetime(2024, 1, 3),
129-
),
130-
)
131-
out = data["_Screener"]
132-
# No bars beyond end_date should leak in.
133-
max_dt = out["datetime"].max()
134-
assert max_dt <= datetime(2024, 1, 3)
135-
136-
137-
def test_inject_pipelines_skips_when_no_ohlcv_sources(caplog):
138-
"""A strategy with pipelines but no OHLCV data sources should log
139-
and skip silently without raising."""
140-
strategy = _StubStrategy(data_sources=[], pipelines=[_Screener])
141-
data = {}
81+
class TestVectorBacktestPipelineInjection(unittest.TestCase):
82+
83+
def test_inject_pipelines_no_pipelines_is_noop(self):
84+
sources, data = _make_data_sources_and_data()
85+
strategy = _StubStrategy(data_sources=sources, pipelines=None)
86+
before = dict(data)
14287

143-
with caplog.at_level(
144-
"WARNING",
145-
logger=(
146-
"investing_algorithm_framework.infrastructure.services."
147-
"backtesting.vector_backtest_service"
148-
),
149-
):
15088
VectorBacktestService._inject_pipelines(
15189
strategy=strategy,
15290
data=data,
15391
backtest_date_range=BacktestDateRange(
15492
start_date=datetime(2024, 1, 2),
155-
end_date=datetime(2024, 1, 3),
93+
end_date=datetime(2024, 1, 4),
15694
),
15795
)
96+
self.assertEqual(data, before)
15897

159-
assert "_Screener" not in data
160-
assert any(
161-
"no OHLCV data sources" in record.message
162-
for record in caplog.records
163-
)
164-
165-
166-
def test_inject_pipelines_re_raises_engine_errors():
167-
"""Pipeline evaluation errors should propagate to the caller so
168-
backtest authors see a clear failure rather than a silent skip."""
98+
def test_inject_pipelines_adds_long_frame_keyed_by_class_name(self):
99+
sources, data = _make_data_sources_and_data()
100+
strategy = _StubStrategy(data_sources=sources, pipelines=[_Screener])
169101

170-
class _Broken(Pipeline):
171-
adv = AverageDollarVolume(window=2)
172-
momentum = Returns(window=2)
102+
VectorBacktestService._inject_pipelines(
103+
strategy=strategy,
104+
data=data,
105+
backtest_date_range=BacktestDateRange(
106+
start_date=datetime(2024, 1, 2),
107+
end_date=datetime(2024, 1, 4),
108+
),
109+
)
173110

174-
sources, data = _make_data_sources_and_data()
175-
# Corrupt one of the inputs to force a polars-level error.
176-
bad_id = sources[0].get_identifier()
177-
data[bad_id] = pl.DataFrame(
178-
{"Datetime": [datetime(2024, 1, 1)], "Close": [10.0]}
179-
)
111+
self.assertIn("_Screener", data)
112+
out = data["_Screener"]
113+
self.assertIsInstance(out, pl.DataFrame)
114+
# Long format: datetime + symbol + factor columns (universe dropped)
115+
self.assertEqual(
116+
set(out.columns), {"datetime", "symbol", "adv", "momentum"}
117+
)
118+
# Universe restricts to top-2 by ADV every bar; BBB always loses
119+
# (volume=1 vs 100/200) so it must never appear in the output.
120+
self.assertNotIn("BBB/EUR", out["symbol"].to_list())
180121

181-
strategy = _StubStrategy(data_sources=sources, pipelines=[_Broken])
122+
def test_inject_pipelines_respects_end_date_no_lookahead(self):
123+
sources, data = _make_data_sources_and_data()
124+
strategy = _StubStrategy(data_sources=sources, pipelines=[_Screener])
182125

183-
with pytest.raises(Exception):
184126
VectorBacktestService._inject_pipelines(
185127
strategy=strategy,
186128
data=data,
187129
backtest_date_range=BacktestDateRange(
188130
start_date=datetime(2024, 1, 2),
189-
end_date=datetime(2024, 1, 4),
131+
end_date=datetime(2024, 1, 3),
190132
),
191133
)
134+
out = data["_Screener"]
135+
# No bars beyond end_date should leak in.
136+
max_dt = out["datetime"].max()
137+
self.assertLessEqual(max_dt, datetime(2024, 1, 3))
138+
139+
def test_inject_pipelines_skips_when_no_ohlcv_sources(self):
140+
"""A strategy with pipelines but no OHLCV data sources should log
141+
and skip silently without raising."""
142+
strategy = _StubStrategy(data_sources=[], pipelines=[_Screener])
143+
data = {}
144+
145+
logger_name = (
146+
"investing_algorithm_framework.infrastructure.services."
147+
"backtesting.vector_backtest_service"
148+
)
149+
with self.assertLogs(logger_name, level=logging.WARNING) as cm:
150+
VectorBacktestService._inject_pipelines(
151+
strategy=strategy,
152+
data=data,
153+
backtest_date_range=BacktestDateRange(
154+
start_date=datetime(2024, 1, 2),
155+
end_date=datetime(2024, 1, 3),
156+
),
157+
)
158+
159+
self.assertNotIn("_Screener", data)
160+
self.assertTrue(
161+
any("no OHLCV data sources" in msg for msg in cm.output)
162+
)
163+
164+
def test_inject_pipelines_re_raises_engine_errors(self):
165+
"""Pipeline evaluation errors should propagate to the caller so
166+
backtest authors see a clear failure rather than a silent skip."""
167+
168+
class _Broken(Pipeline):
169+
adv = AverageDollarVolume(window=2)
170+
momentum = Returns(window=2)
171+
172+
sources, data = _make_data_sources_and_data()
173+
# Corrupt one of the inputs to force a polars-level error.
174+
bad_id = sources[0].get_identifier()
175+
data[bad_id] = pl.DataFrame(
176+
{"Datetime": [datetime(2024, 1, 1)], "Close": [10.0]}
177+
)
178+
179+
strategy = _StubStrategy(data_sources=sources, pipelines=[_Broken])
180+
181+
with self.assertRaises(Exception):
182+
VectorBacktestService._inject_pipelines(
183+
strategy=strategy,
184+
data=data,
185+
backtest_date_range=BacktestDateRange(
186+
start_date=datetime(2024, 1, 2),
187+
end_date=datetime(2024, 1, 4),
188+
),
189+
)
192190

193191

194192
# Suppress the unused import warning — kept for symmetry with other
195193
# vector-backtest tests in the suite.
196194
_ = SimpleNamespace, DataType
195+
196+
197+
if __name__ == "__main__":
198+
unittest.main()

0 commit comments

Comments
 (0)