Risk Management
Core Principles
Risk Limits
Per-Trade Risk:
- Default: 0.5-2.0% of capital per trade
- Maximum: 2.5% per trade (hard limit)
Portfolio Heat:
- Maximum: 2.0% of capital at risk simultaneously
- Monitors aggregate exposure across all positions
Daily Loss Limit:
- Hard stop: -2.5% of capital
- Triggers automatic position flattening
Time-Based Exits:
- EOD auto square-off: 15:25 IST
- Prevents overnight risk
Position Sizing
Risk-Based Position Sizing
python1def calculate_position_size(entry_price, stop_loss_price, account_size, risk_pct=0.02): 2 """ 3 Calculate position size based on risk per trade. 4 5 Args: 6 entry_price: Entry price 7 stop_loss_price: Stop loss price 8 account_size: Total account size 9 risk_pct: Risk percentage per trade (default 2%) 10 11 Returns: 12 Quantity to trade 13 """ 14 risk_amount = account_size * risk_pct 15 risk_per_unit = abs(entry_price - stop_loss_price) 16 17 if risk_per_unit == 0: 18 return 0 19 20 quantity = int(risk_amount / risk_per_unit) 21 return max(quantity, 1) # Minimum 1 unit
ATR-Based Position Sizing
python1def calculate_atr_position_size(entry_price, atr, account_size, risk_pct=0.02, atr_multiplier=2.0): 2 """ 3 Calculate position size using ATR-based stop loss. 4 5 Args: 6 entry_price: Entry price 7 atr: Average True Range 8 account_size: Total account size 9 risk_pct: Risk percentage per trade 10 atr_multiplier: ATR multiplier for stop loss (default 2.0) 11 12 Returns: 13 Quantity to trade 14 """ 15 stop_loss_price = entry_price - (atr * atr_multiplier) 16 return calculate_position_size(entry_price, stop_loss_price, account_size, risk_pct)
Options Position Sizing
python1def calculate_options_position_size(option_ltp, lot_size, account_size, risk_pct=0.02, sl_pct=0.20): 2 """ 3 Calculate options position size. 4 5 Args: 6 option_ltp: Option last traded price 7 lot_size: Lot size (e.g., 50 for NIFTY) 8 account_size: Total account size 9 risk_pct: Risk percentage per trade 10 sl_pct: Stop loss percentage (default 20% for options) 11 12 Returns: 13 Number of lots 14 """ 15 risk_amount = account_size * risk_pct 16 risk_per_lot = option_ltp * lot_size * sl_pct 17 18 if risk_per_lot == 0: 19 return 0 20 21 lots = int(risk_amount / risk_per_lot) 22 return max(lots, 1) # Minimum 1 lot
Portfolio Heat Management
Heat Calculation
python1class PortfolioRiskManager: 2 def __init__(self, account_size, max_heat=0.02): 3 self.account_size = account_size 4 self.max_heat = max_heat # 2% max portfolio heat 5 self.positions = [] 6 7 def calculate_position_risk(self, position): 8 """Calculate risk for a single position""" 9 if position['side'] == 'LONG': 10 risk = abs(position['entry_price'] - position['stop_loss']) * position['quantity'] 11 else: # SHORT 12 risk = abs(position['stop_loss'] - position['entry_price']) * position['quantity'] 13 14 return risk 15 16 def calculate_portfolio_heat(self): 17 """Calculate total portfolio heat""" 18 total_risk = sum(self.calculate_position_risk(pos) for pos in self.positions) 19 heat_pct = total_risk / self.account_size 20 return heat_pct 21 22 def can_add_position(self, new_position_risk): 23 """Check if new position can be added""" 24 current_heat = self.calculate_portfolio_heat() 25 new_heat = (current_heat * self.account_size + new_position_risk) / self.account_size 26 27 return new_heat <= self.max_heat
Heat Monitoring
python1def check_portfolio_heat(positions, account_size, max_heat=0.02): 2 """ 3 Check if portfolio heat is within limits. 4 5 Returns: 6 (is_ok, current_heat, max_heat) 7 """ 8 total_risk = sum( 9 abs(pos['entry_price'] - pos['stop_loss']) * pos['quantity'] 10 for pos in positions 11 ) 12 13 current_heat = total_risk / account_size 14 15 if current_heat > max_heat: 16 logger.warning(f"[REJECTED] Portfolio heat limit: {current_heat:.2%} > {max_heat:.2%}") 17 return False, current_heat, max_heat 18 19 return True, current_heat, max_heat
Stop Loss Management
Fixed Percentage Stop Loss
python1def calculate_stop_loss(entry_price, side, stop_loss_pct=1.5): 2 """ 3 Calculate stop loss price. 4 5 Args: 6 entry_price: Entry price 7 side: 'BUY' (long) or 'SELL' (short) 8 stop_loss_pct: Stop loss percentage 9 10 Returns: 11 Stop loss price 12 """ 13 if side == 'BUY': 14 return entry_price * (1 - stop_loss_pct / 100) 15 else: # SELL (short) 16 return entry_price * (1 + stop_loss_pct / 100)
ATR-Based Stop Loss
python1def calculate_atr_stop_loss(entry_price, atr, side, atr_multiplier=2.0): 2 """ 3 Calculate ATR-based stop loss. 4 5 Args: 6 entry_price: Entry price 7 atr: Average True Range 8 side: 'BUY' (long) or 'SELL' (short) 9 atr_multiplier: ATR multiplier (default 2.0) 10 11 Returns: 12 Stop loss price 13 """ 14 stop_distance = atr * atr_multiplier 15 16 if side == 'BUY': 17 return entry_price - stop_distance 18 else: # SELL (short) 19 return entry_price + stop_distance
Trailing Stop Loss
python1class TrailingStop: 2 def __init__(self, initial_stop, trailing_pct=0.5): 3 self.initial_stop = initial_stop 4 self.trailing_pct = trailing_pct 5 self.current_stop = initial_stop 6 self.highest_price = initial_stop # For long positions 7 8 def update(self, current_price, side='BUY'): 9 """Update trailing stop based on current price""" 10 if side == 'BUY': 11 if current_price > self.highest_price: 12 self.highest_price = current_price 13 self.current_stop = current_price * (1 - self.trailing_pct / 100) 14 else: # SELL (short) 15 if current_price < self.lowest_price: 16 self.lowest_price = current_price 17 self.current_stop = current_price * (1 + self.trailing_pct / 100) 18 19 return self.current_stop 20 21 def is_stopped_out(self, current_price, side='BUY'): 22 """Check if stop loss is hit""" 23 if side == 'BUY': 24 return current_price <= self.current_stop 25 else: # SELL (short) 26 return current_price >= self.current_stop
Take Profit Management
Fixed Take Profit
python1def calculate_take_profit(entry_price, side, take_profit_pct=3.0): 2 """ 3 Calculate take profit price. 4 5 Args: 6 entry_price: Entry price 7 side: 'BUY' (long) or 'SELL' (short) 8 take_profit_pct: Take profit percentage 9 10 Returns: 11 Take profit price 12 """ 13 if side == 'BUY': 14 return entry_price * (1 + take_profit_pct / 100) 15 else: # SELL (short) 16 return entry_price * (1 - take_profit_pct / 100)
Staged Take Profit
python1class StagedTakeProfit: 2 def __init__(self, entry_price, side, tp_levels=[0.5, 1.0, 1.5]): 3 """ 4 Staged take profit with multiple levels. 5 6 Args: 7 entry_price: Entry price 8 side: 'BUY' (long) or 'SELL' (short) 9 tp_levels: List of take profit percentages 10 """ 11 self.entry_price = entry_price 12 self.side = side 13 self.tp_levels = sorted(tp_levels) 14 self.levels_hit = [] 15 16 def calculate_tp_prices(self): 17 """Calculate take profit prices for all levels""" 18 tp_prices = [] 19 for level in self.tp_levels: 20 if self.side == 'BUY': 21 tp_price = self.entry_price * (1 + level / 100) 22 else: # SELL (short) 23 tp_price = self.entry_price * (1 - level / 100) 24 tp_prices.append(tp_price) 25 return tp_prices 26 27 def check_tp_levels(self, current_price): 28 """Check which take profit levels are hit""" 29 tp_prices = self.calculate_tp_prices() 30 hit_levels = [] 31 32 for i, tp_price in enumerate(tp_prices): 33 if i in self.levels_hit: 34 continue 35 36 if self.side == 'BUY' and current_price >= tp_price: 37 hit_levels.append(i) 38 elif self.side == 'SELL' and current_price <= tp_price: 39 hit_levels.append(i) 40 41 self.levels_hit.extend(hit_levels) 42 return hit_levels
Daily Loss Limits
Daily Loss Tracking
python1class DailyLossTracker: 2 def __init__(self, account_size, daily_loss_limit=-0.025): 3 self.account_size = account_size 4 self.daily_loss_limit = daily_loss_limit # -2.5% 5 self.starting_balance = account_size 6 self.current_balance = account_size 7 self.trades_today = [] 8 9 def update_balance(self, new_balance): 10 """Update current balance""" 11 self.current_balance = new_balance 12 13 def add_trade(self, trade_pnl): 14 """Add trade PnL""" 15 self.trades_today.append(trade_pnl) 16 self.current_balance += trade_pnl 17 18 def check_daily_loss(self): 19 """Check if daily loss limit is exceeded""" 20 daily_pnl = self.current_balance - self.starting_balance 21 daily_pnl_pct = daily_pnl / self.starting_balance 22 23 if daily_pnl_pct <= self.daily_loss_limit: 24 logger.error(f"[RISK] Daily loss limit hit: {daily_pnl_pct:.2%}") 25 return True 26 27 return False 28 29 def reset_daily(self): 30 """Reset for new trading day""" 31 self.starting_balance = self.current_balance 32 self.trades_today = []
Risk Monitoring
Real-Time Risk Check
python1def check_signal_risk(signal, portfolio_manager, account_size): 2 """ 3 Comprehensive risk check before entering trade. 4 5 Returns: 6 (is_approved, rejection_reason) 7 """ 8 # 1. Per-trade risk check 9 position_risk = calculate_position_risk(signal) 10 risk_pct = position_risk / account_size 11 12 if risk_pct > 0.025: # 2.5% max per trade 13 return False, "Per-trade risk limit exceeded" 14 15 # 2. Portfolio heat check 16 can_add, current_heat, max_heat = check_portfolio_heat( 17 portfolio_manager.positions, 18 account_size 19 ) 20 21 if not can_add: 22 return False, f"Portfolio heat limit: {current_heat:.2%} > {max_heat:.2%}" 23 24 # 3. Daily loss check 25 if portfolio_manager.daily_loss_tracker.check_daily_loss(): 26 return False, "Daily loss limit exceeded" 27 28 # 4. Position limit check 29 if len(portfolio_manager.positions) >= MAX_POSITIONS: 30 return False, "Maximum position limit reached" 31 32 return True, None
Risk Reporting
Risk Metrics Report
python1def generate_risk_report(portfolio_manager, account_size): 2 """Generate comprehensive risk report""" 3 report = { 4 'account_size': account_size, 5 'current_balance': portfolio_manager.current_balance, 6 'portfolio_heat': portfolio_manager.calculate_portfolio_heat(), 7 'max_heat_limit': 0.02, 8 'daily_pnl': portfolio_manager.daily_loss_tracker.current_balance - portfolio_manager.daily_loss_tracker.starting_balance, 9 'daily_loss_limit': account_size * -0.025, 10 'positions': len(portfolio_manager.positions), 11 'max_positions': MAX_POSITIONS, 12 'total_exposure': sum(pos['quantity'] * pos['entry_price'] for pos in portfolio_manager.positions) 13 } 14 15 return report
Best Practices
- Always use stop losses - Never enter a trade without a defined stop loss
- Respect portfolio heat - Monitor aggregate risk across all positions
- Scale position sizes - Reduce size during drawdowns, increase during winning streaks
- Time-based exits - Always exit before market close (15:25 IST)
- Daily loss limits - Hard stop at -2.5% to prevent catastrophic losses
- Regular monitoring - Check risk metrics throughout the trading day
- Document exceptions - Log any risk limit overrides with justification
Additional Resources
- AITRAPP risk module:
AITRAPP/AITRAPP/packages/core/risk.py - Trading utils:
openalgo/strategies/utils/trading_utils.py - Risk documentation:
AITRAPP/AITRAPP/SECURITY.md