1
+ using Hedgehog2
2
+ using Dates
3
+ using Random
4
+ using Statistics
5
+ using Test
6
+ using Printf
7
+
8
+ function analyze_path_correlation (paths, n_original)
9
+ # Calculate correlations between original and antithetic paths
10
+ correlations = Float64[]
11
+
12
+ for i in 1 : n_original
13
+ original_path = paths[i]
14
+ antithetic_path = paths[i + n_original]
15
+
16
+ # Get price values at each time step
17
+ if eltype (original_path. u) <: AbstractVector
18
+ original_prices = [u[1 ] for u in original_path. u]
19
+ antithetic_prices = [u[1 ] for u in antithetic_path. u]
20
+ else
21
+ original_prices = original_path. u
22
+ antithetic_prices = antithetic_path. u
23
+ end
24
+
25
+ # Calculate correlation
26
+ path_correlation = cor (original_prices, antithetic_prices)
27
+ push! (correlations, path_correlation)
28
+ end
29
+
30
+ return correlations
31
+ end
32
+
33
+ function calculate_return_correlation (paths, n_original)
34
+ # Calculate correlations between log returns of original and antithetic paths
35
+ return_correlations = Float64[]
36
+
37
+ for i in 1 : n_original
38
+ original_path = paths[i]
39
+ antithetic_path = paths[i + n_original]
40
+
41
+ # Get price values at each time step
42
+ if eltype (original_path. u) <: AbstractVector
43
+ original_prices = [u[1 ] for u in original_path. u]
44
+ antithetic_prices = [u[1 ] for u in antithetic_path. u]
45
+ else
46
+ original_prices = original_path. u
47
+ antithetic_prices = antithetic_path. u
48
+ end
49
+
50
+ # Calculate log returns
51
+ original_returns = diff (log .(original_prices))
52
+ antithetic_returns = diff (log .(antithetic_prices))
53
+
54
+ # Skip if either array has issues
55
+ if any (isnan .(original_returns)) || any (isnan .(antithetic_returns)) ||
56
+ any (isinf .(original_returns)) || any (isinf .(antithetic_returns))
57
+ continue
58
+ end
59
+
60
+ # Calculate correlation of returns
61
+ return_corr = cor (original_returns, antithetic_returns)
62
+ push! (return_correlations, return_corr)
63
+ end
64
+
65
+ return return_correlations
66
+ end
67
+
68
+ @testset " Antithetic Path Correlation Tests" begin
69
+ # Common parameters
70
+ Random. seed! (42 )
71
+ trajectories = 100
72
+ steps = 100
73
+ reference_date = Date (2020 , 1 , 1 )
74
+ expiry = reference_date + Year (1 )
75
+ spot = 100.0
76
+ strike = 100.0
77
+
78
+ # Create European call option payoff
79
+ payoff = VanillaOption (strike, expiry, European (), Call (), Spot ())
80
+
81
+ @testset " Black-Scholes Model" begin
82
+ # BS parameters
83
+ rate_bs = 0.05
84
+ sigma_bs = 0.20
85
+
86
+ # Create BS market inputs
87
+ bs_market = BlackScholesInputs (reference_date, rate_bs, spot, sigma_bs)
88
+ bs_prob = PricingProblem (payoff, bs_market)
89
+
90
+ # Create Monte Carlo with antithetic sampling for BS
91
+ bs_seeds = rand (1 : 10 ^ 9 , trajectories)
92
+ bs_strategy = BlackScholesExact (trajectories ÷ 2 , steps, seeds= bs_seeds[1 : trajectories÷ 2 ], antithetic= true )
93
+ bs_method = MonteCarlo (LognormalDynamics (), bs_strategy)
94
+
95
+ # Solve and get paths
96
+ bs_solution = solve (bs_prob, bs_method)
97
+ bs_paths = bs_solution. ensemble. solutions
98
+
99
+ # Analyze correlations
100
+ bs_correlations = analyze_path_correlation (bs_paths, trajectories ÷ 2 )
101
+ bs_return_correlations = calculate_return_correlation (bs_paths, trajectories ÷ 2 )
102
+
103
+ # Report statistics for diagnostic purposes
104
+ println (" Black-Scholes path correlations:" )
105
+ println (" Mean: " , mean (bs_correlations))
106
+ println (" Min: " , minimum (bs_correlations))
107
+ println (" Max: " , maximum (bs_correlations))
108
+
109
+ println (" \n Black-Scholes return correlations:" )
110
+ println (" Mean: " , mean (bs_return_correlations))
111
+ println (" Min: " , minimum (bs_return_correlations))
112
+ println (" Max: " , maximum (bs_return_correlations))
113
+
114
+ # Test that price paths show strong negative correlation
115
+ @test mean (bs_correlations) < - 0.7
116
+
117
+ # Test that log returns show near-perfect negative correlation
118
+ @test mean (bs_return_correlations) < - 0.95
119
+
120
+ # Test product of terminal values is consistent with theoretical value
121
+ bs_original = bs_paths[1 ]
122
+ bs_antithetic = bs_paths[trajectories ÷ 2 + 1 ]
123
+
124
+ if eltype (bs_original. u) <: AbstractVector
125
+ bs_orig_terminal = bs_original. u[end ][1 ]
126
+ bs_anti_terminal = bs_antithetic. u[end ][1 ]
127
+ else
128
+ bs_orig_terminal = bs_original. u[end ]
129
+ bs_anti_terminal = bs_antithetic. u[end ]
130
+ end
131
+
132
+ terminal_product = bs_orig_terminal * bs_anti_terminal
133
+ theoretical_product = spot^ 2 * exp (2 * rate_bs * yearfrac (reference_date, expiry))
134
+
135
+ # The product should be roughly close to the theoretical value
136
+ # Using a large tolerance due to randomness
137
+ @test isapprox (terminal_product, theoretical_product, rtol= 0.2 )
138
+ end
139
+
140
+ @testset " Heston Model" begin
141
+ # Heston parameters
142
+ rate_heston = 0.03
143
+ V0 = 0.04
144
+ κ = 2.0
145
+ θ = 0.04
146
+ σ = 0.3
147
+ ρ = - 0.7
148
+
149
+ # Create Heston market inputs
150
+ heston_market = HestonInputs (reference_date, rate_heston, spot, V0, κ, θ, σ, ρ)
151
+ heston_prob = PricingProblem (payoff, heston_market)
152
+
153
+ # Create Monte Carlo with antithetic sampling for Heston
154
+ heston_seeds = rand (1 : 10 ^ 9 , trajectories)
155
+ heston_strategy = EulerMaruyama (trajectories ÷ 2 , steps, seeds= heston_seeds[1 : trajectories÷ 2 ], antithetic= true )
156
+ heston_method = MonteCarlo (HestonDynamics (), heston_strategy)
157
+
158
+ # Solve and get paths
159
+ heston_solution = solve (heston_prob, heston_method)
160
+ heston_paths = heston_solution. ensemble. solutions
161
+
162
+ # Analyze correlations
163
+ heston_correlations = analyze_path_correlation (heston_paths, trajectories ÷ 2 )
164
+ heston_return_correlations = calculate_return_correlation (heston_paths, trajectories ÷ 2 )
165
+
166
+ # Report statistics for diagnostic purposes
167
+ println (" \n Heston path correlations:" )
168
+ println (" Mean: " , mean (heston_correlations))
169
+ println (" Min: " , minimum (heston_correlations))
170
+ println (" Max: " , maximum (heston_correlations))
171
+
172
+ println (" \n Heston return correlations:" )
173
+ println (" Mean: " , mean (heston_return_correlations))
174
+ println (" Min: " , minimum (heston_return_correlations))
175
+ println (" Max: " , maximum (heston_return_correlations))
176
+
177
+ # For Heston, the correlation might not be as strong due to stochastic volatility
178
+ @test mean (heston_correlations) < - 0.5
179
+
180
+ # For return correlations, we still expect strong negative correlation
181
+ @test mean (heston_return_correlations) < - 0.7
182
+
183
+ # Test percentage of negative correlations
184
+ # In Heston, we expect most but not necessarily all paths to show negative correlation
185
+ percent_negative = sum (heston_correlations .< 0 ) / length (heston_correlations) * 100
186
+ println (" Percentage of negative correlations: $(percent_negative) %" )
187
+ @test percent_negative > 90 # At least 90% should be negative
188
+ end
189
+
190
+ @testset " Correlation Comparison" begin
191
+ # Compare correlations between BS and Heston to verify both are working
192
+ # but may have different characteristics
193
+
194
+ # Repeat setup briefly
195
+ rate_bs = 0.05
196
+ sigma_bs = 0.20
197
+ bs_market = BlackScholesInputs (reference_date, rate_bs, spot, sigma_bs)
198
+ bs_prob = PricingProblem (payoff, bs_market)
199
+ bs_seeds = rand (1 : 10 ^ 9 , trajectories)
200
+ bs_strategy = BlackScholesExact (trajectories ÷ 2 , steps, seeds= bs_seeds[1 : trajectories÷ 2 ], antithetic= true )
201
+ bs_method = MonteCarlo (LognormalDynamics (), bs_strategy)
202
+ bs_solution = solve (bs_prob, bs_method)
203
+ bs_paths = bs_solution. ensemble. solutions
204
+
205
+ rate_heston = 0.03
206
+ V0 = 0.04
207
+ κ = 2.0
208
+ θ = 0.04
209
+ σ = 0.3
210
+ ρ = - 0.7
211
+ heston_market = HestonInputs (reference_date, rate_heston, spot, V0, κ, θ, σ, ρ)
212
+ heston_prob = PricingProblem (payoff, heston_market)
213
+ heston_seeds = rand (1 : 10 ^ 9 , trajectories)
214
+ heston_strategy = EulerMaruyama (trajectories ÷ 2 , steps, seeds= heston_seeds[1 : trajectories÷ 2 ], antithetic= true )
215
+ heston_method = MonteCarlo (HestonDynamics (), heston_strategy)
216
+ heston_solution = solve (heston_prob, heston_method)
217
+ heston_paths = heston_solution. ensemble. solutions
218
+
219
+ # Get path correlations
220
+ bs_correlations = analyze_path_correlation (bs_paths, trajectories ÷ 2 )
221
+ heston_correlations = analyze_path_correlation (heston_paths, trajectories ÷ 2 )
222
+
223
+ # Black-Scholes should typically have stronger negative correlation
224
+ # due to simpler model dynamics
225
+ @test mean (bs_correlations) < mean (heston_correlations)
226
+ end
227
+ end
0 commit comments