Skip to content

Commit 9817a86

Browse files
committed
Testing LSM against binomial tree
1 parent 97a0986 commit 9817a86

File tree

3 files changed

+205
-31
lines changed

3 files changed

+205
-31
lines changed

test/agreement/american_options.jl

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
using Test
2+
using Hedgehog
3+
using Dates
4+
using Random
5+
using Statistics
6+
7+
@testset "LSM vs Binomial Tree Agreement for American Options" begin
8+
9+
@testset "American Put Option Agreement" begin
10+
# Setup common parameters
11+
strike = 100.0
12+
reference_date = Date(2020, 1, 1)
13+
expiry = reference_date + Year(1)
14+
rate = 0.05
15+
spot = 100.0
16+
sigma = 0.2
17+
18+
# Create American put option
19+
american_put = VanillaOption(strike, expiry, American(), Put(), Spot())
20+
market_inputs = BlackScholesInputs(reference_date, rate, spot, sigma)
21+
prob = PricingProblem(american_put, market_inputs)
22+
23+
# Binomial tree method (high number of steps for accuracy)
24+
crr_steps = 1000
25+
crr_method = CoxRossRubinsteinMethod(crr_steps)
26+
crr_solution = solve(prob, crr_method)
27+
28+
# LSM method with fixed seeds for reproducibility
29+
trajectories = 50_000
30+
steps = 100
31+
rng = Xoshiro(12345)
32+
seeds = rand(rng, UInt64, trajectories)
33+
34+
dynamics = LognormalDynamics()
35+
strategy = BlackScholesExact()
36+
config = SimulationConfig(trajectories; steps=steps, seeds=seeds, variance_reduction=Antithetic())
37+
degree = 5
38+
lsm_method = LSM(dynamics, strategy, config, degree)
39+
40+
lsm_solution = solve(prob, lsm_method)
41+
42+
# Test agreement with reasonable tolerance
43+
# LSM has Monte Carlo error, so we allow larger tolerance
44+
relative_error = abs(lsm_solution.price - crr_solution.price) / crr_solution.price
45+
46+
println("American Put Pricing Comparison:")
47+
println(" Binomial Tree ($(crr_steps) steps): $(crr_solution.price)")
48+
println(" LSM ($(trajectories) paths): $(lsm_solution.price)")
49+
println(" Relative Error: $(relative_error * 100)%")
50+
51+
@test isapprox(lsm_solution.price, crr_solution.price; rtol=0.02)
52+
end
53+
54+
@testset "American Call Option Agreement (High Dividend Yield)" begin
55+
# For American calls to have early exercise value, we need high dividend yield
56+
# We simulate this by using a high rate scenario
57+
strike = 100.0
58+
reference_date = Date(2020, 1, 1)
59+
expiry = reference_date + Year(1)
60+
rate = 0.15 # High rate to make early exercise attractive
61+
spot = 120.0 # ITM call
62+
sigma = 0.3
63+
64+
# Create American call option
65+
american_call = VanillaOption(strike, expiry, American(), Call(), Spot())
66+
market_inputs = BlackScholesInputs(reference_date, rate, spot, sigma)
67+
prob = PricingProblem(american_call, market_inputs)
68+
69+
# Binomial tree method
70+
crr_steps = 800
71+
crr_method = CoxRossRubinsteinMethod(crr_steps)
72+
crr_solution = solve(prob, crr_method)
73+
74+
# LSM method
75+
trajectories = 30_000
76+
steps = 100
77+
rng = Xoshiro(54321)
78+
seeds = rand(rng, UInt64, trajectories)
79+
80+
dynamics = LognormalDynamics()
81+
strategy = BlackScholesExact()
82+
config = SimulationConfig(trajectories; steps=steps, seeds=seeds, variance_reduction=Antithetic())
83+
degree = 5
84+
lsm_method = LSM(dynamics, strategy, config, degree)
85+
86+
lsm_solution = solve(prob, lsm_method)
87+
88+
relative_error = abs(lsm_solution.price - crr_solution.price) / crr_solution.price
89+
90+
println("\nAmerican Call Pricing Comparison (High Rate Scenario):")
91+
println(" Binomial Tree ($(crr_steps) steps): $(crr_solution.price)")
92+
println(" LSM ($(trajectories) paths): $(lsm_solution.price)")
93+
println(" Relative Error: $(relative_error * 100)%")
94+
95+
@test isapprox(lsm_solution.price, crr_solution.price; rtol=0.03)
96+
end
97+
98+
@testset "Multiple Strike Agreement Test" begin
99+
# Test agreement across different moneyness levels
100+
reference_date = Date(2020, 1, 1)
101+
expiry = reference_date + Month(6) # Shorter maturity
102+
rate = 0.05
103+
spot = 100.0
104+
sigma = 0.25
105+
106+
strikes = [80.0, 90.0, 100.0, 110.0, 120.0] # OTM to ITM puts
107+
108+
println("\nMultiple Strike Agreement Test (American Puts, 6M maturity):")
109+
println("Strike | Binomial | LSM | Rel Error")
110+
println("-------|----------|----------|----------")
111+
112+
for strike in strikes
113+
# Create American put option
114+
american_put = VanillaOption(strike, expiry, American(), Put(), Spot())
115+
market_inputs = BlackScholesInputs(reference_date, rate, spot, sigma)
116+
prob = PricingProblem(american_put, market_inputs)
117+
118+
# Binomial tree method
119+
crr_steps = 500
120+
crr_method = CoxRossRubinsteinMethod(crr_steps)
121+
crr_solution = solve(prob, crr_method)
122+
123+
# LSM method (smaller number of paths for speed)
124+
trajectories = 20_000
125+
steps = 50
126+
rng = Xoshiro(Int(strike) * 1000) # Different seed per strike
127+
seeds = rand(rng, UInt64, trajectories)
128+
129+
dynamics = LognormalDynamics()
130+
strategy = BlackScholesExact()
131+
config = SimulationConfig(trajectories; steps=steps, seeds=seeds, variance_reduction=Antithetic())
132+
degree = 4
133+
lsm_method = LSM(dynamics, strategy, config, degree)
134+
135+
lsm_solution = solve(prob, lsm_method)
136+
137+
relative_error = abs(lsm_solution.price - crr_solution.price) / crr_solution.price
138+
139+
@printf("%6.1f | %8.4f | %8.4f | %7.2f%%\n",
140+
strike, crr_solution.price, lsm_solution.price, relative_error * 100)
141+
142+
# More lenient tolerance for OTM options (lower absolute prices)
143+
tolerance = strike < spot ? 0.05 : 0.03
144+
@test isapprox(lsm_solution.price, crr_solution.price; rtol=tolerance)
145+
end
146+
end
147+
148+
@testset "Early Exercise Premium Consistency" begin
149+
# Compare early exercise premium: American - European
150+
strike = 110.0
151+
reference_date = Date(2020, 1, 1)
152+
expiry = reference_date + Year(1)
153+
rate = 0.03
154+
spot = 100.0
155+
sigma = 0.3
156+
157+
# American put
158+
american_put = VanillaOption(strike, expiry, American(), Put(), Spot())
159+
market_inputs = BlackScholesInputs(reference_date, rate, spot, sigma)
160+
american_prob = PricingProblem(american_put, market_inputs)
161+
162+
# European put
163+
european_put = VanillaOption(strike, expiry, European(), Put(), Spot())
164+
european_prob = PricingProblem(european_put, market_inputs)
165+
166+
# Prices using both methods
167+
crr_method = CoxRossRubinsteinMethod(800)
168+
bs_method = BlackScholesAnalytic()
169+
170+
american_crr = solve(american_prob, crr_method)
171+
european_bs = solve(european_prob, bs_method)
172+
early_exercise_premium_crr = american_crr.price - european_bs.price
173+
174+
# LSM for American
175+
trajectories = 40_000
176+
steps = 100
177+
rng = Xoshiro(98765)
178+
seeds = rand(rng, UInt64, trajectories)
179+
180+
dynamics = LognormalDynamics()
181+
strategy = BlackScholesExact()
182+
config = SimulationConfig(trajectories; steps=steps, seeds=seeds, variance_reduction=Antithetic())
183+
degree = 5
184+
lsm_method = LSM(dynamics, strategy, config, degree)
185+
186+
american_lsm = solve(american_prob, lsm_method)
187+
early_exercise_premium_lsm = american_lsm.price - european_bs.price
188+
189+
println("\nEarly Exercise Premium Consistency:")
190+
println(" European Put (BS): $(european_bs.price)")
191+
println(" American Put (Binomial): $(american_crr.price)")
192+
println(" American Put (LSM): $(american_lsm.price)")
193+
println(" Early Ex Premium (Binomial): $(early_exercise_premium_crr)")
194+
println(" Early Ex Premium (LSM): $(early_exercise_premium_lsm)")
195+
196+
# Both methods should agree that American >= European
197+
@test american_crr.price >= european_bs.price
198+
@test american_lsm.price >= european_bs.price
199+
200+
# Early exercise premiums should be similar
201+
@test isapprox(early_exercise_premium_lsm, early_exercise_premium_crr; rtol=0.04)
202+
end
203+
end

test/runtests.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ include("unit/rate_curve.jl")
77
include("unit/black_scholes.jl")
88
include("unit/calibration.jl")
99
include("unit/binomial_tree.jl")
10-
include("unit/least_squares_montecarlo.jl")
1110
include("unit/vol_surface.jl")
1211

1312
include("agreement/price_agreement.jl")
1413
include("agreement/greeks_agreement.jl")
1514
include("agreement/montecarlo_black_scholes.jl")
16-
include("agreement/montecarlo_heston.jl")
15+
include("agreement/montecarlo_heston.jl")
16+
include("agreement/american_options.jl")

test/unit/least_squares_montecarlo.jl

Lines changed: 0 additions & 29 deletions
This file was deleted.

0 commit comments

Comments
 (0)