Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 22

Profit-Based Exit Strategies

Recommended resources

Hello! Welcome back to your course on building a trading system in Python.

In our last lesson, we put the final touches on our strategy's entry logic. We integrated a signal-based trigger and a minimum credit filter, ensuring our system not only knows what trade to look for but also when to act and whether the reward is worth the risk.

Now that we have a robust way to enter trades, we must develop an equally robust way to exit them. This lesson marks our first step into position management, focusing on the happy path: closing a trade to lock in profits. We will address the learning outcome: Implement exit logic based on achieving a certain percentage of maximum profit.

1. The Strategy of Taking Profits

For a credit spread, the maximum possible profit is the net credit you receive when you open the position. This profit is fully realized if both options expire out-of-the-money.

Here are the payoff diagrams for the two spread types we are focused on, illustrating this maximum profit point.

Bull Put Spread Payoff Diagram
The payoff diagram for a Bull Put Spread. Maximum profit is achieved when the underlying stock price is at or above the short put's strike price at expiration.
Expiration P/L Graph: Bear Call Spread
The expiration P/L graph for a Bear Call Spread. Maximum profit is achieved when the underlying stock price is at or below the short call's strike price at expiration.

While holding until expiration guarantees the maximum profit on a winning trade, most professional traders prefer to exit earlier. Why?

  • Risk of Reversal: The market can always turn against you. A profitable position can quickly become a losing one.
  • Diminishing Returns: As an option gets closer to expiration, the rate of time decay (theta) slows down, meaning your profits accumulate more slowly.
  • Increased Gamma Risk: Closer to expiration, the option's delta can change very rapidly, making the position more sensitive to small price movements in the underlying asset.
  • Capital Efficiency: Closing a trade frees up capital to be used for new opportunities that may offer a better risk/reward profile.

A common industry practice is to set a profit target, often around 50% of the maximum profit. Let's watch two brief clips from experienced traders discussing their exit rules.

Short Vertical Spreads: Options Strategy Management

First, this video from tastylive succinctly explains their standard approach for managing winning vertical spreads.

Watch the clip from 01:42 to 02:17. Note the simple, clear rule: set profit targets at 50% of max profit.

Credit Spread Trading SECRETS: How to Enter & Exit like a Pro

Similarly, this video from Options With Davis outlines a primary exit rule that combines a profit target with a time-based exit.

Watch from 21:57 to 22:20. The speaker's primary rule is to exit at 50% take profit or at 21 days-to-expiration (DTE). We are focusing on the profit-taking part today.

Both sources converge on a key idea: systematically taking profits at a pre-defined level is a cornerstone of managing credit spreads. Our trading framework must do the same.


2. Calculating the Profit-Take Exit Condition

To implement this logic, we need to compare our current profit to our target profit. The logic can be expressed with a simple formula.

A position should be closed for profit if:

Current P&L ≥ Target Profit

Let's break this down:

  • Initial_Credit: The net premium received when the trade was opened. This is your maximum profit.
  • Target_Profit_Percentage: A configurable value, e.g., 0.50 for 50%.
  • Target_Profit: Initial_Credit * Target_Profit_Percentage.
  • Cost_to_Close: The current market price to buy back the spread (i.e., buy back the short leg and sell the long leg).
  • Current_P&L: Initial_Credit - Cost_to_Close.

So, the condition becomes:
(Initial_Credit - Cost_to_Close) ≥ (Initial_Credit * Target_Profit_Percentage)

With a little algebra, we can rearrange this to be more efficient. We'll exit if the cost to close the position has fallen to a certain target level:

Cost_to_Close ≤ Initial_Credit * (1 - Target_Profit_Percentage)

This is the exact logic we will implement.

To see a complete code example of this logic in action within a larger function, you can review the Alpaca Markets article on the Bull Put Spread.

The Bull Put Spread Explained (and How to Trade in Python)

This article provides a Python function, roll_rinse_bull_put_spread, that contains a complete implementation of exit logic, including a profit target.

Scan through the section titled 'How to Adjust or Exit a Bull Put Spread (Rolling and Rinsing)'. Focus on these lines in the Python code to see the direct implementation of our formula: initial_credit = ..., cost_to_close = ..., target_price = initial_credit * (1 - target_profit_percentage), and exit_due_to_profit = cost_to_close <= target_price.


3. Implementing Profit-Taking in Our Strategy

Now, let's integrate this logic into our framework. We'll start by adding a new parameter, profit_target_pct, to our strategy classes' __init__ method.

Next, we need a method responsible for checking the status of an open position. We will add a new abstract method to our Strategy base class (which you would define in framework/interfaces.py).

# In a file like framework/interfaces.py
from abc import ABC, abstractmethod

# ... other abstract classes

class Strategy(ABC):
    @abstractmethod
    def find_trade_opportunity(self, signal, data_handler, analytics_engine):
        pass

    @abstractmethod
    def manage_position(self, position, data_handler):
        """
        Evaluates an open position and returns management actions.
        For now, returns a string: 'HOLD' or 'CLOSE_PROFIT'.
        """
        pass

Now, let's implement manage_position in our BullPutSpread class. This method will contain our profit-taking logic. It will assume it receives a position object (which we'll represent as a dictionary for now) that contains the details of the original trade, including the initial_credit.

Here is the updated BullPutSpread class in framework/strategies.py:

# In framework/strategies.py

# ... other imports ...

class BullPutSpread(Strategy):
    def __init__(self, symbol: str, params: dict):
        super().__init__(name=f"BullPutSpread_{symbol}")
        self.symbol = symbol
        # Entry parameters
        self.min_dte = params.get('min_dte', 21)
        self.max_dte = params.get('max_dte', 60)
        self.oi_threshold = params.get('oi_threshold', 50)
        self.target_delta = params.get('target_delta', -0.30)
        self.delta_tolerance = params.get('delta_tolerance', 0.05)
        self.spread_width = params.get('spread_width', 5)
        self.min_credit = params.get('min_credit', 0.25)
        # --- NEW Management Parameter ---
        self.profit_target_pct = params.get('profit_target_pct', 0.50)

    def find_trade_opportunity(self, signal, data_handler, analytics_engine):
        # This method remains unchanged from the previous lesson
        # ...
        pass
    
    def _find_spread(self, options_list: list) -> dict or None:
        # This method remains unchanged from the previous lesson
        # ...
        pass

    def _validate_spread(self, spread: dict) -> bool:
        # This method remains unchanged from the previous lesson
        # ...
        pass

    # --- NEW METHOD for Position Management ---
    def manage_position(self, position: dict, data_handler) -> str:
        """
        Checks if the position should be closed based on the profit target.
        Returns an action string: 'HOLD' or 'CLOSE_PROFIT'.
        """
        # 1. Extract necessary info from the position object
        short_leg_symbol = position['short_leg']['symbol']
        long_leg_symbol = position['long_leg']['symbol']
        initial_credit = position['initial_credit']

        # 2. Get current market prices (quotes) for both legs
        short_leg_quote = data_handler.get_latest_quote(short_leg_symbol)
        long_leg_quote = data_handler.get_latest_quote(long_leg_symbol)
        
        if not short_leg_quote or not long_leg_quote:
            print(f"Could not retrieve current quotes for {self.symbol} position. Holding.")
            return 'HOLD'

        # 3. Calculate the current cost to close the spread
        # We buy back the short leg (at ask price) and sell the long leg (at bid price).
        cost_to_close = short_leg_quote['ask_price'] - long_leg_quote['bid_price']

        # 4. Calculate the target price for closing
        target_cost_to_close = initial_credit * (1 - self.profit_target_pct)

        # 5. Check the exit condition
        if cost_to_close <= target_cost_to_close:
            current_pnl = initial_credit - cost_to_close
            profit_pct = (current_pnl / initial_credit) * 100
            print(
                f"PROFIT TARGET HIT for {self.symbol}. "
                f"Current P&L: ${current_pnl:.2f} ({profit_pct:.1f}% of max profit). "
                f"Closing position."
            )
            return 'CLOSE_PROFIT'
        
        return 'HOLD'

Important Notes:

  • The position dictionary is a placeholder. In our full system, the ExecutionHandler will create and manage these objects, populating initial_credit from the actual trade fill price.
  • The data_handler.get_latest_quote() method is also a placeholder for the functionality we will build in Module 3 to get real-time data.
  • The logic for a BearCallSpread is identical. You would implement the same manage_position method in that class.
Test your understanding!

You have a Bear Call Spread position with the following details:

  • initial_credit received was $1.50 per share.
  • Your profit_target_pct is set to 0.60 (60%).
  • The current market quote for the short call is: Bid $0.55, Ask $0.60.
  • The current market quote for the long call is: Bid $0.15, Ask $0.20.

Based on the logic in our manage_position method, should the position be closed? Show your calculation.

Show answer
  1. Calculate the Cost to Close:

    • To close the spread, you buy back the short call (at its ask price) and sell the long call (at its bid price).
    • cost_to_close = (Short Call Ask) - (Long Call Bid)
    • cost_to_close = $0.60 - $0.15 = $0.45
  2. Calculate the Target Cost to Close:

    • target_cost_to_close = initial_credit * (1 - profit_target_pct)
    • target_cost_to_close = $1.50 * (1 - 0.60) = $1.50 * 0.40 = $0.60
  3. Compare and Decide:

    • The condition is cost_to_close <= target_cost_to_close.
    • $0.45 <= $0.60. The condition is TRUE.

Conclusion: Yes, the position should be closed to take profit. The current P&L is $1.50 - $0.45 = $1.05, which is 70% of the max profit, exceeding the 60% target.


Conclusion

Excellent work today! You have now implemented the first—and most enjoyable—piece of position management logic. By defining a clear, systematic rule for taking profits, you are building a system that acts with discipline, securing gains and efficiently managing capital.

Key Takeaways:

  • Exiting a winning credit spread before expiration is a standard practice to reduce risk and improve capital efficiency.
  • A common exit rule is to close the position upon reaching a certain percentage (e.g., 50%) of the maximum possible profit.
  • The exit condition can be calculated as Cost_to_Close <= Initial_Credit * (1 - Target_Profit_Percentage).
  • We've implemented this logic within a new manage_position method in our strategy classes, preparing our framework to handle open trades.

Preview of the Next Lesson:
Managing positions isn't just about taking profits. We also need a plan for when trades move against us. In our next lesson, we will tackle the other side of the coin: implementing stop-loss logic based on the position reaching a predefined loss threshold. This will be a critical component for managing risk.