68 lines
1.8 KiB
Python
68 lines
1.8 KiB
Python
"""Drawdown monitoring and equity curve tracking."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from typing import List
|
|
|
|
from loguru import logger
|
|
|
|
|
|
@dataclass
|
|
class DrawdownState:
|
|
"""Current drawdown statistics."""
|
|
|
|
current_drawdown: float = 0.0
|
|
max_drawdown: float = 0.0
|
|
peak_equity: float = 0.0
|
|
trough_equity: float = 0.0
|
|
is_in_drawdown: bool = False
|
|
|
|
|
|
class DrawdownMonitor:
|
|
"""Track equity curve and compute drawdown metrics."""
|
|
|
|
def __init__(self, max_drawdown_limit: float = 0.15):
|
|
self.limit = max_drawdown_limit
|
|
self._equity_curve: List[float] = []
|
|
self._peak: float = 0.0
|
|
self._max_dd: float = 0.0
|
|
|
|
def update(self, equity: float) -> DrawdownState:
|
|
"""Record new equity point and recalculate drawdown."""
|
|
self._equity_curve.append(equity)
|
|
|
|
if equity > self._peak:
|
|
self._peak = equity
|
|
|
|
current_dd = (self._peak - equity) / self._peak if self._peak > 0 else 0.0
|
|
if current_dd > self._max_dd:
|
|
self._max_dd = current_dd
|
|
|
|
state = DrawdownState(
|
|
current_drawdown=current_dd,
|
|
max_drawdown=self._max_dd,
|
|
peak_equity=self._peak,
|
|
trough_equity=min(self._equity_curve) if self._equity_curve else 0.0,
|
|
is_in_drawdown=current_dd > 0,
|
|
)
|
|
|
|
if current_dd >= self.limit:
|
|
logger.warning(
|
|
"DRAWDOWN ALERT: {:.2%} >= limit {:.2%}", current_dd, self.limit
|
|
)
|
|
|
|
return state
|
|
|
|
@property
|
|
def equity_curve(self) -> List[float]:
|
|
return list(self._equity_curve)
|
|
|
|
@property
|
|
def max_drawdown(self) -> float:
|
|
return self._max_dd
|
|
|
|
def is_breached(self) -> bool:
|
|
"""Return True if max drawdown limit has been exceeded."""
|
|
return self._max_dd >= self.limit
|