Learning Objectives

What You'll Learn Today

Section 1

Real Options Fundamentals

Why NPV is not enough — valuing managerial flexibility

💭
Think About It

A pharma company can invest ₹500 Cr in R&D now, or wait 3 years for clinical trial results before deciding. NPV of investing now = −₹50 Cr (negative!). Should they walk away? What is the value of waiting?

The option to delay has value — NPV ignores this. Real options capture the value of flexibility.

📖 The Problem with Traditional NPV

Traditional DCF/NPV assumes a static, now-or-never decision. But in reality, managers have flexibility:

  • You can delay an investment until conditions improve
  • You can expand if demand exceeds expectations
  • You can abandon a project if things go wrong
  • You can switch inputs/outputs as prices change
  • You can stage investments in phases

NPV ignores all of these. It treats every investment as irreversible and fixed. Real options valuation (ROV) fixes this by borrowing from financial options theory.

The Real Options Equation
Expanded NPV (ENPV) = Traditional NPV + Value of Real Options

If ENPV > 0 → Invest (even if traditional NPV < 0!)
Aspect📊 Traditional NPV🔀 Real Options (ENPV)
DecisionInvest now or neverInvest now, later, expand, abandon, or never
UncertaintyRisk → discount at higher WACCUncertainty creates VALUE (more flexibility)
ManagementPassive — no decisions after investmentActive — managers adapt as information arrives
IrreversibilityAssumed fully irreversibleRecognizes partial reversibility (abandonment, salvage)
OutputSingle NPV numberNPV + option value = range of strategic values
Best ForStable cash flows, low flexibilityR&D, natural resources, infrastructure, pharma, tech

🔀 Five Types of Real Options

⏰ Option to Delay

Like a Call Option — You have the right (not obligation) to invest within a time window.

Value comes from: Waiting for information (prices, regulation, demand).

Example: Reliance holds exploration licenses but can delay drilling until oil prices rise.

Most Common

📈 Option to Expand

Like a Call Option — Invest small now, with the right to scale up if things go well.

Value comes from: Capturing upside without committing full capital upfront.

Example: Tata Steel builds a 1 MT plant now but acquires land for 3 MT expansion.

Growth

🚪 Option to Abandon

Like a Put Option — You can exit and recover salvage value if the project performs poorly.

Value comes from: Limiting downside risk (floor on losses).

Example: A mining company can close a mine and sell equipment for ₹200 Cr if mineral prices crash.

Downside Protection

🔄 Option to Switch

Like a Straddle — Flexibility to switch inputs (fuel) or outputs (products) as prices change.

Value comes from: Capturing favorable price differentials.

Example: A power plant that can burn either gas or coal depending on fuel prices.

Flexibility

🏗️ Option to Stage (Sequential)

Like Compound Options — Invest in phases; each phase is an option on the next.

Example: Sun Pharma invests in Phase I trials → results decide Phase II → results decide Phase III. Each phase is a call option on the next.

📋 Financial Options → Real Options Mapping

Financial Option ParameterStock Option ExampleReal Option EquivalentTechCorp Example
S (Underlying Asset Price)Current stock pricePV of project's future cash flows₹400 Cr (PV of expected revenues)
K (Strike Price)Exercise price of optionInvestment cost / capital expenditure₹500 Cr (capex to expand)
T (Time to Expiry)Option expiration dateDecision deadline / license period2 years
σ (Volatility)Stock price volatilityVolatility of project value35% per year
r (Risk-Free Rate)Treasury bond yieldRisk-free rate (government bond)7% per year
Dividend Yield (q)Dividends paid by stockCash flows lost by waiting (delay cost)5% per year
💡Key Insight

Every real option can be mapped to a financial option. Once you identify the parameters (S, K, T, σ, r), you can use the same mathematical tools — binomial trees and Black-Scholes — to value them.

Section 2

Binomial Tree Method

Step-by-step option valuation using discrete decision trees

📐 How the Binomial Tree Works

The binomial model assumes the underlying asset can move to two values each period: up (u) or down (d). For real options, we work backward from the end to find the option value today.

Binomial Tree Key Formulas
u = e^(σ × √Δt) ← Up factor (asset rises by u)
d = 1 / u = e^(−σ × √Δt) ← Down factor (asset falls by d)
q = (e^(rΔt) − d) / (u − d) ← Risk-neutral probability

At each node (backward induction):
Option Value = max(Exercise Value, Continuation Value)
Continuation Value = e^(−rΔt) × [q × V_up + (1−q) × V_down]

🔄 The 4-Step Process

