"""Tests for empty/zero data edge cases.""" import sys from datetime import timedelta from decimal import Decimal from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "services" / "strategy-engine")) sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "services" / "backtester" / "src")) sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "services" / "order-executor" / "src")) sys.path.insert( 0, str(Path(__file__).resolve().parents[2] / "services" / "portfolio-manager" / "src") ) from backtester.engine import BacktestEngine from backtester.metrics import compute_detailed_metrics from order_executor.risk_manager import RiskManager from portfolio_manager.portfolio import PortfolioTracker from strategies.rsi_strategy import RsiStrategy from shared.models import OrderSide, Signal class TestBacktestEngineEmptyCandles: """BacktestEngine.run([]) should return valid result with 0 trades.""" def test_run_empty_candles(self): strategy = RsiStrategy() engine = BacktestEngine(strategy, initial_balance=Decimal("10000")) result = engine.run([]) assert result.total_trades == 0 assert result.initial_balance == Decimal("10000") assert result.final_balance == Decimal("10000") assert result.profit == Decimal("0") assert result.symbol == "" class TestPortfolioTrackerEmpty: """PortfolioTracker.get_all_positions() on fresh tracker returns empty list.""" def test_fresh_tracker_returns_empty(self): tracker = PortfolioTracker() positions = tracker.get_all_positions() assert positions == [] def test_get_position_returns_none_for_unknown_symbol(self): tracker = PortfolioTracker() assert tracker.get_position("AAPL") is None class TestRiskManagerZeroBalance: """RiskManager.check with zero balance should reject BUY signals.""" def test_zero_balance_rejects_buy(self): rm = RiskManager( max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("3"), ) signal = Signal( strategy="test", symbol="AAPL", side=OrderSide.BUY, price=Decimal("50000"), quantity=Decimal("0.01"), reason="test buy", ) result = rm.check( signal=signal, balance=Decimal("0"), positions={}, daily_pnl=Decimal("0"), ) assert result.allowed is False def test_zero_balance_allows_sell(self): rm = RiskManager( max_position_size=Decimal("0.5"), stop_loss_pct=Decimal("5"), daily_loss_limit_pct=Decimal("3"), ) signal = Signal( strategy="test", symbol="AAPL", side=OrderSide.SELL, price=Decimal("50000"), quantity=Decimal("0.01"), reason="test sell", ) result = rm.check( signal=signal, balance=Decimal("0"), positions={}, daily_pnl=Decimal("0"), ) # SELL doesn't require balance, so should be allowed assert result.allowed is True class TestComputeDetailedMetricsEmpty: """compute_detailed_metrics with empty trades returns zeroed metrics.""" def test_empty_trades(self): metrics = compute_detailed_metrics( trades=[], initial_balance=Decimal("10000"), final_balance=Decimal("10000"), ) assert metrics.total_return == 0.0 assert metrics.total_trades == 0 assert metrics.winning_trades == 0 assert metrics.losing_trades == 0 assert metrics.win_rate == 0.0 assert metrics.sharpe_ratio == 0.0 assert metrics.max_drawdown == 0.0 assert metrics.max_drawdown_duration == timedelta(0)