greeks 1.0
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.
- 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
|