stockfolio 0.1.5 → 0.1.6

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.
@@ -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
-