European Put¶
Background¶
Black Scholes Cn Euro Put
Educational script demonstrating black scholes cn euro put concepts.
What This Code Demonstrates¶
- Parameters ===
Code¶
```python """ Black Scholes Cn Euro Put
Educational script demonstrating black scholes cn euro put concepts. """
============================================================================¶
black_scholes_RUN_CN_SCHEME_FOR_EUROPEAN_PUT.py¶
============================================================================¶
import black_scholes as bs import matplotlib.pyplot as plt import numpy as np
=== Parameters ===¶
if name == "main": S0 = 100 K = 100 T = 1.0 r = 0.05 sigma = 0.2 q = 0 S_min = 0 S_min_log = 1e-3 # For log-space FD S_max = 300 # S_max should be bigger than your S_max of interest if use log space M = 100 # Grid points → NS = NX = M + 1 option_type = "put"
print(f"\n{'='*70}")
print("NUMERICAL METHODS COMPARISON")
print("="*70)
print(f"Option Type: {option_type.upper()}")
print(f"Stock Price (S0): ${S0}")
print(f"Strike Price (K): ${K}")
print(f"Time to Maturity: {T} year")
print(f"Risk-free Rate: {r:.1%}")
print(f"Volatility: {sigma:.1%}")
print(f"Dividend Yield: {q:.1%}")
print(f"Grid Points: {M+1}")
print(f"Stock Price Range: ${S_min} - ${S_max}")
print(f"Log-space Min: ${S_min_log}")
# === Instantiate Black-Scholes model using wrapper ===
bs_model = bs.BlackScholes(S0, K, T, r, sigma, q)
print(f"\nCalculating option prices using different methods...")
# === Run Crank-Nicolson FDM in Original Space ===
print(" Running CN FDM in original space...")
S_orig, V_orig = bs_model.price_numerical(
method="cn",
option_type=option_type,
Smin=S_min,
Smax=S_max,
NS=M+1
)
# === Run Crank-Nicolson FDM in Log-Price Space ===
print(" Running CN FDM in log-price space...")
S_log, V_log = bs_model.price_numerical(
method="cn_log",
option_type=option_type,
Smin=S_min_log,
Smax=S_max,
NX=M+1
)
# === Analytical Black-Scholes Price (Vectorized) ===
print(" Calculating analytical Black-Scholes prices...")
S_all = np.union1d(S_orig, S_log)
S_all.sort()
S_all_safe = np.maximum(S_all, 1e-10) # Avoid log(0)
# Use vectorized utility function for efficiency
if option_type == "call":
V_exact_all = bs.bs_call_price(S_all_safe, K, T, r, sigma, q)
else:
V_exact_all = bs.bs_put_price(S_all_safe, K, T, r, sigma, q)
# === Plot Comparison ===
print(" Generating comparison plot...")
fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(S_orig, V_orig, label='CN FDM (Original Space)', linewidth=8, alpha=0.3, color='blue')
ax.plot(S_log, V_log, label='CN FDM (Log Space)', linewidth=4, alpha=0.8, color='green')
ax.plot(S_all, V_exact_all, 'r--', label='Black-Scholes Analytical', linewidth=2)
# Add reference lines
ax.axvline(x=K, color='gray', linestyle=':', alpha=0.7, label=f'Strike = ${K}')
ax.axvline(x=S0, color='orange', linestyle=':', alpha=0.7, label=f'Current Price = ${S0}')
# Calculate intrinsic value for reference
if option_type == "call":
intrinsic = np.maximum(S_all - K, 0)
else:
intrinsic = np.maximum(K - S_all, 0)
ax.plot(S_all, intrinsic, 'k:', alpha=0.5, label='Intrinsic Value')
ax.set_xlabel('Stock Price ($)', fontsize=12)
ax.set_ylabel('Option Value ($)', fontsize=12)
ax.set_title(f'European {option_type.capitalize()} Option: CN FDM Comparison\n' +
f'Original Space vs Log-Space vs Analytical', fontsize=14)
ax.grid(True, alpha=0.3)
ax.legend()
# Clean up plot appearance
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
plt.tight_layout()
plt.show()
# === Compute Max Errors ===
print(f"\nError Analysis:")
# Get exact values at grid points
if option_type == "call":
V_exact_orig = bs.bs_call_price(np.maximum(S_orig, 1e-10), K, T, r, sigma, q)
V_exact_log = bs.bs_call_price(np.maximum(S_log, 1e-10), K, T, r, sigma, q)
else:
V_exact_orig = bs.bs_put_price(np.maximum(S_orig, 1e-10), K, T, r, sigma, q)
V_exact_log = bs.bs_put_price(np.maximum(S_log, 1e-10), K, T, r, sigma, q)
error_orig = np.max(np.abs(V_orig - V_exact_orig))
error_log = np.max(np.abs(V_log - V_exact_log))
print(f" Max absolute error (original space): ${error_orig:.6f}")
print(f" Max absolute error (log space): ${error_log:.6f}")
# Relative errors
rel_error_orig = error_orig / np.mean(V_exact_orig) * 100
rel_error_log = error_log / np.mean(V_exact_log) * 100
print(f" Max relative error (original space): {rel_error_orig:.4f}%")
print(f" Max relative error (log space): {rel_error_log:.4f}%")
# Determine which method is more accurate
if error_log < error_orig:
print(f" ✓ Log-space method is more accurate by a factor of {error_orig/error_log:.2f}")
else:
print(f" ✓ Original-space method is more accurate by a factor of {error_log/error_orig:.2f}")
# === Price at Current Stock Price ===
print(f"\nPrices at Current Stock Price (S = ${S0}):")
# Analytical price
analytical_call, analytical_put = bs_model.price_analytical()
analytical_price = analytical_call if option_type == "call" else analytical_put
# Find closest grid points
idx_orig = np.argmin(np.abs(S_orig - S0))
idx_log = np.argmin(np.abs(S_log - S0))
price_orig = V_orig[idx_orig]
price_log = V_log[idx_log]
print(f" Analytical Price: ${analytical_price:.6f}")
print(f" CN Original Space: ${price_orig:.6f} (error: ${abs(price_orig - analytical_price):.6f})")
print(f" CN Log Space: ${price_log:.6f} (error: ${abs(price_log - analytical_price):.6f})")
# === Demonstrate other wrapper features ===
print(f"\n{'='*70}")
print("ADDITIONAL WRAPPER FEATURES")
print("="*70)
# Monte Carlo comparison
print(f"Comparing with Monte Carlo simulation...")
mc_results = bs_model.price_monte_carlo(num_paths=100000, plot_histogram=False)
mc_price = mc_results[0] if option_type == "call" else mc_results[1]
mc_std = mc_results[2] if option_type == "call" else mc_results[3]
print(f" Monte Carlo Price: ${mc_price:.6f} ± ${mc_std/np.sqrt(100000):.6f}")
# Method comparison using wrapper
comparison = bs_model.compare_methods(option_type=option_type, numerical_method='cn')
print(f"\nComprehensive Method Comparison:")
print(f" Analytical: ${comparison['analytical']:.6f}")
print(f" Monte Carlo: ${comparison['monte_carlo']['price']:.6f}")
print(f" Numerical (CN): ${comparison['numerical']:.6f}")
print(f"\nMethod Differences:")
print(f" MC vs Analytical: ${comparison['differences']['mc_vs_analytical']:.6f}")
print(f" Numerical vs Analytical: ${comparison['differences']['numerical_vs_analytical']:.6f}")
# Greeks for context
greeks = bs_model.calculate_greeks()
print(f"\nOption Greeks (for reference):")
if option_type == "call":
print(f" Delta: {greeks['delta_call']:.4f}")
else:
print(f" Delta: {greeks['delta_put']:.4f}")
print(f" Gamma: {greeks['gamma']:.6f}")
print(f" Vega: {greeks['vega']:.4f}")
print(f"\n{'='*70}")
print("SUMMARY")
print("="*70)
print(f"✅ Both numerical methods successfully price the European {option_type}")
print(f"✅ Log-space typically handles extreme stock prices better")
print(f"✅ Original-space may be more intuitive for grid setup")
print(f"✅ Analytical price provides excellent benchmark")
print(f"✅ Monte Carlo provides independent verification")
print(f"✅ European options: Numerical should match analytical closely")
print(f"✅ Put options: More sensitive at low stock prices (S→0)")
print("="*70)
```
Exercises¶
Exercise 1. Write the boundary conditions for a European put solved by Crank-Nicolson.
Solution to Exercise 1
\(V(0,t) = Ke^{-r(T-t)}\) (deep OTM: present value of strike). \(V(S_{\max}, t) = 0\) (worthless for large \(S\)). \(V(S,T) = \max(K - S, 0)\) (payoff).
Exercise 2. Verify put-call parity using the CN solutions for the European call and put on the same grid.
Solution to Exercise 2
At each grid point: \(C_i - P_i \approx S_i - Ke^{-r(T-t_j)}\). The deviation from exact parity measures the numerical error of the CN scheme. Typical deviations are \(O(\Delta S^2 + \Delta t^2)\), providing a useful internal consistency check.
Exercise 3. How does the put price behave as \(S \to 0\) and \(S \to \infty\)?
Solution to Exercise 3
As \(S \to 0\): \(P \to Ke^{-r(T-t)}\) (the put is deep ITM and will almost certainly be exercised). As \(S \to \infty\): \(P \to 0\) (the put is deep OTM). The put price is a decreasing, convex function of \(S\).
Exercise 4. Compare the CN solution for the European put with the analytical BS formula at \(S_0 = 100\), \(K = 100\), \(T = 1\), \(r = 0.05\), \(\sigma = 0.20\).
Solution to Exercise 4
The BS put price is approximately $5.57. The CN solution should agree to within \(O(10^{-3})\) with standard grid parameters (\(N_S = 100\), \(N_t = 1000\)). The error is largest near \(S = K\) due to the payoff kink and smallest in the tails where the solution is smooth.