greeks 1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +29 -0
- data/README.md +4 -0
- data/greeks.gemspec +27 -0
- data/lib/greeks/calculations/delta.rb +29 -0
- data/lib/greeks/calculations/gamma.rb +19 -0
- data/lib/greeks/calculations/iv.rb +117 -0
- data/lib/greeks/calculations/normal_distribution.rb +27 -0
- data/lib/greeks/calculations/rho.rb +23 -0
- data/lib/greeks/calculations/theta.rb +23 -0
- data/lib/greeks/calculations/time_values.rb +181 -0
- data/lib/greeks/calculations/vega.rb +17 -0
- data/lib/greeks/version.rb +5 -0
- data/lib/greeks.rb +371 -0
- data/spec/greeks/calculations/delta_spec.rb +9 -0
- data/spec/greeks/calculations/gamma_spec.rb +8 -0
- data/spec/greeks/calculations/iv_option_price_spec.rb +67 -0
- data/spec/greeks/calculations/iv_spec.rb +56 -0
- data/spec/greeks/calculations/iv_vega_spec.rb +66 -0
- data/spec/greeks/calculations/normal_distribution_spec.rb +22 -0
- data/spec/greeks/calculations/rho_spec.rb +9 -0
- data/spec/greeks/calculations/theta_spec.rb +10 -0
- data/spec/greeks/calculations/time_values_spec.rb +79 -0
- data/spec/greeks/calculations/vega_spec.rb +10 -0
- data/spec/greeks/greeks_spec.rb +110 -0
- data/spec/spec_helper.rb +89 -0
- metadata +141 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0d3ca525eaf15c5be09d9cdba6adc4dfd619089d
|
4
|
+
data.tar.gz: e991c828fbdaaff0d99a23e3137115136bd00b5c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c76eff692a4ee4d8710f39a7c3c121133227f67bbc50ee8d3126c22946a17f6b41509a3ca7c79294335b4799ea2f4e54afc50057c4ea6a4343bb925ac0429a49
|
7
|
+
data.tar.gz: 501bfefa7861518237aab7c7cb3187598264fec413c27ec7b345f08267d4e3f830ff3d394e1091553dbc8730ed8e4a6545d850e272b7f9609a51b91aa980b06f
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
greeks (1.0)
|
5
|
+
hash_plus
|
6
|
+
require_all
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
diff-lcs (1.2.4)
|
12
|
+
hash_plus (1.1)
|
13
|
+
require_all (1.2.1)
|
14
|
+
rspec (2.13.0)
|
15
|
+
rspec-core (~> 2.13.0)
|
16
|
+
rspec-expectations (~> 2.13.0)
|
17
|
+
rspec-mocks (~> 2.13.0)
|
18
|
+
rspec-core (2.13.1)
|
19
|
+
rspec-expectations (2.13.0)
|
20
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
21
|
+
rspec-mocks (2.13.1)
|
22
|
+
|
23
|
+
PLATFORMS
|
24
|
+
ruby
|
25
|
+
|
26
|
+
DEPENDENCIES
|
27
|
+
greeks!
|
28
|
+
rspec
|
29
|
+
rspec-expectations
|
data/README.md
ADDED
data/greeks.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "greeks/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = "greeks"
|
7
|
+
gem.version = Math::Greeks::VERSION
|
8
|
+
gem.authors = ["Glenn Nagel"]
|
9
|
+
gem.email = ["glenn@mercury-wireless.com"]
|
10
|
+
gem.homepage = "https://github.com/gnagel/greeks"
|
11
|
+
gem.summary = %q{Calculate greeks for options trading (Implied Volatility, Delta, Gamma, Vega, Rho, and Theta)}
|
12
|
+
gem.description = %q{Calculate greeks (iv, delta, gamma, vega, rho, theta)}
|
13
|
+
gem.license = 'MIT'
|
14
|
+
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib", "tasks"]
|
20
|
+
|
21
|
+
# System
|
22
|
+
gem.add_dependency('require_all')
|
23
|
+
gem.add_dependency('hash_plus')
|
24
|
+
|
25
|
+
gem.add_development_dependency('rspec')
|
26
|
+
gem.add_development_dependency('rspec-expectations')
|
27
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Math
|
2
|
+
module GreekCalculations
|
3
|
+
# Delta
|
4
|
+
# A measurement of the change in the price of an option resulting from a change in the price of the underlying security.
|
5
|
+
# Delta is positive for calls and negative for puts. Delta can be calculated as the dollar change of the option that an
|
6
|
+
# investor can expect for a one-dollar change in the underlying security. For example, let's say an option on a stock
|
7
|
+
# trading at $50 costs $1 and has a delta of $0.50 per dollar of underlying stock price change. If the stock price rises
|
8
|
+
# to $52, the price of the option will increase by $1 (the $2 price change times the $0.50 delta). After the stock price
|
9
|
+
# movement, the option will be worth $2 ($1 initial cost plus $1 delta). Delta can also be calculated as a percentage
|
10
|
+
# change in the option price for a one-percent change in the underlying security; this method of viewing the delta value
|
11
|
+
# is also known as "leverage."
|
12
|
+
def delta(opts)
|
13
|
+
opts.requires_fields(:option_type, :rate_vs_expires, :d1_normal_distribution, :iv)
|
14
|
+
|
15
|
+
return nil if opts[:iv].nil?
|
16
|
+
|
17
|
+
multiplier = case opts[:option_type]
|
18
|
+
when :call
|
19
|
+
1.0
|
20
|
+
when :put
|
21
|
+
-1.0
|
22
|
+
else
|
23
|
+
raise "Invalid option_type = #{opts[:option_type].inspect}"
|
24
|
+
end
|
25
|
+
|
26
|
+
multiplier * opts[:rate_vs_expires] * opts[:d1_normal_distribution]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Math
|
2
|
+
module GreekCalculations
|
3
|
+
# Gamma
|
4
|
+
# A measurement of the change in delta as the price of the underlying stock changes. As the underlying stock price changes,
|
5
|
+
# the delta of the option changes, too. Gamma indicates how quickly your exposure to the price movement of the underlying
|
6
|
+
# security changes as the price of the underlying security varies. For example, if you have a call with a strike of $50
|
7
|
+
# and the stock price is $50, the delta likely will be approximately $0.50 for a one-dollar movement of the stock.
|
8
|
+
# At a stock price of $60, the delta will be greater, closer to $0.75. At a stock price of $40, the delta will be less,
|
9
|
+
# closer to $0.25. In this example, if the stock price changes from $50 to $60, then the delta will change from $0.50 to $0.75.
|
10
|
+
# The $10 change in stock price caused a $0.25 change in delta, so gamma is approximately $0.25/10, or $0.025, in this case.
|
11
|
+
def gamma(opts = {})
|
12
|
+
opts.requires_fields(:stock_price, :option_expires_pct_year_sqrt, :iv, :nd1, :rate_vs_expires)
|
13
|
+
|
14
|
+
return nil if opts[:iv].nil?
|
15
|
+
|
16
|
+
opts[:nd1] * opts[:rate_vs_expires] / (opts[:stock_price] * opts[:iv] * opts[:option_expires_pct_year_sqrt])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module Math
|
2
|
+
module GreekCalculations
|
3
|
+
def iv(opts)
|
4
|
+
opts.requires_fields(:stock_price, :option_strike, :option_expires_pct_year, :option_expires_pct_year_sqrt, :federal_reserve_interest_rate_f, :stock_dividend_rate_f, :option_type, :option_price, :rate_vs_expires, :price_vs_rate_vs_expires, :strike_vs_fed_vs_expires, :price_ratio_log_less_rates)
|
5
|
+
|
6
|
+
return nil if opts[:option_price].nil? || opts[:option_price] < 0
|
7
|
+
|
8
|
+
iv_calc(
|
9
|
+
opts[:stock_price],
|
10
|
+
opts[:option_strike],
|
11
|
+
opts[:option_expires_pct_year],
|
12
|
+
opts[:option_expires_pct_year_sqrt],
|
13
|
+
opts[:federal_reserve_interest_rate_f],
|
14
|
+
opts[:stock_dividend_rate_f],
|
15
|
+
opts[:option_type],
|
16
|
+
opts[:option_price],
|
17
|
+
opts[:price_vs_rate_vs_expires],
|
18
|
+
opts[:price_ratio_log_less_rates],
|
19
|
+
opts[:strike_vs_fed_vs_expires]
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def iv_vega(stock_price, option_strike, option_expires_pct_year, option_expires_pct_year_sqrt, volatility_guess, federal_reserve_interest_rate_f, stock_dividend_rate_f, price_ratio_log_less_rates, price_vs_rate_vs_expires)
|
25
|
+
var_d1 = (price_ratio_log_less_rates + volatility_guess * volatility_guess * option_expires_pct_year / 2) / (volatility_guess * option_expires_pct_year_sqrt)
|
26
|
+
var_nd = Math.exp(-var_d1 * var_d1 / 2) / Math::sqrt(2 * Math::PI)
|
27
|
+
return price_vs_rate_vs_expires * option_expires_pct_year_sqrt * var_nd
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def iv_option_price(stock_price, option_strike, option_expires_pct_year, option_expires_pct_year_sqrt, volatility_guess, federal_reserve_interest_rate_f, stock_dividend_rate_f, option_type, price_ratio_log_less_rates, price_vs_rate_vs_expires, strike_vs_fed_vs_expires)
|
32
|
+
var_d1 = (price_ratio_log_less_rates + volatility_guess * volatility_guess * option_expires_pct_year / 2) / (volatility_guess * option_expires_pct_year_sqrt)
|
33
|
+
var_d2 = var_d1 - volatility_guess * option_expires_pct_year_sqrt
|
34
|
+
|
35
|
+
case option_type
|
36
|
+
when :call
|
37
|
+
return price_vs_rate_vs_expires * normal_distribution(var_d1) - strike_vs_fed_vs_expires * normal_distribution(var_d2)
|
38
|
+
when :put
|
39
|
+
return strike_vs_fed_vs_expires * normal_distribution(-var_d2) - price_vs_rate_vs_expires * normal_distribution(-var_d1)
|
40
|
+
else
|
41
|
+
raise "Invalid option_type = #{option_type.inspect}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def iv_volatility_guess0(stock_price, option_strike, option_expires_pct_year, federal_reserve_interest_rate_f, stock_dividend_rate_f)
|
47
|
+
Math.sqrt(
|
48
|
+
(Math.log(stock_price / option_strike) + (federal_reserve_interest_rate_f - stock_dividend_rate_f) * option_expires_pct_year).abs * 2 / option_expires_pct_year)
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def iv_calc(stock_price, option_strike, option_expires_pct_year, option_expires_pct_year_sqrt, federal_reserve_interest_rate_f, stock_dividend_rate_f, option_type, option_price, price_vs_rate_vs_expires, price_ratio_log_less_rates, strike_vs_fed_vs_expires)
|
53
|
+
# Contstant values for the calculations
|
54
|
+
price_limit = [0.005, 0.01 * option_price].min
|
55
|
+
|
56
|
+
# Lambda for short-hand calculations
|
57
|
+
calc_option_price = lambda { |volatility_guess| iv_option_price(stock_price, option_strike, option_expires_pct_year, option_expires_pct_year_sqrt, volatility_guess, federal_reserve_interest_rate_f, stock_dividend_rate_f, option_type, price_ratio_log_less_rates, price_vs_rate_vs_expires, strike_vs_fed_vs_expires) }
|
58
|
+
|
59
|
+
# Lambda for short-hand calculations
|
60
|
+
calc_vega = lambda { |volatility_guess| iv_vega(stock_price, option_strike, option_expires_pct_year, option_expires_pct_year_sqrt, volatility_guess, federal_reserve_interest_rate_f, stock_dividend_rate_f, price_ratio_log_less_rates, price_vs_rate_vs_expires) }
|
61
|
+
|
62
|
+
# Lambda for short-hand calculations
|
63
|
+
calc_volatility_guess1 = lambda { |var_volatility_guess, var_option_price, var_vega| var_volatility_guess - (var_option_price - option_price) / var_vega }
|
64
|
+
|
65
|
+
# Lambda for short-hand calculations
|
66
|
+
is_terminal_volatility_guess = lambda { |var_option_price| ((option_price - var_option_price).abs < price_limit) }
|
67
|
+
|
68
|
+
# Lambda for short-hand calculations
|
69
|
+
cleanup_volatility_guess = lambda { |volatility_guess| volatility_guess.nil? || volatility_guess <= 0 ? nil : volatility_guess.to_f }
|
70
|
+
|
71
|
+
var_volatility_guess = iv_volatility_guess0(stock_price, option_strike, option_expires_pct_year, federal_reserve_interest_rate_f, stock_dividend_rate_f)
|
72
|
+
var_volatility_guess = 0.1 if var_volatility_guess <= 0
|
73
|
+
var_option_price = calc_option_price.call(var_volatility_guess)
|
74
|
+
|
75
|
+
if is_terminal_volatility_guess.call(var_option_price)
|
76
|
+
return cleanup_volatility_guess.call(var_volatility_guess)
|
77
|
+
end
|
78
|
+
|
79
|
+
var_vega = calc_vega.call(var_volatility_guess)
|
80
|
+
|
81
|
+
var_volatility_guess1 = calc_volatility_guess1.call(var_volatility_guess, var_option_price, var_vega)
|
82
|
+
|
83
|
+
var_step = 1
|
84
|
+
max_steps = 13
|
85
|
+
while ((var_volatility_guess - var_volatility_guess1).abs > 0.0001 && var_step < max_steps)
|
86
|
+
var_volatility_guess = var_volatility_guess1
|
87
|
+
var_option_price = calc_option_price.call(var_volatility_guess)
|
88
|
+
|
89
|
+
if is_terminal_volatility_guess.call(var_option_price)
|
90
|
+
return cleanup_volatility_guess.call(var_volatility_guess)
|
91
|
+
end
|
92
|
+
|
93
|
+
var_vega = calc_vega.call(var_volatility_guess)
|
94
|
+
|
95
|
+
var_volatility_guess1 = calc_volatility_guess1.call(var_volatility_guess, var_option_price, var_vega)
|
96
|
+
if (var_volatility_guess1 < 0)
|
97
|
+
return cleanup_volatility_guess.call(var_volatility_guess1)
|
98
|
+
end
|
99
|
+
|
100
|
+
var_step += 1
|
101
|
+
end
|
102
|
+
|
103
|
+
if (var_step < max_steps)
|
104
|
+
return cleanup_volatility_guess.call(var_volatility_guess1)
|
105
|
+
end
|
106
|
+
|
107
|
+
var_option_price = calc_option_price.call(var_volatility_guess1)
|
108
|
+
|
109
|
+
if is_terminal_volatility_guess.call(var_option_price)
|
110
|
+
return cleanup_volatility_guess.call(var_volatility_guess1)
|
111
|
+
else
|
112
|
+
return nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Math
|
2
|
+
module GreekCalculations
|
3
|
+
# Moddeled after the Excel NORMSDIST function
|
4
|
+
def normal_distribution(value)
|
5
|
+
p = 0.2316419
|
6
|
+
b1 = 0.319381530
|
7
|
+
b2 = -0.356563782
|
8
|
+
b3 = 1.781477937
|
9
|
+
b4 = -1.821255978
|
10
|
+
b5 = 1.330274429
|
11
|
+
|
12
|
+
y = value.abs
|
13
|
+
z = Math.exp(-y*y/2) / Math.sqrt(2 * Math::PI)
|
14
|
+
t = 1 / ( 1 + p * y)
|
15
|
+
cum = 1 - z * (b1*t + b2*t*t + b3*t*t*t + b4*t*t*t*t + b5*t*t*t*t*t)
|
16
|
+
|
17
|
+
cum = 1 - cum if (value < 0)
|
18
|
+
cum
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
# Normal distribution function (Gaussian bell curve)
|
23
|
+
def normal_distribution_gaussian(value)
|
24
|
+
Math.exp(-0.5 * value * value) / Math.sqrt(2 * Math::PI)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Math
|
2
|
+
module GreekCalculations
|
3
|
+
# Rho
|
4
|
+
# The change in the value of an option for a change in the prevailing interest rate that matches the duration of the option,
|
5
|
+
# all else held equal. Generally rho is not a big driver of price changes for options, as interest rates tend to be relatively stable.
|
6
|
+
def rho(opts = {})
|
7
|
+
opts.requires_fields(:option_type, :option_expires_pct_year, :strike_vs_fed_vs_expires, :d2_normal_distribution, :iv)
|
8
|
+
|
9
|
+
return nil if opts[:iv].nil?
|
10
|
+
|
11
|
+
multiplier = case opts[:option_type]
|
12
|
+
when :call
|
13
|
+
1.0
|
14
|
+
when :put
|
15
|
+
-1.0
|
16
|
+
else
|
17
|
+
raise "Invalid option_type = #{opts[:option_type].inspect}"
|
18
|
+
end
|
19
|
+
|
20
|
+
multiplier * opts[:option_expires_pct_year] * opts[:strike_vs_fed_vs_expires] * opts[:d2_normal_distribution] / 100
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Math
|
2
|
+
module GreekCalculations
|
3
|
+
def theta(opts = {})
|
4
|
+
opts.requires_fields(:stock_dividend_rate_f, :federal_reserve_interest_rate_f, :option_type, :option_expires_pct_year_sqrt, :iv, :strike_vs_fed_vs_expires, :price_vs_rate_vs_expires, :nd1, :d1_normal_distribution, :d2_normal_distribution)
|
5
|
+
|
6
|
+
return nil if opts[:iv].nil?
|
7
|
+
|
8
|
+
part0 = opts[:price_vs_rate_vs_expires] * opts[:nd1] * opts[:iv]
|
9
|
+
part1 = 2 * opts[:option_expires_pct_year_sqrt]
|
10
|
+
part2 = opts[:stock_dividend_rate_f] * opts[:price_vs_rate_vs_expires] * opts[:d1_normal_distribution]
|
11
|
+
part3 = opts[:federal_reserve_interest_rate_f] * opts[:strike_vs_fed_vs_expires] * opts[:d2_normal_distribution]
|
12
|
+
|
13
|
+
case opts[:option_type]
|
14
|
+
when :call
|
15
|
+
return (-part0 / part1 + part2 - part3) / 365
|
16
|
+
when :put
|
17
|
+
return (-part0 / part1 - part2 + part3) / 365
|
18
|
+
else
|
19
|
+
raise "Invalid option_type = #{opts[:option_type].inspect}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
module Math
|
2
|
+
module GreekCalculations
|
3
|
+
|
4
|
+
def nil_or_gte0(value)
|
5
|
+
value.nil? || value.to_f < 0 ? nil : value
|
6
|
+
end
|
7
|
+
|
8
|
+
|
9
|
+
# Intrinsic Value
|
10
|
+
# The value that the option would pay if it were executed today. For example, if a stock is trading at $40,
|
11
|
+
# a call on that stock with a strike price of $35 would have $5 of intrinsic value ($40-$35) if it were
|
12
|
+
# exercised today. However, the call should actually be worth more than $5 to account for the value of the
|
13
|
+
# chance of any further appreciation until expiration, and the difference between the price and the
|
14
|
+
# intrinsic value would be the time value.
|
15
|
+
def premium_value(opts)
|
16
|
+
opts.requires_fields(:option_type, :option_strike, :stock_price)
|
17
|
+
|
18
|
+
case opts[:option_type]
|
19
|
+
when :call
|
20
|
+
return [opts[:stock_price] - opts[:option_strike], 0].max
|
21
|
+
when :put
|
22
|
+
return [opts[:option_strike] - opts[:stock_price], 0].max
|
23
|
+
else
|
24
|
+
raise ArgumentError, "Invalid option_type = #{opts[:option_type]}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
# Time Value
|
30
|
+
# The value of an option that captures the chance of further appreciation before expiration.
|
31
|
+
# The value of an option can be broken down into intrinsic value, or the value of the option
|
32
|
+
# if it were exercised today, and time value, or the added value of the option over and above
|
33
|
+
# the intrinsic value. For example, if a stock is trading at $40 and a call with a strike price
|
34
|
+
# of $35 were trading for $7, the call would have a $5 intrinsic value ($40-$35) and a $2 time value ($7-$5).
|
35
|
+
# Time value will decay by expiration assuming the underlying security stays at the same price.
|
36
|
+
def time_value(opts)
|
37
|
+
opts.requires_fields(:option_price, :premium_value)
|
38
|
+
|
39
|
+
return nil if opts[:option_price].nil?
|
40
|
+
return nil if opts[:option_price] < 0
|
41
|
+
|
42
|
+
nil_or_gte0(opts[:option_price] - opts[:premium_value])
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Annualized Premium
|
47
|
+
# The annualized premium is the value of the option divided by the strike price. You can use annualized
|
48
|
+
# premium to develop an intuitive understanding of how much the market is "paying" for a dollar of risk.
|
49
|
+
# For example, if a stock is trading at $50 and you sell a $50 strike 6 month call for $4, you are getting
|
50
|
+
# paid 8% in 6 months, or about 16% annualized, in exchange for being willing to buy at $50, the current price.
|
51
|
+
def annualized_premium_value
|
52
|
+
opts.requires_fields(:option_price, :option_strike, :option_expires_pct_year)
|
53
|
+
|
54
|
+
return nil if opts[:option_price].nil?
|
55
|
+
return nil if opts[:option_price] < 0
|
56
|
+
|
57
|
+
nil_or_gte0(100 * Math.log(1 + opts[:option_price] / opts[:option_strike]) / opts[:option_expires_pct_year])
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
# Annualized Time Value
|
62
|
+
# The time value of the option divided by the strike price, then annualized. You can use annualized
|
63
|
+
# time value to develop an intuitive understanding of how much value the option market is adding to
|
64
|
+
# an in-the-money option beyond the intrinsic value. For example, if a stock is trading at $40 and a
|
65
|
+
# six month call on that stock with a strike price of $35 has an intrinsic value of $5 and a total
|
66
|
+
# value of $7, the time value ($2) divided by the strike is ($2/$40) = 5%. Annualizing that time value
|
67
|
+
# to a one year horizon on a continuously compounded basis yields 9.76% (2 × ln(1 + 0.05)).
|
68
|
+
def annualized_time_value
|
69
|
+
opts.requires_fields(:option_strike, :option_expires_pct_year, :time_value)
|
70
|
+
|
71
|
+
return nil if opts[:time_value].nil?
|
72
|
+
|
73
|
+
nil_or_gte0(100 * Math.log(1.0 + opts[:time_value] / opts[:option_strike]) / opts[:option_expires_pct_year])
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
# Chance of Breakeven
|
78
|
+
# The probability that a stock will be trading beyond the breakeven price as implied by the option price.
|
79
|
+
# Chance of Breakeven can be used to get a sense for the valuation of the option by comparing the markets'
|
80
|
+
# estimate of Chance of Breakeven to estimates derived from your own fundamental research.
|
81
|
+
# If you believe the Chance of Breakeven is less than the probability that a stock will be beyond the
|
82
|
+
# breakeven price at option expiration, then you believe the option is undervalued, and visa versa.
|
83
|
+
def break_even(opts)
|
84
|
+
opts.requires_fields(:option_type, :option_price, :option_strike, :option_expires_pct_year, :option_expires_pct_year_sqrt, :stock_price, :stock_dividend_rate_f, :federal_reserve_interest_rate_f, :iv)
|
85
|
+
|
86
|
+
return nil if opts[:option_price].nil?
|
87
|
+
return nil if opts[:option_price] < 0
|
88
|
+
return nil if opts[:iv].nil?
|
89
|
+
|
90
|
+
part1 = (opts[:federal_reserve_interest_rate_f] - opts[:stock_dividend_rate_f] - opts[:iv] * opts[:iv] / 2) * opts[:option_expires_pct_year]
|
91
|
+
part2 = opts[:iv] * opts[:option_expires_pct_year_sqrt]
|
92
|
+
|
93
|
+
case opts[:option_type]
|
94
|
+
when :call
|
95
|
+
return normal_distribution((Math.log(opts[:stock_price] / (opts[:option_strike] + opts[:option_price])) + part1) / part2)
|
96
|
+
when :put
|
97
|
+
return normal_distribution(-(Math.log(opts[:stock_price] / (opts[:option_strike] - opts[:option_price])) + part1) / part2)
|
98
|
+
else
|
99
|
+
raise ArgumentError, "Invalid option_type = #{opts[:option_type]}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
#####
|
105
|
+
# Misc calculations
|
106
|
+
#####
|
107
|
+
|
108
|
+
|
109
|
+
def misc_price_ratio_log_less_rates(opts)
|
110
|
+
opts.requires_fields(:stock_price, :option_strike, :option_expires_pct_year, :federal_reserve_interest_rate_f, :stock_dividend_rate_f)
|
111
|
+
|
112
|
+
Math.log(opts[:stock_price] / opts[:option_strike]) + (opts[:federal_reserve_interest_rate_f] - opts[:stock_dividend_rate_f]) * opts[:option_expires_pct_year]
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def misc_rate_vs_expires(opts)
|
117
|
+
opts.requires_fields(:option_expires_pct_year, :stock_dividend_rate_f)
|
118
|
+
|
119
|
+
Math.exp(opts[:option_expires_pct_year] * -opts[:stock_dividend_rate_f])
|
120
|
+
end
|
121
|
+
|
122
|
+
|
123
|
+
def misc_price_vs_rate_vs_expires(opts)
|
124
|
+
opts.requires_fields(:stock_price, :option_expires_pct_year, :stock_dividend_rate_f)
|
125
|
+
|
126
|
+
opts[:stock_price] * misc_rate_vs_expires(opts)
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
def misc_strike_vs_fed_vs_expires(opts)
|
131
|
+
opts.requires_fields(:option_strike, :option_expires_pct_year, :federal_reserve_interest_rate_f)
|
132
|
+
|
133
|
+
opts[:option_strike] * Math.exp(opts[:option_expires_pct_year] * -opts[:federal_reserve_interest_rate_f])
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
def misc_nd1(opts)
|
138
|
+
opts.requires_fields(:d1)
|
139
|
+
|
140
|
+
return nil if opts[:d1].nil?
|
141
|
+
|
142
|
+
Math.exp(-0.5 * opts[:d1] * opts[:d1]) / Math.sqrt(2 * Math::PI)
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
def misc_d1(opts)
|
147
|
+
opts.requires_fields(:price_ratio_log_less_rates, :iv, :option_expires_pct_year, :option_expires_pct_year_sqrt)
|
148
|
+
|
149
|
+
return nil if opts[:iv].nil?
|
150
|
+
|
151
|
+
(opts[:price_ratio_log_less_rates] + opts[:iv] * opts[:iv] * opts[:option_expires_pct_year] / 2) / (opts[:iv] * opts[:option_expires_pct_year_sqrt])
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
def misc_d2(opts)
|
156
|
+
opts.requires_fields(:d1, :iv, :option_expires_pct_year_sqrt)
|
157
|
+
|
158
|
+
return nil if opts[:iv].nil?
|
159
|
+
|
160
|
+
opts[:d1] - opts[:iv] * opts[:option_expires_pct_year_sqrt]
|
161
|
+
end
|
162
|
+
|
163
|
+
def misc_d_normal_distribution(opts)
|
164
|
+
opts.requires_fields(:option_type, :d_value)
|
165
|
+
|
166
|
+
return nil if opts[:d_value].nil?
|
167
|
+
|
168
|
+
multiplier = case opts[:option_type]
|
169
|
+
when :call
|
170
|
+
1.0
|
171
|
+
when :put
|
172
|
+
-1.0
|
173
|
+
else
|
174
|
+
raise ArgumentError, "Invalid option_type = #{opts[:option_type]}"
|
175
|
+
end
|
176
|
+
|
177
|
+
normal_distribution(multiplier * opts[:d_value])
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Math
|
2
|
+
module GreekCalculations
|
3
|
+
# Vega
|
4
|
+
# The change in the price of an option for a change in the implied volatility of the option, all else held equal.
|
5
|
+
# In general, as the options market thinks it is more difficult to value a stock, implied volatility and therefore
|
6
|
+
# the price of the options will increase. For example, if an option is trading for $1, the implied volatility is 20%,
|
7
|
+
# and the vega is $0.05, then a one-percentage-point increase in implied volatility to 21% would correspond to an increase in
|
8
|
+
# the price of the option to $1.05. In percentage terms, the vega in this case would be ($0.05/$1.00)/(1 percentage point) = 5%.
|
9
|
+
def vega(opts = {})
|
10
|
+
opts.requires_fields(:price_vs_rate_vs_expires, :nd1, :option_expires_pct_year_sqrt, :iv)
|
11
|
+
|
12
|
+
return nil if opts[:iv].nil?
|
13
|
+
|
14
|
+
opts[:price_vs_rate_vs_expires] * opts[:option_expires_pct_year_sqrt] * opts[:nd1] / 100
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|