AlgoBulls logo

Contact Us

Long Call Diagonal Spread: Ride Trends Across Time

/img/blogs/blog_longCallDiagonal/thumbnail

Looking for a bullish strategy that benefits from a long-term uptrend while earning short-term decay income?

The Long Call Diagonal Spread achieves exactly that. By combining a long-dated call with a short-dated call, it creates a position that profits from both directional movement while earning from the short-term option losing value over time. With layered exit rules: trailing stop-loss and controlled re-entry, it adapts to evolving market conditions while staying risk-aware.

This blog explains the Long Call Diagonal Spread's construction, entry and exit rules, and how smart trade management turns slow directional movement and time decay into compounding reward.

Understanding the Long Call Diagonal Spread Strategy

The Long Call Diagonal Spread is a moderately bullish options strategy designed to benefit from steady upward movement in the underlying, while also reducing the cost of holding a long-dated call option. It takes advantage of time decay on the short leg and higher sensitivity of the long-dated option to directional trends. Here is how it’s set up:

  • Buy a longer-expiry At-the-Money (ATM) Call Option

  • Sell a nearer-expiry Out-of-the-Money (OTM) Call Option

The result is a net debit position, where the cost of the long call is partially offset by the premium received from the short call. While the short call limits maximum upside, it reduces upfront cost, slows time decay, and provides a more controlled, risk-defined structure compared to holding a single long call.


Strategy Variations

  • Basic Long Call Diagonal Spread: As explained before, a straightforward debit structure with fixed risk–reward but without automated management.

  • Enhanced Long Call Diagonal Spread (Our Focus): Uses the same structure but adds an absolute stop-loss, a dynamic trailing stop-loss that adjusts as the spread premium rises, and controlled re-entry logic that reopens the spread when conditions remain favorable. This provides disciplined exits, protects gains, and allows selective re-entries without increasing exposure.


How the Long Call Diagonal Spread Strategy Works

1. Entry Criteria

Eligible Market: The Long Call Diagonal Spread performs best when the underlying is expected to drift higher gradually rather than explode upward in a sharp rally. It works particularly well on broad indices like NIFTY 50, BANK NIFTY, S&P 500, and on stable, liquid large-cap stocks such as AAPL, MSFT, INFY, RELIANCE, where controlled, steady upside is more probable.

Strike Price Selection:

The Long Call Diagonal Spread is built by combining two call options with different strikes and different expiries. The typical setup involves buying a call near the current market price (ATM or slightly ITM) in a longer-dated expiry and selling an OTM call in a nearer-term expiry to generate premium income.

  • Narrow Strike Gap: Reduces the net debit and lowers risk, but also limits upside participation. This setup works well when only a modest, steady rise is expected.

  • Wider Strike Gap: Expands potential reward but comes with a higher upfront cost and more exposure to directional movement.

Expiry Selection:

The key characteristic of a diagonal spread is mismatched expiries, and the choice strongly affects performance:

  • Near-term short expiry: Maximizes theta decay income and is ideal for steady, slow-moving bullish markets.

  • Longer-term long expiry: Gives the long call staying power and allows participation in a broader upward trend.

Lot Size & Positioning:

Use a consistent position size where the net debit = maximum risk. Diagonals benefit from defined risk, so size should be kept uniform across trades.

Timeframes:

The spread can be initiated using 1- to 60-minute charts, with 1- or 5-minute charts giving clearer setups for trending bullish conditions.


2. Exit Rules & Re-Entry Strategy

The Long Call Diagonal Spread operates on a structured exit framework designed to protect capital, lock in gains during sustained uptrends, and re-enter when market conditions continue to favor the trade.

The spread is closed under the following conditions:

Absolute Stop-Loss Breached: If the net spread premium (long call – short call) falls below a predefined percentage of its entry value, the position is exited immediately to prevent deeper losses.

Trailing Stop Triggered: As the spread increases in value, the strategy dynamically updates a trailing stop at a fixed percentage below the highest spread value achieved. If the spread subsequently falls below this trailing stop level, the entire structure is closed to lock in profits accumulated during the uptrend.


Re-Entry Logic

If the position exits due to an absolute stop-loss or trailing stop, but the underlying resumes a stable upward trend, the strategy allows a fresh Long Call Diagonal Spread to be opened at the current ATM level.


The flowchart below summarizes how these entry, exit and re-entry rules interact during live execution.

