Skip to main content

Testing

The whole suite runs offline — no API keys, no network, no alpaca import. That's a direct benefit of the broker abstraction: tests inject in-memory fakes where production injects Alpaca.

Running the tests

make test # the standard way (uv run --extra dev pytest)

make test installs the dev extra (pytest, ruff, scikit-learn, OR-Tools) and runs the whole suite. To run pytest directly or narrow the run:

uv run --extra dev pytest # full suite, default verbosity
uv run --extra dev pytest -q # quiet
uv run --extra dev pytest tests/test_backtest.py # one file
uv run --extra dev pytest -k beta # tests matching "beta"
uv run --extra dev pytest tests/test_live_trader.py::test_hold_is_noop # one test

Notes:

  • No setup needed — no config.py, keys, or network. Tests use the fakes below.
  • The OR-Tools portfolio tests skip automatically if that optional dependency isn't installed; everything else runs with just the dev extra.
  • CI runs the same command on every push/PR, alongside ruff check and ruff format --check (see Coding standards).

Lint & format

uv run ruff check . # lint
uv run ruff format . # auto-format (CI uses --check)

Test doubles (tests/fakes.py)

FakeImplementsUsed for
FakeMarketDataMarketDataProviderdeterministic synthetic OHLCV (random walk + volume spikes)
DictMarketDataMarketDataProviderserves exact fixture frames for precise engine assertions
FakeBrokerBrokerrecords submitted orders / closes for execution tests

What's covered

FileFocus
test_units.pytimeframe parsing, numeric helpers, metric primitives, parameter space
test_strategy.pysizing caps, exit conditions, signal validation, indicator columns
test_scanner.pyvolume-scanner signal logic, config validation, forward scoring
test_backtest.pyengine fills — take-profit, stop-loss, signal exit, end-of-period P&L (exact)
test_live_trader.pyentry → bracket order, dup-position skip, exit close, insufficient funds
test_optimizer.pygrid/random ranking, max-evals cap, Bayesian path
test_portfolio.pycardinality, weight caps, score preference (skipped if OR-Tools absent)
test_offline.pyfull end-to-end backtest / scan / optimize on synthetic data

A real bug the tests caught

grid_search originally materialised the entire parameter grid before capping — billions of combinations for the bundled strategy, which OOM-killed the process (exit 137). The optimizer test surfaced it immediately; the fix computes grid_size() and samples instead. See Optimization.

Engine fill logic is asserted with a ScriptedStrategy that emits a fixed signal per bar, so stop/take/exit/end-of-period behaviour and P&L are checked exactly, independent of any indicator quirk.