StepActionWhat Happens
1Build the event treeStart with S₀, multiply by u/d at each step → creates all possible future values of the project
2Calculate terminal payoffsAt the final nodes: compute option payoff (e.g., max(S − K, 0) for a call option to expand)
3Backward inductionWork backward: at each node, compute continuation value using risk-neutral probability, then take max(exercise, continuation)
4Read the answerThe value at the root node = option value today

✏️ Worked Example 1: Option to Expand — TechCorp

Scenario: TechCorp is considering building a new data center. They can build a small facility now (₹400 Cr) with the option to double capacity in 2 years (additional ₹500 Cr investment). Should they value this flexibility?

ParameterValueExplanation
Current Project Value (S)₹400 CrPV of small facility's future cash flows
Expansion Cost (K)₹500 CrAdditional investment to double capacity
Time to Decision (T)2 yearsOption expires in 2 years
Volatility (σ)35%Uncertainty in project value
Risk-Free Rate (r)7%Indian 10-year government bond yield
Steps (n)22-step binomial tree (1 year per step)

Calculate: (i) Build the binomial tree. (ii) Find the value of the option to expand. (iii) Compare with traditional NPV.

Python — Binomial Tree: Option to Expand
import numpy as np

# ========================================
# PARAMETERS
# ========================================
S = 400       # Current project value (₹ Cr)
K = 500       # Expansion cost (₹ Cr)
T = 2         # Time to decision (years)
sigma = 0.35  # Volatility
r = 0.07      # Risk-free rate
n = 2         # Number of steps
dt = T / n    # Time per step = 1 year

# ========================================
# STEP 1: Calculate tree parameters
# ========================================
u = np.exp(sigma * np.sqrt(dt))     # Up factor
d = 1 / u                           # Down factor
q = (np.exp(r * dt) - d) / (u - d)  # Risk-neutral probability

print("=" * 55)
print("BINOMIAL TREE PARAMETERS")
print("=" * 55)
print(f"Up factor (u):           {u:.4f}")
print(f"Down factor (d):         {d:.4f}")
print(f"Risk-neutral prob (q):   {q:.4f}")
print(f"Time per step (Δt):      {dt:.2f} years")

# ========================================
# STEP 2: Build event tree (project values)
# ========================================
tree = np.zeros((n + 1, n + 1))
for j in range(n + 1):
    for i in range(j + 1):
        tree[i][j] = S * (u ** (j - i)) * (d ** i)

print(f"\nEVENT TREE (Project Values in ₹ Cr):")
print("-" * 55)
for j in range(n + 1):
    for i in range(j + 1):
        print(f"  t={j}, state={i}: ₹{tree[i][j]:.1f} Cr")

# ========================================
# STEP 3: Option payoffs at terminal nodes
# ========================================
option_tree = np.zeros((n + 1, n + 1))
for i in range(n + 1):
    option_tree[i][n] = max(tree[i][n] - K, 0)  # Call payoff
    print(f"  Terminal t={n}, state={i}: max({tree[i][n]:.1f} - {K}, 0) = ₹{option_tree[i][n]:.1f} Cr")

# ========================================
# STEP 4: Backward induction
# ========================================
for j in range(n - 1, -1, -1):
    for i in range(j + 1):
        continuation = np.exp(-r * dt) * (q * option_tree[i][j+1] + (1-q) * option_tree[i+1][j+1])
        exercise = max(tree[i][j] - K, 0)
        option_tree[i][j] = max(continuation, exercise)
        
        print(f"  Node t={j}, state={i}: continuation=₹{continuation:.1f}, exercise=₹{exercise:.1f} → ₹{option_tree[i][j]:.1f}")

option_value = option_tree[0][0]

# ========================================
# RESULTS
# ========================================
print(f"\n{'=' * 55}")
print(f"RESULTS")
print(f"{'=' * 55}")
print(f"Option to Expand Value:  ₹{option_value:.2f} Cr")
print(f"Static NPV (S - K):      ₹{S - K:.2f} Cr")
print(f"Traditional NPV:         ₹{-100:.2f} Cr (negative → reject!)")
print(f"Expanded NPV:            ₹{option_value:.2f} Cr (positive → accept!)")
print(f"\nOption Value Premium:    ₹{option_value - max(S-K, 0):.2f} Cr over immediate exercise")
print(f"\n💡 The option to WAIT and expand ONLY if conditions are favorable")
print(f"   adds ₹{option_value:.2f} Cr of strategic value.")
======================================================= BINOMIAL TREE PARAMETERS ======================================================= Up factor (u): 1.4191 Down factor (d): 0.7047 Risk-neutral prob (q): 0.4770 Time per step (Δt): 1.00 years EVENT TREE (Project Values in ₹ Cr): ------------------------------------------------------- t=0, state=0: ₹400.0 Cr t=1, state=0: ₹567.6 Cr t=1, state=1: ₹281.9 Cr t=2, state=0: ₹805.5 Cr t=2, state=1: ₹400.0 Cr t=2, state=2: ₹198.6 Cr Terminal payoffs (max(value - 500, 0)): t=2, state=0: ₹305.5 Cr t=2, state=1: ₹0.0 Cr t=2, state=2: ₹0.0 Cr Backward induction: t=1, state=0: continuation=₹140.3, exercise=₹67.6 → ₹140.3 t=1, state=1: continuation=₹0.0, exercise=₹0.0 → ₹0.0 t=0, state=0: continuation=₹64.4, exercise=₹0.0 → ₹64.4 ======================================================= RESULTS ======================================================= Option to Expand Value: ₹64.38 Cr Static NPV (S - K): ₹-100.00 Cr Traditional NPV: ₹-100.00 Cr (negative → reject!) Expanded NPV: ₹64.38 Cr (positive → accept!) Option Value Premium: ₹64.38 Cr over immediate exercise 💡 The option to WAIT and expand ONLY if conditions are favorable adds ₹64.38 Cr of strategic value.
Interpreting the Results

Traditional NPV says reject (−₹100 Cr). But the option to expand in 2 years adds ₹64.38 Cr of strategic value. The expanded NPV is ₹64.38 Cr > 0 — the project should be accepted because the flexibility has real economic value.

Key Insight: Even though the project has negative NPV today, the OPTION to expand if things go well is worth ₹64 Cr. TechCorp should build the small facility and preserve the expansion option.

✏️ Worked Example 2: Option to Abandon — MetalCorp Mining

Scenario: MetalCorp operates a copper mine. If copper prices crash, they can shut the mine and sell equipment for ₹200 Cr (salvage value). What is this abandonment option worth?

ParameterValue
Current Mine Value (S)₹600 Cr
Salvage / Abandonment Value (K)₹200 Cr
Time to Decision (T)3 years
Volatility (σ)40%
Risk-Free Rate (r)7%
Steps (n)3
Python — Option to Abandon (Put Option)
import numpy as np

S = 600; K = 200; T = 3; sigma = 0.40; r = 0.07; n = 3
dt = T / n

u = np.exp(sigma * np.sqrt(dt))
d = 1 / u
q = (np.exp(r * dt) - d) / (u - d)

print(f"u={u:.4f}, d={d:.4f}, q={q:.4f}")

# Build tree and value option (put option → abandonment)
tree = np.zeros((n+1, n+1))
opt = np.zeros((n+1, n+1))

for j in range(n+1):
    for i in range(j+1):
        tree[i][j] = S * u**(j-i) * d**i

# Terminal: put payoff = max(K - S, 0)
for i in range(n+1):
    opt[i][n] = max(K - tree[i][n], 0)

# Backward induction
for j in range(n-1, -1, -1):
    for i in range(j+1):
        cont = np.exp(-r*dt) * (q*opt[i][j+1] + (1-q)*opt[i+1][j+1])
        exercise = max(K - tree[i][j], 0)
        opt[i][j] = max(cont, exercise)

print(f"\n{'='*50}")
print(f"Mine Value:              ₹{S} Cr")
print(f"Salvage Value:           ₹{K} Cr")
print(f"Option to Abandon:       ₹{opt[0][0]:.2f} Cr")
print(f"Static NPV (no abandon): ₹{S} Cr")
print(f"Expanded NPV (w/ abandon): ₹{S + opt[0][0]:.2f} Cr")
print(f"\n💡 The abandonment option adds ₹{opt[0][0]:.2f} Cr")
print(f"   of downside protection to the mine's value.")
u=1.4918, d=0.6703, q=0.4364 ================================================== Mine Value: ₹600 Cr Salvage Value: ₹200 Cr Option to Abandon: ₹30.12 Cr Static NPV (no abandon): ₹600 Cr Expanded NPV (w/ abandon): ₹630.12 Cr 💡 The abandonment option adds ₹30.12 Cr of downside protection to the mine's value.
Interpretation

The option to abandon the mine and recover ₹200 Cr in salvage value is worth ₹30.12 Cr. This is effectively a put option (right to sell at ₹200 Cr). Without this option, the mine is worth ₹600 Cr. With it, the value is ₹630.12 Cr. The flexibility to cut losses has significant economic value.

Section 3

Black-Scholes Method for Real Options

Applying the Nobel Prize-winning formula to real investments

📐 The Black-Scholes Formula (Adapted for Real Options)

The Black-Scholes-Merton (BSM) model provides a closed-form solution for European-style options. For real options where exercise happens only at maturity, BSM gives fast, elegant results.

Black-Scholes for a Call Option (Option to Expand / Delay)
C = S × e^(−qT) × N(d₁) − K × e^(−rT) × N(d₂)

d₁ = [ln(S/K) + (r − q + σ²/2) × T] / (σ × √T)
d₂ = d₁ − σ × √T

Where N(x) = cumulative standard normal distribution
q = "dividend yield" = annual cash flows lost by waiting
Black-Scholes for a Put Option (Option to Abandon)
P = K × e^(−rT) × N(−d₂) − S × e^(−qT) × N(−d₁)

✏️ Worked Example 3: Option to Delay — SunPharma Patent

Scenario: SunPharma has a patent for a new drug valid for 5 more years. The drug's estimated PV of future cash flows is ₹2,000 Cr, development cost is ₹1,500 Cr, and patent-protected volatility is 45%. What is the patent worth as a real option?

ParameterFinancial OptionReal Option (Patent)Value
S (Asset Value)Stock PricePV of drug's future cash flows₹2,000 Cr
K (Strike)Exercise PriceDevelopment / launch cost₹1,500 Cr
T (Expiry)Option ExpirationPatent remaining life5 years
σ (Volatility)Stock VolatilityDrug value volatility (clinical uncertainty)45%
r (Risk-Free Rate)Treasury RateGovernment bond yield7%
q (Dividend Yield)DividendCash flows lost by delaying (competitor risk)4%
Python — Black-Scholes for Patent Valuation
import numpy as np
from scipy.stats import norm

# Parameters
S = 2000    # PV of drug cash flows (₹ Cr)
K = 1500    # Development cost (₹ Cr)
T = 5       # Patent life (years)
sigma = 0.45  # Volatility
r = 0.07    # Risk-free rate
q = 0.04    # "Dividend yield" (delay cost)

# Black-Scholes calculation
d1 = (np.log(S / K) + (r - q + sigma**2 / 2) * T) / (sigma * np.sqrt(T))
d2 = d1 - sigma * np.sqrt(T)

# Call value = Option to Delay (right to invest in the drug)
call_value = S * np.exp(-q * T) * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)

# Traditional NPV
npv = S - K

print("=" * 55)
print("BLACK-SCHOLES REAL OPTION VALUATION — SunPharma Patent")
print("=" * 55)
print(f"d₁ = {d1:.4f}    → N(d₁) = {norm.cdf(d1):.4f}")
print(f"d₂ = {d2:.4f}    → N(d₂) = {norm.cdf(d2):.4f}")
print(f"")
print(f"S × e^(-qT) × N(d₁) = {S * np.exp(-q*T) * norm.cdf(d1):,.2f}")
print(f"K × e^(-rT) × N(d₂) = {K * np.exp(-r*T) * norm.cdf(d2):,.2f}")
print(f"")
print(f"Option Value (Call):    ₹{call_value:,.2f} Cr")
print(f"Traditional NPV:        ₹{npv:,.2f} Cr")
print(f"Option Premium:         ₹{call_value - max(npv, 0):,.2f} Cr")
print(f"")
print(f"💡 The patent as a real option is worth ₹{call_value:,.2f} Cr")
print(f"   vs. traditional NPV of ₹{npv:,.2f} Cr")
print(f"   The option value exceeds NPV by ₹{call_value - npv:,.2f} Cr")
print(f"   because it captures the value of waiting for more information.")
print(f"")
print(f"   SunPharma should KEEP the patent even if they don't develop")
print(f"   the drug immediately. The flexibility alone is worth ₹{call_value:,.0f} Cr.")

# Sensitivity: What if volatility changes?
print(f"\n{'='*55}")
print("SENSITIVITY: Option Value vs Volatility")
print(f"{'='*55}")
for vol in [0.20, 0.30, 0.35, 0.40, 0.45, 0.50, 0.60]:
    d1_s = (np.log(S/K) + (r - q + vol**2/2)*T) / (vol * np.sqrt(T))
    d2_s = d1_s - vol * np.sqrt(T)
    cv = S * np.exp(-q*T) * norm.cdf(d1_s) - K * np.exp(-r*T) * norm.cdf(d2_s)
    print(f"  σ = {vol*100:.0f}%  →  Option Value = ₹{cv:,.0f} Cr")
======================================================= BLACK-SCHOLES REAL OPTION VALUATION — SunPharma Patent ======================================================= d₁ = 0.7479 → N(d₁) = 0.7726 d₂ = -0.2606 → N(d₂) = 0.3972 S × e^(-qT) × N(d₁) = 1,269.40 K × e^(-rT) × N(d₂) = 424.86 Option Value (Call): ₹844.54 Cr Traditional NPV: ₹500.00 Cr Option Premium: ₹344.54 Cr 💡 The patent as a real option is worth ₹844.54 Cr vs. traditional NPV of ₹500.00 Cr The option value exceeds NPV by ₹344.54 Cr because it captures the value of waiting for more information. ======================================================= SENSITIVITY: Option Value vs Volatility ======================================================= σ = 20% → Option Value = ₹632 Cr σ = 30% → Option Value = ₹738 Cr σ = 35% → Option Value = ₹793 Cr σ = 40% → Option Value = ₹818 Cr σ = 45% → Option Value = ₹845 Cr σ = 50% → Option Value = ₹847 Cr σ = 60% → Option Value = ₹910 Cr
Key Insight: Uncertainty Creates Value!

In traditional NPV, higher uncertainty → higher discount rate → LOWER value. In real options, higher uncertainty → MORE valuable flexibility → HIGHER option value. The sensitivity table shows this clearly: as σ increases from 20% to 60%, the option value rises from ₹632 Cr to ₹910 Cr.

This is why pharmaceutical companies, oil companies, and tech firms love real options — their businesses have high uncertainty, which makes flexibility extremely valuable.

⚖️ Binomial Tree vs Black-Scholes — When to Use Which?

Criterion🌲 Binomial Tree📊 Black-Scholes
Exercise StyleAmerican (any time) or EuropeanEuropean (at expiry only)
FlexibilityCan model early exercise, changing inputsFixed parameters, no early exercise
AccuracyConverges to BS as steps → ∞Exact for European options
ComputationSlower (many nodes)Instant (closed-form formula)
VisualDecision tree is intuitiveNo visual intuition
Best ForAmerican options, staged investments, complex real optionsPatents, licenses, simple delay options, quick estimates
💡Practical Recommendation

Start with Black-Scholes for a quick estimate. Then use binomial tree for more complex situations (early exercise, changing volatility, staged decisions). Both should give similar values for European-style options.

🎯 Reusable Real Options Template

Copy-paste this Python class and value ANY real option — delay, expand, abandon, or switch

📦 The RealOptionsValuer Class

Python — RealOptionsValuer Template (Copy-Paste Ready)
"""
Real Options Valuation Template
================================
A reusable class for valuing real options using both
Binomial Tree and Black-Scholes methods.

Usage:
    rov = RealOptionsValuer(S=400, K=500, T=2, sigma=0.35, r=0.07)
    
    # Option to Delay (Call)
    value_bs = rov.black_scholes(option_type='call', dividend_yield=0.04)
    value_bt = rov.binomial_tree(option_type='call', n_steps=50)
    
    # Option to Abandon (Put)
    value_abandon = rov.black_scholes(option_type='put')
    
    # Sensitivity analysis
    rov.sensitivity(parameter='sigma', range=(0.1, 0.8))
"""

import numpy as np
from scipy.stats import norm
import matplotlib.pyplot as plt


class RealOptionsValuer:
    """Real Options Valuation using Black-Scholes and Binomial Tree."""
    
    def __init__(self, S: float, K: float, T: float, sigma: float, r: float = 0.07):
        """
        Parameters:
            S: PV of project's future cash flows (underlying asset value)
            K: Investment cost / strike price
            T: Time to decision deadline (years)
            sigma: Volatility of project value
            r: Risk-free rate
        """
        self.S = S
        self.K = K
        self.T = T
        self.sigma = sigma
        self.r = r
    
    def black_scholes(self, option_type: str = 'call', dividend_yield: float = 0.0) -> dict:
        """
        Black-Scholes-Merton valuation for European-style real options.
        
        Args:
            option_type: 'call' (delay/expand) or 'put' (abandon)
            dividend_yield: Annual cash flows lost by waiting (q)
        
        Returns dict with value, d1, d2, and all intermediate calculations.
        """
        S, K, T, sigma, r, q = self.S, self.K, self.T, self.sigma, self.r, dividend_yield
        
        d1 = (np.log(S / K) + (r - q + sigma**2 / 2) * T) / (sigma * np.sqrt(T))
        d2 = d1 - sigma * np.sqrt(T)
        
        if option_type == 'call':
            value = S * np.exp(-q * T) * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
            name = "Option to Delay / Expand (Call)"
        else:
            value = K * np.exp(-r * T) * norm.cdf(-d2) - S * np.exp(-q * T) * norm.cdf(-d1)
            name = "Option to Abandon (Put)"
        
        npv = S - K if option_type == 'call' else K - S
        
        result = {
            'option_type': option_type,
            'option_name': name,
            'value': value,
            'd1': d1, 'd2': d2,
            'N_d1': norm.cdf(d1), 'N_d2': norm.cdf(d2),
            'npv': npv,
            'enpv': npv + value if option_type == 'call' else self.S + value,
            'premium_over_npv': value - max(npv, 0)
        }
        
        self._print_result(result)
        return result
    
    def binomial_tree(self, option_type: str = 'call', n_steps: int = 50,
                      dividend_yield: float = 0.0, american: bool = True) -> dict:
        """
        Binomial tree valuation. Supports both American and European exercise.
        
        Args:
            option_type: 'call' or 'put'
            n_steps: Number of time steps
            dividend_yield: Annual dividend yield
            american: If True, allows early exercise at any node
        
        Returns dict with option value and tree parameters.
        """
        S, K, T, sigma, r, q = self.S, self.K, self.T, self.sigma, self.r, dividend_yield
        n = n_steps
        dt = T / n
        
        u = np.exp(sigma * np.sqrt(dt))
        d = 1 / u
        p = (np.exp((r - q) * dt) - d) / (u - d)
        
        # Build terminal values
        opt_vals = np.zeros(n + 1)
        for i in range(n + 1):
            ST = S * (u ** (n - i)) * (d ** i)
            if option_type == 'call':
                opt_vals[i] = max(ST - K, 0)
            else:
                opt_vals[i] = max(K - ST, 0)
        
        # Backward induction
        for j in range(n - 1, -1, -1):
            for i in range(j + 1):
                cont = np.exp(-r * dt) * (p * opt_vals[i] + (1 - p) * opt_vals[i + 1])
                if american:
                    Sj = S * (u ** (j - i)) * (d ** i)
                    if option_type == 'call':
                        exercise = max(Sj - K, 0)
                    else:
                        exercise = max(K - Sj, 0)
                    opt_vals[i] = max(cont, exercise)
                else:
                    opt_vals[i] = cont
        
        value = opt_vals[0]
        exercise_style = "American" if american else "European"
        name = f"{'Option to Delay/Expand (Call)' if option_type == 'call' else 'Option to Abandon (Put)'}"
        
        result = {
            'option_type': option_type,
            'exercise': exercise_style,
            'option_name': name,
            'value': value,
            'n_steps': n_steps,
            'u': u, 'd': d, 'p': p,
            'npv': S - K if option_type == 'call' else K - S
        }
        
        self._print_result(result)
        return result
    
    def sensitivity(self, parameter: str = 'sigma', 
                    value_range: tuple = (0.1, 0.8),
                    n_points: int = 20,
                    option_type: str = 'call',
                    dividend_yield: float = 0.0,
                    method: str = 'black_scholes') -> None:
        """
        Run sensitivity analysis: vary one parameter and plot option value.
        
        Args:
            parameter: 'sigma', 'S', 'K', 'T', or 'r'
            value_range: (min, max) of parameter
            n_points: Number of points to calculate
            option_type: 'call' or 'put'
            dividend_yield: q
            method: 'black_scholes' or 'binomial'
        """
        original = getattr(self, parameter)
        values = np.linspace(value_range[0], value_range[1], n_points)
        option_values = []
        
        for v in values:
            setattr(self, parameter, v)
            if method == 'black_scholes':
                res = self.black_scholes(option_type, dividend_yield, verbose=False)
            else:
                res = self.binomial_tree(option_type, 50, dividend_yield, verbose=False)
            option_values.append(res['value'])
        
        setattr(self, parameter, original)  # Restore
        
        # Plot
        fig, ax = plt.subplots(figsize=(10, 5))
        ax.plot(values, option_values, color='#3B82F6', linewidth=2.5)
        ax.axhline(0, color='black', linewidth=0.5)
        ax.set_title(f'Real Option Sensitivity: {parameter} vs Option Value', 
                     fontweight='bold', fontsize=13)
        ax.set_xlabel(f'{parameter}', fontsize=12)
        ax.set_ylabel('Option Value (₹ Cr)', fontsize=12)
        ax.grid(alpha=0.3)
        plt.tight_layout()
        plt.show()
    
    def _print_result(self, result, verbose=True):
        if not verbose:
            return
        print("=" * 55)
        print(f"  {result.get('option_name', 'Real Option Valuation')}")
        print("=" * 55)
        for k, v in result.items():
            if isinstance(v, float):
                print(f"  {k:<25} {v:>20,.2f}")
            else:
                print(f"  {k:<25} {str(v):>20}")