Long Call Diagonal Spread Strategy Execution Flowchart
Figure 1: Long Call Diagonal Spread Strategy Execution Flowchart (Values align with the first sample in the upcoming Profit & Loss Calculations section).

This section walks through the Python implementation of the Long Call Diagonal Spread strategy, focusing on strike selection, exit logic and automated re-entry handling.

Selecting Instruments and Strikes for Entry

python
 def strategy_select_instruments_for_entry(self, candle, instruments_bucket):
       selected_instruments, meta = [], []

       for instrument in instruments_bucket:
           self.logger.debug(f"Checking entry conditions for base instrument: {instrument} | Determining ATM/OTM option instruments and verifying if CE orders are already placed.")

           # Skip the base instrument if active order already exists
           if self.child_instrument_main_orders.get(instrument):
               continue

           # Retrieve LTP of the base instrument to setup child instruments
           base_instrument_ltp = self.broker.get_ltp(instrument)

           # Track re-entry count for this instrument
           re_entry_count = self.re_entry_count.get(instrument, 0)

           # If re-entry count exceeds the allowed limit, skip further re-entries
           if re_entry_count >= self.re_entry_limit:
               continue

           # otherwise increment re-entry count
           else:
               self.re_entry_count[instrument] = self.re_entry_count.get(instrument, 0) + 1

           # leg_wise_list structure:
           # (action, strike_direction, no_of_strikes, expiry_index)
           #
           # expiry_index:
           #   0 -> selects the *first* expiry in the generated list (typically the current / near-month expiry)
           #   1 -> selects the *second* expiry in the generated list (typically the next / far-month expiry)
           #
           # These indices correspond to the order of expiry dates returned by:
           # self.options_instruments_set_up_all_expiries(...)
           # which usually produces: [current_month_expiry, next_month_expiry]
           leg_wise_list = [
               (BrokerOrderTransactionTypeConstants.BUY, OptionsStrikeDirection.ATM.value, 0, 1),
               (BrokerOrderTransactionTypeConstants.SELL, OptionsStrikeDirection.OTM.value, self.no_of_otm_strikes_leg_sell, 0)
           ]

           for action, strike_direction, no_of_strikes, expiry in leg_wise_list:
               self.options_instruments_set_up_all_expiries(instrument, 'CE', base_instrument_ltp)  # Set up option instruments for available expiries
               child_instrument = self.get_child_instrument_details(instrument, 'CE', strike_direction, no_of_strikes, expiry)  # Retrieve ATM child instrument details for the given instrument

               # Map the base instrument to its corresponding child instrument in the instruments' mapper. This allows tracking of relationships between base and child instruments for further processing.
               self.instruments_mapper.add_mappings(instrument, child_instrument)

               selected_instruments.append(child_instrument)
               meta.append({"action": action})

       return selected_instruments, meta

Code Explanation

  1. Skip base instruments with active positions
python
if self.child_instrument_main_orders.get(instrument):
    continue
  • If there is already an active spread (i.e., at least one child order mapped to this base), the strategy skips this instrument.

  • This prevents:

    • Opening overlapping diagonal spreads

    • Accidental pyramiding or duplicated exposure on the same base

  1. Fetch base instrument LTP
python
base_instrument_ltp = self.broker.get_ltp(instrument)
  • The LTP of the base instrument is fetched once per iteration and reused for:

    • Strike discovery

    • ATM / OTM determination across both legs

  • This ensures:

    • Consistent strike selection

    • Better performance (avoids redundant broker calls)

  1. Re-entry tracking and limits
python
re_entry_count = self.re_entry_count.get(instrument, 0)

if re_entry_count >= self.re_entry_limit:
    continue
else:
    self.re_entry_count[instrument] = self.re_entry_count.get(instrument, 0) + 1

Each base instrument has its own re-entry counter:

  • If the re-entry limit is exceeded, the instrument is skipped
  • Otherwise, the re-entry count is incremented immediately
  • Re-entry count is incremented before order placement; any order failure immediately terminates further entries for the session.
  1. Define the diagonal structure
python
leg_wise_list = [
               (BrokerOrderTransactionTypeConstants.BUY, OptionsStrikeDirection.ATM.value, 0, 1),
               (BrokerOrderTransactionTypeConstants.SELL, OptionsStrikeDirection.OTM.value, self.no_of_otm_strikes_leg_sell, 0)
           ]
  • The diagonal structure is encoded using a compact leg_wise_list, allowing the strategy to define both legs declaratively in a single place.

  • By looping over this structure, the same selection logic is reused for both the long far-month call and the short near-month call, ensuring consistency and reducing duplication in strike and expiry handling.

  1. Setup option instruments across all expiries
