stockfolio 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []