Sitemap

Critical Correction to VaR Calculations for Complex Option Strategies

5 min readMar 20, 2025

--

Press enter or click to view image in full size

Abstract

This brief technical note addresses a significant flaw in our Value-at-Risk (VaR) implementation for complex option strategies. The correction ensures proper valuation of strategies involving both options and underlying assets, with critical implications for risk management for protective puts, covered calls, and spread strategies.

The Mathematical Error

My previous VaR calculation implementation contained a systematic error in handling complex option strategies, particularly those involving positions in both derivatives and their underlying assets.

The core issue manifested in the portfolio valuation logic:

# My previous implementation (simplified)
for i in range(n_positions):
if 'Call' in strategies[i]:
option_price = black_scholes_calc(sim_price, strikes[i],
remaining_maturity, rates[i],
vols[i], 'call')
else:
option_price = black_scholes_calc(sim_price, strikes[i],
remaining_maturity, rates[i],
vols[i], 'put')

portfolio_value += quantities[i] * option_price

This approach incorrectly valued complex strategies by applying a simplistic binary logic — treating each position as either a call or put option, while ignoring the underlying asset components.

For protective puts (long stock + long put) and covered calls (long stock + short call), this resulted in significant VaR underestimation by failing to incorporate the underlying asset’s price sensitivity.

Mathematical Implication

For a protective put portfolio with value $V$:

# Correct valuation for protective put
V_correct = S + P(S, K, t, r, sigma) # Stock price + Put option value

# Previous incorrect implementation
V_incorrect = P(S, K, t, r, sigma) # Only Put option value

Where $S$ is the underlying asset price and $P$ is the put option value.

Get Navnoor Bawa’s stories in your inbox

Join Medium for free to get updates from this writer.

I systematically understated risk, particularly during large market moves, as the delta exposure from the underlying asset (typically around 1.0) was entirely omitted.

The Correction

My enhanced implementation properly handles complex strategies by implementing strategy-specific valuation logic:

# For Protective Put: Current value = Stock value + Put value
if strategy == "Protective Put":
stock_value = spot_price
put_value = black_scholes_calc(spot_price, strikes[i],
maturities[i], rates[i],
vols[i], 'put')
position_value = stock_value + put_value
elif strategy == "Covered Call Writing":
stock_value = spot_price
call_value = black_scholes_calc(spot_price, strikes[i],
maturities[i], rates[i],
vols[i], 'call')
position_value = stock_value - call_value
elif "Bull Call Spread" in strategy:
# Long lower strike call, short higher strike call
lower_strike = strikes[i] * 0.9 # Simplified for example
upper_strike = strikes[i] * 1.1 # Simplified for example
long_call = black_scholes_calc(spot_price, lower_strike, maturities[i], rates[i], vols[i], 'call')
short_call = black_scholes_calc(spot_price, upper_strike, maturities[i], rates[i], vols[i], 'call')
position_value = long_call - short_call
elif "Long Straddle" in strategy:
# Long call and long put
call_value = black_scholes_calc(spot_price, strikes[i], maturities[i], rates[i], vols[i], 'call')
put_value = black_scholes_calc(spot_price, strikes[i], maturities[i], rates[i], vols[i], 'put')
position_value = call_value + put_value
# Additional strategy-specific handlers...

This correction properly accounts for the risk exposure from both components of complex strategies.

Quantitative Impact

Press enter or click to view image in full size

The correction produces materially different VaR estimates for key strategies:

Press enter or click to view image in full size

Based on S&P 500 price = $5,675, 30-day ATM options, 1-day horizon, 15% volatility

# VaR calculation example for a protective put (simplified)
def calculate_protective_put_var(S=5675, K=5675, T=30/365, r=0.05, sigma=0.15,
confidence=0.95, horizon=1/252, n_sim=10000):
# 1. Calculate current value (stock + put)
put_value = black_scholes_calc(S, K, T, r, sigma, 'put')
current_value = S + put_value

# 2. Simulate price paths
price_changes = np.random.normal((r - 0.5 * sigma**2) * horizon,
sigma * np.sqrt(horizon), n_sim)
simulated_prices = S * np.exp(price_changes)

# 3. Calculate portfolio values at horizon
portfolio_values = np.zeros(n_sim)
for i in range(n_sim):
remaining_T = max(0, T - horizon)
future_put = black_scholes_calc(simulated_prices[i], K, remaining_T, r, sigma, 'put')
portfolio_values[i] = simulated_prices[i] + future_put