python
self.options_instruments_set_up_all_expiries(instrument, 'CE', base_instrument_ltp)

This prepares:

  • Call option instruments
  • Across multiple expiries
  • Centered around the current base LTP

This step is critical because diagonal spreads require:

  • Access to both near and far expiries
  • A consistent strike grid across expiries

7. Select the correct child instrument per leg

python
child_instrument = self.get_child_instrument_details(
    instrument, 'CE', strike_direction, no_of_strikes, expiry
)
  • This helper:

    • Chooses ATM or OTM strike
    • Applies the configured strike offset
    • Selects the expiry using expiry_index
  • The same function is reused for both legs, keeping the logic DRY and consistent.

8. Map base-to-child relationships

python
self.instruments_mapper.add_mappings(instrument, child_instrument)
  • This mapping is essential for:

    • Treating multiple option legs as a single logical strategy structure
    • Avoiding orphaned or naked legs
    • Resolving the base instrument from a child instrument during exit selection and position management

Creating an Order for a Position

python
def strategy_enter_position(self, candle, instrument, meta):
       _order = None
       child_instrument = instrument
       base_instrument = self.instruments_mapper.get_base_instrument(child_instrument)
       main_orders = self.child_instrument_main_orders.get(base_instrument)
       action_other_leg = BrokerOrderTransactionTypeConstants.BUY if meta['action'] == BrokerOrderTransactionTypeConstants.SELL else BrokerOrderTransactionTypeConstants.SELL

       # If the opposite leg is expected but not present, abort entry and clear any partial state for this base instrument
       if main_orders and not main_orders.get(action_other_leg):
           self.child_instrument_main_orders.pop(base_instrument, None)
           return _order

       # Attempt to place market order for the current leg
       try:
           _order = self.broker.OrderRegular(instrument=child_instrument, order_transaction_type=meta['action'], order_code=self.order_code, order_variety=BrokerOrderVarietyConstants.MARKET, quantity=self.number_of_lots * child_instrument.lot_size)
       except Exception as e:
           # Order placement failure is logged; downstream validation will handle cleanup
           self.logger.debug(f"No orders placed due to the following error: {e}")

       # Store details of orders
       self.child_instrument_main_orders.setdefault(base_instrument, {})[meta['action']] = _order

       if not check_order_placed_successfully(_order):
           # Protection logic incase any of the legs fail to get placed - this will help avoid having naked positions
           self.logger.critical('Order placement failed for one of the legs. Exiting position for other leg, if possible and stopping further entries..')
           if _order:
               self.exit_all_positions_for_base_instrument(base_instrument)
           self.execution_complete = True  # Stop further strategy execution for safety
       return _order

Code Explanation

  1. Base & Child Instrument Resolution
python
child_instrument = instrument
base_instrument = self.instruments_mapper.get_base_instrument(child_instrument)
main_orders = self.child_instrument_main_orders.get(base_instrument)
  • child_instrument is the specific option contract being traded (ATM/OTM, near/far expiry).
  • base_instrument is resolved via the instruments mapper, allowing:
    • Grouping multiple legs under a single logical structure
    • Coordinated risk management, exits, and cleanup
  • main_orders holds all active legs (BUY/SELL) already placed for this base instrument.
  1. Determining the Opposite Leg
python
action_other_leg = BUY if meta['action'] == SELL else SELL
  • For a diagonal spread, the strategy expects both a BUY and a SELL leg.
  • This line dynamically computes the complementary action for validation.
  1. Guard Against Incomplete Structures
python
if main_orders and not main_orders.get(action_other_leg):
    self.child_instrument_main_orders.pop(base_instrument, None)
    return _order
  • Purpose is to prevent half-built diagonal spreads.

    • If one leg exists but the expected opposite leg is missing:
      • Any partial state for this base instrument is cleared
      • Entry is aborted immediately
    • This avoids scenarios where:
      • Only the near-month or far-month leg is live
      • The strategy accidentally carries unintended directional exposure

This is a pre-emptive consistency check, not a post-failure cleanup.

  1. Exception Handling During Order Placement
python
except Exception as e:
    self.logger.debug(f"No orders placed due to the following error: {e}")
  • Exceptions during order placement are logged but not raised here.
  • Cleanup and strategy halting are handled by downstream validation, ensuring:
    • Centralized failure handling
    • No duplicated exit logic
  1. Storing the Order Reference
