trading_formulas 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +34 -0
- data/Rakefile +10 -0
- data/lib/trading_formulas/bermudan_options.rb +124 -0
- data/lib/trading_formulas/binomial_options.rb +753 -0
- data/lib/trading_formulas/black_scholes.rb +369 -0
- data/lib/trading_formulas/version.rb +3 -0
- data/lib/trading_formulas.rb +8 -0
- data/test/test_bermudan_options.rb +65 -0
- data/test/test_binomial_options.rb +393 -0
- data/test/test_black_scholes.rb +329 -0
- data/trading_formulas.gemspec +18 -0
- metadata +62 -0
@@ -0,0 +1,753 @@
|
|
1
|
+
module TradingFormulas
|
2
|
+
##
|
3
|
+
# Author:: Matt.Osentoski (matt.osentoski@gmail.com)
|
4
|
+
#
|
5
|
+
# This module contains formulas based on binomial equations
|
6
|
+
# Converted to Python from "Financial Numerical Recipes in C" by:
|
7
|
+
# Bernt Arne Odegaard
|
8
|
+
# http://finance.bi.no/~bernt/gcc_prog/index.html
|
9
|
+
#
|
10
|
+
class BinomialOptions
|
11
|
+
|
12
|
+
##
|
13
|
+
# American Option (Call) using binomial approximations
|
14
|
+
# +s+: spot (underlying) price
|
15
|
+
# +k+: strike (exercise) price,
|
16
|
+
# +r+: interest rate
|
17
|
+
# +sigma+: volatility
|
18
|
+
# +t+: time to maturity
|
19
|
+
# +steps+: Number of steps in binomial tree
|
20
|
+
# *Rreturns* Option price
|
21
|
+
#
|
22
|
+
def self.call(s, k, r, sigma, t, steps)
|
23
|
+
r_tmp = Math.exp(r*(t/steps))
|
24
|
+
r_inv = 1.0/r_tmp
|
25
|
+
u = Math.exp(sigma*Math.sqrt(t/steps))
|
26
|
+
d = 1.0/u
|
27
|
+
p_up = (r_tmp-d)/(u-d)
|
28
|
+
p_down = 1.0-p_up
|
29
|
+
prices = Array.new(steps+1) # price of underlying
|
30
|
+
prices[0] = s*(d**steps) # fill in the endnodes.
|
31
|
+
uu = u*u
|
32
|
+
|
33
|
+
(1..(steps+1)).each do |i|
|
34
|
+
prices[i] = uu*prices[i-1]
|
35
|
+
end
|
36
|
+
call_values = Array.new(steps+1) # value of corresponding call
|
37
|
+
(0..(steps+1)).each do |i|
|
38
|
+
call_values[i] = [0.0, (prices[i]-k)].max # call payoffs at maturity
|
39
|
+
end
|
40
|
+
|
41
|
+
(steps-1).downto(0) do |step|
|
42
|
+
(0..(step+1)).each do |i|
|
43
|
+
call_values[i] = (p_up*call_values[i+1]+p_down*call_values[i])*r_inv
|
44
|
+
prices[i] = d*prices[i+1]
|
45
|
+
call_values[i] = [call_values[i],prices[i]-k].max # check for exercise
|
46
|
+
end
|
47
|
+
end
|
48
|
+
return call_values[0]
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# American Option (Put) using binomial approximations
|
53
|
+
# +s+: spot (underlying) price
|
54
|
+
# +k+: strike (exercise) price,
|
55
|
+
# +r+: interest rate
|
56
|
+
# +sigma+: volatility
|
57
|
+
# +t+: time to maturity
|
58
|
+
# +steps+: Number of steps in binomial tree
|
59
|
+
# *Returns*: Option price
|
60
|
+
#
|
61
|
+
def self.put(s, k, r, sigma, t, steps)
|
62
|
+
r_tmp = Math.exp(r*(t/steps)) # interest rate for each step
|
63
|
+
r_inv = 1.0/r_tmp # inverse of interest rate
|
64
|
+
u = Math.exp(sigma*Math.sqrt(t/steps)) # up movement
|
65
|
+
uu = u*u
|
66
|
+
d = 1.0/u
|
67
|
+
p_up = (r_tmp-d)/(u-d)
|
68
|
+
p_down = 1.0-p_up
|
69
|
+
prices = Array.new(steps+1) # price of underlying
|
70
|
+
prices[0] = s*(d**steps)
|
71
|
+
|
72
|
+
(1..(steps+1)).each do |i|
|
73
|
+
prices[i] = uu*prices[i-1]
|
74
|
+
end
|
75
|
+
|
76
|
+
put_values = Array.new(steps+1) # value of corresponding put
|
77
|
+
|
78
|
+
(0..(steps+1)).each do |i|
|
79
|
+
put_values[i] = [0.0, (k-prices[i])].max # put payoffs at maturity
|
80
|
+
end
|
81
|
+
|
82
|
+
(steps-1).downto(0) do |step|
|
83
|
+
(0..(steps+1)).each do |i|
|
84
|
+
put_values[i] = (p_up*put_values[i+1].to_f+p_down*put_values[i])*r_inv
|
85
|
+
prices[i] = d*prices[i+1].to_f
|
86
|
+
put_values[i] = [put_values[i],(k-prices[i])].max # check for exercise
|
87
|
+
end
|
88
|
+
end
|
89
|
+
return put_values[0]
|
90
|
+
end
|
91
|
+
|
92
|
+
##
|
93
|
+
# Delta of an American Option (Call) using binomial approximations
|
94
|
+
# +s+: spot (underlying) price
|
95
|
+
# +k+: strike (exercise) price,
|
96
|
+
# +r+: interest rate
|
97
|
+
# +sigma+: volatility
|
98
|
+
# +t+: time to maturity
|
99
|
+
# +steps+: Number of steps in binomial tree
|
100
|
+
# *Returns* Delta of the option
|
101
|
+
def self.delta_call(s, k, r, sigma, t, steps)
|
102
|
+
r_tmp = Math.exp(r*(t/steps))
|
103
|
+
r_inv = 1.0/r_tmp
|
104
|
+
u = Math.exp(sigma*Math.sqrt(t/steps))
|
105
|
+
d = 1.0/u
|
106
|
+
uu= u*u
|
107
|
+
pUp = (r_tmp-d)/(u-d)
|
108
|
+
pDown = 1.0 - pUp
|
109
|
+
|
110
|
+
prices = Array.new(steps+1)
|
111
|
+
prices[0] = s*(d**steps)
|
112
|
+
|
113
|
+
(1..(steps+1)).each do |i|
|
114
|
+
prices[i] = uu*prices[i-1].to_f
|
115
|
+
end
|
116
|
+
|
117
|
+
call_values = Array.new(steps+1)
|
118
|
+
|
119
|
+
(0..(steps+1)).each do |i|
|
120
|
+
call_values[i] = [0.0, (prices[i]-k)].max
|
121
|
+
end
|
122
|
+
|
123
|
+
(steps-1).downto(1) do |step|
|
124
|
+
(0..(steps+1)).each do |i|
|
125
|
+
prices[i] = d*prices[i+1].to_f
|
126
|
+
call_values[i] = (pDown*call_values[i].to_f+pUp*call_values[i+1].to_f)*r_inv
|
127
|
+
call_values[i] = [call_values[i], (prices[i]-k)].max # check for exercise
|
128
|
+
end
|
129
|
+
end
|
130
|
+
delta = (call_values[1]-call_values[0])/(s*u-s*d)
|
131
|
+
return delta
|
132
|
+
end
|
133
|
+
|
134
|
+
##
|
135
|
+
# Delta of an American Option (Put) using binomial approximations
|
136
|
+
# +s+: spot (underlying) price
|
137
|
+
# +k+: strike (exercise) price,
|
138
|
+
# +r+: interest rate
|
139
|
+
# +sigma+: volatility
|
140
|
+
# +t+: time to maturity
|
141
|
+
# +steps+: Number of steps in binomial tree
|
142
|
+
# *Returns* Delta of the option
|
143
|
+
def self.delta_put(s, k, r, sigma, t, steps)
|
144
|
+
prices = Array.new(steps+1)
|
145
|
+
put_values = Array.new(steps+1)
|
146
|
+
r_tmp = Math.exp(r*(t/steps))
|
147
|
+
r_inv = 1.0/r_tmp
|
148
|
+
u = Math.exp(sigma*Math.sqrt(t/steps))
|
149
|
+
d = 1.0/u
|
150
|
+
uu= u*u
|
151
|
+
pUp = (r_tmp-d)/(u-d)
|
152
|
+
pDown = 1.0 - pUp
|
153
|
+
prices[0] = s*(d**steps)
|
154
|
+
|
155
|
+
(1..(steps+1)).each do |i|
|
156
|
+
prices[i] = uu*prices[i-1].to_f
|
157
|
+
end
|
158
|
+
|
159
|
+
(0..(steps+1)).each do |i|
|
160
|
+
put_values[i] = [0.0, (k - prices[i])].max
|
161
|
+
end
|
162
|
+
|
163
|
+
(steps-1).downto(1) do |step|
|
164
|
+
(0..(steps+1)).each do |i|
|
165
|
+
prices[i] = d*prices[i+1].to_f
|
166
|
+
put_values[i] = (pDown*put_values[i].to_f+pUp*put_values[i+1].to_f)*r_inv
|
167
|
+
put_values[i] = [put_values[i], (k-prices[i])].max # check for exercise
|
168
|
+
end
|
169
|
+
end
|
170
|
+
delta = (put_values[1]-put_values[0])/(s*u-s*d)
|
171
|
+
return delta
|
172
|
+
end
|
173
|
+
|
174
|
+
##
|
175
|
+
# Calculate partial derivatives for an American Option (Call) using
|
176
|
+
# binomial approximations.
|
177
|
+
# (NOTE: Originally, this method used argument pointer references as a
|
178
|
+
# way of returning the partial derivatives in C++. I've removed these
|
179
|
+
# references from the method signature and chose to return a tuple instead.)
|
180
|
+
# +s+: spot (underlying) price
|
181
|
+
# +k+: strike (exercise) price,
|
182
|
+
# +r+: interest rate
|
183
|
+
# +sigma+: volatility
|
184
|
+
# +time+: time to maturity
|
185
|
+
# +steps+: Number of steps in binomial tree
|
186
|
+
# *Returns* Tuple of partial derivatives: (Delta, Gamma, Theta, Vega, Rho)
|
187
|
+
# delta: partial wrt S
|
188
|
+
# gamma: second partial wrt S
|
189
|
+
# theta: partial wrt time
|
190
|
+
# vega: partial wrt sigma
|
191
|
+
# rho: partial wrt r
|
192
|
+
def self.partials_call(s, k, r, sigma, time, steps)
|
193
|
+
prices = Array.new(steps+1)
|
194
|
+
call_values = Array.new(steps+1)
|
195
|
+
delta_t =(time/steps)
|
196
|
+
r_tmp = Math.exp(r*delta_t)
|
197
|
+
r_inv = 1.0/r_tmp
|
198
|
+
u = Math.exp(sigma*Math.sqrt(delta_t))
|
199
|
+
d = 1.0/u
|
200
|
+
uu= u*u
|
201
|
+
pUp = (r_tmp-d)/(u-d)
|
202
|
+
pDown = 1.0 - pUp
|
203
|
+
prices[0] = s*(d**steps)
|
204
|
+
|
205
|
+
(1..(steps+1)).each do |i|
|
206
|
+
prices[i] = uu*prices[i-1].to_f
|
207
|
+
end
|
208
|
+
|
209
|
+
(0..(steps+1)).each do |i|
|
210
|
+
call_values[i] = [0.0, (prices[i]-k)].max
|
211
|
+
end
|
212
|
+
|
213
|
+
(steps-1).downto(2) do |step|
|
214
|
+
(0..(steps+1)).each do |i|
|
215
|
+
prices[i] = d*prices[i+1].to_f
|
216
|
+
call_values[i] = (pDown*call_values[i].to_f+pUp*call_values[i+1].to_f)*r_inv
|
217
|
+
call_values[i] = [call_values[i], (prices[i]-k)].max # check for exercise
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
f22 = call_values[2]
|
222
|
+
f21 = call_values[1]
|
223
|
+
f20 = call_values[0]
|
224
|
+
(0..1).each do |i|
|
225
|
+
prices[i] = d*prices[i+1]
|
226
|
+
call_values[i] = (pDown*call_values[i]+pUp*call_values[i+1])*r_inv
|
227
|
+
call_values[i] = [call_values[i], (prices[i]-k)].max # check for exercise
|
228
|
+
end
|
229
|
+
|
230
|
+
f11 = call_values[1]
|
231
|
+
f10 = call_values[0]
|
232
|
+
prices[0] = d*prices[1]
|
233
|
+
call_values[0] = (pDown*call_values[0]+pUp*call_values[1])*r_inv
|
234
|
+
call_values[0] = [call_values[0], (s-k)].max # check for exercise on first date
|
235
|
+
f00 = call_values[0]
|
236
|
+
delta = (f11-f10)/(s*u-s*d)
|
237
|
+
h = 0.5 * s * ( uu - d*d)
|
238
|
+
gamma = ( (f22-f21)/(s*(uu-1)) - (f21-f20)/(s*(1-d*d)) ) / h
|
239
|
+
theta = (f21-f00) / (2*delta_t)
|
240
|
+
diff = 0.02
|
241
|
+
tmp_sigma = sigma+diff
|
242
|
+
tmp_prices = TradingFormulas::BinomialOptions.call(s,k,r,tmp_sigma,time,steps)
|
243
|
+
vega = (tmp_prices-f00)/diff
|
244
|
+
diff = 0.05
|
245
|
+
tmp_r = r+diff
|
246
|
+
tmp_prices = TradingFormulas::BinomialOptions.call(s,k,tmp_r,sigma,time,steps)
|
247
|
+
rho = (tmp_prices-f00)/diff
|
248
|
+
return delta, gamma, theta, vega, rho
|
249
|
+
end
|
250
|
+
|
251
|
+
##
|
252
|
+
# Calculate partial derivatives for an American Option (Put) using
|
253
|
+
# binomial approximations.
|
254
|
+
# (NOTE: Originally, this method used argument pointer references as a
|
255
|
+
# way of returning the partial derivatives in C++. I've removed these
|
256
|
+
# references from the method signature and chose to return a tuple instead.)
|
257
|
+
# +s+: spot (underlying) price
|
258
|
+
# +k+: strike (exercise) price,
|
259
|
+
# +r+: interest rate
|
260
|
+
# +sigma+: volatility
|
261
|
+
# +time+: time to maturity
|
262
|
+
# +steps+: Number of steps in binomial tree
|
263
|
+
# *Returns*: Tuple of partial derivatives: (Delta, Gamma, Theta, Vega, Rho)
|
264
|
+
# delta: partial wrt S
|
265
|
+
# gamma: second partial wrt S
|
266
|
+
# theta: partial wrt time
|
267
|
+
# vega: partial wrt sigma
|
268
|
+
# rho: partial wrt r
|
269
|
+
def self.partials_put(s, k, r, sigma, time, steps)
|
270
|
+
prices = Array.new(steps+1)
|
271
|
+
put_values = Array.new(steps+1)
|
272
|
+
delta_t =(time/steps)
|
273
|
+
r_tmp = Math.exp(r*delta_t)
|
274
|
+
r_inv = 1.0/r_tmp
|
275
|
+
u = Math.exp(sigma*Math.sqrt(delta_t))
|
276
|
+
d = 1.0/u
|
277
|
+
uu= u*u
|
278
|
+
pUp = (r_tmp-d)/(u-d)
|
279
|
+
pDown = 1.0 - pUp
|
280
|
+
prices[0] = s*(d**steps)
|
281
|
+
|
282
|
+
(1..(steps+1)).each do |i|
|
283
|
+
prices[i] = uu*prices[i-1].to_f
|
284
|
+
end
|
285
|
+
|
286
|
+
(0..(steps+1)).each do |i|
|
287
|
+
put_values[i] = [0.0, (k-prices[i])].max
|
288
|
+
end
|
289
|
+
|
290
|
+
(steps-1).downto(2) do |step|
|
291
|
+
(0..(steps+1)).each do |i|
|
292
|
+
prices[i] = d*prices[i+1].to_f
|
293
|
+
put_values[i] = (pDown*put_values[i].to_f+pUp*put_values[i+1].to_f)*r_inv
|
294
|
+
put_values[i] = [put_values[i], k-prices[i]].max # check for exercise
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
f22 = put_values[2]
|
299
|
+
f21 = put_values[1]
|
300
|
+
f20 = put_values[0]
|
301
|
+
i_tmp = 0
|
302
|
+
(0..1).each do |i|
|
303
|
+
prices[i] = d*prices[i+1]
|
304
|
+
put_values[i] = (pDown*put_values[i]+pUp*put_values[i+1])*r_inv
|
305
|
+
put_values[i] = [put_values[i], k-prices[i]].max # check for exercise
|
306
|
+
i_tmp = i
|
307
|
+
end
|
308
|
+
|
309
|
+
f11 = put_values[1]
|
310
|
+
f10 = put_values[0]
|
311
|
+
prices[0] = d*prices[1]
|
312
|
+
put_values[0] = (pDown*put_values[0]+pUp*put_values[1])*r_inv
|
313
|
+
put_values[0] = [put_values[0], k-prices[i_tmp]].max # check for exercise
|
314
|
+
f00 = put_values[0]
|
315
|
+
delta = (f11-f10)/(s*(u-d))
|
316
|
+
h = 0.5 * s *( uu - d*d)
|
317
|
+
gamma = ( (f22-f21)/(s*(uu-1.0)) - (f21-f20)/(s*(1.0-d*d)) ) / h
|
318
|
+
theta = (f21-f00) / (2*delta_t)
|
319
|
+
diff = 0.02
|
320
|
+
tmp_sigma = sigma+diff
|
321
|
+
tmp_prices = TradingFormulas::BinomialOptions.put(s,k,r,tmp_sigma,time,steps)
|
322
|
+
vega = (tmp_prices-f00)/diff
|
323
|
+
diff = 0.05
|
324
|
+
tmp_r = r+diff
|
325
|
+
tmp_prices = TradingFormulas::BinomialOptions.put(s,k,tmp_r,sigma,time,steps)
|
326
|
+
rho = (tmp_prices-f00)/diff
|
327
|
+
return delta, gamma, theta, vega, rho
|
328
|
+
end
|
329
|
+
|
330
|
+
##
|
331
|
+
# American Option (Call) for dividends with specific (discrete) dollar amounts
|
332
|
+
# using binomial approximations
|
333
|
+
# +s+: spot (underlying) price
|
334
|
+
# +k+: strike (exercise) price,
|
335
|
+
# +r+: interest rate
|
336
|
+
# +sigma+: volatility
|
337
|
+
# +t+: time to maturity
|
338
|
+
# +steps+: Number of steps in binomial tree
|
339
|
+
# +dividend_times+: Array of dividend times. (Ex: [0.25, 0.75] for 1/4 and 3/4 of a year)
|
340
|
+
# +dividend_amounts+: Array of dividend amounts for the 'dividend_times'
|
341
|
+
# *Returns* Option price
|
342
|
+
def self.discrete_dividends_call(s, k, r, sigma, t, steps, dividend_times, dividend_amounts)
|
343
|
+
no_dividends = dividend_times.count
|
344
|
+
if (no_dividends==0)
|
345
|
+
return TradingFormulas::BinomialOptions.call(s,k,r,sigma,t,steps) # just do regular
|
346
|
+
end
|
347
|
+
steps_before_dividend = (dividend_times[0]/t*steps).to_i
|
348
|
+
r_tmp = Math.exp(r*(t/steps))
|
349
|
+
r_inv = 1.0/r_tmp
|
350
|
+
u = Math.exp(sigma*Math.sqrt(t/steps))
|
351
|
+
d = 1.0/u
|
352
|
+
pUp = (r_tmp-d)/(u-d)
|
353
|
+
pDown = 1.0 - pUp
|
354
|
+
dividend_amount = dividend_amounts[0]
|
355
|
+
tmp_dividend_times = Array.new(no_dividends-1) # temporaries with
|
356
|
+
tmp_dividend_amounts = Array.new(no_dividends-1) # one less dividend
|
357
|
+
(0..(no_dividends-2)).each do |i|
|
358
|
+
tmp_dividend_amounts[i] = dividend_amounts[i+1].to_f
|
359
|
+
tmp_dividend_times[i] = dividend_times[i+1].to_f - dividend_times[0].to_f
|
360
|
+
end
|
361
|
+
prices = Array.new(steps_before_dividend+1)
|
362
|
+
call_values = Array.new(steps_before_dividend+1)
|
363
|
+
prices[0] = s*(d**steps_before_dividend)
|
364
|
+
|
365
|
+
(1..(steps_before_dividend+1)).each do |i|
|
366
|
+
prices[i] = u*u*prices[i-1].to_f
|
367
|
+
end
|
368
|
+
|
369
|
+
(0..(steps_before_dividend+1)).each do |i|
|
370
|
+
value_alive = TradingFormulas::BinomialOptions.discrete_dividends_call(prices[i]-dividend_amount,k, r, sigma,
|
371
|
+
t-dividend_times[0], # time after first dividend
|
372
|
+
steps-steps_before_dividend,
|
373
|
+
tmp_dividend_times,
|
374
|
+
tmp_dividend_amounts)
|
375
|
+
call_values[i] = [value_alive,(prices[i]-k)].max # compare to exercising now
|
376
|
+
end
|
377
|
+
|
378
|
+
(steps_before_dividend-1).downto(0) do |step|
|
379
|
+
(0..(steps+1)).each do |i|
|
380
|
+
prices[i] = d*prices[i+1].to_f
|
381
|
+
call_values[i] = (pDown*call_values[i].to_f+pUp*call_values[i+1].to_f)*r_inv
|
382
|
+
call_values[i] = [call_values[i], prices[i]-k].max
|
383
|
+
end
|
384
|
+
end
|
385
|
+
return call_values[0]
|
386
|
+
end
|
387
|
+
|
388
|
+
##
|
389
|
+
# American Option (Put) for dividends with specific (discrete) dollar amounts
|
390
|
+
# using binomial approximations
|
391
|
+
# +s+: spot (underlying) price
|
392
|
+
# +k+: strike (exercise) price,
|
393
|
+
# +r+: interest rate
|
394
|
+
# +sigma+: volatility
|
395
|
+
# +t+: time to maturity
|
396
|
+
# +steps+: Number of steps in binomial tree
|
397
|
+
# +dividend_times+: Array of dividend times. (Ex: [0.25, 0.75] for 1/4 and 3/4 of a year)
|
398
|
+
# +dividend_amounts+: Array of dividend amounts for the 'dividend_times'
|
399
|
+
# *Returns* Option price
|
400
|
+
def self.discrete_dividends_put(s, k, r, sigma, t, steps, dividend_times, dividend_amounts)
|
401
|
+
# given an amount of dividend, the binomial tree does not recombine, have to
|
402
|
+
# start a new tree at each ex-dividend date.
|
403
|
+
# do this recursively, at each ex dividend date, at each step, put the
|
404
|
+
# binomial formula starting at that point to calculate the value of the live
|
405
|
+
# option, and compare that to the value of exercising now.
|
406
|
+
|
407
|
+
no_dividends = dividend_times.count
|
408
|
+
if (no_dividends == 0) # just take the regular binomial
|
409
|
+
return TradingFormulas::BinomialOptions.put(s,k,r,sigma,t,steps)
|
410
|
+
end
|
411
|
+
steps_before_dividend = (dividend_times[0]/t*steps).to_i
|
412
|
+
|
413
|
+
r_tmp = Math.exp(r*(t/steps))
|
414
|
+
r_inv = 1.0/r_tmp
|
415
|
+
u = Math.exp(sigma*Math.sqrt(t/steps))
|
416
|
+
uu= u*u
|
417
|
+
d = 1.0/u
|
418
|
+
pUp = (r_tmp-d)/(u-d)
|
419
|
+
pDown = 1.0 - pUp
|
420
|
+
dividend_amount = dividend_amounts[0]
|
421
|
+
|
422
|
+
tmp_dividend_times = Array.new(no_dividends-1) # temporaries with
|
423
|
+
tmp_dividend_amounts = Array.new(no_dividends-1) # one less dividend
|
424
|
+
(0..(no_dividends-2)).each do |i|
|
425
|
+
tmp_dividend_amounts[i] = dividend_amounts[i+1].to_f
|
426
|
+
tmp_dividend_times[i]= dividend_times[i+1].to_f - dividend_times[0].to_f
|
427
|
+
end
|
428
|
+
|
429
|
+
prices = Array.new(steps_before_dividend+1)
|
430
|
+
put_values = Array.new(steps_before_dividend+1)
|
431
|
+
prices[0] = s*(d**steps_before_dividend)
|
432
|
+
|
433
|
+
(1..(steps_before_dividend+1)).each do |i|
|
434
|
+
prices[i] = uu*prices[i-1]
|
435
|
+
end
|
436
|
+
|
437
|
+
(0..(steps_before_dividend+1)).each do |i|
|
438
|
+
value_alive = TradingFormulas::BinomialOptions.discrete_dividends_put(
|
439
|
+
prices[i]-dividend_amount, k, r, sigma,
|
440
|
+
t-dividend_times[0], # time after first dividend
|
441
|
+
steps-steps_before_dividend,
|
442
|
+
tmp_dividend_times, tmp_dividend_amounts)
|
443
|
+
# what is the value of keeping the option alive? Found recursively,
|
444
|
+
# with one less dividend, the stock price is current value
|
445
|
+
# less the dividend.
|
446
|
+
put_values[i] = [value_alive,(k-prices[i])].max # compare to exercising now
|
447
|
+
end
|
448
|
+
|
449
|
+
(steps_before_dividend-1).downto(0) do |step|
|
450
|
+
(0..(steps+1)).each do |i|
|
451
|
+
prices[i] = d*prices[i+1].to_f
|
452
|
+
put_values[i] = (pDown*put_values[i].to_f+pUp*put_values[i+1].to_f)*r_inv
|
453
|
+
put_values[i] = [put_values[i], k-prices[i]].max# check for exercise
|
454
|
+
end
|
455
|
+
end
|
456
|
+
return put_values[0]
|
457
|
+
end
|
458
|
+
|
459
|
+
##
|
460
|
+
# American Option (Call) with proportional dividend payments
|
461
|
+
# using binomial approximations
|
462
|
+
# +s+: spot (underlying) price
|
463
|
+
# +k+: strike (exercise) price,
|
464
|
+
# +r+: interest rate
|
465
|
+
# +sigma+: volatility
|
466
|
+
# +t+: time to maturity
|
467
|
+
# +steps+: Number of steps in binomial tree
|
468
|
+
# +dividend_times+: Array of dividend times. (Ex: [0.25, 0.75] for 1/4 and 3/4 of a year)
|
469
|
+
# +dividend_yields+: Array of dividend yields for the 'dividend_times'
|
470
|
+
# *Returns* Option price
|
471
|
+
def self.proportional_dividends_call(s, k, r, sigma, t, steps, dividend_times, dividend_yields)
|
472
|
+
# note that the last dividend date should be before the expiry date, problems if dividend at terminal node
|
473
|
+
no_dividends= dividend_times.count
|
474
|
+
if (no_dividends == 0)
|
475
|
+
return TradingFormulas::BinomialOptions.call(s,k,r,sigma,t,steps) # price w/o dividends
|
476
|
+
end
|
477
|
+
|
478
|
+
delta_t = t/steps
|
479
|
+
r_tmp = Math.exp(r*delta_t)
|
480
|
+
r_inv = 1.0/r_tmp
|
481
|
+
u = Math.exp(sigma*Math.sqrt(delta_t))
|
482
|
+
uu= u*u
|
483
|
+
d = 1.0/u
|
484
|
+
pUp = (r_tmp-d)/(u-d)
|
485
|
+
pDown = 1.0 - pUp
|
486
|
+
dividend_steps = Array.new(no_dividends) # when dividends are paid
|
487
|
+
|
488
|
+
(0..(no_dividends)).each do |i|
|
489
|
+
dividend_steps[i] = (dividend_times[i].to_f/t*steps).to_i
|
490
|
+
end
|
491
|
+
prices = Array.new(steps+1)
|
492
|
+
call_prices = Array.new(steps+1)
|
493
|
+
prices[0] = s*(d**steps)# adjust downward terminal prices by dividends
|
494
|
+
|
495
|
+
(0..(no_dividends)).each do |i|
|
496
|
+
prices[0] = prices[0].to_f * (1.0-dividend_yields[i].to_f)
|
497
|
+
end
|
498
|
+
|
499
|
+
(1..(steps+1)).each do |i|
|
500
|
+
prices[i] = uu*prices[i-1].to_f
|
501
|
+
end
|
502
|
+
|
503
|
+
(1..(steps+1)).each do |i|
|
504
|
+
call_prices[i] = [0.0, (prices[i]-k)].max
|
505
|
+
end
|
506
|
+
|
507
|
+
(steps-1).downto(0) do |step|
|
508
|
+
(0..(no_dividends)).each do |i| # check whether dividend paid
|
509
|
+
if (step==dividend_steps[i])
|
510
|
+
(0..(step+2)).each do |j|
|
511
|
+
prices[j]*=(1.0/(1.0-dividend_yields[i].to_f))
|
512
|
+
end
|
513
|
+
end
|
514
|
+
end
|
515
|
+
(0..(step+1)).each do |i|
|
516
|
+
call_prices[i] = (pDown*call_prices[i].to_f+pUp*call_prices[i+1].to_f)*r_inv
|
517
|
+
prices[i] = d*prices[i+1].to_f
|
518
|
+
call_prices[i] = [call_prices[i].to_f, prices[i].to_f-k].max #check for exercise
|
519
|
+
end
|
520
|
+
end
|
521
|
+
return call_prices[0]
|
522
|
+
end
|
523
|
+
|
524
|
+
##
|
525
|
+
# American Option (Put) with proportional dividend payments
|
526
|
+
# using binomial approximations
|
527
|
+
# +s+: spot (underlying) price
|
528
|
+
# +k+: strike (exercise) price,
|
529
|
+
# +r+: interest rate
|
530
|
+
# +sigma+: volatility
|
531
|
+
# +t+: time to maturity
|
532
|
+
# +steps+: Number of steps in binomial tree
|
533
|
+
# +dividend_times+: Array of dividend times. (Ex: [0.25, 0.75] for 1/4 and 3/4 of a year)
|
534
|
+
# +dividend_yields+: Array of dividend yields for the 'dividend_times'
|
535
|
+
# *Returns* Option price
|
536
|
+
def self.proportional_dividends_put(s, k, r, sigma, t, steps, dividend_times, dividend_yields)
|
537
|
+
# when one assume a dividend yield, the binomial tree recombines
|
538
|
+
# note that the last dividend date should be before the expiry date
|
539
|
+
no_dividends= dividend_times.count
|
540
|
+
if (no_dividends == 0) # just take the regular binomial
|
541
|
+
return TradingFormulas::BinomialOptions.put(s,k,r,sigma,t,steps)
|
542
|
+
end
|
543
|
+
|
544
|
+
r_tmp = Math.exp(r*(t/steps))
|
545
|
+
r_inv = 1.0/r_tmp
|
546
|
+
u = Math.exp(sigma*Math.sqrt(t/steps))
|
547
|
+
uu= u*u
|
548
|
+
d = 1.0/u
|
549
|
+
pUp = (r_tmp-d)/(u-d)
|
550
|
+
pDown = 1.0 - pUp
|
551
|
+
dividend_steps = Array.new(no_dividends) # when dividends are paid
|
552
|
+
|
553
|
+
(0..(no_dividends)).each do |i|
|
554
|
+
dividend_steps[i] = (dividend_times[i].to_f/t*steps).to_i
|
555
|
+
end
|
556
|
+
prices = Array.new(steps+1)
|
557
|
+
put_prices = Array.new(steps+1)
|
558
|
+
prices[0] = s*(d**steps);
|
559
|
+
|
560
|
+
(0..(no_dividends)).each do |i|
|
561
|
+
prices[0] = prices[0] * (1.0-dividend_yields[i].to_f)
|
562
|
+
end
|
563
|
+
|
564
|
+
(1..(steps+1)).each do |i|
|
565
|
+
prices[i] = uu*prices[i-1].to_f #terminal tree nodes
|
566
|
+
end
|
567
|
+
|
568
|
+
(1..(steps+1)).each do |i|
|
569
|
+
put_prices[i] = [0.0, (k-prices[i])].max
|
570
|
+
end
|
571
|
+
|
572
|
+
(steps-1).downto(0) do |step|
|
573
|
+
(0..(no_dividends)).each do |i| # check whether dividend paid
|
574
|
+
if (step==dividend_steps[i])
|
575
|
+
(0..(step+2)).each do |j|
|
576
|
+
prices[j]*=(1.0/(1.0-dividend_yields[i].to_f))
|
577
|
+
end
|
578
|
+
end
|
579
|
+
end
|
580
|
+
(0..(step+1)).each do |i|
|
581
|
+
prices[i] = d*prices[i+1].to_f
|
582
|
+
put_prices[i] = (pDown*put_prices[i].to_f+pUp*put_prices[i+1].to_f)*r_inv
|
583
|
+
put_prices[i] = [put_prices[i].to_f, k-prices[i].to_f].max # check for exercise
|
584
|
+
end
|
585
|
+
end
|
586
|
+
return put_prices[0]
|
587
|
+
end
|
588
|
+
|
589
|
+
##
|
590
|
+
# American Option (Call) with continuous payouts using binomial
|
591
|
+
# approximations.
|
592
|
+
# (NOTE: Originally, this method was called: 'option_price_call_american_binomial'
|
593
|
+
# that name was already in use and didn't mention the 'payout' properties of
|
594
|
+
# the method, so the new name is: 'option_price_call_american_binomial_payout')
|
595
|
+
# +s+: spot (underlying) price
|
596
|
+
# +k+: strike (exercise) price,
|
597
|
+
# +r+: interest rate
|
598
|
+
# +y+: continuous payout
|
599
|
+
# +sigma+: volatility
|
600
|
+
# +t+: time to maturity
|
601
|
+
# +steps+: Number of steps in binomial tree
|
602
|
+
# *Returns* Option price
|
603
|
+
def self.continuous_payout_call(s, k, r, y, sigma, t, steps)
|
604
|
+
r_tmp = Math.exp(r*(t/steps)) # interest rate for each step
|
605
|
+
r_inv = 1.0/r_tmp # inverse of interest rate
|
606
|
+
u = Math.exp(sigma*Math.sqrt(t/steps)) # up movement
|
607
|
+
uu = u*u
|
608
|
+
d = 1.0/u
|
609
|
+
p_up = (Math.exp((r-y)*(t/steps))-d)/(u-d)
|
610
|
+
p_down = 1.0-p_up
|
611
|
+
prices = Array.new(steps+1) # price of underlying
|
612
|
+
prices[0] = s*(d**steps)
|
613
|
+
|
614
|
+
(1..(steps+1)).each do |i|
|
615
|
+
prices[i] = uu*prices[i-1].to_f # fill in the endnodes.
|
616
|
+
end
|
617
|
+
|
618
|
+
call_values = Array.new(steps+1) # value of corresponding call
|
619
|
+
(1..(steps+1)).each do |i| # call payoffs at maturity
|
620
|
+
call_values[i] = [0.0, (prices[i].to_f-k)].max
|
621
|
+
end
|
622
|
+
|
623
|
+
(steps-1).downto(0) do |step|
|
624
|
+
(0..(step+1)).each do |i|
|
625
|
+
call_values[i] = (p_up*call_values[i+1].to_f+p_down*call_values[i].to_f)*r_inv
|
626
|
+
prices[i] = d*prices[i+1].to_f
|
627
|
+
call_values[i] = [call_values[i].to_f,prices[i].to_f-k].max # check for exercise
|
628
|
+
end
|
629
|
+
end
|
630
|
+
return call_values[0]
|
631
|
+
end
|
632
|
+
|
633
|
+
##
|
634
|
+
# American Option (Put) with continuous payouts using binomial
|
635
|
+
# approximations.
|
636
|
+
# (NOTE: Originally, this method was called: 'option_price_call_american_binomial'
|
637
|
+
# that name was already in use and didn't mention the 'payout' properties of
|
638
|
+
# the method, so the new name is: 'option_price_call_american_binomial_payout')
|
639
|
+
# +s+: spot (underlying) price
|
640
|
+
# +k+: strike (exercise) price,
|
641
|
+
# +r+: interest rate
|
642
|
+
# +y+: continuous payout
|
643
|
+
# +sigma+: volatility
|
644
|
+
# +t+: time to maturity
|
645
|
+
# +steps+: Number of steps in binomial tree
|
646
|
+
# *Returns* Option price
|
647
|
+
def self.continuous_payout_put(s, k, r, y, sigma, t, steps)
|
648
|
+
r_tmp = Math.exp(r*(t/steps)) # interest rate for each step
|
649
|
+
r_inv = 1.0/r_tmp # inverse of interest rate
|
650
|
+
u = Math.exp(sigma*Math.sqrt(t/steps)) # up movement
|
651
|
+
uu = u*u
|
652
|
+
d = 1.0/u
|
653
|
+
p_up = (Math.exp((r-y)*(t/steps))-d)/(u-d)
|
654
|
+
p_down = 1.0-p_up
|
655
|
+
prices = Array.new(steps+1) # price of underlying
|
656
|
+
put_values = Array.new(steps+1) # value of corresponding put
|
657
|
+
prices[0] = s*(d**steps) # fill in the endnodes.
|
658
|
+
|
659
|
+
(1..(steps+1)).each do |i|
|
660
|
+
prices[i] = uu*prices[i-1].to_f
|
661
|
+
end
|
662
|
+
|
663
|
+
(1..(steps+1)).each do |i|
|
664
|
+
put_values[i] = [0.0, (k-prices[i].to_f)].max # put payoffs at maturity
|
665
|
+
end
|
666
|
+
|
667
|
+
(steps-1).downto(0) do |step|
|
668
|
+
(0..(step+1)).each do |i|
|
669
|
+
put_values[i] = (p_up*put_values[i+1].to_f+p_down*put_values[i].to_f)*r_inv
|
670
|
+
prices[i] = d*prices[i+1].to_f
|
671
|
+
put_values[i] = [put_values[i].to_f,(k-prices[i].to_f)].max # check for exercise
|
672
|
+
end
|
673
|
+
end
|
674
|
+
return put_values[0]
|
675
|
+
end
|
676
|
+
|
677
|
+
##
|
678
|
+
#European Option (Call) using binomial approximations
|
679
|
+
# +s+: spot (underlying) price
|
680
|
+
# +k+: strike (exercise) price,
|
681
|
+
# +r+: interest rate
|
682
|
+
# +sigma+: volatility
|
683
|
+
# +t+: time to maturity
|
684
|
+
# +steps+: Number of steps in binomial tree
|
685
|
+
# *Returns* Option price
|
686
|
+
def self.european_call(s, k, r, sigma, t, steps)
|
687
|
+
r_tmp = Math.exp(r*(t/steps)) # interest rate for each step
|
688
|
+
r_inv = 1.0/r_tmp # inverse of interest rate
|
689
|
+
u = Math.exp(sigma*Math.sqrt(t/steps)) # up movement
|
690
|
+
uu = u*u;
|
691
|
+
d = 1.0/u;
|
692
|
+
p_up = (r_tmp-d)/(u-d);
|
693
|
+
p_down = 1.0-p_up;
|
694
|
+
prices = Array.new(steps+1) # price of underlying
|
695
|
+
prices[0] = s*(d**steps) # fill in the endnodes.
|
696
|
+
|
697
|
+
(1..(steps+1)).each do |i|
|
698
|
+
prices[i] = uu*prices[i-1].to_f
|
699
|
+
end
|
700
|
+
|
701
|
+
call_values = Array.new(steps+1) # value of corresponding call
|
702
|
+
|
703
|
+
(0..(steps+1)).each do |i|
|
704
|
+
call_values[i] = [0.0, (prices[i]-k)].max # call payoffs at maturity
|
705
|
+
end
|
706
|
+
|
707
|
+
(steps-1).downto(0) do |step|
|
708
|
+
(0..(step+1)).each do |i|
|
709
|
+
call_values[i] = (p_up*call_values[i+1].to_f+p_down*call_values[i].to_f)*r_inv
|
710
|
+
end
|
711
|
+
end
|
712
|
+
return call_values[0]
|
713
|
+
end
|
714
|
+
|
715
|
+
##
|
716
|
+
#European Option (Put) using binomial approximations
|
717
|
+
# +s+: spot (underlying) price
|
718
|
+
# +k+: strike (exercise) price,
|
719
|
+
# +r+: interest rate
|
720
|
+
# +sigma+: volatility
|
721
|
+
# +t+: time to maturity
|
722
|
+
# +steps+: Number of steps in binomial tree
|
723
|
+
# *Returns* Option price
|
724
|
+
def self.european_put(s, k, r, sigma, t, steps)
|
725
|
+
r_tmp = Math.exp(r*(t/steps)) # interest rate for each step
|
726
|
+
r_inv = 1.0/r_tmp # inverse of interest rate
|
727
|
+
u = Math.exp(sigma*Math.sqrt(t/steps)) # up movement
|
728
|
+
uu = u*u
|
729
|
+
d = 1.0/u
|
730
|
+
p_up = (r_tmp-d)/(u-d)
|
731
|
+
p_down = 1.0-p_up
|
732
|
+
prices = Array.new(steps+1) # price of underlying
|
733
|
+
prices[0] = s*(d**steps) # fill in the endnodes.
|
734
|
+
|
735
|
+
(1..(steps+1)).each do |i|
|
736
|
+
prices[i] = uu*prices[i-1].to_f
|
737
|
+
end
|
738
|
+
|
739
|
+
put_values = Array.new(steps+1) # value of corresponding put
|
740
|
+
(0..(steps+1)).each do |i|
|
741
|
+
put_values[i] = [0.0, (k-prices[i])].max # put payoffs at maturity
|
742
|
+
end
|
743
|
+
|
744
|
+
(steps-1).downto(0) do |step|
|
745
|
+
(0..(step+1)).each do |i|
|
746
|
+
put_values[i] = (p_up*put_values[i+1].to_f+p_down*put_values[i].to_f)*r_inv
|
747
|
+
end
|
748
|
+
end
|
749
|
+
return put_values[0]
|
750
|
+
end
|
751
|
+
|
752
|
+
end
|
753
|
+
end
|