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.
![]() |
|---|
| 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
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
- Skip base instruments with active positions
-
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
-
- Fetch base instrument LTP
-
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)
-
- Re-entry tracking and limits
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.
- Define the diagonal structure
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.
- Setup option instruments across all expiries
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
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
-
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
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
- Base & Child Instrument Resolution
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.
- Determining the Opposite Leg
- For a diagonal spread, the strategy expects both a BUY and a SELL leg.
- This line dynamically computes the complementary action for validation.
- Guard Against Incomplete Structures
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
- If one leg exists but the expected opposite leg is missing:
This is a pre-emptive consistency check, not a post-failure cleanup.
- Exception Handling During Order Placement
- 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
- Storing the Order Reference
- 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
- Validation & Naked Position Protection
If the current leg fails to place successfully:
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
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
- Retrieve Latest Prices for Both Legs
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:
- Long far-month call (BUY)
- Short near-month call (SELL)
- These LTPs are used to compute the current diagonal spread value.
- One-time Initialization of Entry & Risk Levels
- This block runs only once, on the first exit check after entry.
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.
- Derived Risk Levels
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
- Absolute Stop-loss Check
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
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
- Trailing Stop-loss Activation & Updates
Trailing stop logic is engaged only when the spread makes new highs.
First Activation
- 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
- Trailing stop is recalculated as a fixed percentage below the new high
- This stop only moves upward, never downward
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:
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
self.strategy_parameters = {}
validate_parameters()
Error:
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
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:
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
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:
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.
![]() |
|---|
| 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 Price | P&L With Trailing Stop-Loss | P&L Without Trailing Stop-Loss |
|---|---|---|
| Rises to $504.44 then drops to $500 | Exit around $4.00 (Stop ≈ $4.44 × 0.9 = $4.00) = $0 | $0 |
| Rises to $506 then drops | Exit around $5.4 (Stop ≈ $6 × 0.9 = $5.4) = +$1.4 | –$4.00 (Maximum Loss) |
| Drops to $496.8 | Exit 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) = ₹162A 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 Price | P&L With Trailing Stop-Loss | P&L Without Trailing Stop-Loss |
|---|---|---|
| Rises to ₹2411.11, then drops to 2400 | Exit around ₹100 (Stop ≈ ₹111.11 × 0.9 ≈ ₹100) = ₹0 | ₹0 |
| Rises to ₹2480, then drops | Exit around ₹162 (Stop ≈ ₹180 × 0.9 ≈ ₹162) = +₹62 | - ₹100 (Maximum Loss) |
| Drops to ₹2320, then drops | Exit 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.
| Feature | Long Call Diagonal Spread | Bull Call Spread |
|---|---|---|
| Market Outlook | Moderately bullish – expect a gradual rise | Moderately bullish – expect a faster move |
| Option Types Used | Long far-month Call + Short near-month Call | Long Call + Short Call (same expiry) |
| Initial Trade Type | Debit (net premium paid) | Debit (net premium paid) |
| Expiry Structure | Diagonal (different expiries) | Vertical (same expiry) |
| Maximum Profit | Partially capped; depends on short-call expiry and rolls | Strike difference − net debit |
| Maximum Loss | Net debit paid | Net debit paid |
| Breakeven Point | Lower strike + net debit | Lower strike + net debit |
| Implied Volatility | Partially hedged vega (long far-month, short near-month) | Long vega (benefits from rising IV) |
| Theta Impact | Mixed theta; often positive due to short near-month call | Negative 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.



