@@ -16,23 +16,22 @@ def solve(self):
16
16
:return: the solver instance with the computed option values
17
17
"""
18
18
19
- S = self .equation .generate_asset_grid ( )
20
- T = self .equation .generate_time_grid ( )
19
+ S = self .equation .generate_grid ( self . equation . S_max , self . equation . s_nodes )
20
+ T = self .equation .generate_grid ( self . equation . expiry , self . equation . t_nodes )
21
21
22
22
dt_max = 1 / ((self .equation .s_nodes ** 2 ) * (self .equation .sigma ** 2 )) # cfl condition to ensure stability
23
23
24
24
if self .equation .t_nodes is None :
25
25
dt = 0.9 * dt_max
26
26
self .equation .t_nodes = int (self .equation .expiry / dt )
27
- dt = self .equation .expiry / self .equation .t_nodes # to ensure that the expiration time is integer time steps away
27
+ dt = self .equation .expiry / self .equation .t_nodes
28
28
else :
29
- # possible fix - set a check to see that user-defined value is within cfl condition
30
29
dt = T [1 ] - T [0 ]
31
30
32
31
if dt > dt_max :
33
32
raise ValueError ("User-defined t nodes is too small and exceeds the CFL condition. Possible action: Increase number of t nodes for stability!" )
34
33
35
- dS = S [1 ] - S [0 ]
34
+ ds = S [1 ] - S [0 ]
36
35
37
36
V = np .zeros ((self .equation .s_nodes + 1 , self .equation .t_nodes + 1 ))
38
37
@@ -44,19 +43,25 @@ def solve(self):
44
43
else :
45
44
raise ValueError ("Invalid option type - please choose between call/put" )
46
45
46
+ delta = np .zeros ((self .equation .s_nodes + 1 , self .equation .t_nodes + 1 ))
47
+ gamma = np .zeros ((self .equation .s_nodes + 1 , self .equation .t_nodes + 1 ))
48
+ theta = np .zeros ((self .equation .s_nodes + 1 , self .equation .t_nodes + 1 ))
49
+
47
50
for tau in reversed (range (self .equation .t_nodes )):
48
51
for i in range (1 , self .equation .s_nodes ):
49
- delta = (V [i + 1 , tau + 1 ] - V [i - 1 , tau + 1 ]) / (2 * dS )
50
- gamma = (V [i + 1 , tau + 1 ] - 2 * V [i ,tau + 1 ] + V [i - 1 , tau + 1 ]) / (dS ** 2 )
51
- theta = - 0.5 * ( self .equation .sigma ** 2 ) * (S [i ] ** 2 ) * gamma - self .equation .rate * S [i ] * delta + self .equation .rate * V [i , tau + 1 ]
52
- V [i , tau ] = V [i , tau + 1 ] - (theta * dt )
52
+ delta [ i , tau ] = (V [i + 1 , tau + 1 ] - V [i - 1 , tau + 1 ]) / (2 * ds )
53
+ gamma [ i , tau ] = (V [i + 1 , tau + 1 ] - 2 * V [i ,tau + 1 ] + V [i - 1 , tau + 1 ]) / (ds ** 2 )
54
+ theta [ i , tau ] = - 0.5 * (self .equation .sigma ** 2 ) * (S [i ] ** 2 ) * gamma [ i , tau ] - self .equation .rate * S [i ] * delta [ i , tau ] + self .equation .rate * V [i , tau + 1 ]
55
+ V [i , tau ] = V [i , tau + 1 ] - (theta [ i , tau ] * dt )
53
56
54
57
# setting boundary conditions
55
58
lower , upper = self .__set_boundary_conditions (T , tau )
56
59
V [0 , tau ] = lower
57
60
V [self .equation .s_nodes , tau ] = upper
58
61
59
- return sol .SolutionBlackScholes (V ,S ,T )
62
+ delta , gamma , theta = self .__calculate_greeks_at_boundary (delta , gamma , theta , tau , V , S , ds )
63
+
64
+ return sol .SolutionBlackScholes (V ,S ,T , delta , gamma , theta )
60
65
61
66
def __set_boundary_conditions (self , T , tau ):
62
67
"""
@@ -78,6 +83,20 @@ def __set_boundary_conditions(self, T, tau):
78
83
79
84
return lower_boundary , upper_boundary
80
85
86
+ def __calculate_greeks_at_boundary (self , delta , gamma , theta , tau , V , S , ds ):
87
+ delta [0 , tau ] = (V [1 , tau + 1 ] - V [0 , tau + 1 ]) / ds # Forward difference for lower boundary
88
+ delta [self .equation .s_nodes , tau ] = (V [self .equation .s_nodes , tau + 1 ] - V [self .equation .s_nodes - 1 , tau + 1 ]) / ds # Backward difference for upper boundary
89
+
90
+ gamma [0 , tau ] = (V [2 , tau + 1 ] - 2 * V [1 , tau + 1 ] + V [0 , tau + 1 ]) / (ds ** 2 ) # Forward approximation
91
+ gamma [self .equation .s_nodes , tau ] = (V [self .equation .s_nodes , tau + 1 ] - 2 * V [self .equation .s_nodes - 1 , tau + 1 ] + V [self .equation .s_nodes - 2 , tau + 1 ]) / (ds ** 2 ) # Backward approximation
92
+
93
+ # Calculate theta for boundary points using the same formula
94
+ theta [0 , tau ] = - 0.5 * (self .equation .sigma ** 2 ) * (S [0 ]** 2 ) * gamma [0 , tau ] - self .equation .rate * S [0 ] * delta [0 , tau ] + self .equation .rate * V [0 , tau + 1 ]
95
+ theta [self .equation .s_nodes , tau ] = - 0.5 * (self .equation .sigma ** 2 ) * (S [- 1 ]** 2 ) * gamma [self .equation .s_nodes , tau ] - self .equation .rate * S [- 1 ] * delta [self .equation .s_nodes , tau ] + self .equation .rate * V [self .equation .s_nodes , tau + 1 ]
96
+
97
+ return delta , gamma , theta
98
+
99
+
81
100
class BlackScholesCNSolver :
82
101
83
102
def __init__ (self , equation : bse .BlackScholesEquation ):
@@ -90,22 +109,27 @@ def solve(self):
90
109
:return: the solver instance with the computed option values
91
110
"""
92
111
93
- S = self .equation .generate_asset_grid ( )
94
- T = self .equation .generate_time_grid ( )
112
+ S = self .equation .generate_grid ( self . equation . S_max , self . equation . s_nodes )
113
+ T = self .equation .generate_grid ( self . equation . expiry , self . equation . t_nodes )
95
114
96
- dS = S [1 ] - S [0 ]
97
- dT = T [1 ] - T [0 ]
115
+ ds = S [1 ] - S [0 ]
116
+ dt = T [1 ] - T [0 ]
98
117
99
- alpha = 0.25 * dT * ((self .equation .sigma ** 2 ) * (S ** 2 ) / (dS ** 2 ) - self .equation .rate * S / dS )
100
- beta = - dT * 0.5 * (self .equation .sigma ** 2 * (S ** 2 ) / (dS ** 2 ) + self .equation .rate )
101
- gamma = 0.25 * dT * (self .equation .sigma ** 2 * (S ** 2 ) / (dS ** 2 ) + self .equation .rate * S / dS )
118
+ a = 0.25 * dt * ((self .equation .sigma ** 2 ) * (S ** 2 ) / (ds ** 2 ) - self .equation .rate * S / ds )
119
+ b = - dt * 0.5 * (self .equation .sigma ** 2 * (S ** 2 ) / (ds ** 2 ) + self .equation .rate )
120
+ c = 0.25 * dt * (self .equation .sigma ** 2 * (S ** 2 ) / (ds ** 2 ) + self .equation .rate * S / ds )
102
121
103
- lhs = sparse .diags ([- alpha [2 :], 1 - beta [1 :], - gamma [1 :- 1 ]], [- 1 , 0 , 1 ], shape = (self .equation .s_nodes - 1 , self .equation .s_nodes - 1 ), format = 'csr' )
104
- rhs = sparse .diags ([alpha [2 :], 1 + beta [1 :], gamma [1 :- 1 ]], [- 1 , 0 , 1 ], shape = (self .equation .s_nodes - 1 , self .equation .s_nodes - 1 ) , format = 'csr' )
122
+ lhs = sparse .diags ([- a [2 :], 1 - b [1 :], - c [1 :- 1 ]], [- 1 , 0 , 1 ], shape = (self .equation .s_nodes - 1 , self .equation .s_nodes - 1 ), format = 'csr' )
123
+ rhs = sparse .diags ([a [2 :], 1 + b [1 :], c [1 :- 1 ]], [- 1 , 0 , 1 ], shape = (self .equation .s_nodes - 1 , self .equation .s_nodes - 1 ) , format = 'csr' )
105
124
106
125
V = np .zeros ((self .equation .s_nodes + 1 , self .equation .t_nodes + 1 ))
107
126
108
- # setting terminal condition (for all values of S at time T)
127
+ delta = np .zeros ((self .equation .s_nodes + 1 , self .equation .t_nodes + 1 ))
128
+ gamma = np .zeros ((self .equation .s_nodes + 1 , self .equation .t_nodes + 1 ))
129
+ theta = np .zeros ((self .equation .s_nodes + 1 , self .equation .t_nodes + 1 ))
130
+
131
+
132
+ # setting terminal condition (for all values of S at time T)
109
133
if self .equation .option_type == 'call' :
110
134
V [:,- 1 ] = np .maximum ((S - self .equation .strike_price ), 0 )
111
135
@@ -123,10 +147,29 @@ def solve(self):
123
147
rhs_vector = rhs @ V [1 :- 1 , tau + 1 ]
124
148
125
149
# Apply boundary conditions to the RHS vector
126
- rhs_vector [0 ] += alpha [1 ] * (V [0 , tau + 1 ] + V [0 , tau ])
127
- rhs_vector [- 1 ] += gamma [self .equation .s_nodes - 1 ] * (V [- 1 , tau + 1 ] + V [- 1 , tau ])
150
+ rhs_vector [0 ] += a [1 ] * (V [0 , tau + 1 ] + V [0 , tau ])
151
+ rhs_vector [- 1 ] += c [self .equation .s_nodes - 1 ] * (V [- 1 , tau + 1 ] + V [- 1 , tau ])
128
152
129
153
# Solve the linear system for interior points
130
154
V [1 :- 1 , tau ] = spsolve (lhs , rhs_vector )
131
155
132
- return sol .SolutionBlackScholes (V ,S ,T )
156
+ # Calculate Greeks for interior points
157
+ delta [1 :- 1 , tau ] = (V [2 :, tau ] - V [:- 2 , tau ]) / (2 * ds )
158
+ gamma [1 :- 1 , tau ] = (V [2 :, tau ] - 2 * V [1 :- 1 , tau ] + V [:- 2 , tau ]) / (ds ** 2 )
159
+ theta [1 :- 1 , tau ] = - 0.5 * (self .equation .sigma ** 2 ) * (S [1 :- 1 ]** 2 ) * gamma [1 :- 1 , tau ] - self .equation .rate * S [1 :- 1 ] * delta [1 :- 1 , tau ] + self .equation .rate * V [1 :- 1 , tau ]
160
+
161
+ delta , gamma , theta = self .__calculate_greeks_at_boundary (delta , gamma , theta , tau , V , S , ds )
162
+
163
+ return sol .SolutionBlackScholes (V ,S ,T , delta , gamma , theta )
164
+
165
+ def __calculate_greeks_at_boundary (self , delta , gamma , theta , tau , V , S , ds ):
166
+ delta [0 , tau ] = (V [1 , tau + 1 ] - V [0 , tau + 1 ]) / ds
167
+ delta [self .equation .s_nodes , tau ] = (V [self .equation .s_nodes , tau + 1 ] - V [self .equation .s_nodes - 1 , tau + 1 ]) / ds
168
+
169
+ gamma [0 , tau ] = (V [2 , tau + 1 ] - 2 * V [1 , tau + 1 ] + V [0 , tau + 1 ]) / (ds ** 2 )
170
+ gamma [self .equation .s_nodes , tau ] = (V [self .equation .s_nodes , tau + 1 ] - 2 * V [self .equation .s_nodes - 1 , tau + 1 ] + V [self .equation .s_nodes - 2 , tau + 1 ]) / (ds ** 2 )
171
+
172
+ theta [0 , tau ] = - 0.5 * (self .equation .sigma ** 2 ) * (S [0 ]** 2 ) * gamma [0 , tau ] - self .equation .rate * S [0 ] * delta [0 , tau ] + self .equation .rate * V [0 , tau + 1 ]
173
+ theta [self .equation .s_nodes , tau ] = - 0.5 * (self .equation .sigma ** 2 ) * (S [- 1 ]** 2 ) * gamma [self .equation .s_nodes , tau ] - self .equation .rate * S [- 1 ] * delta [self .equation .s_nodes , tau ] + self .equation .rate * V [self .equation .s_nodes , tau + 1 ]
174
+
175
+ return delta , gamma , theta
0 commit comments