# 4. Calculate P&L distribution
pnl = portfolio_values - current_value

# 5. Calculate VaR
var_index = int(n_sim * (1 - confidence))
sorted_pnl = np.sort(pnl)
var = -sorted_pnl[var_index]

return var
Press enter or click to view image in full size

Implications for Risk Management

This correction is particularly critical for:

  1. Long-Short Strategies: Properly accounts for net delta exposure
  2. Portfolio Hedging: Correctly calculates hedge effectiveness
  3. Stress Testing: Produces realistic results under extreme scenarios
  4. Capital Allocation: Proper risk budgeting across strategy types

Broader Applications

Beyond the immediate correction, this implementation identifies a general principle for VaR calculations: strategy-specific valuation logic is essential for accurate risk measurement in option portfolios.

The solution extends naturally to other complex strategies, including:

# Bull Call Spread implementation
def bull_call_spread_value(S, K_low, K_high, T, r, sigma):
long_call = black_scholes_calc(S, K_low, T, r, sigma, 'call')
short_call = black_scholes_calc(S, K_high, T, r, sigma, 'call')
return long_call - short_call

# Iron Condor implementation
def iron_condor_value(S, K_put_low, K_put_high, K_call_low, K_call_high, T, r, sigma):
# Bull put spread
short_put = black_scholes_calc(S, K_put_high, T, r, sigma, 'put')
long_put = black_scholes_calc(S, K_put_low, T, r, sigma, 'put')
# Bear call spread
short_call = black_scholes_calc(S, K_call_low, T, r, sigma, 'call')
long_call = black_scholes_calc(S, K_call_high, T, r, sigma, 'call')
return (short_put - long_put) + (short_call - long_call)

Each strategy requires specific treatment rather than generic option pricing.

Implementation

The complete correction has been integrated into my options pricing platform with comprehensive testing against analytical approximations for simple cases.

def var_calculator(strategies, quantities, spot_price, strikes, maturities, rates, vols,
confidence=0.95, horizon=1/252, n_simulations=10000,
use_t_dist=True, degrees_of_freedom=5, use_garch=False):
"""
Calculate Value-at-Risk for an options portfolio with proper strategy handling
"""
# Calculate current portfolio value with strategy-specific valuation
current_portfolio_value = 0
for i in range(len(strategies)):
strategy = strategies[i]

if strategy == "Protective Put":
# Stock + Put
stock_value = spot_price
put_value = black_scholes_calc(spot_price, strikes[i], maturities[i], rates[i], vols[i], 'put')
position_value = stock_value + put_value
elif strategy == "Covered Call Writing":
# Stock - Call
stock_value = spot_price
call_value = black_scholes_calc(spot_price, strikes[i], maturities[i], rates[i], vols[i], 'call')
position_value = stock_value - call_value
elif 'Call' in strategy:
position_value = black_scholes_calc(spot_price, strikes[i], maturities[i], rates[i], vols[i], 'call')
else: # Put options
position_value = black_scholes_calc(spot_price, strikes[i], maturities[i], rates[i], vols[i], 'put')

current_portfolio_value += quantities[i] * position_value

# Generate price paths
if use_t_dist:
# Student's t-distribution for fat tails
t_random = np.random.standard_t(df=degrees_of_freedom, size=n_simulations)
price_changes = (rates.mean() - 0.5 * volatility**2) * horizon + \
volatility * np.sqrt(horizon) * t_random * np.sqrt((degrees_of_freedom-2)/degrees_of_freedom)
else:
# Normal distribution
price_changes = np.random.normal((rates.mean() - 0.5 * volatility**2) * horizon,
volatility * np.sqrt(horizon), n_simulations)

simulated_prices = spot_price * np.exp(price_changes)

# Calculate P&L distribution for VaR estimation
# Full implementation on my GitHub

Conclusion

This correction addresses a fundamental flaw in VaR calculations for complex option strategies. My enhanced implementation properly accounts for the full risk exposure across all strategy components, providing significantly more accurate risk metrics for quantitative portfolio management.

Implementation Access
GitHub: github.com/NavnoorBawa/options-pricing
Web Application: comprehensive-option-pricing-and-strategy-analyzer.streamlit.app

I’m open to suggestions! Kindly DM me on LinkedIn if you want any other features added or if you notice any mistakes that need correction.

--

--

Navnoor Bawa
Navnoor Bawa

Written by Navnoor Bawa

Quantitative Researcher. How hedge funds actually make money, worked out from 13Fs and CFTC filings. Research lands first at navnoorbawa.substack.com