python
self.child_instrument_main_orders.setdefault(base_instrument, {})[meta['action']] = _order
  • Stores the order (successful or failed reference) under:
    • Base instrument
    • Action type (BUY / SELL)
  • This enables:
    • Coordinated exits
    • Validation of full structure completion
    • Base-level risk checks instead of leg-level handling
  1. Validation & Naked Position Protection
python
if not check_order_placed_successfully(_order):

If the current leg fails to place successfully:

python
self.logger.critical(...)
self.exit_all_positions_for_base_instrument(base_instrument)
self.execution_complete = True

Key protections implemented here:

  • Immediately exits all other legs already placed for this base instrument
  • Prevents:
    • Naked exposure
    • Accidental directional positions
  • Sets execution_complete = True to:
    • Stop further entries for the remainder of the run
    • Avoid compounding errors after an inconsistent state

Exit Strategy: Target and Stop-losses

python
  def _check_exit_conditions(self, child_leg_orders_dict):
       """
       Evaluate exit rules for the Long Call Diagonal Spread.
       Checks:
       - Absolute stop-loss: exit if spread drops below the stop premium threshold.
       - Trailing stop-loss: activate after sufficient upside and update trailing stop.
       """

       # Retrieve latest traded prices (LTP) for both legs
       ltp_leg_buy = self.broker.get_ltp(child_leg_orders_dict[BrokerOrderTransactionTypeConstants.BUY].instrument)
       ltp_leg_sell = self.broker.get_ltp(child_leg_orders_dict[BrokerOrderTransactionTypeConstants.SELL].instrument)

       # Initialize levels at first check after entry
       if not self.spread_entry:
           entry_price_leg_buy = child_leg_orders_dict[BrokerOrderTransactionTypeConstants.BUY].entry_price
           entry_price_leg_sell = child_leg_orders_dict[BrokerOrderTransactionTypeConstants.SELL].entry_price
           self.spread_entry = entry_price_leg_buy - entry_price_leg_sell  # spread at entry
           self.stoploss_premium = self.spread_entry * (1 - self.stoploss_percentage / 100)
           self.highest = self.spread_entry
           self.trailing_stop = self.stoploss_premium

       # Current spread price
       self.spread_current = ltp_leg_buy - ltp_leg_sell

       # Absolute Stoploss Check
       if self.spread_current < self.stoploss_premium:
           self.logger.debug(f"Absolute stoploss triggered: Current Net Premium: {self.spread_current:.2f} | Absolute Stoploss threshold: {self.stoploss_premium:.2f} | "
                             f"Exiting positions...")

           # Reset so next entry can be reinitialized
           self.highest = self.spread_entry = self.trailing_stop = self.trailing_stoploss_activated = None

           return True

       # Trailing Stop-loss (TSL) check
       # Activate trailing stop only after spread moves by at least trailing % above entry.
       # Update trailing stop whenever current spread exceeds previous high
       if self.spread_current > self.highest:
           if not self.trailing_stoploss_activated:
               self.logger.info(f"Trailing Stoploss Activated")
               self.trailing_stoploss_activated = True
           new_stop = self.spread_current * (1 - self.tsl_percentage / 100)
           self.logger.info(f"Trailing Stoploss Adjusted: Current Spread price: {self.spread_current:.2f} | "
                            f"Previous Spread price: {self.highest:.2f} |"
                            f"Old Stop: {self.trailing_stop:.2f} | "
                            f"New Stop: {new_stop:.2f} | "
                            f"(Trail % = {self.tsl_percentage})")
           self.trailing_stop = new_stop
           self.highest = self.spread_current
           self.stoploss_premium = self.trailing_stop

       return False

Code Explanation

  1. Retrieve Latest Prices for Both Legs
python
ltp_leg_buy = self.broker.get_ltp(child_leg_orders_dict[BUY].instrument)
ltp_leg_sell = self.broker.get_ltp(child_leg_orders_dict[SELL].instrument)
  • Fetches the latest traded price (LTP) for:
    1. Long far-month call (BUY)
    2. Short near-month call (SELL)
  • These LTPs are used to compute the current diagonal spread value.
  1. One-time Initialization of Entry & Risk Levels
python
if not self.spread_entry:
  • This block runs only once, on the first exit check after entry.
python
entry_price_leg_buy = ...
entry_price_leg_sell = ...
self.spread_entry = entry_price_leg_buy - entry_price_leg_sell
  • Captures the actual filled spread at entry, not a theoretical value.
  • This ensures all risk management is based on real execution prices.
  1. Derived Risk Levels
