-
Notifications
You must be signed in to change notification settings - Fork 187
Open
Description
Under Project > Workflow, add a Unit Test section.
You cannot run "normal" unit tests with LEAN CLI, but we can use backtests (lean backtest) to run them.
For example, we can use this algorithm:
# region imports
from AlgorithmImports import *
# endregion
RUN_UNIT_TEST = True
class PortfolioAllocator:
"""Testable business logic for portfolio allocation"""
def __init__(self, target_symbols):
self.target_symbols = target_symbols
def calculate_weights(self):
"""Calculate equal weights for all symbols"""
weight = 1.0 / len(self.target_symbols)
return {symbol: weight for symbol in self.target_symbols}
def should_rebalance(self, portfolio):
"""Determine if portfolio needs rebalancing"""
return not portfolio.invested
class UnitTestAlgorithm(QCAlgorithm):
def initialize(self):
self.set_start_date(2024, 9, 19)
self.set_cash(100000)
# Run unit tests before setup (only in backtest mode)
if RUN_UNIT_TEST and not self.live_mode:
self._run_unit_tests()
self.add_equity("SPY", Resolution.MINUTE)
self.add_equity("BND", Resolution.MINUTE)
self.add_equity("AAPL", Resolution.MINUTE)
# Initialize testable allocator
self._allocator = PortfolioAllocator(["SPY", "BND", "AAPL"])
def _run_unit_tests(self):
"""Run unit tests during algorithm initialization"""
self.debug("=== Running Unit Tests ===")
# Test 1: Equal weight calculation
allocator = PortfolioAllocator(["SPY", "BND", "AAPL"])
weights = allocator.calculate_weights()
assert len(weights) == 3, "Should have 3 weights"
assert abs(weights["SPY"] - 0.3333) < 0.0001, "SPY weight incorrect"
assert abs(weights["BND"] - 0.3333) < 0.0001, "BND weight incorrect"
assert abs(weights["AAPL"] - 0.3333) < 0.0001, "AAPL weight incorrect"
self.debug("✓ Test 1 passed: Equal weight calculation")
# Test 2: Weights sum to 1.0
total_weight = sum(weights.values())
assert abs(total_weight - 1.0) < 0.0001, f"Weights sum to {total_weight}, not 1.0"
self.debug("✓ Test 2 passed: Weights sum to 1.0")
# Test 3: Single symbol allocation
single_allocator = PortfolioAllocator(["SPY"])
single_weights = single_allocator.calculate_weights()
assert single_weights["SPY"] == 1.0, "Single symbol should have 100% weight"
self.debug("✓ Test 3 passed: Single symbol allocation")
# Test 4: Two symbol allocation
dual_allocator = PortfolioAllocator(["SPY", "BND"])
dual_weights = dual_allocator.calculate_weights()
assert abs(dual_weights["SPY"] - 0.5) < 0.0001, "SPY should be 50%"
assert abs(dual_weights["BND"] - 0.5) < 0.0001, "BND should be 50%"
self.debug("✓ Test 4 passed: Two symbol allocation")
# Test 5: Mock portfolio rebalance logic
class MockPortfolio:
def __init__(self, invested):
self.invested = invested
assert allocator.should_rebalance(MockPortfolio(False)) == True, "Should rebalance when not invested"
assert allocator.should_rebalance(MockPortfolio(True)) == False, "Should not rebalance when invested"
self.debug("✓ Test 5 passed: Rebalance logic")
self.debug("=== All Unit Tests Passed ===")
def on_data(self, data: Slice):
if self._allocator.should_rebalance(self.portfolio):
weights = self._allocator.calculate_weights()
for symbol, weight in weights.items():
self.set_holdings(symbol, weight)Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels