stockfolio 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $LOAD_PATH << './lib'
3
+ $: << File.dirname($0) + "/../lib"
4
4
 
5
+ require 'rubygems'
5
6
  require 'stockfolio'
7
+
6
8
  begin
7
9
  StockFolio::Runner.start
8
10
  rescue Boson::OptionParser::Error => e
@@ -5,9 +5,12 @@ require_relative 'stockfolio/portfolio'
5
5
  require_relative 'stockfolio/transaction'
6
6
  require_relative 'stockfolio/watchlist'
7
7
 
8
+ require_relative 'stockfolio/formatters'
9
+
8
10
  module StockFolio
9
11
  autoload :Runner, 'stockfolio/runner'
10
12
  autoload :Web, 'stockfolio/web'
13
+ autoload :ConsoleGraph, 'stockfolio/consolegraph'
11
14
  end
12
15
 
13
16
  #DataMapper::Logger.new($stdout, :debug)
@@ -17,6 +20,9 @@ rcfile = ENV['STOCKFOLIO_YML'] || Dir.home + '/.stockfolio.yml'
17
20
  if File.exists?(rcfile)
18
21
  config = YAML::load(File.open(rcfile))
19
22
  ENV['STOCKFOLIO_DB'] = config['db'] || nil
23
+ if (config[:database])
24
+ ENV['STOCKFOLIO_DB'] = config[:database][:location] || nil
25
+ end
20
26
  end
21
27
 
22
28
  dbfile = ENV['STOCKFOLIO_DB'] || Dir.home + '/.stockfolio.db'
@@ -0,0 +1,52 @@
1
+ module StockFolio
2
+ class ConsoleGraph
3
+
4
+ def initialize(values)
5
+ @height = 25
6
+ @width = 120
7
+
8
+ min = nil
9
+ max = nil
10
+ values.each do |value|
11
+ value = value.to_f
12
+ min = nil == min ? value : min
13
+ max = nil == max ? value : max
14
+
15
+ min = value < min ? value : min
16
+ max = value > max ? value : max
17
+ end
18
+
19
+ heights = []
20
+ values.each do |value|
21
+ heights << (value.to_f * @height / max)
22
+ end
23
+
24
+ matrix = []
25
+ 0.upto(@height) do |i|
26
+ j = 0
27
+ matrix[i] = []
28
+ heights.each do |height|
29
+ if height >= i
30
+ matrix[i][j] = true
31
+ else
32
+ matrix[i][j] = false
33
+ end
34
+ j = j + 1
35
+ end
36
+ end
37
+
38
+
39
+ # Draw now
40
+ puts ' ^'
41
+ matrix.reverse!
42
+ matrix.each do |row|
43
+ line = ' |' + row.map { |f| f ? "*" : " " }.join('')
44
+ puts line
45
+ end
46
+ puts ' +' + '-' * values.length + '>'
47
+ end
48
+ end
49
+ end
50
+
51
+
52
+ #StockFolio::ConsoleGraph.new([1,2,3,4,5,6,7,8,9,10,9,8,7,7,6,5,4,3,2,1])
@@ -0,0 +1,21 @@
1
+ require 'hirb'
2
+
3
+ module Hirb::Helpers::Table::Filters
4
+ def to_dollars(amount)
5
+ if amount
6
+ amount = amount.to_f
7
+ if amount < 0
8
+ sprintf('($%0.2f)',0-amount).gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1,")
9
+ else
10
+ sprintf('$%0.2f',amount).gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1,")
11
+ end
12
+ end
13
+ end
14
+
15
+ def to_percent(value)
16
+ if value
17
+ "#{(100.0 * value).round(1)}%"
18
+ end
19
+ end
20
+ end
21
+
@@ -31,6 +31,22 @@ class StockFolio::Runner < Boson::Runner
31
31
  def historical(symbol,start_date,end_date)
32
32
  history = StockFolio::Web.historical(symbol, DateTime.parse(start_date), DateTime.parse(end_date))
33
33
  puts Hirb::Helpers::Table.render(history, :fields => [:date, :open, :close, :low, :high, :volume])
34
+ # http://tagaholic.me/hirb/doc/classes/Hirb/Helpers/Table.html#M000008
35
+ # :headers => { :date => "Date", ... }
36
+ # :description => false
37
+ # :filters => { :date => lambda { |f} }}
38
+ # https://github.com/change/method_profiler/blob/master/lib/method_profiler/hirb.rb
39
+ end
40
+
41
+ def graph_historical(symbol,start_date,end_date)
42
+ history = StockFolio::Web.historical(symbol, DateTime.parse(start_date), DateTime.parse(end_date))
43
+
44
+ values = []
45
+ history.each do |row|
46
+ values << row[:close]
47
+ end
48
+ values.reverse!
49
+ StockFolio::ConsoleGraph.new(values)
34
50
  end
35
51
 
36
52
  desc "Search symbol"
@@ -71,8 +87,16 @@ class StockFolio::Runner < Boson::Runner
71
87
  portfolios = []
72
88
  if nil == name
73
89
  portfolios = Portfolio.all
90
+ if portfolios.size == 0
91
+ puts "No portfolio found"
92
+ exit
93
+ end
74
94
  else
75
95
  portfolios = Portfolio.all(:name => name)
96
+ if portfolios.size == 0
97
+ puts "Portfolio #{name} not found"
98
+ exit
99
+ end
76
100
  end
77
101
 
78
102
  positions = {}
@@ -108,28 +132,54 @@ class StockFolio::Runner < Boson::Runner
108
132
  end
109
133
 
110
134
  pos = []
135
+
136
+ total = {}
137
+ total[:symbol] = "Total"
138
+ total[:daygain] = 0
139
+ total[:cost] = 0
140
+ total[:value] = 0
141
+ total[:gain] = 0
142
+
111
143
  positions.each do |symbol,position|
112
144
  p = {}
113
- p["Symbol"] = symbol.split(":")[1]
114
- p["Last Price"] = "$#{position[:l].to_f.round(2)}"
115
- p["Change"] = "#{position[:c]} (#{position[:cp]}%)"
145
+ p[:symbol] = symbol.split(":")[1]
146
+ p[:last_price] = position[:l].to_f
147
+ p[:change] = "#{position[:c]} (#{position[:cp]}%)"
116
148
 
117
149
  if position[:quantity] > 0
118
- p["Day's Gain"] = (position[:quantity] * position[:c].to_f).round(2)
119
- p["Shares"] = position[:quantity]
120
- p["Cost Basis"] = "$#{position[:cost].round(2)}"
121
- p["Market Value"] = "$#{position[:value].round(2)}"
122
- p["Gain"] = "$#{(position[:value] - position[:cost]).round(2)}"
123
- p["Gain %"] = "#{(100.0 * (position[:value] - position[:cost]) / position[:cost]).round(1)}%"
150
+ p[:daygain] = position[:quantity] * position[:c].to_f
151
+ p[:quantity] = position[:quantity]
152
+ p[:cost] = position[:cost].to_f
153
+ p[:value] = position[:value].to_f
154
+ p[:gain] = position[:value] - position[:cost]
155
+ p[:gain_p] = (position[:value] - position[:cost]) / position[:cost]
156
+
157
+ total[:daygain] = total[:daygain] + p[:daygain]
158
+ total[:cost] = total[:cost] + position[:cost]
159
+ total[:value] = total[:value] + position[:value]
160
+ total[:gain] = total[:gain] + position[:value] - position[:cost]
124
161
  else
125
- p["Gain"] = "$#{(0 - position[:balance]).round(2)}"
126
- p["Gain %"] = "#{(100.0 * (0 - position[:balance]) / position[:cost]).round(1)}%"
162
+ p[:gain] = (0 - position[:balance])
163
+ p[:gain_p] = (0 - position[:balance]) / position[:cost]
164
+
127
165
  end
128
166
  pos << p
129
167
  end
130
168
 
131
- puts Hirb::Helpers::Table.render(pos, :fields => ["Symbol", "Last Price", "Change", "Day's Gain", "Shares", "Cost Basis", "Market Value", "Gain", "Gain %"])
132
-
169
+ pos.sort! { |a,b| a[:symbol] <=> b[:symbol] }
170
+
171
+ total[:gain_p] = (total[:value] - total[:cost]) / total[:cost]
172
+
173
+ pos << total
174
+
175
+ puts Hirb::Helpers::Table.render(pos,
176
+ :fields => [:symbol, :last_price, :change, :daygain, :quantity, :cost, :value, :gain, :gain_p],
177
+ :headers => {:symbol => "Symbol", :last_price => "Last Price", :change => "Change", :daygain => "Day's Gain", :quantity => "Shares", :cost => "Cost Basis", :value => "Market Value", :gain => "Gain", :gain_p => "Gain %"},
178
+ :filters => { :last_price => :to_dollars, :daygain => :to_dollars, :cost => :to_dollars, :value => :to_dollars, :gain => :to_dollars, :gain_p => :to_percent },
179
+ :description => false
180
+
181
+ )
182
+ puts "Market is #{StockFolio::Web.market_status}"
133
183
  end
134
184
 
135
185
  common_transaction_options
@@ -298,12 +348,38 @@ class StockFolio::Runner < Boson::Runner
298
348
  def print_quotes(quote)
299
349
  if nil != quote
300
350
  # Beautify those
351
+ extended = false
352
+ quote.sort! { |a,b| a["t"] <=> b["t"] }
301
353
  quote.each do |q|
302
- q["Symbol"] = q["t"]
303
- q["Last Price"] = "$#{q["l"]}"
304
- q["Change"] = "#{q["c"]} (#{q["cp"]}%)"
354
+ #q["Symbol"] = q["t"]
355
+ #q["Last Price"] = "$#{q["l"]}"
356
+
357
+ #q["t"] = "#{q["e"]}:#{q["t"]}"
358
+ q[:change] = "#{q["c"]} (#{q["cp"]}%)"
359
+ if q["el"]
360
+ q[:echange] = "#{q["ec"]} (#{q["ecp"]}%)"
361
+ extended = true
362
+ else
363
+ q["elt"] = q["lt"]
364
+ end
305
365
  end
306
- puts Hirb::Helpers::Table.render(quote, fields: ["Symbol", "Last Price", "Change"])
366
+ headers = { "t" => "Symbol", "l" => "Last Price", :change => "Change", "lt" => "Last Updated" }
367
+
368
+ fields = ["t", "l", :change, "lt"]
369
+ filters = { "l" => :to_dollars }
370
+
371
+ if extended
372
+ fields = ["t", "l", :change, "el", :echange, "elt"]
373
+ headers = { "t" => "Symbol", "l" => "Close Price", :change => "Change", "el" => "After Hours Price", :echange => "After Hours Change", "elt" => "Last Updated" }
374
+ filters = { "l" => :to_dollars, "el" => :to_dollars }
375
+ end
376
+
377
+ puts Hirb::Helpers::Table.render(quote,
378
+ :fields => fields,
379
+ :headers => headers,
380
+ :filters => filters,
381
+ :description => false
382
+ )
307
383
  end
308
384
 
309
385
  end
@@ -1,3 +1,3 @@
1
1
  module StockFolio
2
- VERSION = '0.1.5'
2
+ VERSION = '0.1.6'
3
3
  end
@@ -12,7 +12,9 @@ class StockFolio::Web
12
12
  if data.empty?
13
13
  return nil
14
14
  end
15
- JSON.parse(data.slice(3, data.length).strip)
15
+ r = JSON.parse(data.slice(3, data.length).strip)
16
+ #puts r
17
+ r
16
18
  end
17
19
 
18
20
  def self.historical(symbol, startDate, endDate)
@@ -59,4 +61,27 @@ class StockFolio::Web
59
61
  result["matches"]
60
62
  end
61
63
 
64
+ def self.market_status()
65
+ url = "http://www.nasdaq.com/dynamic_includes/marketstatus.js"
66
+ resp = Net::HTTP.get_response(URI.parse(url))
67
+ data = resp.body
68
+ if data.empty?
69
+ return false
70
+ end
71
+
72
+ parts = data.split('=')
73
+ status = parts[1].gsub('"', '').gsub(';', '').strip
74
+ if status == "O"
75
+ return "Opened"
76
+ elsif status == "A"
77
+ return "After hours"
78
+ elsif status == "C"
79
+ return "Closed"
80
+ else
81
+ return nil
82
+ end
83
+
84
+ end
85
+
62
86
  end
87
+
metadata CHANGED
@@ -1,136 +1,138 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: stockfolio
3
- version: !ruby/object:Gem::Version
4
- prerelease: false
5
- segments:
6
- - 0
7
- - 1
8
- - 5
9
- version: 0.1.5
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.6
5
+ prerelease:
10
6
  platform: ruby
11
- authors:
7
+ authors:
12
8
  - Jerome Poichet
13
9
  autorequire:
14
10
  bindir: bin
15
11
  cert_chain: []
16
-
17
- date: 2012-03-22 00:00:00 -07:00
18
- default_executable:
19
- dependencies:
20
- - !ruby/object:Gem::Dependency
12
+ date: 2012-04-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
21
15
  name: boson
22
- prerelease: false
23
- requirement: &id001 !ruby/object:Gem::Requirement
16
+ requirement: !ruby/object:Gem::Requirement
24
17
  none: false
25
- requirements:
26
- - - ">="
27
- - !ruby/object:Gem::Version
28
- segments:
29
- - 0
30
- version: "0"
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
31
22
  type: :runtime
32
- version_requirements: *id001
33
- - !ruby/object:Gem::Dependency
34
- name: hirb
35
23
  prerelease: false
36
- requirement: &id002 !ruby/object:Gem::Requirement
24
+ version_requirements: !ruby/object:Gem::Requirement
37
25
  none: false
38
- requirements:
39
- - - ">="
40
- - !ruby/object:Gem::Version
41
- segments:
42
- - 0
43
- version: "0"
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: hirb
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
44
38
  type: :runtime
45
- version_requirements: *id002
46
- - !ruby/object:Gem::Dependency
47
- name: data_mapper
48
39
  prerelease: false
49
- requirement: &id003 !ruby/object:Gem::Requirement
40
+ version_requirements: !ruby/object:Gem::Requirement
50
41
  none: false
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- segments:
55
- - 0
56
- version: "0"
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: data_mapper
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
57
54
  type: :runtime
58
- version_requirements: *id003
59
- - !ruby/object:Gem::Dependency
60
- name: dm-migrations
61
55
  prerelease: false
62
- requirement: &id004 !ruby/object:Gem::Requirement
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: dm-migrations
64
+ requirement: !ruby/object:Gem::Requirement
63
65
  none: false
64
- requirements:
65
- - - ">="
66
- - !ruby/object:Gem::Version
67
- segments:
68
- - 0
69
- version: "0"
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
70
  type: :runtime
71
- version_requirements: *id004
72
- - !ruby/object:Gem::Dependency
73
- name: dm-sqlite-adapter
74
71
  prerelease: false
75
- requirement: &id005 !ruby/object:Gem::Requirement
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ - !ruby/object:Gem::Dependency
79
+ name: dm-sqlite-adapter
80
+ requirement: !ruby/object:Gem::Requirement
76
81
  none: false
