Testing Utilities¶
Module: rustybt.testing
Purpose: Comprehensive testing utilities for strategy development and validation
Status: Production-ready
Overview¶
RustyBT provides extensive testing utilities to help develop, validate, and maintain trading strategies with high confidence. These utilities cover unit testing, integration testing, property-based testing, and strategy validation.
Testing Philosophy: Strategies involve capital at risk. Comprehensive testing is mandatory, not optional.
Quick Start¶
Basic Strategy Test¶
import pytest
from decimal import Decimal
from rustybt.algorithm import TradingAlgorithm
from rustybt.utils.run_algo import run_algorithm
from rustybt.testing import ZiplineTestCase
from rustybt.testing import create_data_portal, tmp_asset_finder
import pandas as pd
class MyStrategy(TradingAlgorithm):
def initialize(self):
self.asset = self.symbol('AAPL')
def handle_data(self, context, data):
self.order_target_percent(self.asset, 0.95)
class TestMyStrategy(ZiplineTestCase):
def test_strategy_execution(self):
"""Test strategy executes without errors."""
results = run_algorithm(
strategy_class=MyStrategy,
start='2020-01-01',
end='2020-12-31',
capital_base=100000,
data_frequency='daily'
)
# Verify results
assert results['portfolio_value'].iloc[-1] > Decimal("0")
assert len(results['transactions']) > 0
def test_strategy_performance(self):
"""Test strategy achieves positive returns."""
results = run_algorithm(
strategy_class=MyStrategy,
start='2020-01-01',
end='2020-12-31',
capital_base=100000
)
final_value = results['portfolio_value'].iloc[-1]
initial_value = results['portfolio_value'].iloc[0]
total_return = (final_value - initial_value) / initial_value
assert total_return > Decimal("0"), "Strategy should have positive returns"
Core Testing Classes¶
ZiplineTestCase¶
Module: rustybt.testing.fixtures
Base test class with automatic resource management and cleanup.
Features:
- Automatic setup/teardown via ExitStack
- Per-test and per-class fixture support
- Temporary directory management
- Asset finder creation
- Data portal setup
Usage:
from rustybt.testing import ZiplineTestCase
class TestMyStrategy(ZiplineTestCase):
@classmethod
def init_class_fixtures(cls):
"""Set up fixtures shared across all tests in class."""
super().init_class_fixtures() # ALWAYS call super()
# Create trading calendar
cls.trading_calendar = cls.enter_class_context(
get_calendar('NYSE')
)
# Create asset finder
cls.asset_finder = cls.enter_class_context(
tmp_asset_finder(num_assets=10)
)
def init_instance_fixtures(self):
"""Set up fixtures for each test method."""
super().init_instance_fixtures() # ALWAYS call super()
# Create temporary directory
self.temp_dir = self.enter_instance_context(
tmp_dir()
)
# Create data portal
self.data_portal = create_data_portal(
asset_finder=self.asset_finder,
trading_calendar=self.trading_calendar
)
def test_something(self):
# Use self.asset_finder, self.data_portal, etc.
...
Important Methods:
# Register context managers (auto-cleanup)
self.enter_instance_context(context_manager)
self.enter_class_context(context_manager)
# Register cleanup callbacks
self.add_instance_callback(callback_func)
self.add_class_callback(callback_func)
Test Fixtures¶
Asset Creation¶
Create Asset Finder:
from rustybt.testing import tmp_asset_finder
# Create asset finder for testing
asset_finder = tmp_asset_finder()
Create Data Portal:
from rustybt.testing import create_data_portal
data_portal = create_data_portal(
asset_finder=asset_finder,
trading_calendar=trading_calendar,
start_date='2020-01-01',
end_date='2023-12-31',
data_frequency='daily'
)
Strategy Testing Patterns¶
Pattern 1: Basic Execution Test¶
def test_strategy_executes(self):
"""Verify strategy runs to completion without errors."""
results = run_algorithm(
strategy_class=MyStrategy,
start='2020-01-01',
end='2020-12-31',
capital_base=100000
)
# Basic assertions
assert results is not None
assert 'portfolio_value' in results
assert len(results) > 0
assert results['portfolio_value'].iloc[-1] > Decimal("0")
Pattern 2: Performance Test¶
from rustybt.analytics.risk import RiskAnalytics
def test_strategy_performance(self):
"""Verify strategy achieves positive risk-adjusted returns."""
results = run_algorithm(
strategy_class=MyStrategy,
start='2020-01-01',
end='2023-12-31',
capital_base=100000
)
# Calculate risk metrics
risk = RiskAnalytics(results)
# Assertions
assert risk.total_return > Decimal("0"), "Positive returns required"
assert risk.sharpe_ratio > Decimal("1.0"), "Sharpe ratio >= 1.0 required"
assert risk.max_drawdown < Decimal("0.20"), "Max drawdown < 20% required"
Pattern 3: Parametric Test¶
import pytest
@pytest.mark.parametrize("lookback_period", [10, 20, 50, 100])
@pytest.mark.parametrize("rebalance_frequency", ['daily', 'weekly', 'monthly'])
def test_parameter_combinations(self, lookback_period, rebalance_frequency):
"""Test strategy across parameter space."""
class ParametricStrategy(TradingAlgorithm):
def initialize(self):
self.lookback_period = lookback_period
self.rebalance_frequency = rebalance_frequency
self.asset = self.symbol('AAPL')
def handle_data(self, context, data):
# Strategy logic using parameters
...
results = run_algorithm(
strategy_class=ParametricStrategy,
start='2020-01-01',
end='2023-12-31',
capital_base=100000
)
# Verify strategy works for all parameter combinations
assert results['portfolio_value'].iloc[-1] > Decimal("0")
Property-Based Testing¶
Module: hypothesis (third-party integration)
Property-based testing generates random test cases to verify strategy invariants.
Example - Portfolio Value Invariant¶
from hypothesis import given, strategies as st
from decimal import Decimal
@given(
starting_cash=st.decimals(min_value=Decimal("10000"), max_value=Decimal("1000000")),
asset_price=st.decimals(min_value=Decimal("1"), max_value=Decimal("1000")),
shares=st.integers(min_value=0, max_value=1000)
)
def test_portfolio_value_invariant(self, starting_cash, asset_price, shares):
"""Portfolio value = cash + sum(position values)."""
# Create simple portfolio
cash = starting_cash - (asset_price * Decimal(shares))
position_value = asset_price * Decimal(shares)
portfolio_value = cash + position_value
# Invariant: Portfolio value should equal starting cash
assert portfolio_value == starting_cash
Best Practices¶
1. Test Strategy Incrementally¶
# ✅ GOOD: Test each component separately
def test_initialize(self):
"""Test strategy initialization."""
strategy = MyStrategy()
strategy.initialize()
assert hasattr(strategy, 'asset')
def test_signal_generation(self):
"""Test signal generation logic."""
...
def test_full_strategy(self):
"""Test complete strategy."""
...
2. Use Fixtures for Common Setup¶
import pytest
@pytest.fixture
def strategy():
"""Create strategy instance."""
return MyStrategy()
@pytest.fixture
def test_data():
"""Create test data."""
return create_data_portal(...)
def test_with_fixtures(self, strategy, test_data):
# Use strategy and test_data
...
3. Test Edge Cases¶
def test_zero_volume_handling(self):
"""Test handling of zero-volume bars."""
...
def test_missing_data_handling(self):
"""Test handling of missing price data."""
...
def test_extreme_volatility_handling(self):
"""Test handling of extreme volatility."""
...
Summary¶
Testing Checklist: - [ ] Unit tests for each strategy component - [ ] Integration tests for complete workflow - [ ] Property-based tests for invariants - [ ] Performance benchmarks - [ ] Edge case tests (flash crashes, missing data, etc.) - [ ] Parametric tests across parameter ranges - [ ] CI/CD integration
Remember: Comprehensive testing is mandatory for strategies involving real capital. Test early, test often, test thoroughly.
Related Documentation¶
- Data Management - Test data creation
- Order Management - Order testing
- Analytics - Performance validation
- Live Trading - Live trading validation
Comprehensive testing utilities and best practices for RustyBT strategies and systems.
Overview¶
RustyBT provides a robust testing framework including unit testing utilities, property-based testing with Hypothesis, strategy testing patterns, backtesting validation, and mock trading environments.
Key Features¶
- Property-Based Testing: Hypothesis integration for testing financial invariants
- Test Data Generation: Realistic OHLCV data generation for testing
- Strategy Testing Patterns: Reusable patterns for testing trading strategies
- Backtesting Validation: Ensure backtest correctness and consistency
- Mock Environments: Simulated brokers and data feeds for testing
- Zero-Mock Enforcement: Tools to prevent mock code in production
Quick Navigation¶
Core Testing¶
- Property-Based Testing - Testing with Hypothesis (see below)
- Test Data Generation - Creating realistic test data (see project tests/)
- Strategy Testing Patterns - Common testing patterns (see project tests/)
- Backtesting Validation - Validating backtest correctness (see project tests/)
- Mock Environments - Paper brokers and data feeds (see project tests/)
Property-Based Testing¶
Overview¶
Property-based testing with Hypothesis generates thousands of test cases automatically, finding edge cases that manual testing misses.
See the Hypothesis documentation for comprehensive examples and usage patterns.
Best Practices¶
1. Test at Multiple Levels¶
# Unit tests: Individual components
def test_order_creation():
order = Order(...)
assert order.is_valid()
# Integration tests: Components working together
def test_order_execution():
broker = PaperBroker()
order_id = broker.submit_order(...)
assert broker.get_order(order_id).status == 'filled'
# End-to-end tests: Complete workflow
def test_full_strategy():
result = run_backtest(strategy, ...)
assert result.sharpe_ratio > 1.0
2. Use Property-Based Testing for Invariants¶
@given(...)
def test_portfolio_invariant(...):
# Test fundamental properties that must always hold
assert portfolio.value == cash + positions_value
3. Test Edge Cases¶
def test_zero_cash():
portfolio = Portfolio(starting_cash=Decimal("0"))
# Should handle gracefully
def test_negative_prices():
with pytest.raises(ValueError):
bar = Bar(open=-100, ...)
def test_missing_data():
# Strategy should handle missing data without crashing
pass
4. Validate Against Known Results¶
def test_moving_average_calculation():
"""Test MA matches known result."""
prices = [10, 20, 30, 40, 50]
ma = calculate_ma(prices, window=3)
# Known result for last 3: (30 + 40 + 50) / 3 = 40
assert ma[-1] == 40
See Also¶
- Property-Based Testing Guide - See Hypothesis documentation
- Strategy Testing Guide - See project tests/ directory
- Zero-Mock Enforcement - See project architecture documentation
- Coding Standards - See project architecture documentation
Examples¶
See tests/ directory for complete examples:
tests/test_portfolio.py- Portfolio testing examplestests/test_strategies.py- Strategy testing patternstests/property_tests/- Property-based testing examplestests/integration/- Integration testing examples