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.
@@ -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