python
self.stoploss_premium = self.spread_entry * (1 - self.stoploss_percentage / 100)
self.highest = self.spread_entry
self.trailing_stop = self.stoploss_premium
  • Absolute stop-loss is set as a percentage below the entry spread.
  • highest tracks the best spread value seen so far.
  • The initial trailing stop is conservative and equal to the absolute stop.

This design ensures:

  • Clean state initialization
  • No recalculation drift on subsequent ticks
  • Predictable behavior across re-entries
  1. Absolute Stop-loss Check
python
if self.spread_current < self.stoploss_premium:

If the spread drops below the allowed loss threshold:

  • Logs the stop-loss trigger
  • Signals exit immediately
  • Resets internal state variables so the next entry can reinitialize cleanly
python
self.highest = self.spread_entry = self.trailing_stop = self.trailing_stoploss_activated = None
return True

Why reset here?

Diagonal spreads rely on stateful trailing logic. Resetting ensures:

  • No stale highs or stops carry into the next trade
  • Each new entry starts with a clean risk baseline
  1. Trailing Stop-loss Activation & Updates
python
if self.spread_current > self.highest:

Trailing stop logic is engaged only when the spread makes new highs.

First Activation

python
if not self.trailing_stoploss_activated:
    self.trailing_stoploss_activated = True
  • Marks the moment the trailing stop becomes active.
  • From this point onward, the stop-loss is no longer static: it is recalculated dynamically as a percentage below the highest spread value achieved.

Trailing Stop Adjustment

python
new_stop = self.spread_current * (1 - tsl%)
  • Trailing stop is recalculated as a fixed percentage below the new high
  • This stop only moves upward, never downward
python
self.trailing_stop = new_stop
self.highest = self.spread_current
self.stoploss_premium = self.trailing_stop

This ensures:

  • Profits are progressively locked in
  • Any reversal after a peak will trigger an exit at a protected level

Validating Strategy Parameters in Bear Put Spread Strategy

The following code snippet validates key inputs before execution:

python
def validate_parameters(self):
       """ Validates required strategy parameters. """
       check_argument(
           self.strategy_parameters, "extern_function", lambda _: len(_) >= 4,
           err_message=(
               "Need 4 parameters for this strategy: \n"
               "(1) NUMBER_OF_OTM_STRIKES_SELL_LEG \n"
               "(2) STOPLOSS_PERCENTAGE \n"
               "(3) TRAILING_STOPLOSS_PERCENTAGE \n"
               "(4) RE_ENTRY_LIMIT"
           )
       )

       # Validate numeric strategy parameters
       for param in (self.re_entry_limit, self.no_of_otm_strikes_leg_sell):
           check_argument(param, "extern_function", is_positive_int, "Value should be a positive integer")

       # Validate percentage strategy parameters
       for param in (self.stoploss_percentage, self.tsl_percentage,):
           check_argument(param, "extern_function", is_nonnegative_int_or_float, "Value should be >0")

Code Explanation:

The validate_parameters function ensures that all necessary inputs are correctly set before execution.

Failure Case 1: Missing Required Parameters

python
self.strategy_parameters = {}
validate_parameters()

Error:

python
Need 4 parameters for this strategy:
(1) NUMBER_OF_OTM_STRIKES_SELL_LEG
(2) STOPLOSS_PERCENTAGE
(3) TRAILING_STOPLOSS_PERCENTAGE
(4) RE_ENTRY_LIMIT

Reason: The strategy cannot proceed without its minimum configuration.

Failure Case 2: Invalid integer fields

python
self.strategy_parameters = {
    "NUMBER_OF_OTM_STRIKES_SELL_LEG": 0,   # invalid
    "STOPLOSS_PERCENTAGE": 20,
    "TRAILING_STOPLOSS_PERCENTAGE": 10,
    "RE_ENTRY_LIMIT": -1                  # invalid
}

Error raised:

python
Value should be a positive integer

Reason: Validation fails because is_positive_int rejects zero and negative values; only strictly positive integers are allowed.

Failure Case 3: Invalid percentage / type fields

python
self.strategy_parameters = {
    "NUMBER_OF_OTM_STRIKES_SELL_LEG": 1,
    "STOPLOSS_PERCENTAGE": -5,          # invalid
    "TRAILING_STOPLOSS_PERCENTAGE": "ten",  # invalid type
    "RE_ENTRY_LIMIT": 2,
}

Error:

python
Value should be >0

Reason: STOPLOSS_PERCENTAGE and TRAILING_STOPLOSS_PERCENTAGE must be valid numeric values greater than zero. Any non-numeric input or non-positive value fails validation and causes execution to halt.

Ideal Market Conditions for Long Call Diagonal Spread

When to Use

The Long Call Diagonal Spread performs best in markets expected to rise gradually over time, rather than explode upward suddenly or remain choppy. It benefits when a moderately bullish trend is developing and the trader wants directional exposure with reduced time decay and lower cost compared to buying a single long call.

Typical scenarios include:

  • Post-Breakout Continuation: After a resistance level is crossed and the price is likely to trend higher in steps rather than surge immediately.

  • Typical scenarios include: Post-Breakout Continuation: After price moves above a previously established price ceiling and is expected to continue trending higher in a step-by-step manner rather than accelerating sharply.

  • Bullish Earnings Outlook: Ahead of results or data where positive sentiment may drive a steady, sustained rise rather than a sharp gap.

  • Supportive Macro or Policy Signals: When economic indicators, liquidity conditions, or policy announcements point toward gradual strength in the broader market.

  • Balanced Implied Volatility Environment: Best when IV is moderate, high enough for the short near-term call to provide meaningful premium income, but not so high that the long-dated call becomes excessively expensive.

Interpreting the Long Call Diagonal Spread Payoff Structure

The following payoff diagram illustrates the profit-and-loss dynamics of a Low-Risk Long Call Diagonal Spread on the SPY ETF index matching the parameters described earlier.

Profit and Loss Diagram
Figure 2: Profit and Loss Diagram

The green curve highlights the profitable region of the long call diagonal spread at the expiry of the near-month short call.

In this construction, a far-month ATM call ($500) is bought while a near-month OTM call ($510) is sold.

The spread typically achieves its highest value when the index trades close to the short call strike ($510) at near-month expiry. At this level, the short call expires with little or no intrinsic value, while the long call retains significant time value, creating a peak in the payoff curve.

Beyond the short strike, profits begin to taper. As the index rises further, the short call’s intrinsic value increases faster than the long call’s incremental gains, causing the spread value to decline gradually rather than flatten. Unlike vertical spreads, profit is not strictly capped, but is constrained by the short call leg.

The red section reflects the loss region. If the index remains well below the long call strike ($500), both calls are out-of-the-money and the spread loses value as the long call decays. The maximum loss is limited to the net debit paid.

Dashed markers show risk-management exits applied during the life of the trade:

  • Absolute Stop-Loss (−20%) exits the position if the spread falls 20% from entry.
  • Trailing Stop (10% trail) moves upward as the spread strengthens; for example, if the spread reaches $6, the stop resets near $5.4, locking in profits if the move reverses.

Profit & Loss Calculations for Low and High Risk Versions

1. Low Risk Long Call Diagonal Spread Strategy

The example below illustrates a low-risk diagonal spread setup on the S&P 500 ETF (SPY): a stable, highly liquid US index ETF.

Key Parameters

  • Stop-Loss (stoploss_percentage): 20% of the net debit

  • Trailing Stop-Loss (tsl_percentage): 10% below the highest spread value after entry

  • Instrument: SPY (S&P 500 ETF)

  • Expiry Structure:

    • Long Call: Next-month expiry

    • Short Call: Near-month expiry

  • Strikes (num_of_otm_strikes_sell_leg): Buy ATM 500 Call (far-month),
    Sell OTM 510 Call (near-month)

Calculations

(Assume a simple illustrative 1:1 relationship between SPY movements and the diagonal spread.)

  • Total Premium Paid (Net Debit):
    Premium Paid – Premium Received = $10 – $6 = $4

  • Absolute Stop-Loss (0.2 × Net Debit):

    $4 × 0.20 = $0.80

Maximum loss capped at $0.80.

  • Trailing Stop-Loss:

    Set 10% below the highest spread level. It activates only after spread increases above the threshold (original spread ÷ (1-TSL%))

    If spread increases from $4 to $6:

    TSL = 6 × (1 − 0.10) = $5.4

    A reversal below $5.4 exits the position, protecting profits.

  • Maximum Profit:

    This strategy does not have a fixed maximum profit. At the expiry of the near-month short call, profit is typically highest when the underlying trades close to the short strike, allowing the short call to expire with little or no intrinsic value while the long call retains significant time value. The exact profit level depends on the remaining time to expiry and implied volatility of the long call and must be estimated using an option-pricing model.

