trading_formulas 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,369 @@
1
+ module TradingFormulas
2
+ ##
3
+ # Author:: Matt.Osentoski (matt.osentoski@gmail.com)
4
+ #
5
+ # This module contains formulas based on the Black Scholes model
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 BlackScholes
11
+
12
+ ##
13
+ # Normal distribution
14
+ #
15
+ # +z+: Value to apply to a Normal distribution
16
+ # *Returns* Normal distribution
17
+ #
18
+ def self.n(z)
19
+ return (1.0/Math.sqrt(2.0*Math::PI))*Math.exp(-0.5*z*z)
20
+ end
21
+
22
+ ##
23
+ # Cumulative normal distribution
24
+ #
25
+ # +z+: Value to apply to a Cumulative normal distribution
26
+ # *Returns* Cumulative normal distribution
27
+ #
28
+ def self.N(z)
29
+ if (z > 6.0) # this guards against overflow
30
+ return 1.0
31
+ end
32
+ if (z < -6.0)
33
+ return 0.0
34
+ end
35
+
36
+ b1 = 0.31938153
37
+ b2 = -0.356563782
38
+ b3 = 1.781477937
39
+ b4 = -1.821255978
40
+ b5 = 1.330274429
41
+ p = 0.2316419
42
+ c2 = 0.3989423
43
+
44
+ a = z.abs
45
+ t = 1.0/(1.0+a*p)
46
+ b = c2*Math.exp((-z)*(z/2.0))
47
+ n = ((((b5*t+b4)*t+b3)*t+b2)*t+b1)*t
48
+ n = 1.0-b*n
49
+ if ( z < 0.0 )
50
+ n = 1.0 - n
51
+ end
52
+ return n
53
+ end
54
+
55
+ ##
56
+ # Black Scholes formula (Call)
57
+ # Black and Scholes (1973) and Merton (1973)
58
+ #
59
+ # +s+: spot (underlying) price
60
+ # +k+: strike (exercise) price,
61
+ # +r+: interest rate
62
+ # +sigma+: volatility
63
+ # +time+: time to maturity
64
+ # *Returns* Option price
65
+ #
66
+ def self.call(s, k, r, sigma, time)
67
+ time_sqrt = Math.sqrt(time)
68
+ d1 = (Math.log(s/k)+r*time)/(sigma*time_sqrt)+0.5*sigma*time_sqrt
69
+ d2 = d1-(sigma*time_sqrt)
70
+ return s*N(d1) - k*Math.exp(-r*time)*N(d2)
71
+ end
72
+
73
+ ##
74
+ # Black Scholes formula (Put)
75
+ # Black and Scholes (1973) and Merton (1973)
76
+ #
77
+ # +s+: spot (underlying) price
78
+ # +k+: strike (exercise) price,
79
+ # +r+: interest rate
80
+ # +sigma+: volatility
81
+ # +time+: time to maturity
82
+ # *Returns* Option price
83
+ #
84
+ def self.put(s, k, r, sigma, time)
85
+ time_sqrt = Math.sqrt(time)
86
+ d1 = (Math.log(s/k)+r*time)/(sigma*time_sqrt) + 0.5*sigma*time_sqrt
87
+ d2 = d1-(sigma*time_sqrt)
88
+ return k*Math.exp(-r*time)*N(-d2) - s*N(-d1)
89
+ end
90
+
91
+ ##
92
+ # Delta of the Black Scholes formula (Call)
93
+ # +s+: spot (underlying) price
94
+ # +k+: strike (exercise) price,
95
+ # +r+: interest rate
96
+ # +sigma+: volatility
97
+ # +time+: time to maturity
98
+ # *Returns* Delta of the option
99
+ #
100
+ def self.delta_call(s, k, r, sigma, time)
101
+ time_sqrt = Math.sqrt(time)
102
+ d1 = (Math.log(s/k)+r*time)/(sigma*time_sqrt) + 0.5*sigma*time_sqrt
103
+ delta = N(d1)
104
+ return delta
105
+ end
106
+
107
+ ##
108
+ # Delta of the Black Scholes formula (Put)
109
+ # +s+: spot (underlying) price
110
+ # +k+: strike (exercise) price,
111
+ # +r+: interest rate
112
+ # +sigma+: volatility
113
+ # +time+: time to maturity
114
+ # *Returns* Delta of the option
115
+ #
116
+ def self.delta_put(s, k, r, sigma, time)
117
+ time_sqrt = Math.sqrt(time)
118
+ d1 = (Math.log(s/k)+r*time)/(sigma*time_sqrt) + 0.5*sigma*time_sqrt
119
+ delta = -N(-d1)
120
+ return delta
121
+ end
122
+
123
+ ##
124
+ # Calculates implied volatility for the Black Scholes formula using
125
+ # binomial search algorithm
126
+ # (NOTE: In the original code a large negative number was used as an
127
+ # exception handling mechanism. This has been replace with a generic
128
+ # 'Exception' that is thrown. The original code is in place and commented
129
+ # if you want to use the pure version of this code)
130
+ #
131
+ # +s+: spot (underlying) price
132
+ # +k+: strike (exercise) price,
133
+ # +r+: interest rate
134
+ # +time+: time to maturity
135
+ # +option_price+: The price of the option
136
+ # *Returns* Sigma (implied volatility)
137
+ # *Raises* Exception if there is a problem with the binomial search
138
+ #
139
+ def self.implied_volatility_call_bisections(s,k,r,time,option_price)
140
+ if (option_price<0.99*(s-k*Math.exp(-time*r))) # check for arbitrage violations.
141
+ return 0.0 # Option price is too low if this happens
142
+ end
143
+
144
+ # simple binomial search for the implied volatility.
145
+ # relies on the value of the option increasing in volatility
146
+ accuracy = 1.0e-5 # make this smaller for higher accuracy
147
+ max_iterations = 100
148
+ high_value = 1e10
149
+ #ERROR = -1e40 // <--- original code
150
+
151
+ # want to bracket sigma. first find a maximum sigma by finding a sigma
152
+ # with a estimated price higher than the actual price.
153
+ sigma_low=1e-5
154
+ sigma_high=0.3
155
+ price = call(s,k,r,sigma_high,time)
156
+ while (price < option_price)
157
+ sigma_high = 2.0 * sigma_high # keep doubling.
158
+ price = call(s,k,r,sigma_high,time)
159
+ if (sigma_high>high_value)
160
+ #return ERROR # panic, something wrong. // <--- original code
161
+ raise "panic, something wrong." # Comment this line if you uncomment the line above
162
+ end
163
+ end
164
+
165
+ (0..max_iterations).each do |i|
166
+ sigma = (sigma_low+sigma_high)*0.5
167
+ price = call(s,k,r,sigma,time)
168
+ test = (price-option_price)
169
+ if (test.abs<accuracy)
170
+ return sigma
171
+ end
172
+ if (test < 0.0)
173
+ sigma_low = sigma
174
+ else
175
+ sigma_high = sigma
176
+ end
177
+ end
178
+ #return ERROR // <--- original code
179
+ raise "An error occurred" # Comment this line if you uncomment the line above
180
+ end
181
+
182
+ ##
183
+ # Calculates implied volatility for the Black Scholes formula using
184
+ # the Newton-Raphson formula
185
+ # (NOTE: In the original code a large negative number was used as an
186
+ # exception handling mechanism. This has been replace with a generic
187
+ # 'Exception' that is thrown. The original code is in place and commented
188
+ # if you want to use the pure version of this code)
189
+ #
190
+ # +s+: spot (underlying) price
191
+ # +k+: strike (exercise) price,
192
+ # +r+: interest rate
193
+ # +time+: time to maturity
194
+ # +option_price+: The price of the option
195
+ # *Returns* Sigma (implied volatility)
196
+ # *Raises* Exception if there is a problem with the newton formula
197
+ #
198
+ def self.implied_volatility_call_newton(s, k, r, time, option_price)
199
+ if (option_price<0.99*(s-k*Math.exp(-time*r))) # check for arbitrage violations. Option price is too low if this happens
200
+ return 0.0
201
+ end
202
+
203
+ max_iterations = 100
204
+ accuracy = 1.0e-5
205
+ t_sqrt = Math.sqrt(time)
206
+
207
+ sigma = (option_price/s)/(0.398*t_sqrt) # find initial value
208
+
209
+ (0..max_iterations).each do |i|
210
+ price = call(s,k,r,sigma,time)
211
+ diff = option_price -price
212
+ if (diff.abs<accuracy)
213
+ return sigma
214
+ end
215
+ d1 = (Math.log(s/k)+r*time)/(sigma*t_sqrt) + 0.5*sigma*t_sqrt
216
+ vega = s * t_sqrt * n(d1)
217
+ sigma = sigma + diff/vega
218
+ end
219
+ #return -99e10 # something screwy happened, should throw exception // <--- original code
220
+ raise "An error occurred" # Comment this line if you uncomment the line above
221
+ end
222
+
223
+ ##
224
+ # Calculate partial derivatives for a Black Scholes Option (Call)
225
+ # (NOTE: Originally, this method used argument pointer references as a
226
+ # way of returning the partial derivatives in C++. I've removed these
227
+ # references from the method signature and chose to return a tuple instead.)
228
+ # +s+: spot (underlying) price
229
+ # +k+: strike (exercise) price,
230
+ # +r+: interest rate
231
+ # +sigma+: volatility
232
+ # +time+: time to maturity
233
+ # *Returns* Tuple of partial derivatives: (Delta, Gamma, Theta, Vega, Rho)
234
+ # delta: partial wrt S
235
+ # gamma: second partial wrt S
236
+ # theta: partial wrt time
237
+ # vega: partial wrt sigma
238
+ # rho: partial wrt r
239
+ #
240
+ def self.partials_call(s, k, r, sigma, time)
241
+ time_sqrt = Math.sqrt(time)
242
+ d1 = (Math.log(s/k)+r*time)/(sigma*time_sqrt) + 0.5*sigma*time_sqrt
243
+ d2 = d1-(sigma*time_sqrt)
244
+ delta = N(d1)
245
+ gamma = n(d1)/(s*sigma*time_sqrt)
246
+ theta =- (s*sigma*n(d1))/(2*time_sqrt) - r*k*Math.exp( -r*time)*N(d2)
247
+ vega = s * time_sqrt*n(d1)
248
+ rho = k*time*Math.exp(-r*time)*N(d2)
249
+ return delta, gamma, theta, vega, rho
250
+ end
251
+
252
+ ##
253
+ # Calculate partial derivatives for a Black Scholes Option (Put)
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
+ # *Returns* Tuple of partial derivatives: (Delta, Gamma, Theta, Vega, Rho)
263
+ # Delta: partial wrt S
264
+ # Gamma: second partial wrt S
265
+ # Theta: partial wrt time
266
+ # Vega: partial wrt sigma
267
+ # Rho: partial wrt r
268
+ #
269
+ def self.partials_put(s, k, r, sigma, time)
270
+ time_sqrt = Math.sqrt(time)
271
+ d1 = (Math.log(s/k)+r*time)/(sigma*time_sqrt) + 0.5*sigma*time_sqrt
272
+ d2 = d1-(sigma*time_sqrt)
273
+ delta = -N(-d1)
274
+ gamma = n(d1)/(s*sigma*time_sqrt)
275
+ theta = -(s*sigma*n(d1)) / (2*time_sqrt)+ r*k * Math.exp(-r*time) * N(-d2)
276
+ vega = s * time_sqrt * n(d1)
277
+ rho = -k*time*Math.exp(-r*time) * N(-d2)
278
+ return delta, gamma, theta, vega, rho
279
+ end
280
+
281
+ ##
282
+ # European option (Call) with a continuous payout.
283
+ # The continuous payout would be for fees associated with the asset.
284
+ # For example, storage costs.
285
+ # +s+: spot (underlying) price
286
+ # +x+: strike (exercise) price,
287
+ # +r+: interest rate
288
+ # +q+: yield on underlying
289
+ # +sigma+: volatility
290
+ # +time+: time to maturity
291
+ # *Returns* Option price
292
+ #
293
+ def self.european_call_payout(s, x, r, q, sigma, time)
294
+ sigma_sqr = sigma**2
295
+ time_sqrt = Math.sqrt(time)
296
+ d1 = (Math.log(s/x) + (r-q + 0.5*sigma_sqr)*time)/(sigma*time_sqrt)
297
+ d2 = d1-(sigma*time_sqrt)
298
+ call_price = s * Math.exp(-q*time)* N(d1) - x * Math.exp(-r*time) * N(d2)
299
+ return call_price
300
+ end
301
+
302
+ ##
303
+ # European option (Put) with a continuous payout.
304
+ # The continuous payout would be for fees associated with the asset.
305
+ # For example, storage costs.
306
+ # +s+: spot (underlying) price
307
+ # +x+: strike (exercise) price,
308
+ # +r+: interest rate
309
+ # +q+: yield on underlying
310
+ # +sigma+: volatility
311
+ # +time+: time to maturity
312
+ # *Returns* Option price
313
+ #
314
+ def self.european_put_payout(s, k, r, q, sigma, time)
315
+ sigma_sqr = sigma**2
316
+ time_sqrt = Math.sqrt(time)
317
+ d1 = (Math.log(s/k) + (r-q + 0.5*sigma_sqr)*time)/(sigma*time_sqrt)
318
+ d2 = d1-(sigma*time_sqrt)
319
+ put_price = k * Math.exp(-r*time)*N(-d2)-s*Math.exp(-q*time)*N(-d1)
320
+ return put_price
321
+ end
322
+
323
+ ##
324
+ # European option for known dividends (Call)
325
+ # +s+: spot (underlying) price
326
+ # +k+: strike (exercise) price,
327
+ # +r+: interest rate
328
+ # +sigma+: volatility
329
+ # +time_to_maturity+: time to maturity
330
+ # +dividend_times+: Array of dividend times. (Ex: [0.25, 0.75] for 1/4 and 3/4 of a year)
331
+ # +dividend_amounts+: Array of dividend amounts for the 'dividend_times'
332
+ # *Returns* Option price
333
+ #
334
+ def self.european_call_dividends(s, k, r, sigma, time_to_maturity,
335
+ dividend_times, dividend_amounts )
336
+ adjusted_s = s
337
+ dividend_times.each_index do |i|
338
+ if (dividend_times[i]<=time_to_maturity)
339
+ adjusted_s = adjusted_s - dividend_amounts[i] * Math.exp(-r*dividend_times[i])
340
+ end
341
+ end
342
+ return call(adjusted_s,k,r,sigma,time_to_maturity)
343
+ end
344
+
345
+ ##
346
+ # European option for known dividends (Put)
347
+ # +s+: spot (underlying) price
348
+ # +k+: strike (exercise) price,
349
+ # +r+: interest rate
350
+ # +sigma+: volatility
351
+ # +time_to_maturity+: time to maturity
352
+ # +dividend_times+: Array of dividend times. (Ex: [0.25, 0.75] for 1/4 and 3/4 of a year)
353
+ # +dividend_amounts+: Array of dividend amounts for the 'dividend_times'
354
+ # *Returns*: Option price
355
+ #
356
+ def self.european_put_dividends(s, k, r, sigma, time_to_maturity,
357
+ dividend_times, dividend_amounts )
358
+ # reduce the current stock price by the amount of dividends.
359
+ adjusted_s=s
360
+ dividend_times.each_index do |i|
361
+ if (dividend_times[i]<=time_to_maturity)
362
+ adjusted_s = adjusted_s - dividend_amounts[i] * Math.exp(-r*dividend_times[i])
363
+ end
364
+ end
365
+ return put(adjusted_s,k,r,sigma,time_to_maturity)
366
+ end
367
+
368
+ end
369
+ end
@@ -0,0 +1,3 @@
1
+ module TradingFormulas
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,8 @@
1
+ require "trading_formulas/version"
2
+ require "trading_formulas/bermudan_options"
3
+ require "trading_formulas/black_scholes"
4
+ require "trading_formulas/binomial_options"
5
+
6
+ module TradingFormulas
7
+
8
+ end
@@ -0,0 +1,65 @@
1
+ require 'test/unit'
2
+ require 'trading_formulas'
3
+
4
+ class BermudanOptionsTest < Test::Unit::TestCase
5
+
6
+ def test_call
7
+ s = 80
8
+ k = 100
9
+ r = 0.20
10
+ q = 0.0
11
+ sigma = 0.25
12
+ time = 1.0
13
+ steps = 500
14
+ potential_exercise_times = [0.25, 0.5, 0.75]
15
+ test_val = TradingFormulas::BermudanOptions.call(s, k, r, q, sigma, time,
16
+ potential_exercise_times, steps)
17
+ assert_equal(7.14016, test_val.round(5))
18
+ end
19
+
20
+ ##
21
+ # Negative test case
22
+ def test_invalid_call
23
+ s = 80
24
+ k = 100
25
+ r = 0.20
26
+ q = 0.0
27
+ sigma = 0.25
28
+ time = 1.0
29
+ steps = 500
30
+ potential_exercise_times = [0.25, 0.5, 0.75]
31
+ test_val = TradingFormulas::BermudanOptions.call(s, k, r, q, sigma, time,
32
+ potential_exercise_times, steps)
33
+ assert_not_equal(7.14019, test_val.round(5))
34
+ end
35
+
36
+ def test_put
37
+ s = 80
38
+ k = 100
39
+ r = 0.20
40
+ q = 0.0
41
+ sigma = 0.25
42
+ time = 1.0
43
+ steps = 500
44
+ potential_exercise_times = [0.25, 0.5, 0.75]
45
+ test_val = TradingFormulas::BermudanOptions.put(s, k, r, q, sigma, time,
46
+ potential_exercise_times, steps)
47
+ assert_equal(15.8869, test_val.round(4))
48
+ end
49
+
50
+ ##
51
+ # Negative test case
52
+ def test_invalid_put
53
+ s = 80
54
+ k = 100
55
+ r = 0.20
56
+ q = 0.0
57
+ sigma = 0.25
58
+ time = 1.0
59
+ steps = 500
60
+ potential_exercise_times = [0.25, 0.5, 0.75]
61
+ test_val = TradingFormulas::BermudanOptions.put(s, k, r, q, sigma, time,
62
+ potential_exercise_times, steps)
63
+ assert_not_equal(15.8861, test_val.round(4))
64
+ end
65
+ end