77
- requirements:
78
- - - ">="
79
- - !ruby/object:Gem::Version
80
- segments:
81
- - 0
82
- version: "0"
82
+ requirements:
83
+ - - ! '>='
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
83
86
  type: :runtime
84
- version_requirements: *id005
85
- - !ruby/object:Gem::Dependency
86
- name: json
87
87
  prerelease: false
88
- requirement: &id006 !ruby/object:Gem::Requirement
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ! '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ - !ruby/object:Gem::Dependency
95
+ name: json
96
+ requirement: !ruby/object:Gem::Requirement
89
97
  none: false
90
- requirements:
91
- - - ">="
92
- - !ruby/object:Gem::Version
93
- segments:
94
- - 0
95
- version: "0"
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
96
102
  type: :runtime
97
- version_requirements: *id006
98
- - !ruby/object:Gem::Dependency
99
- name: uri
100
103
  prerelease: false
101
- requirement: &id007 !ruby/object:Gem::Requirement
104
+ version_requirements: !ruby/object:Gem::Requirement
102
105
  none: false
103
- requirements:
104
- - - ">="
105
- - !ruby/object:Gem::Version
106
- segments:
107
- - 0
108
- version: "0"
109
- type: :runtime
110
- version_requirements: *id007
111
- - !ruby/object:Gem::Dependency
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
112
111
  name: gems
113
- prerelease: false
114
- requirement: &id008 !ruby/object:Gem::Requirement
112
+ requirement: !ruby/object:Gem::Requirement
115
113
  none: false
116
- requirements:
117
- - - ">="
118
- - !ruby/object:Gem::Version
119
- segments:
120
- - 0
121
- version: "0"
114
+ requirements:
115
+ - - ! '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
122
118
  type: :runtime
123
- version_requirements: *id008
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
124
126
  description:
125
127
  email: poitch@gmail.com
126
- executables:
128
+ executables:
127
129
  - stockfolio
128
130
  extensions: []
129
-
130
131
  extra_rdoc_files: []
131
-
132
- files:
132
+ files:
133
133
  - lib/stockfolio/check.rb
134
+ - lib/stockfolio/consolegraph.rb
135
+ - lib/stockfolio/formatters.rb
134
136
  - lib/stockfolio/portfolio.rb
135
137
  - lib/stockfolio/runner.rb
136
138
  - lib/stockfolio/transaction.rb
@@ -139,38 +141,29 @@ files:
139
141
  - lib/stockfolio/web.rb
140
142
  - lib/stockfolio.rb
141
143
  - bin/stockfolio
142
- has_rdoc: true
143
144
  homepage: http://github.com/poitch/stockfolio
144
145
  licenses: []
145
-
146
146
  post_install_message:
147
147
  rdoc_options: []
148
-
149
- require_paths:
148
+ require_paths:
150
149
  - lib
151
150
  - lib
152
- required_ruby_version: !ruby/object:Gem::Requirement
151
+ required_ruby_version: !ruby/object:Gem::Requirement
153
152
  none: false
154
- requirements:
155
- - - ">="
156
- - !ruby/object:Gem::Version
157
- segments:
158
- - 0
159
- version: "0"
160
- required_rubygems_version: !ruby/object:Gem::Requirement
153
+ requirements:
154
+ - - ! '>='
155
+ - !ruby/object:Gem::Version
156
+ version: '0'
157
+ required_rubygems_version: !ruby/object:Gem::Requirement
161
158
  none: false
162
- requirements:
163
- - - ">="
164
- - !ruby/object:Gem::Version
165
- segments:
166
- - 0
167
- version: "0"
159
+ requirements:
160
+ - - ! '>='
161
+ - !ruby/object:Gem::Version
162
+ version: '0'
168
163
  requirements: []
169
-
170
164
  rubyforge_project:
171
- rubygems_version: 1.3.7
165
+ rubygems_version: 1.8.21
172
166
  signing_key:
173
167
  specification_version: 3
174
168
  summary: Track stock portfolio from the command line
175
169
  test_files: []
176
-