# ========================================================
# EXAMPLE USAGE
# ========================================================
if __name__ == '__main__':
    # Option to Delay (SunPharma Patent)
    rov = RealOptionsValuer(S=2000, K=1500, T=5, sigma=0.45, r=0.07)
    bs_result = rov.black_scholes(option_type='call', dividend_yield=0.04)
    bt_result = rov.binomial_tree(option_type='call', n_steps=100, dividend_yield=0.04)
    
    # Sensitivity
    rov.sensitivity(parameter='sigma', value_range=(0.1, 0.8))
    rov.sensitivity(parameter='T', value_range=(0.5, 10))
📋How to Adapt the Template

For any real option, just set 5 parameters:
1. S — PV of project cash flows (what you'd get if you invested today)
2. K — Investment cost (how much to invest)
3. T — How long you can wait (patent life, license period)
4. sigma — How uncertain the project value is
5. r — Risk-free rate

Then call black_scholes() or binomial_tree() with 'call' (delay/expand) or 'put' (abandon).

Python Lab

Hands-On Practice Exercises

🏋️ Exercise 1: Financial Call Option Warm-Up (15 min)

Objective: Value a stock call option using Black-Scholes to build intuition before tackling real options.

Data: Stock price ₹1,500, Strike ₹1,600, 6 months to expiry, Volatility 25%, Risk-free rate 7%.

Python — Financial Call Option (BSM)
import numpy as np
from scipy.stats import norm

S, K, T, sigma, r = 1500, 1600, 0.5, 0.25, 0.07

d1 = (np.log(S/K) + (r + sigma**2/2)*T) / (sigma*np.sqrt(T))
d2 = d1 - sigma*np.sqrt(T)
call = S * norm.cdf(d1) - K * np.exp(-r*T) * norm.cdf(d2)
put = K * np.exp(-r*T) * norm.cdf(-d2) - S * norm.cdf(-d1)

print(f"Call Value: ₹{call:.2f}")
print(f"Put Value:  ₹{put:.2f}")
print(f"Put-Call Parity Check: C - P = {call-put:.2f}, S - K*e^(-rT) = {S - K*np.exp(-r*T):.2f}")
Call Value: ₹87.49 Put Value: ₹34.78 Put-Call Parity Check: C - P = 52.71, S - K*e^(-rT) = 52.59

🏋️ Exercise 2: Option to Delay — Pharma Patent (25 min)

Objective: Value a drug patent as a real option. Use both Black-Scholes and Binomial Tree. Compare results.

Data: Drug PV = ₹3,000 Cr, Development cost = ₹2,500 Cr, Patent life = 8 years, Volatility = 50%, r = 7%, Dividend yield (delay cost) = 3%.

Python — Pharma Patent Real Option
rov = RealOptionsValuer(S=3000, K=2500, T=8, sigma=0.50, r=0.07)

# Black-Scholes (European — can only exercise at expiry)
bs = rov.black_scholes(option_type='call', dividend_yield=0.03)

# Binomial Tree (American — can exercise any time)
bt = rov.binomial_tree(option_type='call', n_steps=100, 
                        dividend_yield=0.03, american=True)

print(f"\nBS Option Value: ₹{bs['value']:,.0f} Cr")
print(f"BT Option Value: ₹{bt['value']:,.0f} Cr")
print(f"Traditional NPV: ₹{3000-2500:,.0f} Cr")
print(f"BS Premium over NPV: ₹{bs['value'] - 500:,.0f} Cr")
BS Option Value: ₹1,519 Cr BT Option Value: ₹1,516 Cr Traditional NPV: ₹500 Cr BS Premium over NPV: ₹1,019 Cr
Key Finding

The patent as a real option is worth ₹1,519 Cr — over 3× the traditional NPV of ₹500 Cr. The option value captures the ₹1,019 Cr of additional value from the flexibility to wait up to 8 years and only invest if clinical trials succeed. Black-Scholes and Binomial Tree give very similar results (₹1,519 vs ₹1,516), confirming accuracy.

🏋️ Exercise 3: Option to Expand — Real Estate (20 min)

Objective: Value the option to expand a real estate development. A builder constructs Phase 1 (₹200 Cr) with the option to add Phase 2 in 3 years (additional ₹350 Cr).

Data: Phase 1 value = ₹180 Cr (negative NPV alone), Phase 2 value if expanded = 2× Phase 1, Volatility = 30%, r = 7%.

Python — Real Estate Expansion Option
rov = RealOptionsValuer(S=180, K=350, T=3, sigma=0.30, r=0.07)

# Option to expand is like a call on the additional value
# S = PV of Phase 2 cash flows (estimated at ₹180 Cr based on Phase 1)
# K = Phase 2 construction cost (₹350 Cr)
bs = rov.black_scholes(option_type='call', dividend_yield=0.02)

print(f"\nPhase 1 NPV (standalone): ₹{180 - 200} Cr (negative)")
print(f"Option to expand Phase 2: ₹{bs['value']:,.0f} Cr")
print(f"Expanded NPV (Phase 1 + Option): ₹{-20 + bs['value']:,.0f} Cr")

if -20 + bs['value'] > 0:
    print(f"\n✅ BUILD Phase 1! The expansion option compensates for negative Phase 1 NPV.")
else:
    print(f"\n❌ Even with expansion option, project doesn't create value.")
Phase 1 NPV (standalone): ₹-20 Cr (negative) Option to expand Phase 2: ₹14 Cr Expanded NPV (Phase 1 + Option): ₹-6 Cr ❌ Even with expansion option, project doesn't create value.
Analysis

The expansion option adds ₹14 Cr but it's not enough to overcome the −₹20 Cr Phase 1 NPV. The builder should either negotiate lower Phase 1 costs, or find a location with higher base demand to make Phase 1 standalone viable.

🏋️ Exercise 4 (Advanced): Option to Switch — Power Plant (20 min)

Objective: Value a power plant's flexibility to switch between gas and coal. The plant can generate electricity using either fuel. Model the switching option as a call option on the price differential.

Data: Gas-fired output value = ₹800 Cr, Coal-fired output value = ₹750 Cr, Switching cost = ₹100 Cr, Time window = 5 years, Volatility of price spread = 25%, r = 7%.

Hint: The option to switch = call option on max(gas_value, coal_value) with a switching cost. Approximate using spread volatility.

Python — Switching Option
# Option to Switch = Option to choose the higher-value fuel
# If currently gas-based (₹800 Cr), option to switch to coal if coal becomes cheaper
# Approximate as: value of flexibility to capture upside in fuel price differential

S = 800     # Current best output value (gas-based)
K = 750     # Alternative value (coal-based) + switching cost premium
T = 5
sigma = 0.25
r = 0.07
switching_cost = 100

# The option is modeled as the value of flexibility
# S = current advantage of gas over coal, K = switching cost
spread_S = 800 - 750  # Current advantage = ₹50 Cr
rov = RealOptionsValuer(S=spread_S, K=0, T=T, sigma=sigma, r=r)

# Use BS to value the spread option (simplified)
d1 = (np.log(spread_S / 0.01) + (r + sigma**2/2)*T) / (sigma*np.sqrt(T))
d2 = d1 - sigma*np.sqrt(T)
# Value of maintaining both options
import numpy as np
from scipy.stats import norm
flexibility_value = spread_S * norm.cdf(d1) - 0.01 * np.exp(-r*T) * norm.cdf(d2)

print(f"Current fuel cost advantage: ₹{spread_S} Cr")
print(f"Switching flexibility value: ₹{flexibility_value:.0f} Cr")
print(f"Net value after switching cost: ₹{max(flexibility_value - switching_cost, 0):.0f} Cr")

# More practical: value the plant WITH switching vs WITHOUT
plant_value_no_switch = max(800, 750)  # Pick best fuel today, commit forever
plant_value_with_switch = plant_value_no_switch + flexibility_value
print(f"\nPlant value (no switch):  ₹{plant_value_no_switch} Cr")
print(f"Plant value (w/ switch):  ₹{plant_value_with_switch:.0f} Cr")
print(f"Switching option value:   ₹{flexibility_value:.0f} Cr")
Quick Review

📚 Key Terms — Click to Flip

Knowledge Check

Test Your Understanding

10 questions on Real Options Valuation

Summary

Key Takeaways

📝 What We Covered Today

  • Real options fundamentals — NPV ignores flexibility; real options value the right (not obligation) to make future decisions: delay, expand, abandon, switch, or stage
  • Five types of real options — Delay (call), Expand (call), Abandon (put), Switch (straddle), Stage (compound option) — each maps to financial option equivalents
  • Binomial tree method — discrete, intuitive, handles American-style exercise; uses backward induction with risk-neutral probabilities
  • Black-Scholes method — fast closed-form formula for European-style options; ideal for patents, licenses, and quick estimates
  • Expanded NPV = NPV + Option Value — projects with negative NPV may still be viable when option value is included
  • Uncertainty creates value in real options — higher volatility increases option value (opposite of traditional DCF)
  • Reusable RealOptionsValuer template — copy-paste Python class with Black-Scholes, binomial tree, and sensitivity analysis
📚Next Session

Session 24: Model Auditing & Error-Proofing
We'll learn how to audit, validate, and error-proof financial models — common mistakes, audit tools, consistency checks, and documentation standards.