penfold 1.0.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.
- data/.autotest +23 -0
- data/History.txt +6 -0
- data/Manifest.txt +22 -0
- data/README.rdoc +167 -0
- data/Rakefile +13 -0
- data/bin/penfold +14 -0
- data/bin/penfold-position +173 -0
- data/bin/penfold-try +184 -0
- data/lib/argument_processor.rb +6 -0
- data/lib/commission.rb +44 -0
- data/lib/core_ext.rb +32 -0
- data/lib/covered_call_early_exit.rb +26 -0
- data/lib/covered_call_exit.rb +82 -0
- data/lib/covered_call_expiry_itm_exit.rb +26 -0
- data/lib/covered_call_expiry_otm_exit.rb +24 -0
- data/lib/covered_call_position.rb +86 -0
- data/lib/market.rb +254 -0
- data/lib/option.rb +96 -0
- data/lib/penfold.rb +20 -0
- data/lib/stock.rb +15 -0
- data/portfolio.yml +277 -0
- data/test/test_option_calendar.rb +74 -0
- data/test/test_penfold.rb +315 -0
- metadata +128 -0
data/.autotest
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
|
3
|
+
require 'autotest/restart'
|
4
|
+
|
5
|
+
# Autotest.add_hook :initialize do |at|
|
6
|
+
# at.extra_files << "../some/external/dependency.rb"
|
7
|
+
#
|
8
|
+
# at.libs << ":../some/external"
|
9
|
+
#
|
10
|
+
# at.add_exception 'vendor'
|
11
|
+
#
|
12
|
+
# at.add_mapping(/dependency.rb/) do |f, _|
|
13
|
+
# at.files_matching(/test_.*rb$/)
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# %w(TestA TestB).each do |klass|
|
17
|
+
# at.extra_class_map[klass] = "test/test_misc.rb"
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
|
21
|
+
# Autotest.add_hook :run_command do |at|
|
22
|
+
# system "rake build"
|
23
|
+
# end
|
data/History.txt
ADDED
data/Manifest.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
.autotest
|
2
|
+
History.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.rdoc
|
5
|
+
Rakefile
|
6
|
+
bin/penfold
|
7
|
+
bin/penfold-position
|
8
|
+
bin/penfold-try
|
9
|
+
lib/argument_processor.rb
|
10
|
+
lib/commission.rb
|
11
|
+
lib/core_ext.rb
|
12
|
+
lib/covered_call_early_exit.rb
|
13
|
+
lib/covered_call_exit.rb
|
14
|
+
lib/covered_call_expiry_itm_exit.rb
|
15
|
+
lib/covered_call_expiry_otm_exit.rb
|
16
|
+
lib/covered_call_position.rb
|
17
|
+
lib/market.rb
|
18
|
+
lib/option.rb
|
19
|
+
lib/penfold.rb
|
20
|
+
lib/stock.rb
|
21
|
+
portfolio.yml
|
22
|
+
test/test_penfold.rb
|
data/README.rdoc
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
= penfold
|
2
|
+
|
3
|
+
http://github.com/aasmith/penfold
|
4
|
+
|
5
|
+
== DESCRIPTION
|
6
|
+
|
7
|
+
Penfold is an assistant for screening potentital and tracking current
|
8
|
+
covered call positions.
|
9
|
+
|
10
|
+
== FEATURES
|
11
|
+
|
12
|
+
* Reports on the current profitability of a current position.
|
13
|
+
* Shows the potential profitability of hypothetical positions.
|
14
|
+
|
15
|
+
== TODO
|
16
|
+
|
17
|
+
* Add portfolio read/write frontend.
|
18
|
+
* Allow penfold-try to use a default ITM strike price.
|
19
|
+
* Some arguments to penfold-try are incomplete.
|
20
|
+
* Make commission class more flexible.
|
21
|
+
|
22
|
+
== USAGE
|
23
|
+
|
24
|
+
Usage: penfold [position|try|show|list|add|remove] [options]
|
25
|
+
|
26
|
+
=== Position
|
27
|
+
|
28
|
+
Reports on a current position.
|
29
|
+
|
30
|
+
Usage: penfold position [[-n NAME|-A] exit options]
|
31
|
+
|
32
|
+
Position options:
|
33
|
+
-n, --name NAME Name of position in portfolio
|
34
|
+
-A, --all Use all positions in portfolio
|
35
|
+
|
36
|
+
Exit options:
|
37
|
+
-s, --exit-stock-price PRICE Stock price to exit at
|
38
|
+
-o, --exit-option-price PRICE Option price to exit at
|
39
|
+
-e, --exit-days DAYS Exit in n DAYS (default 0)
|
40
|
+
-E, --expires Hold position until expiry
|
41
|
+
|
42
|
+
Common options:
|
43
|
+
-c, --commission=NAME Commission fees to apply
|
44
|
+
-h, --help Show this message
|
45
|
+
-v, --verbose
|
46
|
+
|
47
|
+
=== Try
|
48
|
+
|
49
|
+
Tries out a hypothetical position.
|
50
|
+
|
51
|
+
Usage: penfold try [[[ticker options] entry options] exit options]
|
52
|
+
|
53
|
+
Ticker options:
|
54
|
+
-t, --ticker=SYMBOL Stock ticker SYMBOL to try
|
55
|
+
-n, --num-shares=NUM Number of shares in position
|
56
|
+
-d, --option-date=DATE Expiry DATE of option contract
|
57
|
+
-x, --option-strike=PRICE Option strike PRICE
|
58
|
+
|
59
|
+
Entry options:
|
60
|
+
-s, --entry-stock-price=PRICE Stock PRICE to enter at
|
61
|
+
-o, --entry-option-price=PRICE Option PRICE to enter at
|
62
|
+
|
63
|
+
Exit options:
|
64
|
+
-S, --exit-stock-price=PRICE Stock PRICE to exit at
|
65
|
+
-O, --exit-option-price=PRICE Option PRICE to exit at
|
66
|
+
-e, --exit-days=DAYS Exit in n DAYS (default expiry)
|
67
|
+
-E, --expires Hold position until expiry
|
68
|
+
|
69
|
+
Common options:
|
70
|
+
-c, --commission=NAME Commission fees to apply
|
71
|
+
-L, --last Use last price instead of bid/ask for stock pricing
|
72
|
+
-h, --help Show this message
|
73
|
+
-v, --verbose
|
74
|
+
|
75
|
+
|
76
|
+
== EXAMPLES
|
77
|
+
|
78
|
+
# Commands for checking a current position
|
79
|
+
|
80
|
+
# check the profit of a position, using current market pricing
|
81
|
+
# assuming an immediate exit
|
82
|
+
|
83
|
+
penfold position --name=example
|
84
|
+
|
85
|
+
# check the profit of a position, using a hypothetical exit price,
|
86
|
+
# assuming an exit 3 days from now
|
87
|
+
|
88
|
+
penfold position
|
89
|
+
--name=example
|
90
|
+
--exit-stock-price=4.56
|
91
|
+
--exit-option-price=0.52
|
92
|
+
--exit-days=3
|
93
|
+
|
94
|
+
# check the profit of a position, using current market pricing
|
95
|
+
# assuming the expiry of the contract
|
96
|
+
|
97
|
+
penfold position --name=example --expires
|
98
|
+
|
99
|
+
|
100
|
+
# Commands for checking a potentital position
|
101
|
+
|
102
|
+
# check the profit of stock XYZ, with an August 2010 option with $4 strike,
|
103
|
+
# using current market pricing and assuming an expiry of the contract
|
104
|
+
|
105
|
+
penfold try --stock=XYZ --num-shares=1000 --option-date=100821 --option-strike=4
|
106
|
+
|
107
|
+
# check the profit of stock XYZ with an August 2010 option with $4 strike,
|
108
|
+
# using provided entry pricing and assuming an exit in 5 days with provided
|
109
|
+
# exit pricing
|
110
|
+
|
111
|
+
penfold try
|
112
|
+
--stock=XYZ --num-shares=1000 --option-date=100821 --option-strike=4
|
113
|
+
--stock-entry-price=4.25 --option-entry-price=0.35
|
114
|
+
--exit-days=5 --stock-exit-price=4.51 --option-exit-price=0.39
|
115
|
+
|
116
|
+
|
117
|
+
# Batch position checks
|
118
|
+
|
119
|
+
# Show all positions assuming expiry with current market prices
|
120
|
+
penfold position --all --expires
|
121
|
+
|
122
|
+
# Show all positions assuming immediate exit with current market prices
|
123
|
+
penfold position --all
|
124
|
+
|
125
|
+
|
126
|
+
# Portfolio commands
|
127
|
+
|
128
|
+
# List current portfolio positions
|
129
|
+
|
130
|
+
penfold [show|list]
|
131
|
+
|
132
|
+
penfold add example
|
133
|
+
--stock=XYZ --num-shares=1000 --option-date=100821 --option-strike=4
|
134
|
+
--stock-entry-price=4.5 --option-entry-price=0.34
|
135
|
+
|
136
|
+
penfold remove example
|
137
|
+
|
138
|
+
penfold rename example new_example
|
139
|
+
|
140
|
+
|
141
|
+
== REQUIREMENTS
|
142
|
+
|
143
|
+
* nokogiri, if fetching quotes
|
144
|
+
|
145
|
+
== LICENSE
|
146
|
+
|
147
|
+
Copyright (c) 2010 Andrew A. Smith
|
148
|
+
|
149
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
150
|
+
a copy of this software and associated documentation files (the
|
151
|
+
'Software'), to deal in the Software without restriction, including
|
152
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
153
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
154
|
+
permit persons to whom the Software is furnished to do so, subject to
|
155
|
+
the following conditions:
|
156
|
+
|
157
|
+
The above copyright notice and this permission notice shall be
|
158
|
+
included in all copies or substantial portions of the Software.
|
159
|
+
|
160
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
161
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
162
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
163
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
164
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
165
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
166
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
167
|
+
|
data/Rakefile
ADDED
data/bin/penfold
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
LIB_DIR = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
|
+
$LOAD_PATH << LIB_DIR
|
8
|
+
|
9
|
+
require 'lib/penfold'
|
10
|
+
|
11
|
+
class Parser
|
12
|
+
Options = Struct.new(
|
13
|
+
:portfolio, :all, :name, :exit_stock_price, :exit_option_price, :days, :commission, :last
|
14
|
+
)
|
15
|
+
|
16
|
+
def self.parse(args)
|
17
|
+
options = Options.new
|
18
|
+
options.portfolio = "portfolio.yml"
|
19
|
+
options.days = 0
|
20
|
+
|
21
|
+
parser = OptionParser.new do |o|
|
22
|
+
|
23
|
+
o.banner = "Usage: penfold position [[-n NAME|-A] exit options]"
|
24
|
+
|
25
|
+
o.separator ""
|
26
|
+
o.separator "Position options:"
|
27
|
+
|
28
|
+
o.on("-n", "--name NAME", "Name of position in portfolio", /\w+/) do |name|
|
29
|
+
options.name = name
|
30
|
+
end
|
31
|
+
|
32
|
+
o.on("-A", "--all", "Use all positions in portfolio") do
|
33
|
+
options.all = true
|
34
|
+
end
|
35
|
+
|
36
|
+
o.separator ""
|
37
|
+
o.separator "Exit options:"
|
38
|
+
|
39
|
+
o.on("-s", "--exit-stock-price PRICE", "Stock price to exit at", Float) do |price|
|
40
|
+
options.exit_stock_price = price * 100
|
41
|
+
end
|
42
|
+
|
43
|
+
o.on("-o", "--exit-option-price PRICE", "Option price to exit at", Float) do |price|
|
44
|
+
options.exit_option_price = price * 100
|
45
|
+
end
|
46
|
+
|
47
|
+
o.on("-e", "--exit-days DAYS", "Exit in n DAYS (default 0)", Integer) do |days|
|
48
|
+
options.days = days
|
49
|
+
end
|
50
|
+
|
51
|
+
o.on("-E", "--expires", "Hold position until expiry") do
|
52
|
+
options.days = 999999
|
53
|
+
end
|
54
|
+
|
55
|
+
o.separator ""
|
56
|
+
o.separator "Common options:"
|
57
|
+
|
58
|
+
o.on("-c", "--commission=NAME", "Commission fees to apply", /\w+/) do |name|
|
59
|
+
options.commission = Commission.const_get(name)
|
60
|
+
end
|
61
|
+
|
62
|
+
o.on("-L", "--last", "Use last price instead of bid/ask for ",
|
63
|
+
"stock pricing. Best used when the market ",
|
64
|
+
"is closed.") do
|
65
|
+
options.last = true
|
66
|
+
end
|
67
|
+
|
68
|
+
o.on_tail("-h", "--help", "Show this message") do
|
69
|
+
puts o
|
70
|
+
exit
|
71
|
+
end
|
72
|
+
|
73
|
+
o.on_tail("-v", "--verbose") do
|
74
|
+
$VERBOSE = true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
parser.parse!(args)
|
79
|
+
options
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
options = Parser.parse(ARGV) rescue abort($!.message)
|
84
|
+
|
85
|
+
portfolio = YAML.load(File.read(options.portfolio))
|
86
|
+
|
87
|
+
positions = if options.all or options.name.nil?
|
88
|
+
portfolio
|
89
|
+
else
|
90
|
+
[[options.name, YAML.load(File.read(options.portfolio))[options.name]]]
|
91
|
+
end
|
92
|
+
|
93
|
+
positions.each do |position_name, position|
|
94
|
+
position.commission = options.commission if options.commission
|
95
|
+
|
96
|
+
begin
|
97
|
+
exit_option_price =
|
98
|
+
options.exit_option_price ||
|
99
|
+
Market.fetch(position.option.to_ticker_s).ask
|
100
|
+
|
101
|
+
exit_stock_price =
|
102
|
+
options.exit_stock_price ||
|
103
|
+
Market.fetch(position.stock.symbol.upcase).
|
104
|
+
send(options.last ? :last : :bid)
|
105
|
+
|
106
|
+
rescue => e
|
107
|
+
puts e if $VERBOSE
|
108
|
+
puts "Unable to fetch data for position #{position_name}, skipping"
|
109
|
+
next
|
110
|
+
end
|
111
|
+
|
112
|
+
closing_position = CoveredCallExit.new(
|
113
|
+
:opening_position => position,
|
114
|
+
:exit_date => Date.today + options.days,
|
115
|
+
:stock_price => exit_stock_price,
|
116
|
+
:option_price => exit_option_price
|
117
|
+
)
|
118
|
+
|
119
|
+
output = <<EOT
|
120
|
+
Position: %s
|
121
|
+
Entry: %s @ %s, %s @ %s [spread %s]
|
122
|
+
IV %.2f%% Probability (Max) Profit (%.2f%%) %.2f%%
|
123
|
+
|
124
|
+
Stock Total %s (inc %s comm)
|
125
|
+
- Call Sale %s (inc %s comm)
|
126
|
+
= Net Outlay %s (%s per share)
|
127
|
+
|
128
|
+
Downside Protection: %s
|
129
|
+
|
130
|
+
Exit: %s @ %s, %s @ %s on %s [spread %s]
|
131
|
+
|
132
|
+
#{closing_position.explain.chomp}
|
133
|
+
|
134
|
+
Period Return: %s
|
135
|
+
Annualized Return: %s
|
136
|
+
Days in Position: %s
|
137
|
+
|
138
|
+
EOT
|
139
|
+
|
140
|
+
puts output % [
|
141
|
+
(position_name + " ").ljust(70, "="),
|
142
|
+
position.option,
|
143
|
+
position.option.price.to_money_s,
|
144
|
+
position.stock.symbol,
|
145
|
+
position.stock.price.to_money_s,
|
146
|
+
(position.stock.price - position.option.price).to_money_s,
|
147
|
+
|
148
|
+
position.implied_volatility * 100,
|
149
|
+
position.probability_max_profit * 100,
|
150
|
+
position.probability_profit * 100,
|
151
|
+
|
152
|
+
position.stock_total.to_money_s.rjust(12),
|
153
|
+
position.commission.stock_entry.to_money_s,
|
154
|
+
position.call_sale.to_money_s.rjust(12),
|
155
|
+
position.commission.option_entry.to_money_s,
|
156
|
+
position.net_outlay.to_money_s.rjust(12),
|
157
|
+
position.net_per_share.to_money_s,
|
158
|
+
|
159
|
+
position.downside_protection.to_percent_s,
|
160
|
+
|
161
|
+
closing_position.option,
|
162
|
+
closing_position.option.price.to_money_s,
|
163
|
+
closing_position.stock.symbol,
|
164
|
+
closing_position.stock.price.to_money_s,
|
165
|
+
closing_position.exit_date,
|
166
|
+
(closing_position.stock.price - closing_position.option.price).to_money_s,
|
167
|
+
|
168
|
+
closing_position.period_return.to_percent_s,
|
169
|
+
closing_position.annualized_return.to_percent_s,
|
170
|
+
closing_position.days_in_position
|
171
|
+
]
|
172
|
+
|
173
|
+
end
|
data/bin/penfold-try
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
LIB_DIR = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
7
|
+
$LOAD_PATH << LIB_DIR
|
8
|
+
|
9
|
+
require 'lib/penfold'
|
10
|
+
|
11
|
+
class Parser
|
12
|
+
Options = Struct.new(
|
13
|
+
:symbol, :num_shares, :option_date, :option_strike,
|
14
|
+
:entry_stock_price, :entry_option_price,
|
15
|
+
:exit_stock_price, :exit_option_price, :days,
|
16
|
+
:commission, :last
|
17
|
+
)
|
18
|
+
|
19
|
+
def self.parse(args)
|
20
|
+
options = Options.new
|
21
|
+
options.days = 999999
|
22
|
+
options.commission = Commission::FREE
|
23
|
+
|
24
|
+
parser = OptionParser.new do |o|
|
25
|
+
o.banner = "Usage: penfold try [[[ticker options] entry options] exit options]"
|
26
|
+
|
27
|
+
o.separator ""
|
28
|
+
o.separator "Ticker options:"
|
29
|
+
|
30
|
+
o.on("-t", "--ticker=SYMBOL", "Stock ticker SYMBOL to try", /\w+/) do |symbol|
|
31
|
+
options.symbol = symbol
|
32
|
+
end
|
33
|
+
|
34
|
+
o.on("-n", "--num-shares=NUM", "Number of shares in position", Integer) do |num_shares|
|
35
|
+
options.num_shares = num_shares
|
36
|
+
end
|
37
|
+
|
38
|
+
o.on("-d", "--option-date=DATE", "Expiry DATE of option contract", Integer) do |date|
|
39
|
+
date = date.to_s
|
40
|
+
date = "20#{date}" unless date =~ /^20/
|
41
|
+
|
42
|
+
options.option_date = Date.parse(date)
|
43
|
+
end
|
44
|
+
|
45
|
+
o.on("-x", "--option-strike=PRICE", "Option strike PRICE", Float) do |price|
|
46
|
+
options.option_strike = price * 100
|
47
|
+
end
|
48
|
+
|
49
|
+
o.separator ""
|
50
|
+
o.separator "Entry options:"
|
51
|
+
|
52
|
+
o.on("-s", "--entry-stock-price=PRICE", "Stock PRICE to enter at", Float) do |price|
|
53
|
+
options.entry_stock_price = price * 100
|
54
|
+
end
|
55
|
+
|
56
|
+
o.on("-o", "--entry-option-price=PRICE", "Option PRICE to enter at", Float) do |price|
|
57
|
+
options.entry_option_price = price * 100
|
58
|
+
end
|
59
|
+
|
60
|
+
o.separator ""
|
61
|
+
o.separator "Exit options:"
|
62
|
+
|
63
|
+
o.on("-S", "--exit-stock-price=PRICE", "Stock PRICE to exit at", Float) do |price|
|
64
|
+
options.exit_stock_price = price * 100
|
65
|
+
end
|
66
|
+
|
67
|
+
o.on("-O", "--exit-option-price=PRICE", "Option PRICE to exit at", Float) do |price|
|
68
|
+
options.exit_option_price = price * 100
|
69
|
+
end
|
70
|
+
|
71
|
+
o.on("-e", "--exit-days=DAYS", "Exit in n DAYS (default expiry)", Integer) do |days|
|
72
|
+
options.days = days
|
73
|
+
end
|
74
|
+
|
75
|
+
o.on("-E", "--expires", "Hold position until expiry") do
|
76
|
+
options.days = 999999
|
77
|
+
end
|
78
|
+
|
79
|
+
o.separator ""
|
80
|
+
o.separator "Common options:"
|
81
|
+
|
82
|
+
o.on("-c", "--commission=NAME", "Commission fees to apply", /\w+/) do |name|
|
83
|
+
options.commission = Commission.const_get(name)
|
84
|
+
end
|
85
|
+
|
86
|
+
o.on("-L", "--last", "Use last price instead of bid/ask for stock pricing") do
|
87
|
+
options.last = true
|
88
|
+
end
|
89
|
+
|
90
|
+
o.on_tail("-h", "--help", "Show this message") do
|
91
|
+
puts o
|
92
|
+
exit
|
93
|
+
end
|
94
|
+
|
95
|
+
o.on_tail("-v", "--verbose") do
|
96
|
+
$VERBOSE = true
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
parser.parse!(args)
|
101
|
+
options
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
options = Parser.parse(ARGV) rescue abort($!.message)
|
106
|
+
|
107
|
+
unless options.symbol && options.num_shares && options.option_date && options.option_strike
|
108
|
+
abort "Must provide all of -t, -n, -d, -x"
|
109
|
+
end
|
110
|
+
|
111
|
+
options.entry_stock_price ||= Market.fetch(options.symbol).send(options.last ? :last : :ask)
|
112
|
+
|
113
|
+
stock = Stock.new(
|
114
|
+
:symbol => options.symbol,
|
115
|
+
:price => options.entry_stock_price
|
116
|
+
)
|
117
|
+
|
118
|
+
call = Call.new(
|
119
|
+
:stock => stock,
|
120
|
+
:strike => options.option_strike,
|
121
|
+
:expires => options.option_date
|
122
|
+
)
|
123
|
+
|
124
|
+
call.price = options.entry_option_price || Market.fetch(call.to_ticker_s).bid
|
125
|
+
|
126
|
+
opening_position = CoveredCallPosition.new(
|
127
|
+
:num_shares => options.num_shares,
|
128
|
+
:date_established => Date.today,
|
129
|
+
:option => call,
|
130
|
+
:commission => options.commission
|
131
|
+
)
|
132
|
+
|
133
|
+
closing_position = CoveredCallExit.new(
|
134
|
+
:opening_position => opening_position,
|
135
|
+
:exit_date => Date.today + options.days,
|
136
|
+
:stock_price => options.exit_stock_price || options.entry_stock_price,
|
137
|
+
:option_price => options.exit_option_price || options.entry_option_price
|
138
|
+
)
|
139
|
+
|
140
|
+
output = <<EOT
|
141
|
+
Entry: %s @ %s, %s @ %s
|
142
|
+
|
143
|
+
Stock Total %s (inc %s comm)
|
144
|
+
- Call Sale %s (inc %s comm)
|
145
|
+
= Net Outlay %s (%s per share)
|
146
|
+
|
147
|
+
Downside Protection: %s
|
148
|
+
|
149
|
+
Exit: %s @ %s, %s @ %s on %s
|
150
|
+
|
151
|
+
#{closing_position.explain.chomp}
|
152
|
+
|
153
|
+
Period Return: %s
|
154
|
+
Annualized Return: %s
|
155
|
+
Days in Position: %s
|
156
|
+
|
157
|
+
EOT
|
158
|
+
|
159
|
+
puts output % [
|
160
|
+
opening_position.option,
|
161
|
+
opening_position.option.price.to_money_s,
|
162
|
+
opening_position.stock.symbol,
|
163
|
+
opening_position.stock.price.to_money_s,
|
164
|
+
opening_position.stock_total.to_money_s.rjust(12),
|
165
|
+
opening_position.commission.stock_entry.to_money_s,
|
166
|
+
opening_position.call_sale.to_money_s.rjust(12),
|
167
|
+
opening_position.commission.total_option_entry(opening_position.num_shares).to_money_s,
|
168
|
+
opening_position.net_outlay.to_money_s.rjust(12),
|
169
|
+
opening_position.net_per_share.to_money_s,
|
170
|
+
|
171
|
+
opening_position.downside_protection.to_percent_s,
|
172
|
+
|
173
|
+
closing_position.option,
|
174
|
+
closing_position.option.price.to_money_s,
|
175
|
+
closing_position.stock.symbol,
|
176
|
+
closing_position.stock.price.to_money_s,
|
177
|
+
closing_position.exit_date,
|
178
|
+
|
179
|
+
closing_position.period_return.to_percent_s,
|
180
|
+
closing_position.annualized_return.to_percent_s,
|
181
|
+
closing_position.days_in_position
|
182
|
+
]
|
183
|
+
|
184
|
+
|
data/lib/commission.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
class Commission
|
2
|
+
include ArgumentProcessor
|
3
|
+
|
4
|
+
attr_accessor :shares, :contracts
|
5
|
+
|
6
|
+
def initialize(args = {})
|
7
|
+
if instance_of? Commission
|
8
|
+
raise ArgumentError, "Commission cannot be instantiated"
|
9
|
+
end
|
10
|
+
|
11
|
+
process_args(args)
|
12
|
+
|
13
|
+
@shares ||= 0
|
14
|
+
@contracts ||= 0
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Commission::Free < Commission
|
19
|
+
def option_entry; 0 end
|
20
|
+
def stock_entry; 0 end
|
21
|
+
def option_assignment; 0 end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Commission::OptionsHouse < Commission
|
25
|
+
def option_entry
|
26
|
+
contracts.zero? ? 0 : 8_50 + (contracts * 15)
|
27
|
+
end
|
28
|
+
|
29
|
+
def stock_entry
|
30
|
+
shares.zero? ? 0 : 2_95
|
31
|
+
end
|
32
|
+
|
33
|
+
def option_assignment
|
34
|
+
contracts.zero? ? 0 : 5_00
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class Commission::OptionsHouseAlt < Commission::OptionsHouse
|
39
|
+
def option_entry
|
40
|
+
return 0 if contracts.zero?
|
41
|
+
|
42
|
+
contracts <= 5 ? 5_00 : contracts * 1_00
|
43
|
+
end
|
44
|
+
end
|
data/lib/core_ext.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
class Date
|
4
|
+
undef inspect
|
5
|
+
def inspect
|
6
|
+
"#<Date: #{strftime("%c")}>"
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class String
|
11
|
+
def commify
|
12
|
+
reverse.gsub(/(\d\d\d)(?=\d)(?!\d*\.)/, '\1,').reverse
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Numeric
|
17
|
+
def to_money_s
|
18
|
+
("$%g" % (self / 100.0)).commify.sub(/\.(\d)\Z/, '.\10')
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_percent_s(p = nil)
|
22
|
+
(p ? "%.#{p}f%%" : "%g%%") % (self * 100)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Array
|
27
|
+
def in_groups_of(n)
|
28
|
+
raise ArgumentError, "Data is not in multiples of #{n}" unless size % n == 0
|
29
|
+
|
30
|
+
inject([[]]) { |a,e| (a.last.size == n) ? (a << [e]) : (a.last << e); a }
|
31
|
+
end
|
32
|
+
end
|