🚀 AI One-Click Generation of quantconnect Strategy Code

How to Implement Volatility-Based Position Sizing Using ATR in QuantConnect

quantconnect | Published on: 2025/12/11
The following content is generated by EasyQuant .

Problem Description

How do I calculate my position size based on the ATR of a stock?

Solution

To calculate position size based on the Average True Range (ATR), you typically use a Risk-Based Sizing model. The goal is to ensure that if your stop loss (based on ATR) is hit, you only lose a fixed percentage of your total account equity (e.g., 1% or 2%).

The formula is:
$$ \text{Shares} = \frac{\text{Total Account Value} \times \text{Risk %}}{\text{ATR} \times \text{Multiplier}} $$

Below is a complete, runnable QuantConnect strategy using the PEP8 (snake_case) API format.

ATR Position Sizing Strategy

#region imports
from AlgorithmImports import *
#endregion

class ATRPositionSizingAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2021, 1, 1)
        self.set_end_date(2023, 1, 1)
        self.set_cash(100000)

        # 1. Add Security
        self.symbol = self.add_equity("SPY", Resolution.DAILY).symbol

        # 2. Initialize ATR Indicator
        # 14-period ATR using Simple Moving Average
        self.atr_indicator = self.atr(self.symbol, 14, MovingAverageType.SIMPLE, Resolution.DAILY)

        # 3. Strategy Parameters
        self.risk_per_trade = 0.02  # Risk 2% of total equity per trade
        self.atr_multiplier = 2.0   # Stop loss is placed 2 ATRs away from entry

        # Warm up the indicator so it is ready immediately when data starts
        self.set_warm_up(14)

    def on_data(self, data: Slice):
        # Ensure indicator is ready and we are not warming up
        if self.is_warming_up or not self.atr_indicator.is_ready:
            return

        # Check if data exists for the symbol
        if not data.bars.contains_key(self.symbol):
            return

        # Simple Entry Logic: Enter if we have no holdings
        if not self.portfolio.invested:
            self.enter_position_with_atr_sizing()

    def enter_position_with_atr_sizing(self):
        # A. Get Current Data
        current_price = self.securities[self.symbol].price
        current_atr = self.atr_indicator.current.value
        total_equity = self.portfolio.total_portfolio_value

        # B. Calculate Risk Variables
        # The maximum dollar amount we are willing to lose
        risk_amount = total_equity * self.risk_per_trade
        
        # The price distance from entry to stop loss
        stop_loss_distance = current_atr * self.atr_multiplier

        # Avoid division by zero
        if stop_loss_distance == 0:
            return

        # C. Calculate Share Quantity
        # Shares = Risk Amount / Risk per Share
        quantity = int(risk_amount / stop_loss_distance)

        # Ensure we have enough buying power (optional check, MarketOrder handles partial fills usually)
        # But logically, if the stop is very tight, quantity might exceed leverage. 
        # Here we just ensure quantity > 0.
        if quantity <= 0:
            return

        # D. Execute Orders
        self.market_order(self.symbol, quantity)
        
        # Place a Stop Loss Order based on the ATR calculation
        stop_price = current_price - stop_loss_distance
        self.stop_market_order(self.symbol, -quantity, stop_price)

        self.log(f"Entry: {current_price} | ATR: {current_atr:.2f} | Size: {quantity} | Stop: {stop_price:.2f}")

Key Implementation Details

  1. self.atr(...): This helper method creates an AverageTrueRange indicator and automatically registers it to receive data updates.
  2. self.set_warm_up(14): This ensures the algorithm processes data before the start date so the ATR indicator is ready (is_ready) immediately upon the first trading day.
  3. The Calculation:
    • Risk Amount: total_equity * 0.02 (We are willing to lose $2,000 on a $100k account).
    • Stop Distance: ATR * 2.0 (If ATR is $5, the stop is $10 away).
    • Quantity: $2,000 / $10 = 200 shares.
    • Result: If the price drops $10 (hitting the stop), you lose $10 * 200 shares = $2,000 (exactly 2% of your account).
  4. int(...): Share counts for equities must be integers.

Q&A

Q: Why use ATR for position sizing instead of a fixed percentage of capital?
A: ATR sizing normalizes volatility. It allows you to trade larger positions on stable stocks (low ATR) and smaller positions on volatile stocks (high ATR), keeping your dollar risk constant regardless of the asset's behavior.

Q: How do I handle "Insufficient Buying Power" errors with this method?
A: If the calculated quantity requires more cash than you have (or exceeds your leverage), QuantConnect will reject the order. You can add a check using self.portfolio.margin_remaining or self.calculate_order_quantity to cap the size at your maximum buying power if the ATR calculation suggests a position that is too large.

Q: Can I use this for Futures or Forex?
A: Yes, but the calculation changes slightly. For Forex/Futures, you must account for the SymbolProperties.ContractMultiplier. The formula becomes: Quantity = Risk Amount / (Stop Distance * Contract Multiplier).