What You'll Learn Today
Real Options Fundamentals
Why NPV is not enough — valuing managerial flexibility
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.
Expanded NPV (ENPV) = Traditional NPV + Value of Real OptionsIf ENPV > 0 → Invest (even if traditional NPV < 0!)
| Aspect | 📊 Traditional NPV | 🔀 Real Options (ENPV) |
|---|---|---|
| Decision | Invest now or never | Invest now, later, expand, abandon, or never |
| Uncertainty | Risk → discount at higher WACC | Uncertainty creates VALUE (more flexibility) |
| Management | Passive — no decisions after investment | Active — managers adapt as information arrives |
| Irreversibility | Assumed fully irreversible | Recognizes partial reversibility (abandonment, salvage) |
| Output | Single NPV number | NPV + option value = range of strategic values |
| Best For | Stable cash flows, low flexibility | R&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 Parameter | Stock Option Example | Real Option Equivalent | TechCorp Example |
|---|---|---|---|
| S (Underlying Asset Price) | Current stock price | PV of project's future cash flows | ₹400 Cr (PV of expected revenues) |
| K (Strike Price) | Exercise price of option | Investment cost / capital expenditure | ₹500 Cr (capex to expand) |
| T (Time to Expiry) | Option expiration date | Decision deadline / license period | 2 years |
| σ (Volatility) | Stock price volatility | Volatility of project value | 35% per year |
| r (Risk-Free Rate) | Treasury bond yield | Risk-free rate (government bond) | 7% per year |
| Dividend Yield (q) | Dividends paid by stock | Cash flows lost by waiting (delay cost) | 5% per year |
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.
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.
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 probabilityAt 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
| Step | Action | What Happens |
|---|---|---|
| 1 | Build the event tree | Start with S₀, multiply by u/d at each step → creates all possible future values of the project |
| 2 | Calculate terminal payoffs | At the final nodes: compute option payoff (e.g., max(S − K, 0) for a call option to expand) |
| 3 | Backward induction | Work backward: at each node, compute continuation value using risk-neutral probability, then take max(exercise, continuation) |
| 4 | Read the answer | The 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?
| Parameter | Value | Explanation |
|---|---|---|
| Current Project Value (S) | ₹400 Cr | PV of small facility's future cash flows |
| Expansion Cost (K) | ₹500 Cr | Additional investment to double capacity |
| Time to Decision (T) | 2 years | Option expires in 2 years |
| Volatility (σ) | 35% | Uncertainty in project value |
| Risk-Free Rate (r) | 7% | Indian 10-year government bond yield |
| Steps (n) | 2 | 2-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.
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.")
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?
| Parameter | Value |
|---|---|
| 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 |
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.")
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.
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.
C = S × e^(−qT) × N(d₁) − K × e^(−rT) × N(d₂)d₁ = [ln(S/K) + (r − q + σ²/2) × T] / (σ × √T)d₂ = d₁ − σ × √TWhere N(x) = cumulative standard normal distributionq = "dividend yield" = annual cash flows lost by waiting
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?
| Parameter | Financial Option | Real Option (Patent) | Value |
|---|---|---|---|
| S (Asset Value) | Stock Price | PV of drug's future cash flows | ₹2,000 Cr |
| K (Strike) | Exercise Price | Development / launch cost | ₹1,500 Cr |
| T (Expiry) | Option Expiration | Patent remaining life | 5 years |
| σ (Volatility) | Stock Volatility | Drug value volatility (clinical uncertainty) | 45% |
| r (Risk-Free Rate) | Treasury Rate | Government bond yield | 7% |
| q (Dividend Yield) | Dividend | Cash flows lost by delaying (competitor risk) | 4% |
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")
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 Style | American (any time) or European | European (at expiry only) |
| Flexibility | Can model early exercise, changing inputs | Fixed parameters, no early exercise |
| Accuracy | Converges to BS as steps → ∞ | Exact for European options |
| Computation | Slower (many nodes) | Instant (closed-form formula) |
| Visual | Decision tree is intuitive | No visual intuition |
| Best For | American options, staged investments, complex real options | Patents, licenses, simple delay options, quick estimates |
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.
📦 The RealOptionsValuer Class
"""
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))
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).
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%.
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}")
🏋️ 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%.
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")
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%.
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.")
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.
# 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")
📚 Key Terms — Click to Flip
Test Your Understanding
10 questions on Real Options Valuation
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
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.