Maximum profit at near-month expiry occurs when:

  • The short call expires with minimal intrinsic value, and
  • The long call retains maximum combined intrinsic + extrinsic value.
    Formally:

Max Profit = maxₛ - Long Call Value at short expiry − Short Call Intrinsic − Net Debit

= maxₛ - Long Call Value at short expiry − Short Call Intrinsic − 4

Where:

  • Long Call Value at short expiry is the total option value (intrinsic + time value) of the long call with remaining time to expiry
  • S is the underlying price at the expiry of the short (near-month) call
  • Breakeven:

    Note: 1:1 simplification cannot be used for breakeven because the long call retains extrinsic value at the short call’s expiry. Intrinsic-only modeling understates the long call’s value, resulting in an artificially high breakeven estimate. Diagonal spread breakevens therefore cannot be expressed in closed form and must be derived using an option-pricing model.

    Breakeven occurs where:
    Long Call Value at Short Expiry = Net Debit + Short Call Intrinsic = 4 + max(S - 510, 0)

Trailing Stop-Loss Outcomes (10% trail)

Entry spread = $4 • Absolute Stoploss = $3.20

Spot PriceP&L With Trailing Stop-LossP&L Without Trailing Stop-Loss
Rises to $504.44 then drops to $500Exit around $4.00 (Stop ≈ $4.44 × 0.9 = $4.00) = $0$0
Rises to $506 then dropsExit around $5.4 (Stop ≈ $6 × 0.9 = $5.4) = +$1.4–$4.00 (Maximum Loss)
Drops to $496.8Exit at $3.2 = - $0.8–$4.00 (Maximum Loss)

2. High Risk Long Call Diagonal Spread Strategy

The diagram below illustrates the profit and loss profile for a high-risk Long Call Diagonal Spread setup:

Figure 3: Profit and Loss Diagram: High Risk Version

Key Parameters

  • Stop-Loss (stoploss_percentage): 20% of the net debit

  • Trailing Stop-Loss (tsl_percentage): 10% below the highest spread value after entry

  • Instrument: RELIANCE

  • Expiry Structure:

    • Long Call: Far-month expiry

    • Short Call: Near-month expiry

  • Strikes (num_of_otm_strikes_sell_leg): Buy ATM 2400 Call (far-month), Sell OTM 2550 Call (near-month)


Calculations

(Assume a simple illustrative 1:1 relationship between RELIANCE movements and the diagonal spread.)

  • Total Premium Paid (Net Debit):

Premium Paid – Premium Received = ₹250 – ₹150 = ₹100

  • Absolute Stop-Loss (0.2 × Net Debit):
    ₹100 × 0.20 = ₹20
    Maximum loss capped at ₹20.

  • Trailing Stop-Loss:
    Set to 10% below the highest spread level.
    If spread rises from ₹100 to ₹180:
    TSL = 180 × (1 - 0.10) = ₹162

    A reversal below ₹162 exits the position, protecting profits.

  • Maximum Profit:

    Same as the low-risk diagonal spread. Profit is not fixed and depends on the valuation of the long call at short expiry.

    Max Profit = maxₛ Long Call Value at short expiry − Short Call Intrinsic − Net Debit

    = maxₛ Long Call Value at short expiry − Short Call Intrinsic − 100

  • Breakeven:

    Same as for the low-risk diagonal spread. Breakeven is not fixed for diagonal spreads and depends on the valuation of the long call at the expiry of the short leg.

    Breakeven occurs where: Long Call Value at short expiry = Net Debit + Short Call Intrinsic = 100 + max(S - 2500, 0)

Trailing Stop-Loss Outcomes (10% trail)

Entry spread = ₹100 • Absolute Stoploss = ₹80

Spot PriceP&L With Trailing Stop-LossP&L Without Trailing Stop-Loss
Rises to ₹2411.11, then drops to 2400Exit around ₹100 (Stop ≈ ₹111.11 × 0.9 ≈ ₹100) = ₹0₹0
Rises to ₹2480, then dropsExit around ₹162 (Stop ≈ ₹180 × 0.9 ≈ ₹162) = +₹62- ₹100 (Maximum Loss)
Drops to ₹2320, then dropsExit around ₹80 = - ₹80- ₹100 (Maximum Loss)

Comparing Long Call Diagonal Spread vs. Bull Call Spread

