wco_models 3.1.0.184 → 3.1.0.188
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 +4 -4
- data/README.txt +10 -1
- data/app/models/iro/alert.rb +63 -0
- data/app/models/iro/datapoint.rb +187 -0
- data/app/models/iro/date.rb +10 -0
- data/app/models/iro/option.rb +154 -0
- data/app/models/iro/option_black_scholes.rb +149 -0
- data/app/models/iro/position.rb +304 -0
- data/app/models/iro/priceitem.rb +93 -0
- data/app/models/iro/purse.rb +71 -0
- data/app/models/iro/stock.rb +141 -0
- data/app/models/iro/strategy.rb +284 -0
- data/app/models/wco/profile.rb +3 -0
- data/app/models/wco_email/message_stub.rb +2 -5
- data/app/views/wco/profiles/_form.haml +10 -0
- data/config/initializers/00_s3.rb +13 -0
- data/config/initializers/00_s3.rb-example +13 -0
- data/config/initializers/08_integrations.rb +9 -0
- data/config/initializers/08_integrations.rb-example +6 -0
- metadata +16 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f8e0b2d84f450124f19512789d6ab1c3811497c536e05486d410b25a9d9e77a
|
4
|
+
data.tar.gz: d88b39a9d47c902bfac62d8f97f34e7cb12ca15f7e3730ebf166b73856a18806
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2bbcc52eb28b820913e885171a0006d2f28d123144ae51dbf6092b84bf54640b03255a731715c9e29664b6b17ea036b143b87e691bf28053c63191c39b2aca9
|
7
|
+
data.tar.gz: e39abef4f5a2b7d3eaf494312737267b02b5515ad13942d9b0c81462a32af03a5a640c577cdadd0a1e80a32d62bd98811d1da19eba7f32e8a5c8ed36d0adac9b
|
data/README.txt
CHANGED
@@ -1,2 +1,11 @@
|
|
1
1
|
|
2
|
-
|
2
|
+
Wco Models.
|
3
|
+
|
4
|
+
== Test ==
|
5
|
+
|
6
|
+
Login to the localstack container, then:
|
7
|
+
|
8
|
+
awslocal s3api put-object --bucket wco-email-ses-development \
|
9
|
+
--key 00nn652jk1395ujdr3l11ib06jam0oevjqv2o4g1 \
|
10
|
+
--body /opt/tmp/00nn652jk1395ujdr3l11ib06jam0oevjqv2o4g1
|
11
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
|
2
|
+
class Iro::Alert
|
3
|
+
include Mongoid::Document
|
4
|
+
include Mongoid::Timestamps
|
5
|
+
include Mongoid::Paranoia
|
6
|
+
store_in collection: 'iro_alerts'
|
7
|
+
|
8
|
+
# SLEEP_TIME_SECONDS = Rails.env.production? ? 60 : 15
|
9
|
+
|
10
|
+
DIRECTION_ABOVE = 'ABOVE'
|
11
|
+
DIRECTION_BELOW = 'BELOW'
|
12
|
+
def self.directions_list
|
13
|
+
[ nil, DIRECTION_ABOVE, DIRECTION_BELOW ]
|
14
|
+
end
|
15
|
+
|
16
|
+
STATUS_ACTIVE = 'active'
|
17
|
+
STATUS_INACTIVE = 'inactive'
|
18
|
+
STATUSES = [ nil, 'active', 'inactive' ]
|
19
|
+
field :status, default: STATUS_ACTIVE
|
20
|
+
def self.active
|
21
|
+
where( status: STATUS_ACTIVE )
|
22
|
+
end
|
23
|
+
|
24
|
+
field :class_name, default: 'Iro::Stock'
|
25
|
+
validates :class_name, presence: true
|
26
|
+
|
27
|
+
field :symbol, type: String
|
28
|
+
validates :symbol, presence: true
|
29
|
+
|
30
|
+
field :direction, type: String
|
31
|
+
validates :direction, presence: true
|
32
|
+
|
33
|
+
field :strike, type: Float
|
34
|
+
validates :strike, presence: true
|
35
|
+
|
36
|
+
def do_run
|
37
|
+
alert = self
|
38
|
+
begin
|
39
|
+
price = Tda::Stock.get_quote( alert.symbol )&.last
|
40
|
+
return if !price
|
41
|
+
|
42
|
+
if ( alert.direction == alert.class::DIRECTION_ABOVE && price >= alert.strike ) ||
|
43
|
+
( alert.direction == alert.class::DIRECTION_BELOW && price <= alert.strike )
|
44
|
+
|
45
|
+
if Rails.env.production?
|
46
|
+
Iro::AlertMailer.stock_alert( alert.id.to_s ).deliver_later
|
47
|
+
else
|
48
|
+
Iro::AlertMailer.stock_alert( alert.id.to_s ).deliver_now
|
49
|
+
end
|
50
|
+
alert.update({ status: alert.class::STATUS_INACTIVE })
|
51
|
+
print '^'
|
52
|
+
|
53
|
+
end
|
54
|
+
rescue => err
|
55
|
+
puts! err, 'err'
|
56
|
+
::ExceptionNotifier.notify_exception(
|
57
|
+
err,
|
58
|
+
data: { alert: alert }
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
|
2
|
+
##
|
3
|
+
## Datapoints are at most daily!
|
4
|
+
## See Priceitem for intra-day data
|
5
|
+
##
|
6
|
+
class Iro::Datapoint
|
7
|
+
include Mongoid::Document
|
8
|
+
include Mongoid::Timestamps
|
9
|
+
store_in collection: 'iro_datapoints'
|
10
|
+
|
11
|
+
field :kind
|
12
|
+
validates :kind, presence: true
|
13
|
+
index({ kind: -1 })
|
14
|
+
KIND_CRYPTO = 'CRYPTO'
|
15
|
+
KIND_STOCK = 'STOCK'
|
16
|
+
KIND_OPTION = 'OPTION' ## but not PUT or CALL
|
17
|
+
KIND_CURRENCY = 'CURRENCY'
|
18
|
+
KIND_TREASURY = 'TREASURY'
|
19
|
+
|
20
|
+
field :symbol ## ticker, but use 'symbol' here
|
21
|
+
## crypto
|
22
|
+
SYMBOL_BTC = 'BTC'
|
23
|
+
SYMBOL_ETH = 'ETH'
|
24
|
+
## currencies
|
25
|
+
SYMBOL_JPY = 'JPY'
|
26
|
+
SYMBOL_COP = 'COP'
|
27
|
+
SUMBOL_EUR = 'EUR'
|
28
|
+
## treasuries
|
29
|
+
SYMBOL_T1MO = 'T1MO'
|
30
|
+
SYMBOL_T2MO = 'T2MO'
|
31
|
+
SYMBOL_T3MO = 'T3MO'
|
32
|
+
SYMBOL_T4MO = 'T4MO'
|
33
|
+
SYMBOL_T6MO = 'T6MO'
|
34
|
+
SYMBOL_T1YR = 'T1YR'
|
35
|
+
SYMBOL_T2YR = 'T2YR'
|
36
|
+
SYMBOL_T3YR = 'T3YR'
|
37
|
+
SYMBOL_T5YR = 'T5YR'
|
38
|
+
SYMBOL_T7YR = 'T7YR'
|
39
|
+
SYMBOL_T10YR = 'T10YR'
|
40
|
+
SYMBOL_T20YR = 'T20YR'
|
41
|
+
SYMBOL_T30YR = 'T30YR'
|
42
|
+
|
43
|
+
field :date, type: Date
|
44
|
+
index({ kind: -1, date: -1 })
|
45
|
+
validates :date, uniqueness: { scope: [ :symbol ] }
|
46
|
+
|
47
|
+
field :quote_at, type: DateTime
|
48
|
+
index({ kind: -1, quote_at: -1 })
|
49
|
+
validates :quote_at, uniqueness: { scope: [ :kind, :symbol ] } ## scope-by-kind is unnecessary here? _vp_ 2024-08-08
|
50
|
+
|
51
|
+
field :open, type: Float
|
52
|
+
field :high, type: Float
|
53
|
+
field :low, type: Float
|
54
|
+
def close; value; end
|
55
|
+
def close= a; value= a; end
|
56
|
+
|
57
|
+
field :value, type: Float
|
58
|
+
validates :value, presence: true
|
59
|
+
|
60
|
+
|
61
|
+
field :volume, type: Integer
|
62
|
+
|
63
|
+
|
64
|
+
def self.test_0trash
|
65
|
+
add_fields = { '$addFields': {
|
66
|
+
'date_string': {
|
67
|
+
'$dateToString': { 'format': "%Y-%m-%d", 'date': "$created_at" }
|
68
|
+
}
|
69
|
+
} }
|
70
|
+
# group = { '$group': {
|
71
|
+
# '_id': "$date_string",
|
72
|
+
# 'my_doc': { '$first': "$$ROOT" }
|
73
|
+
# } }
|
74
|
+
group = { '$group': {
|
75
|
+
'_id': "$date",
|
76
|
+
'my_doc': { '$first': "$$ROOT" }
|
77
|
+
} }
|
78
|
+
lookup = { '$lookup': {
|
79
|
+
'from': 'iro_dates',
|
80
|
+
'localField': 'date_string',
|
81
|
+
'foreignField': 'date',
|
82
|
+
'as': 'dates',
|
83
|
+
} }
|
84
|
+
lookup_merge = { '$replaceRoot': {
|
85
|
+
'newRoot': { '$mergeObjects': [
|
86
|
+
{ '$arrayElemAt': [ "$dates", 0 ] }, "$$ROOT"
|
87
|
+
] }
|
88
|
+
} }
|
89
|
+
match = { '$match': {
|
90
|
+
'kind': 'some-type',
|
91
|
+
'created_at': {
|
92
|
+
'$gte': '2023-12-01'.to_datetime,
|
93
|
+
'$lte': '2023-12-31'.to_datetime,
|
94
|
+
}
|
95
|
+
} }
|
96
|
+
|
97
|
+
outs = Iro::Datapoint.collection.aggregate([
|
98
|
+
add_fields,
|
99
|
+
lookup, lookup_merge,
|
100
|
+
match,
|
101
|
+
|
102
|
+
{ '$sort': { 'date_string': 1 } },
|
103
|
+
group,
|
104
|
+
# { '$replaceRoot': { 'newRoot': "$my_doc" } },
|
105
|
+
# { '$project': { '_id': 0, 'date_string': 1, 'value': 1 } },
|
106
|
+
])
|
107
|
+
|
108
|
+
puts! 'result'
|
109
|
+
pp outs.to_a
|
110
|
+
# puts! outs.to_a, 'result'
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.test
|
114
|
+
lookup = { '$lookup': {
|
115
|
+
'from': 'iro_datapoints',
|
116
|
+
'localField': 'date',
|
117
|
+
'foreignField': 'date',
|
118
|
+
'pipeline': [
|
119
|
+
{ '$sort': { 'value': -1 } },
|
120
|
+
],
|
121
|
+
'as': 'datapoints',
|
122
|
+
} }
|
123
|
+
lookup_merge = { '$replaceRoot': {
|
124
|
+
'newRoot': { '$mergeObjects': [
|
125
|
+
{ '$arrayElemAt': [ "$datapoints", 0 ] }, "$$ROOT"
|
126
|
+
] }
|
127
|
+
} }
|
128
|
+
|
129
|
+
|
130
|
+
match = { '$match': {
|
131
|
+
'date': {
|
132
|
+
'$gte': '2023-12-25',
|
133
|
+
'$lte': '2023-12-31',
|
134
|
+
}
|
135
|
+
} }
|
136
|
+
|
137
|
+
group = { '$group': {
|
138
|
+
'_id': "$date",
|
139
|
+
'my_doc': { '$first': "$$ROOT" }
|
140
|
+
} }
|
141
|
+
|
142
|
+
outs = Iro::Date.collection.aggregate([
|
143
|
+
match,
|
144
|
+
|
145
|
+
lookup,
|
146
|
+
lookup_merge,
|
147
|
+
|
148
|
+
group,
|
149
|
+
{ '$replaceRoot': { 'newRoot': "$my_doc" } },
|
150
|
+
# { '$replaceRoot': { 'newRoot': "$my_doc" } },
|
151
|
+
|
152
|
+
|
153
|
+
{ '$project': { '_id': 0, 'date': 1, 'value': 1 } },
|
154
|
+
{ '$sort': { 'date': 1 } },
|
155
|
+
])
|
156
|
+
|
157
|
+
puts! 'result'
|
158
|
+
pp outs.to_a
|
159
|
+
# puts! outs.to_a, 'result'
|
160
|
+
end
|
161
|
+
|
162
|
+
def self.import_stock symbol:, path:
|
163
|
+
csv = CSV.read(path, headers: true)
|
164
|
+
csv.each do |row|
|
165
|
+
flag = create({
|
166
|
+
kind: KIND_STOCK,
|
167
|
+
symbol: symbol,
|
168
|
+
date: row['Date'],
|
169
|
+
quote_at: row['Date'],
|
170
|
+
|
171
|
+
volume: row['Volume'],
|
172
|
+
|
173
|
+
open: row['Open'],
|
174
|
+
high: row['High'],
|
175
|
+
low: row['Low'],
|
176
|
+
value: row['Close'],
|
177
|
+
})
|
178
|
+
if flag.persisted?
|
179
|
+
print '^'
|
180
|
+
else
|
181
|
+
puts flag.errors.messages
|
182
|
+
end
|
183
|
+
end
|
184
|
+
puts 'ok'
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
|
2
|
+
class Iro::Option
|
3
|
+
include Mongoid::Document
|
4
|
+
include Mongoid::Timestamps
|
5
|
+
include Mongoid::Paranoia
|
6
|
+
include Iro::OptionBlackScholes
|
7
|
+
store_in collection: 'iro_options'
|
8
|
+
|
9
|
+
attr_accessor :recompute
|
10
|
+
|
11
|
+
belongs_to :stock, class_name: 'Iro::Stock', inverse_of: :strategies
|
12
|
+
def ticker; stock.ticker; end
|
13
|
+
# field :ticker
|
14
|
+
# validates :ticker, presence: true
|
15
|
+
|
16
|
+
CALL = 'CALL'
|
17
|
+
PUT = 'PUT'
|
18
|
+
|
19
|
+
field :symbol
|
20
|
+
## each option can be a leg in a position, no uniqueness
|
21
|
+
# validates :symbol, uniqueness: true, presence: true
|
22
|
+
|
23
|
+
field :put_call, type: :string # 'PUT' or 'CALL'
|
24
|
+
validates :put_call, presence: true
|
25
|
+
|
26
|
+
field :delta, type: :float
|
27
|
+
|
28
|
+
field :strike, type: :float
|
29
|
+
validates :strike, presence: true
|
30
|
+
|
31
|
+
field :expires_on, type: :date
|
32
|
+
validates :expires_on, presence: true
|
33
|
+
def self.expirations_list full: false, n: 5
|
34
|
+
out = [[nil,nil]]
|
35
|
+
day = Date.today - 5.days
|
36
|
+
n.times do
|
37
|
+
next_exp = day.next_occurring(:thursday).next_occurring(:friday)
|
38
|
+
if !next_exp.workday?
|
39
|
+
next_exp = Time.previous_business_day( next_exp )
|
40
|
+
end
|
41
|
+
|
42
|
+
out.push([ next_exp.strftime('%b %e'), next_exp.strftime('%Y-%m-%d') ])
|
43
|
+
day = next_exp
|
44
|
+
end
|
45
|
+
return out
|
46
|
+
# [
|
47
|
+
# [ nil, nil ],
|
48
|
+
# [ 'Mar 22', '2024-03-22'.to_date ],
|
49
|
+
# [ 'Mar 28', '2024-03-28'.to_date ],
|
50
|
+
# [ 'Apr 5', '2024-04-05'.to_date ],
|
51
|
+
# [ 'Mar 12', '2024-03-12'.to_date ],
|
52
|
+
# [ 'Mar 19', '2024-03-19'.to_date ],
|
53
|
+
# ]
|
54
|
+
end
|
55
|
+
|
56
|
+
field :begin_price, type: :float
|
57
|
+
field :begin_delta, type: :float
|
58
|
+
field :end_price, type: :float
|
59
|
+
field :end_delta, type: :float
|
60
|
+
|
61
|
+
|
62
|
+
has_one :outer, class_name: 'Iro::Position', inverse_of: :outer
|
63
|
+
has_one :inner, class_name: 'Iro::Position', inverse_of: :inner
|
64
|
+
|
65
|
+
field :last, type: :float
|
66
|
+
|
67
|
+
## for TDA
|
68
|
+
def symbol
|
69
|
+
if !self[:symbol]
|
70
|
+
p_c_ = put_call == 'PUT' ? 'P' : 'C'
|
71
|
+
strike_ = strike.to_i == strike ? strike.to_i : strike
|
72
|
+
sym = "#{stock.ticker}_#{expires_on.strftime("%m%d%y")}#{p_c_}#{strike_}" # XYZ_011819P45
|
73
|
+
self[:symbol] = sym
|
74
|
+
save
|
75
|
+
end
|
76
|
+
self[:symbol]
|
77
|
+
end
|
78
|
+
|
79
|
+
before_save :sync, if: ->() { !Rails.env.test? } ## do not sync in test
|
80
|
+
def sync
|
81
|
+
out = Tda::Option.get_quote({
|
82
|
+
contractType: put_call,
|
83
|
+
strike: strike,
|
84
|
+
expirationDate: expires_on,
|
85
|
+
ticker: ticker,
|
86
|
+
})
|
87
|
+
puts! out, 'option sync'
|
88
|
+
self.end_price = ( out.bid + out.ask ) / 2 rescue 0
|
89
|
+
self.end_delta = out.delta if out.delta
|
90
|
+
# self.save
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.max_pain hash
|
94
|
+
outs = {}
|
95
|
+
|
96
|
+
%w| put call |.each do |contractType|
|
97
|
+
dates = hash["#{contractType}ExpDateMap"]
|
98
|
+
dates.each do |_date, strikes| ## _date="2023-02-10:5"
|
99
|
+
date = _date.split(':')[0].to_date.to_s
|
100
|
+
outs[date] ||= {
|
101
|
+
'all' => {},
|
102
|
+
'call' => {},
|
103
|
+
'put' => {},
|
104
|
+
'summary' => {},
|
105
|
+
}
|
106
|
+
|
107
|
+
strikes.each do |_strike, _v| ## _strike="18.5"
|
108
|
+
strike = _strike.to_f
|
109
|
+
|
110
|
+
## calls
|
111
|
+
mem_c = 0
|
112
|
+
strikes.keys.reverse.each do |_key|
|
113
|
+
if _key == _strike
|
114
|
+
break
|
115
|
+
end
|
116
|
+
key = _key.to_f
|
117
|
+
tmp = hash["callExpDateMap"][_date][_key][0]['openInterest'] * ( key - strike )
|
118
|
+
mem_c += tmp
|
119
|
+
end
|
120
|
+
outs[date]['call'][_strike] = mem_c
|
121
|
+
|
122
|
+
## puts
|
123
|
+
mem_p = 0
|
124
|
+
strikes.keys.each do |_key|
|
125
|
+
if _key == _strike
|
126
|
+
break
|
127
|
+
end
|
128
|
+
key = _key.to_f
|
129
|
+
tmp = hash["putExpDateMap"][_date][_key][0]['openInterest'] * ( strike - key )
|
130
|
+
mem_p += tmp
|
131
|
+
end
|
132
|
+
outs[date]['put'][_strike] = mem_p
|
133
|
+
outs[date]['all'][_strike] = mem_c + mem_p
|
134
|
+
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
## compute summary
|
140
|
+
outs.each do |date, types|
|
141
|
+
all = types['all']
|
142
|
+
outs[date]['summary'] = { 'value' => all.keys[0] }
|
143
|
+
all.each do |strike, amount|
|
144
|
+
if amount < all[ outs[date]['summary']['value'] ]
|
145
|
+
outs[date]['summary']['value'] = strike
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
return outs
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
|
2
|
+
require 'distribution'
|
3
|
+
N = Distribution::Normal
|
4
|
+
|
5
|
+
module Iro::OptionBlackScholes
|
6
|
+
|
7
|
+
##
|
8
|
+
## black-scholes pricing
|
9
|
+
##
|
10
|
+
|
11
|
+
=begin
|
12
|
+
##
|
13
|
+
##
|
14
|
+
##
|
15
|
+
annual to daily:
|
16
|
+
|
17
|
+
AR = ((DR + 1)^365 – 1) x 100
|
18
|
+
|
19
|
+
##
|
20
|
+
##
|
21
|
+
##
|
22
|
+
From: https://www.investopedia.com/articles/optioninvestor/07/options_beat_market.asp
|
23
|
+
|
24
|
+
K :: strike price
|
25
|
+
S_t :: last
|
26
|
+
r :: risk-free rate
|
27
|
+
t :: time to maturity
|
28
|
+
|
29
|
+
C = S_t N( d1 ) - K e^-rt N( d2 )
|
30
|
+
|
31
|
+
d1 = ln( St / K ) + (r + theta**2 / 2 )t
|
32
|
+
/{ theta_s * sqrt( t ) }
|
33
|
+
|
34
|
+
d2 = d1 - theta_s sqrt( t )
|
35
|
+
|
36
|
+
##
|
37
|
+
## From: https://en.wikipedia.org/wiki/Black%E2%80%93Scholes_model
|
38
|
+
##
|
39
|
+
|
40
|
+
D :: e^(rt) # discount factor
|
41
|
+
F :: e^(rt) S # forward price of underlying
|
42
|
+
|
43
|
+
C(F,t) = D[ N(d1)F - N(d2)K ]
|
44
|
+
|
45
|
+
d1 = ln(F/K) + stdev**2 t / 2
|
46
|
+
/{ stdev sqrt(t) }
|
47
|
+
d2 = d1 - stdev sqrt(t)
|
48
|
+
|
49
|
+
##
|
50
|
+
## From: https://www.daytrading.com/options-pricing-models
|
51
|
+
##
|
52
|
+
C0 = S0N(d1) – Xe-rtN(d2)
|
53
|
+
|
54
|
+
C0 = current call premium
|
55
|
+
S0 = current stock price
|
56
|
+
N(d1) = the probability that a value in a normal distribution will be less than d
|
57
|
+
N(d2) = the probability that the option will be in the money by expiration
|
58
|
+
X = strike price of the option
|
59
|
+
T = time until expiration (expressed in years)
|
60
|
+
r = risk-free interest rate
|
61
|
+
e = 2.71828, the base of the natural logarithm
|
62
|
+
ln = natural logarithm function
|
63
|
+
σ = standard deviation of the stock’s annualized rate of return (compounded continuously)
|
64
|
+
d1 = ln(S0/X) + (r + σ2/2)Tσ√T
|
65
|
+
|
66
|
+
d2 = d1 – σ√T
|
67
|
+
|
68
|
+
Note that:
|
69
|
+
|
70
|
+
Xe-rt = X/ert = the present value of the strike price using a continuously compounded interest rate
|
71
|
+
|
72
|
+
##
|
73
|
+
## From: https://www.wallstreetmojo.com/black-scholes-model/
|
74
|
+
##
|
75
|
+
|
76
|
+
|
77
|
+
## init
|
78
|
+
require 'distribution'
|
79
|
+
N = Distribution::Normal
|
80
|
+
stock = Iro::Stock.find_by ticker: 'NVDA'
|
81
|
+
strike = 910.0
|
82
|
+
r = Iro::Option.rate_daily
|
83
|
+
stdev = 91.0
|
84
|
+
t = 7.0
|
85
|
+
expires_on = '2024-03-22'
|
86
|
+
|
87
|
+
=end
|
88
|
+
def d1
|
89
|
+
last = stock.last
|
90
|
+
r = self.class.rate_annual
|
91
|
+
|
92
|
+
out = Math.log( last / strike ) + ( r + stdev**2 / 2 ) * t
|
93
|
+
out = out /( stdev * Math.sqrt(t) )
|
94
|
+
return out
|
95
|
+
end
|
96
|
+
def d2
|
97
|
+
last = stock.last
|
98
|
+
r = self.class.rate_annual
|
99
|
+
|
100
|
+
out = d1 - stdev * Math.sqrt( t )
|
101
|
+
return out
|
102
|
+
end
|
103
|
+
def t
|
104
|
+
# t = 1.0 / 365 * Date.today.business_days_until( expires_on )
|
105
|
+
t = 1.0 / 365 * (expires_on - Date.today).to_i
|
106
|
+
end
|
107
|
+
def stdev
|
108
|
+
recompute = nil
|
109
|
+
stock.stdev( recompute: recompute )
|
110
|
+
end
|
111
|
+
def call_price
|
112
|
+
last = stock.last
|
113
|
+
r = self.class.rate_annual
|
114
|
+
|
115
|
+
out = N.cdf( d1 ) * last - N.cdf( d2 ) * strike * Math::E**( -1 * r * t )
|
116
|
+
return out
|
117
|
+
end
|
118
|
+
|
119
|
+
def put_price
|
120
|
+
last = stock.last
|
121
|
+
r = self.class.rate_annual
|
122
|
+
|
123
|
+
out = N.cdf(-d2) * strike * exp(-r*t) - N.cdf(-d1) * last
|
124
|
+
return out
|
125
|
+
end
|
126
|
+
|
127
|
+
|
128
|
+
def self.rate_annual
|
129
|
+
0.05
|
130
|
+
end
|
131
|
+
|
132
|
+
=begin
|
133
|
+
# test
|
134
|
+
|
135
|
+
inn = 100
|
136
|
+
n.times { inn = inn*(1.0+out) }
|
137
|
+
inn
|
138
|
+
|
139
|
+
=end
|
140
|
+
def self.rate_daily
|
141
|
+
n = 250.0 # days
|
142
|
+
# n = 12 # months
|
143
|
+
|
144
|
+
out = (1.0+self.rate_annual)**(1.0/n) - 1.0
|
145
|
+
puts! out, 'rate_daily'
|
146
|
+
return out
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|