🚀 AI One-Click Generation of quantconnect Strategy Code

A Practical Guide to Setting Stop-Loss and Take-Profit Orders in Your QuantConnect Strategy

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

Problem Description

Can you show me how to add a stop-loss and a take-profit to my orders?

Solution

To add a Stop Loss and Take Profit in QuantConnect, the most robust method is to place these orders inside the on_order_event handler immediately after your entry order is filled. This ensures your protection levels are calculated based on the actual execution price, not the theoretical market price.

Here is a complete, runnable strategy that demonstrates how to enter a position and manage a "One-Cancels-Other" (OCO) logic manually.

Strategy Logic

  1. Entry: Buy SPY when not invested.
  2. Protection: Upon entry fill, place a Stop Market Order (Stop Loss) 5% below the fill price and a Limit Order (Take Profit) 10% above the fill price.
  3. Management: If the Stop Loss is hit, cancel the Take Profit. If the Take Profit is hit, cancel the Stop Loss.

Python Code Implementation

# region imports
from AlgorithmImports import *
# endregion

class StopLossTakeProfitExample(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2023, 1, 1)
        self.set_end_date(2023, 12, 1)
        self.set_cash(100000)
        
        self.symbol = self.add_equity("SPY", Resolution.MINUTE).symbol
        
        # Track order tickets to manage cancellations
        self.entry_ticket = None
        self.stop_loss_ticket = None
        self.take_profit_ticket = None

    def on_data(self, data: Slice):
        # Simple Entry Logic: Enter if we have no position and no pending entry
        if not self.portfolio.invested and self.entry_ticket is None:
            quantity = self.calculate_order_quantity(self.symbol, 0.9)
            self.entry_ticket = self.market_order(self.symbol, quantity)
            self.debug(f"Entry Order Placed: {quantity} shares")

    def on_order_event(self, order_event: OrderEvent):
        # We only care about filled orders
        if order_event.status != OrderStatus.FILLED:
            return

        # 1. Handle Entry Fill: Place SL and TP
        if self.entry_ticket is not None and order_event.order_id == self.entry_ticket.order_id:
            fill_price = order_event.fill_price
            quantity = order_event.fill_quantity
            
            # Calculate prices (Long Position Example)
            # Stop Loss at 5% loss
            stop_price = fill_price * 0.95
            # Take Profit at 10% gain
            limit_price = fill_price * 1.10
            
            # Place orders (Quantity is negative to sell)
            self.stop_loss_ticket = self.stop_market_order(self.symbol, -quantity, stop_price)
            self.take_profit_ticket = self.limit_order(self.symbol, -quantity, limit_price)
            
            self.log(f"Entry Filled at {fill_price}. SL: {stop_price}, TP: {limit_price}")

        # 2. Handle Stop Loss Fill: Cancel Take Profit
        if self.stop_loss_ticket is not None and order_event.order_id == self.stop_loss_ticket.order_id:
            self.log("Stop Loss Hit. Cancelling Take Profit.")
            if self.take_profit_ticket is not None:
                self.take_profit_ticket.cancel()
            self.reset_tickets()

        # 3. Handle Take Profit Fill: Cancel Stop Loss
        if self.take_profit_ticket is not None and order_event.order_id == self.take_profit_ticket.order_id:
            self.log("Take Profit Hit. Cancelling Stop Loss.")
            if self.stop_loss_ticket is not None:
                self.stop_loss_ticket.cancel()
            self.reset_tickets()

    def reset_tickets(self):
        """Resets tickets to None to allow new trades."""
        self.entry_ticket = None
        self.stop_loss_ticket = None
        self.take_profit_ticket = None

Key Concepts Explained

1. stop_market_order (Stop Loss)

This creates an order that triggers a market sell once the price drops to (or below) a specific level.

  • Syntax: self.stop_market_order(symbol, quantity, stop_price)
  • Note: For a long position, quantity must be negative (selling).

2. limit_order (Take Profit)

This creates an order to sell at a specific price or better.

  • Syntax: self.limit_order(symbol, quantity, limit_price)

3. on_order_event

This is the event handler where you manage the lifecycle of your orders.

  • Why use it? In live trading, slippage occurs. If you calculate your SL/TP in on_data using the current close price, your risk calculations might be off. Using order_event.fill_price ensures your SL/TP are calculated exactly from where you entered the market.

4. Ticket Management

The OrderTicket objects returned by order methods allow you to update or cancel orders later.

  • One-Cancels-Other (OCO): QuantConnect does not have a native "OCO" order type that sits on the broker server for all brokerages. You must simulate it in code. When the SL fills, you explicitly call self.take_profit_ticket.cancel(), and vice versa.

Q&A: Common Questions about SL/TP in QuantConnect

Q: Can I use a Trailing Stop Loss?
A: Yes. You can use self.stop_market_order initially. Then, in on_data, check if the price has moved in your favor and use self.stop_loss_ticket.update() to modify the stop price upwards.

Q: What happens if I am Shorting?
A: The logic reverses.

  • Entry: Sell (quantity < 0).
  • Stop Loss: Buy Stop (quantity > 0, price > entry).
  • Take Profit: Buy Limit (quantity > 0, price < entry).

Q: Is there an easier way without writing all this code?
A: Yes, you can use the Risk Management Framework. In your initialize method, you can add:

self.add_risk_management(MaximumDrawdownPercentPerSecurity(0.05))

However, this only handles the Stop Loss (liquidating positions that lose 5%). It does not handle Take Profits or specific price targets as precisely as the manual method above.