stockfolio 0.1.1

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.
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << './lib'
4
+
5
+ require 'stockfolio'
6
+ StockFolio::Runner.start
@@ -0,0 +1,30 @@
1
+ require 'data_mapper' # requires all the gems listed above
2
+ require 'dm-migrations'
3
+
4
+ require_relative 'stockfolio/portfolio'
5
+ require_relative 'stockfolio/transaction'
6
+ require_relative 'stockfolio/watchlist'
7
+
8
+ module StockFolio
9
+ autoload :Runner, 'stockfolio/runner'
10
+ autoload :Web, 'stockfolio/web'
11
+ end
12
+
13
+ #DataMapper::Logger.new($stdout, :debug)
14
+
15
+ rcfile = ENV['STOCKFOLIO_YML'] || Dir.home + '/.stockfolio.yml'
16
+
17
+ if File.exists?(rcfile)
18
+ config = YAML::load(File.open(rcfile))
19
+ ENV['STOCKFOLIO_DB'] = config['db'] || nil
20
+ end
21
+
22
+ dbfile = ENV['STOCKFOLIO_DB'] || Dir.home + '/.stockfolio.db'
23
+ dbfile = File.expand_path(dbfile)
24
+
25
+ # A Sqlite3 connection to a persistent database
26
+ DataMapper.setup(:default, "sqlite://#{dbfile}")
27
+
28
+ DataMapper.finalize
29
+ DataMapper.auto_upgrade!
30
+
@@ -0,0 +1,10 @@
1
+ class Portfolio
2
+ include DataMapper::Resource
3
+
4
+ property :id, Serial
5
+ property :name, String
6
+ property :created_at, DateTime
7
+
8
+ has n, :transactions
9
+ end
10
+
@@ -0,0 +1,194 @@
1
+ require 'boson/runner'
2
+ require 'hirb'
3
+
4
+ class StockFolio::Runner < Boson::Runner
5
+
6
+ def self.common_transaction_options
7
+ option :portfolio, :type => :string, :desc => 'Portfolio name', :required => true
8
+ option :symbol, :type=>:string, :desc => 'Symbol of stock', :required => true
9
+ option :price, :type => :numeric, :desc => 'Price paid', :required => true
10
+ option :quantity, :type => :numeric, :desc => 'Quantity', :required => true
11
+ option :fee, :type => :numeric, :desc => 'Transaction fee'
12
+ option :date, :type => :string, :desc => 'Transaction date'
13
+ end
14
+
15
+ def quote(symbol)
16
+ quote = StockFolio::Web.quote(symbol)
17
+ print_quotes(quote)
18
+ end
19
+
20
+ desc 'List porfolios'
21
+ def portfolios
22
+ q = Portfolio.all
23
+ portfolios = []
24
+ q.each do |p|
25
+ portfolios << {
26
+ :name => p.name,
27
+ :created_at => p.created_at
28
+ }
29
+ end
30
+
31
+ puts Hirb::Helpers::Table.render(portfolios, :fields => [:name, :created_at])
32
+ end
33
+
34
+ desc 'Current positions'
35
+ def positions(name = nil)
36
+ portfolios = []
37
+ if nil == name
38
+ portfolios = Portfolio.all
39
+ else
40
+ portfolios = Portfolio.all(:name => name)
41
+ end
42
+
43
+ positions = {}
44
+ portfolios.each do |portfolio|
45
+ #puts "Positions for #{portfolio.name}"
46
+ portfolio.transactions.each do |transaction|
47
+ #puts "#{transaction.order_name} #{transaction.quantity} #{transaction.symbol}"
48
+ if positions[transaction.symbol]
49
+ positions[transaction.symbol][:quantity] = positions[transaction.symbol][:quantity] + transaction.quantity
50
+ #if transaction.quantity > 0
51
+ positions[transaction.symbol][:cost] = positions[transaction.symbol][:cost] + transaction.quantity * transaction.price + transaction.real_fee
52
+ #end
53
+ positions[transaction.symbol][:balance] = positions[transaction.symbol][:balance] + transaction.quantity * transaction.price + transaction.real_fee
54
+ else
55
+ positions[transaction.symbol] = {
56
+ :symbol => transaction.symbol,
57
+ :cost => transaction.quantity * transaction.price + transaction.real_fee,
58
+ :balance => transaction.quantity * transaction.price,
59
+ :quantity => transaction.quantity
60
+ }
61
+ end
62
+ end
63
+ end
64
+
65
+ # Get current prices
66
+ quotes = StockFolio::Web.quote(positions.keys.join(","))
67
+ quotes.each do |q|
68
+ symbol = "#{q["e"]}:#{q["t"]}"
69
+ positions[symbol][:l] = q["l"]
70
+ positions[symbol][:c] = q["c"]
71
+ positions[symbol][:cp] = q["cp"]
72
+ positions[symbol][:value] = positions[symbol][:quantity] * q["l"].to_f
73
+ end
74
+
75
+ pos = []
76
+ positions.each do |symbol,position|
77
+ p = {}
78
+ p["Symbol"] = symbol.split(":")[1]
79
+ p["Last Price"] = "$#{position[:l].to_f.round(2)}"
80
+ p["Change"] = "#{position[:c]} (#{position[:cp]}%)"
81
+
82
+ if position[:quantity] > 0
83
+ p["Day's Gain"] = (position[:quantity] * position[:c].to_f).round(2)
84
+ p["Shares"] = position[:quantity]
85
+ p["Cost Basis"] = "$#{position[:cost].round(2)}"
86
+ p["Market Value"] = "$#{position[:value].round(2)}"
87
+ p["Gain"] = "$#{(position[:value] - position[:cost]).round(2)}"
88
+ p["Gain %"] = "#{(100.0 * (position[:value] - position[:cost]) / position[:cost]).round(1)}%"
89
+ else
90
+ p["Gain"] = "$#{(0 - position[:balance]).round(2)}"
91
+ p["Gain %"] = "#{(100.0 * (0 - position[:balance]) / position[:cost]).round(1)}%"
92
+ end
93
+ pos << p
94
+ end
95
+
96
+ puts Hirb::Helpers::Table.render(pos, :fields => ["Symbol", "Last Price", "Change", "Day's Gain", "Shares", "Cost Basis", "Market Value", "Gain", "Gain %"])
97
+
98
+ end
99
+
100
+ common_transaction_options
101
+ desc 'Add buy transaction'
102
+ def buy(options={})
103
+ transaction = transaction_from_options(options)
104
+ end
105
+
106
+ common_transaction_options
107
+ desc 'Add sell transaction'
108
+ def sell(options={})
109
+ options[:quantity] = 0 - options[:quantity]
110
+ transaction = transaction_from_options(options)
111
+ end
112
+
113
+ desc 'Create Portfolio'
114
+ def create(name)
115
+ Portfolio.create(:name => name, :created_at => DateTime.now)
116
+ end
117
+
118
+ desc 'Add to watchlist'
119
+ def watch(symbol)
120
+ # Validate symbol
121
+ quote = StockFolio::Web.quote(symbol)
122
+ if nil != quote
123
+ quote.each do |q|
124
+ WatchList.create(
125
+ :symbol => "#{q["e"]}:#{q["t"]}",
126
+ :created_at => DateTime.now
127
+ )
128
+ puts "Added #{q["t"]}"
129
+ end
130
+ end
131
+ end
132
+
133
+ desc 'Show Watchlist'
134
+ def watchlist
135
+ items = WatchList.all(:order => [:symbol.asc])
136
+ symbols = []
137
+ items.each do |item|
138
+ symbols << item.symbol
139
+ end
140
+ quote = StockFolio::Web.quote(symbols.join(","))
141
+ print_quotes(quote)
142
+ end
143
+
144
+ private
145
+ def transaction_from_options(options={})
146
+ # Do we have this portfolio?!?
147
+ portfolios = Portfolio.all(:name => options[:portfolio])
148
+ if portfolios.size == 0
149
+ puts "Portfolio #{options[:portfolio]} does not exist"
150
+ return
151
+ end
152
+
153
+ portfolio = portfolios[0]
154
+ puts "Portfolio #{portfolio.id} - #{portfolio.name}"
155
+
156
+ # Validate the symbol
157
+ quote = StockFolio::Web.quote(options[:symbol])
158
+ if nil == quote
159
+ puts "Symbol #{options[:symbol]} not found"
160
+ return
161
+ end
162
+
163
+ transaction = Transaction.new(
164
+ :symbol => options[:symbol],
165
+ :quantity => options[:quantity],
166
+ :price => options[:price],
167
+
168
+
169
+ :fee => 0,
170
+ :portfolio => portfolio,
171
+ :executed_at => DateTime.now,
172
+ :created_at => DateTime.now
173
+ )
174
+ transaction.fee = options[:fee] if options[:fee]
175
+ transaction.executed_at = DateTime.parse(options[:date]) if options[:date]
176
+
177
+
178
+ transaction.save
179
+
180
+ end
181
+
182
+ def print_quotes(quote)
183
+ if nil != quote
184
+ # Beautify those
185
+ quote.each do |q|
186
+ q["Symbol"] = q["t"]
187
+ q["Last Price"] = "$#{q["l"]}"
188
+ q["Change"] = "#{q["c"]} (#{q["cp"]}%)"
189
+ end
190
+ puts Hirb::Helpers::Table.render(quote, fields: ["Symbol", "Last Price", "Change"])
191
+ end
192
+
193
+ end
194
+ end
@@ -0,0 +1,31 @@
1
+ class Transaction
2
+ include DataMapper::Resource
3
+
4
+ property :id, Serial
5
+ property :symbol, String
6
+ property :quantity, Integer
7
+ property :price, Float
8
+ property :executed_at, DateTime
9
+ property :fee, Float
10
+ property :created_at, DateTime
11
+
12
+ belongs_to :portfolio
13
+
14
+ def order_name
15
+ if quantity > 0
16
+ return "BUY "
17
+ else
18
+ return "SELL"
19
+ end
20
+ end
21
+
22
+ def real_fee
23
+ if fee == nil
24
+ return 0
25
+ else
26
+ return fee
27
+ end
28
+ end
29
+ end
30
+
31
+
@@ -0,0 +1,3 @@
1
+ module StockFolio
2
+ VERSION = '0.1.1'
3
+ end
@@ -0,0 +1,8 @@
1
+ class WatchList
2
+ include DataMapper::Resource
3
+
4
+ property :id, Serial
5
+ property :symbol, String
6
+ property :created_at, DateTime
7
+ end
8
+
@@ -0,0 +1,16 @@
1
+ require 'json'
2
+ require 'net/http'
3
+
4
+ class StockFolio::Web
5
+ def self.quote(symbol)
6
+ url = "http://finance.google.com/finance/info?client=ig&q=#{symbol}"
7
+
8
+ resp = Net::HTTP.get_response(URI.parse(url))
9
+ data = resp.body
10
+
11
+ if data.empty?
12
+ return nil
13
+ end
14
+ JSON.parse(data.slice(3, data.length).strip)
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stockfolio
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jerome Poichet
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-22 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: boson
16
+ requirement: &2165393860 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2165393860
25
+ - !ruby/object:Gem::Dependency
26
+ name: hirb
27
+ requirement: &2165393380 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *2165393380
36
+ description:
37
+ email: poitch@gmail.com
38
+ executables:
39
+ - stockfolio
40
+ extensions: []
41
+ extra_rdoc_files: []
42
+ files:
43
+ - lib/stockfolio/portfolio.rb
44
+ - lib/stockfolio/runner.rb
45
+ - lib/stockfolio/transaction.rb
46
+ - lib/stockfolio/version.rb
47
+ - lib/stockfolio/watchlist.rb
48
+ - lib/stockfolio/web.rb
49
+ - lib/stockfolio.rb
50
+ - bin/stockfolio
51
+ homepage: http://github.com/poitch/stockfolio
52
+ licenses: []
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ requirements: []
71
+ rubyforge_project:
72
+ rubygems_version: 1.8.10
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: Track stock portfolio from the command line
76
+ test_files: []