|
@@ -1,6 +1,7 @@
|
|
|
from __future__ import annotations
|
|
|
import logging
|
|
|
-from typing import TYPE_CHECKING
|
|
|
+from typing import TYPE_CHECKING, Optional
|
|
|
+from datetime import datetime, timezone
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
from src.stats import TradingStats
|
|
@@ -22,7 +23,9 @@ class DrawdownMonitor:
|
|
|
"""
|
|
|
self.stats = stats
|
|
|
self.peak_balance = 0.0
|
|
|
+ self.peak_balance_timestamp: Optional[datetime] = None
|
|
|
self.max_drawdown_pct = 0.0
|
|
|
+ self.current_drawdown_pct = 0.0
|
|
|
self._load_state()
|
|
|
|
|
|
def _load_state(self):
|
|
@@ -30,14 +33,19 @@ class DrawdownMonitor:
|
|
|
try:
|
|
|
peak_balance_str = self.stats._get_metadata('drawdown_peak_balance')
|
|
|
max_drawdown_pct_str = self.stats._get_metadata('drawdown_max_drawdown_pct')
|
|
|
+ peak_balance_timestamp_str = self.stats._get_metadata('drawdown_peak_balance_timestamp')
|
|
|
|
|
|
self.peak_balance = float(peak_balance_str) if peak_balance_str else 0.0
|
|
|
self.max_drawdown_pct = float(max_drawdown_pct_str) if max_drawdown_pct_str else 0.0
|
|
|
+ if peak_balance_timestamp_str:
|
|
|
+ self.peak_balance_timestamp = datetime.fromisoformat(peak_balance_timestamp_str)
|
|
|
|
|
|
# If peak balance is zero, initialize it with the initial account balance.
|
|
|
if self.peak_balance == 0.0:
|
|
|
initial_balance_str = self.stats._get_metadata('initial_balance')
|
|
|
- self.peak_balance = float(initial_balance_str) if initial_balance_str else 0.0
|
|
|
+ if initial_balance_str:
|
|
|
+ self.peak_balance = float(initial_balance_str)
|
|
|
+ self.peak_balance_timestamp = datetime.now(timezone.utc)
|
|
|
|
|
|
logger.info(f"DrawdownMonitor state loaded: Peak Balance=${self.peak_balance:,.2f}, Max Drawdown={self.max_drawdown_pct:.2f}%")
|
|
|
|
|
@@ -49,6 +57,8 @@ class DrawdownMonitor:
|
|
|
try:
|
|
|
self.stats._set_metadata('drawdown_peak_balance', str(self.peak_balance))
|
|
|
self.stats._set_metadata('drawdown_max_drawdown_pct', str(self.max_drawdown_pct))
|
|
|
+ if self.peak_balance_timestamp:
|
|
|
+ self.stats._set_metadata('drawdown_peak_balance_timestamp', self.peak_balance_timestamp.isoformat())
|
|
|
logger.debug("DrawdownMonitor state saved.")
|
|
|
except Exception as e:
|
|
|
logger.error(f"Error saving DrawdownMonitor state: {e}", exc_info=True)
|
|
@@ -64,13 +74,16 @@ class DrawdownMonitor:
|
|
|
|
|
|
if current_balance > self.peak_balance:
|
|
|
self.peak_balance = current_balance
|
|
|
+ self.peak_balance_timestamp = datetime.now(timezone.utc)
|
|
|
state_changed = True
|
|
|
|
|
|
if self.peak_balance > 0:
|
|
|
drawdown = (self.peak_balance - current_balance) / self.peak_balance
|
|
|
+ self.current_drawdown_pct = drawdown * 100
|
|
|
+
|
|
|
# Only update if the new drawdown is significantly larger
|
|
|
- if (drawdown * 100) > self.max_drawdown_pct + 0.01:
|
|
|
- self.max_drawdown_pct = drawdown * 100
|
|
|
+ if self.current_drawdown_pct > self.max_drawdown_pct + 0.01:
|
|
|
+ self.max_drawdown_pct = self.current_drawdown_pct
|
|
|
state_changed = True
|
|
|
|
|
|
if state_changed:
|
|
@@ -78,4 +91,12 @@ class DrawdownMonitor:
|
|
|
|
|
|
def get_max_drawdown(self) -> float:
|
|
|
"""Returns the maximum drawdown percentage."""
|
|
|
- return self.max_drawdown_pct
|
|
|
+ return self.max_drawdown_pct
|
|
|
+
|
|
|
+ def get_current_drawdown(self) -> float:
|
|
|
+ """Returns the current drawdown percentage since the last peak."""
|
|
|
+ return self.current_drawdown_pct
|
|
|
+
|
|
|
+ def get_peak_balance_timestamp(self) -> Optional[datetime]:
|
|
|
+ """Returns the timestamp of the last peak balance."""
|
|
|
+ return self.peak_balance_timestamp
|