Files
crypto_trading/backend/tests/test_risk.py
T
matitos 03dfa369e5
CI/CD / backend (pull_request) Successful in 6m18s
CI/CD / frontend (pull_request) Successful in 4m55s
CI/CD / build (pull_request) Skipped
CI/CD / deploy (pull_request) Skipped
CI/CD / cleanup (pull_request) Skipped
Risk controls, alerts, API token, more exchanges, presets
- RiskManager halts + flattens on max daily loss / drawdown, caps position
  size and trades per day; adds a manual kill switch (POST /api/bot/kill).
- Telegram and webhook notifications on trades, errors and halts.
- Optional API bearer token on /api, injected server-side by the frontend
  nginx so the browser never sees it.
- Kraken and Coinbase market data (public); Coinbase live broker. Live on an
  unsupported exchange now errors instead of silently falling back to paper.
- Strategy presets backed by the StrategyConfig table (CRUD API + UI).
- Idempotent additive-column migration so existing SQLite databases upgrade in
  place (fixes the new strategy_configs.name column).
2026-09-18 19:26:29 +00:00

64 lines
1.7 KiB
Python

from __future__ import annotations
import pytest
from app.engine.risk import RiskManager
def test_daily_loss_detects_and_halts() -> None:
risk = RiskManager(max_daily_loss=100.0)
risk.reset(10_000)
assert risk.update(9_950) is None
reason = risk.update(9_900)
assert reason is not None and "daily loss" in reason
assert risk.halted is False # detection only; caller decides
risk.halt(reason)
assert risk.halted is True
assert risk.can_enter() is False
def test_drawdown_halts() -> None:
risk = RiskManager(max_drawdown_pct=10.0)
risk.reset(10_000)
risk.update(11_000)
assert risk.update(10_000) is None
reason = risk.update(9_850)
assert reason is not None and "drawdown" in reason
risk.halt(reason)
assert risk.halted
def test_halt_is_sticky() -> None:
risk = RiskManager(max_daily_loss=100.0)
risk.reset(10_000)
reason = risk.update(9_800)
risk.halt(reason or "loss")
assert risk.halted
# A recovery must not silently resume trading.
assert risk.update(10_500) is not None
assert risk.halted
def test_max_trades_per_day() -> None:
risk = RiskManager(max_trades_per_day=2)
risk.reset(10_000)
assert risk.can_enter()
risk.record_entry()
risk.record_entry()
assert risk.can_enter() is False
assert "max trades" in (risk.entry_block_reason() or "")
def test_clamp_size() -> None:
risk = RiskManager(max_position_pct=0.25)
assert risk.clamp_size(1.0) == pytest.approx(0.25)
assert risk.clamp_size(0.1) == pytest.approx(0.1)
assert risk.clamp_size(-1.0) == pytest.approx(0.0)
def test_disabled_limits_never_halt() -> None:
risk = RiskManager()
risk.reset(10_000)
assert risk.update(1.0) is None
assert risk.can_enter()