In the earlier section we built the Long Call Diagonal Spread and demonstrated its advantages over basic long call buying.

A natural extension is to compare the Long Call Diagonal Spread with its closest relative: the Bull Call Spread.

Both strategies are bullish debit spreads, but they differ meaningfully in terms of expiry structure, risk profile, time-decay behavior and management flexibility.

The Long Call Diagonal Spread and the Bull Call Spread are both defined-risk bullish strategies.

While both are debit spreads using call options, the diagonal spread uses different expiries, giving it distinct advantages in flexibility, theta behavior, and re-entry management.

Despite sharing the same directional bias, they differ heavily in structure, time decay characteristics, and payoff profile.

FeatureLong Call Diagonal SpreadBull Call Spread
Market OutlookModerately bullish – expect a gradual riseModerately bullish – expect a faster move
Option Types UsedLong far-month Call + Short near-month CallLong Call + Short Call (same expiry)
Initial Trade TypeDebit (net premium paid)Debit (net premium paid)
Expiry StructureDiagonal (different expiries)Vertical (same expiry)
Maximum ProfitPartially capped; depends on short-call expiry and rollsStrike difference − net debit
Maximum LossNet debit paidNet debit paid
Breakeven PointLower strike + net debitLower strike + net debit
Implied VolatilityPartially hedged vega (long far-month, short near-month)Long vega (benefits from rising IV)
Theta ImpactMixed theta; often positive due to short near-month callNegative theta; time decay works against you
Figure 4: Long Call Diagonal Spread vs. Bull Call Spread Mind Tree

Which One Is Right for You?

If you expect a gradual, steady rise and want a position that benefits from both directional movement and time decay, the Long Call Diagonal Spread is a strong fit. You pay a defined debit, gain exposure through the long-dated call, and generate short-term income by selling the nearer-term call.

If you anticipate a clean, faster bullish move and prefer a simpler structure, the Bull Call Spread works better. It offers straightforward payoff limits, fixed risk, and a direct link between spot movement and profit.

Both strategies are suitable for bullish markets, choose the Long Call Diagonal when you want flexibility, income, and durability, or choose the Bull Call Spread when you want a cleaner vertical setup with a faster directional bias.

Customize the Strategy with Your Own Parameters!

Traders can fine-tune strike selection, expiry choice, stop-loss levels, and profit targets based on risk tolerance.

💡 Want to see the complete strategy? Check out the full implementation here.

👉 For testing on the Phoenix platform of AlgoBulls, head over to our site now!

👉 A Jupyter notebook for this strategy is coming soon. Meanwhile, check out All Available Notebooks for Testing Custom Strategies!

Final Thoughts

The Long Call Diagonal Spread becomes especially powerful when combined with structured risk management - pairing spread-based stop controls, adaptive trailing logic, and re-entry mechanisms to stay aligned with evolving market conditions.

Key Risk-Mitigation Techniques

✔ Incorporated in the Strategy (Recap)

  • Adaptive stop control: An absolute stop-loss that transitions into a trailing stop as the spread moves favorably, protecting capital and profits.

  • Dynamic profit protection: Trailing stop ratchets upward with new highs and exits on defined reversals.

  • Controlled re-entry: Allows fresh diagonal entries when bullish structure resumes, without compounding risk.

✔ Additional Enhancements

  • High-frequency P&L tracking: Monitoring the spread value more frequently than the candle timeframe (e.g., every 30 seconds on a 1-minute chart) can improve the detection of early reversals, especially when the underlying trends slowly but pulls back abruptly.

  • Position adjustments: Traders can dynamically adjust strike selection, expiry choice, or lot size as volatility and trend conditions evolve. In periods of rising IV or slower trends, shifting the long leg deeper ITM or further out in expiry can reduce sensitivity to short-term noise, while the short near-term call continues to harvest theta.

  • Alternative Exit Framework (Fixed Stops & Targets): In calmer or more range-bound conditions, traders may choose to disable the trailing stop and operate with a fixed absolute stop-loss and predefined target profit instead.

Disclaimer

The information provided in this article is for educational and informational purposes only and does not constitute financial, investment, or legal advice. The views and opinions expressed are based on the interpretation by the author of this article 'Long Call Diagonal Spread: Ride Trends Across Time'. While we strive for accuracy, readers are advised to consult with regulatory authorities, financial experts, or legal professionals before making any trading or investment decisions. AlgoBulls is not responsible for any direct or indirect implications arising from the use of this information.