Download tests/test_risk.py from raghava4u/Trading-Bot-M20: direct link, hf CLI and curl.
- Browser
- Download file 6.2 kB
-
https://huggingface.co/raghava4u/Trading-Bot-M20/resolve/main/tests/test_risk.py
- Command line
-
hf download hf://raghava4u/Trading-Bot-M20/tests/test_risk.py
-
curl -L -o test_risk.py https://huggingface.co/raghava4u/Trading-Bot-M20/resolve/main/tests/test_risk.py
6.2 kB
| """ | |
| tests/test_risk.py — Risk check pipeline tests. | |
| Covers: | |
| - Safe mode blocking | |
| - Daily loss limit | |
| - Max positions limit | |
| - 3 consecutive losses → pause + half size | |
| - Profit lock after daily target | |
| - Position sizing with ATR | |
| """ | |
| from __future__ import annotations | |
| import datetime | |
| from unittest.mock import patch | |
| import pytest | |
| import config | |
| from contracts import FinalScore | |
| from execution import risk as risk_module | |
| def reset_risk_state(): | |
| """Reset risk module state before each test.""" | |
| risk_module._last_reset_date = None # force reset | |
| risk_module.reset_daily_state(equity=100_000.0) | |
| yield | |
| def _make_final(symbol: str, decision: str = "BUY", score: float = 0.7) -> FinalScore: | |
| return FinalScore( | |
| symbol=symbol, | |
| timestamp=datetime.datetime.now(datetime.timezone.utc), | |
| technical=score, | |
| sentiment=0.5, | |
| final=score, | |
| decision=decision, | |
| ) | |
| class TestDailyLossLimit: | |
| def test_blocks_after_max_loss(self, mock_close, mock_cancel): | |
| """After exceeding daily loss limit, all trades should be blocked.""" | |
| risk_module.update_pnl(realized=-(config.MAX_DAILY_LOSS_USD + 5.0), unrealized=0.0) | |
| allowed, reason = risk_module.run_risk_checks( | |
| symbol="AAPL", | |
| final_score=_make_final("AAPL"), | |
| open_position_count=0, | |
| open_positions={}, | |
| alert_callback=lambda *a, **kw: None, | |
| ) | |
| assert not allowed | |
| assert "daily_loss" in reason.lower() or "loss" in reason.lower() | |
| def test_allows_within_limit(self): | |
| risk_module.update_pnl(realized=-10.0, unrealized=0.0) | |
| allowed, reason = risk_module.run_risk_checks( | |
| symbol="AAPL", | |
| final_score=_make_final("AAPL"), | |
| open_position_count=0, | |
| open_positions={}, | |
| alert_callback=lambda *a, **kw: None, | |
| ) | |
| assert allowed | |
| class TestMaxPositions: | |
| def test_blocks_at_max(self): | |
| allowed, reason = risk_module.run_risk_checks( | |
| symbol="AAPL", | |
| final_score=_make_final("AAPL"), | |
| open_position_count=config.MAX_OPEN_POSITIONS, | |
| open_positions={}, | |
| alert_callback=lambda *a, **kw: None, | |
| ) | |
| assert not allowed | |
| def test_allows_below_max(self): | |
| allowed, reason = risk_module.run_risk_checks( | |
| symbol="AAPL", | |
| final_score=_make_final("AAPL"), | |
| open_position_count=2, | |
| open_positions={}, | |
| alert_callback=lambda *a, **kw: None, | |
| ) | |
| assert allowed | |
| class TestConsecutiveLosses: | |
| def test_pause_after_3_losses(self): | |
| """3 consecutive losses → 30 min pause.""" | |
| for _ in range(3): | |
| risk_module.record_trade_result(-100.0) | |
| allowed, reason = risk_module.run_risk_checks( | |
| symbol="AAPL", | |
| final_score=_make_final("AAPL"), | |
| open_position_count=0, | |
| open_positions={}, | |
| alert_callback=lambda *a, **kw: None, | |
| ) | |
| assert not allowed | |
| def test_half_size_after_3_losses(self): | |
| """After 3 losses, half_size_mode should be True.""" | |
| for _ in range(3): | |
| risk_module.record_trade_result(-100.0) | |
| # Trigger the risk check which sets half_size_mode | |
| with patch("execution.broker.safe_mode_active", False): | |
| risk_module.run_risk_checks( | |
| symbol="AAPL", | |
| final_score=_make_final("AAPL"), | |
| open_position_count=0, | |
| open_positions={}, | |
| alert_callback=lambda *a, **kw: None, | |
| ) | |
| assert risk_module.half_size_mode | |
| class TestProfitLock: | |
| def test_locks_after_target(self): | |
| """After hitting the configured daily target, lock profits.""" | |
| risk_module.update_pnl(realized=config.TARGET_DAILY_PROFIT_USD + 5.0, unrealized=0.0) | |
| allowed, reason = risk_module.run_risk_checks( | |
| symbol="AAPL", | |
| final_score=_make_final("AAPL"), | |
| open_position_count=0, | |
| open_positions={}, | |
| alert_callback=lambda *a, **kw: None, | |
| ) | |
| assert not allowed | |
| assert "profit" in reason.lower() or "target" in reason.lower() | |
| class TestPositionSizing: | |
| def test_size_within_risk_budget(self, mock_frac): | |
| qty, stop_dist, is_frac = risk_module.compute_position_size( | |
| equity=100_000.0, | |
| atr_value=2.0, | |
| symbol="AAPL", | |
| ) | |
| # Risk per trade = 1% of $100k = $1000 | |
| # With ATR stop = 2.0 * 1.0 = $2.0 | |
| # Qty = 1000 / 2.0 = 500 | |
| assert qty > 0 | |
| assert stop_dist > 0 | |
| def test_fractional_flag(self, mock_frac): | |
| qty, _, is_frac = risk_module.compute_position_size( | |
| equity=100.0, # tiny account → fractional | |
| atr_value=2.0, | |
| symbol="AAPL", | |
| ) | |
| if qty > 0 and qty < 1: | |
| assert is_frac | |
| class TestSafeMode: | |
| def test_safe_mode_blocks(self): | |
| allowed, reason = risk_module.run_risk_checks( | |
| symbol="AAPL", | |
| final_score=_make_final("AAPL"), | |
| open_position_count=0, | |
| open_positions={}, | |
| alert_callback=lambda *a, **kw: None, | |
| ) | |
| assert not allowed | |
| assert "safe" in reason.lower() | |