Files
QuantumEdge/risk_management 2.0.py
B-Wear 407fe4bb5e Add files via upload
updates 

Signed-off-by: B-Wear <Bwear008@gmail.com>
2025-03-29 19:36:23 -04:00

249 lines
9.4 KiB
Python

import numpy as np
import pandas as pd
from typing import Dict, Tuple, Optional
import logging
from dataclasses import dataclass
from datetime import datetime
logger = logging.getLogger(__name__)
@dataclass
class RiskMetrics:
"""Data class to store risk metrics"""
daily_drawdown: float
max_drawdown: float
sharpe_ratio: float
sortino_ratio: float
win_rate: float
profit_factor: float
avg_win: float
avg_loss: float
max_consecutive_losses: int
max_consecutive_wins: int
class RiskManager:
def __init__(self, config: Dict):
self.config = config
self.initial_capital = config['initial_capital']
self.current_capital = self.initial_capital
self.risk_per_trade = config['risk_per_trade']
self.max_daily_loss = config['max_daily_loss']
self.max_drawdown = config['max_drawdown']
self.max_positions = config['max_positions']
self.leverage = config['leverage']
self.stop_loss_pct = config['stop_loss_pct']
self.take_profit_pct = config['take_profit_pct']
# Performance tracking
self.trades_history = []
self.daily_pnl = []
self.positions = {}
self.risk_metrics = None
def calculate_position_size(self,
price: float,
stop_loss: float,
account_balance: float) -> Tuple[float, float]:
"""
Calculate position size based on risk parameters
"""
# Calculate risk amount in base currency
risk_amount = account_balance * self.risk_per_trade
# Calculate position size based on stop loss
price_distance = abs(price - stop_loss)
position_size = risk_amount / price_distance
# Apply leverage
position_size *= self.leverage
# Calculate required margin
required_margin = (position_size * price) / self.leverage
# Ensure we don't exceed maximum position size
max_position = account_balance * self.leverage / price
position_size = min(position_size, max_position)
return position_size, required_margin
def validate_trade(self,
symbol: str,
position_size: float,
price: float,
stop_loss: float,
take_profit: float) -> bool:
"""
Validate if a trade meets risk management criteria
"""
# Check if we have too many open positions
if len(self.positions) >= self.max_positions:
logger.warning("Maximum number of positions reached")
return False
# Check if symbol is already in a position
if symbol in self.positions:
logger.warning(f"Position already exists for {symbol}")
return False
# Calculate potential loss
potential_loss = position_size * abs(price - stop_loss)
# Check if potential loss exceeds daily limit
if potential_loss > self.max_daily_loss:
logger.warning("Trade exceeds maximum daily loss limit")
return False
# Check if stop loss is too far
stop_loss_distance = abs(price - stop_loss) / price
if stop_loss_distance > self.stop_loss_pct:
logger.warning("Stop loss distance exceeds maximum allowed")
return False
# Check if take profit is reasonable
take_profit_distance = abs(take_profit - price) / price
if take_profit_distance > self.take_profit_pct:
logger.warning("Take profit distance exceeds maximum allowed")
return False
return True
def update_position(self,
symbol: str,
entry_price: float,
current_price: float,
position_size: float,
position_type: str) -> Optional[float]:
"""
Update position and check for stop loss or take profit
"""
if symbol not in self.positions:
self.positions[symbol] = {
'entry_price': entry_price,
'current_price': current_price,
'position_size': position_size,
'position_type': position_type,
'entry_time': datetime.now()
}
return None
position = self.positions[symbol]
pnl = 0
# Calculate unrealized PnL
if position_type == 'long':
pnl = (current_price - entry_price) * position_size
else:
pnl = (entry_price - current_price) * position_size
# Check stop loss
if position_type == 'long':
if current_price <= entry_price * (1 - self.stop_loss_pct):
pnl = -position_size * entry_price * self.stop_loss_pct
del self.positions[symbol]
return pnl
else:
if current_price >= entry_price * (1 + self.stop_loss_pct):
pnl = -position_size * entry_price * self.stop_loss_pct
del self.positions[symbol]
return pnl
# Check take profit
if position_type == 'long':
if current_price >= entry_price * (1 + self.take_profit_pct):
pnl = position_size * entry_price * self.take_profit_pct
del self.positions[symbol]
return pnl
else:
if current_price <= entry_price * (1 - self.take_profit_pct):
pnl = position_size * entry_price * self.take_profit_pct
del self.positions[symbol]
return pnl
# Update current price
position['current_price'] = current_price
return None
def calculate_risk_metrics(self) -> RiskMetrics:
"""
Calculate various risk metrics
"""
if not self.trades_history:
return None
# Convert trades history to DataFrame
df = pd.DataFrame(self.trades_history)
# Calculate daily returns
daily_returns = df.groupby(df['exit_time'].dt.date)['pnl'].sum()
# Calculate drawdown
cumulative_returns = (1 + daily_returns).cumprod()
rolling_max = cumulative_returns.expanding().max()
drawdowns = (cumulative_returns - rolling_max) / rolling_max
# Calculate metrics
daily_drawdown = drawdowns.min()
max_drawdown = drawdowns.min()
# Calculate Sharpe ratio
risk_free_rate = 0.02 # 2% annual risk-free rate
excess_returns = daily_returns - risk_free_rate/252
sharpe_ratio = np.sqrt(252) * excess_returns.mean() / excess_returns.std()
# Calculate Sortino ratio
downside_returns = excess_returns[excess_returns < 0]
sortino_ratio = np.sqrt(252) * excess_returns.mean() / downside_returns.std()
# Calculate win rate and profit factor
winning_trades = df[df['pnl'] > 0]
losing_trades = df[df['pnl'] < 0]
win_rate = len(winning_trades) / len(df)
profit_factor = abs(winning_trades['pnl'].sum() / losing_trades['pnl'].sum())
# Calculate average win and loss
avg_win = winning_trades['pnl'].mean()
avg_loss = losing_trades['pnl'].mean()
# Calculate consecutive wins and losses
df['consecutive'] = (df['pnl'] > 0).astype(int)
max_consecutive_wins = df['consecutive'].groupby((df['consecutive'] != df['consecutive'].shift()).cumsum()).sum().max()
max_consecutive_losses = df['consecutive'].groupby((df['consecutive'] != df['consecutive'].shift()).cumsum()).sum().min()
self.risk_metrics = RiskMetrics(
daily_drawdown=daily_drawdown,
max_drawdown=max_drawdown,
sharpe_ratio=sharpe_ratio,
sortino_ratio=sortino_ratio,
win_rate=win_rate,
profit_factor=profit_factor,
avg_win=avg_win,
avg_loss=avg_loss,
max_consecutive_losses=max_consecutive_losses,
max_consecutive_wins=max_consecutive_wins
)
return self.risk_metrics
def should_stop_trading(self) -> bool:
"""
Check if trading should be stopped based on risk metrics
"""
if not self.risk_metrics:
return False
# Stop if maximum drawdown is exceeded
if self.risk_metrics.max_drawdown < -self.max_drawdown:
logger.warning("Maximum drawdown exceeded. Stopping trading.")
return True
# Stop if win rate is too low
if self.risk_metrics.win_rate < self.config['min_win_rate']:
logger.warning("Win rate below minimum threshold. Stopping trading.")
return True
# Stop if profit factor is too low
if self.risk_metrics.profit_factor < self.config['min_profit_factor']:
logger.warning("Profit factor below minimum threshold. Stopping trading.")
return True
return False