qticker 1.0.5 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 494c931926f1f8c3f6192e66db4a075a35a56198
4
- data.tar.gz: b2f124d4e050d554f5681108b28e0f62ce82424f
3
+ metadata.gz: 0745f12aa2f16c3d7ea823422960cfe8ba8890dd
4
+ data.tar.gz: 8c708da85dab21449ba719530a3b1588ff9cf60a
5
5
  SHA512:
6
- metadata.gz: 67182ffe4f96c7427ac6822a603e2884786859dcd8c27074d35d2a2b0acbe13a78647cce4d052a48f1ad8458a795aae55dc06ab864ce336601b01831c783ba93
7
- data.tar.gz: 2e0286a1c602d99806552cad09ead288e5d4387fc9f0c1722b4d94de2f2f677e514fea9532d4a3cf84425344f0679b553603071f9b0edffafc9cbfd2e9d930aa
6
+ metadata.gz: 7ac5910a40bd4729b4348a5b1fcb01f73265eaddf64720a61fe9c5377d1d251eb0183fc72493f2f58e7c82f2744a5904c2d6f3d02694804aba8a6454ecc4b56d
7
+ data.tar.gz: a4693099fd176c612cb0a67185ea8806cc251fd0472595db88d967d10c44fa6f1f873f66bf0daa3a80ace63b9cdd8ad393302dfcbe472cc804c63ccde04933af
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require_relative '../config/environment.rb'
3
+ require_relative '../lib/qticker.rb'
4
4
 
5
- cli = MainCli.new
6
-
7
- cli.welcome("Quick Ticker", -> {cli.ticker_symbol_prompt})
5
+ cli = QuickTicker::MainCli.new
6
+ cli.welcome("Quick Ticker", -> {cli.ticker_symbol_prompt})
data/lib/cli.rb CHANGED
@@ -1,51 +1,108 @@
1
- class Cli
1
+ module QuickTicker
2
2
 
3
- attr_accessor :stock, :scraper
3
+ class Cli
4
4
 
5
- def initialize(cli = nil)
6
- self.scraper = Scraper.new(self)
7
- end
5
+ attr_accessor :stock, :scraper, :last_option_lambda, :exit_message
8
6
 
9
- def welcome(mode_name, mode_lambda)
10
- print "\nWelcome to " + mode_name + "!\n"
11
- mode_lambda.()
12
- end
7
+ def initialize(cli = nil)
8
+ self.scraper = QuickTicker::Scraper.new(self)
9
+ end
13
10
 
14
- def display_quote
15
- self.stock.display
16
- self.stock.quote.display
17
- self.stock_option_menu("Display a company description", -> { self.display_desc })
18
- end
11
+ def welcome(mode_name, mode_lambda)
12
+ print "\nWelcome to " + mode_name + "!\n"
13
+ mode_lambda.()
14
+ end
19
15
 
20
- def display_desc
21
- self.stock.display
22
- self.stock.desc.display
23
- self.stock_option_menu("Redisplay your quote", -> { self.display_quote })
24
- end
16
+ def display_stock_header
17
+ print "\n#{self.stock.name} (#{self.stock.exchange}:#{self.stock.symbol})\n\n"
18
+ end
25
19
 
26
- def stock_option_menu(opt_1_string, opt_1_lambda = nil)
27
- gets
28
- puts "1. #{opt_1_string} for #{self.stock.symbol}."
29
- puts "2. Enter another ticker symbol."
30
- puts "Enter any other key to exit."
31
- gets.strip.gsub('.', '')
32
- end
20
+ def display_stock_quote
21
+ puts "Current: #{self.stock.quote.price} #{stock.quote.change}(#{stock.quote.change_pct}%)"
22
+ puts "Open: #{self.stock.quote.open}"
23
+ puts "Volume: #{self.stock.quote.volume}"
24
+ puts "Avg Vol: #{self.stock.quote.volume_avg}"
25
+ puts "Mkt Cap: #{self.stock.quote.mkt_cap}"
26
+ puts "P/E(ttm): #{self.stock.quote.pe_ttm}"
27
+ puts "Yield: #{self.stock.quote.div_yld}%"
28
+ end
33
29
 
34
- def symbol_validation(symbol, valid, fixture_url = nil)
35
- # stock_array[0] is a stock if one was succesfully created and nil otherwise.
36
- # stock_array[1] indicates whether the symbol cooresponds to a mutual fund.
37
- stock_array = self.scraper.load_gfs(symbol, fixture_url)
38
- self.stock = stock_array[0]
39
- valid = self.stock.nil? ? false : true
40
- puts "Invalid ticker symbol." if !valid
41
- puts "Mutual funds are not currently not supported." if stock_array[1]
42
- self.display_quote if valid
43
- # returns an array.
44
- # array[0] - whether the entered symbol was valid
45
- # array[1] = stock_array[1] - whether the entered
46
- # symbol was a mutal fund.
47
- [valid, stock_array[1]]
48
- end
30
+ def display_stock_description
31
+ print "#{self.stock.description.sector} : #{self.stock.description.industry}\n\n"
32
+ puts "#{self.stock.description.summary}".fit
33
+ end
34
+
35
+ def display_stock_related_companies
36
+ output = "Symbol Price Mkt Cap\n"
37
+ self.stock.related_companies.each do |stock_related_company|
38
+ output += (stock_related_company.symbol + ' ' * 8)[0,8]
39
+ output += (stock_related_company.price + ' ' * 8)[0,8]
40
+ output += ("#{stock_related_company.change}(#{stock_related_company.change_pct}%)" + ' ' * 16)[0,16]
41
+ output += stock_related_company.mkt_cap + "\n"
42
+ end
43
+ puts output
44
+ end
45
+
46
+ def fetch_stock_quote
47
+ self.display_stock_header
48
+ self.display_stock_quote
49
+ self.call_stock_option_menu({ option_1_string: "Display a company description", option_2_string: "Display related companies", option_1_lambda: -> { self.fetch_stock_description }, option_2_lambda: -> { self.fetch_stock_related_companies }, last_option_lambda: -> {self.last_option_lambda.()} })
50
+ end
49
51
 
52
+ def fetch_stock_description
53
+ self.display_stock_header
54
+ self.display_stock_description
55
+ self.call_stock_option_menu({ option_1_string: "Display a quote", option_2_string: "Display related companies", option_1_lambda: -> { self.fetch_stock_quote }, option_2_lambda: -> { self.fetch_stock_related_companies }, last_option_lambda: -> {self.last_option_lambda.()} })
56
+ end
57
+
58
+ def fetch_stock_related_companies
59
+ self.display_stock_header
60
+ self.display_stock_related_companies
61
+ self.call_stock_option_menu({ option_1_string: "Display a quote", option_2_string: "Display a company description", option_1_lambda: -> { self.fetch_stock_quote }, option_2_lambda: -> { self.fetch_stock_description }, last_option_lambda: -> {self.last_option_lambda.()} })
62
+ end
63
+
64
+ def call_stock_option_menu(option_hash)
65
+ process_stock_option_menu_input(display_stock_option_menu(option_hash[:option_1_string], option_hash[:option_2_string]), option_hash[:option_1_lambda], option_hash[:option_2_lambda], option_hash[:last_option_lambda])
66
+ end
67
+
68
+ def display_stock_option_menu(option_1_string, option_2_string)
69
+ gets
70
+ puts "1. #{option_1_string} for #{self.stock.symbol}."
71
+ puts "2. #{option_2_string} for #{self.stock.symbol}."
72
+ puts "3. Enter another ticker symbol."
73
+ puts "Enter any other key to exit."
74
+ gets.strip.gsub('.', '')
75
+ end
76
+
77
+ def process_stock_option_menu_input(input, option_1_lambda, option_2_lambda, option_3_lambda)
78
+ if input == "1"
79
+ option_1_lambda.()
80
+ elsif input == "2"
81
+ option_2_lambda.()
82
+ elsif input == "3"
83
+ option_3_lambda.()
84
+ else
85
+ puts self.exit_message
86
+ return nil
87
+ end
88
+ end
89
+
90
+ def symbol_validation(symbol, fixture_url = nil)
91
+ # stock_array[0] is a stock if one was succesfully created and nil otherwise.
92
+ # stock_array[1] indicates whether the symbol cooresponds to a mutual fund.
93
+ stock_array = self.scraper.load_gfs(symbol, fixture_url)
94
+ self.stock = stock_array[0]
95
+ valid = self.stock.nil? ? false : true
96
+ puts "Invalid ticker symbol." if !valid
97
+ puts "Mutual funds are not currently not supported." if stock_array[1]
98
+ self.fetch_stock_quote if valid
99
+ # returns an array.
100
+ # array[0] - whether the entered symbol was valid
101
+ # array[1] = stock_array[1] - whether the entered
102
+ # symbol was a mutal fund.
103
+ [valid, stock_array[1]]
104
+ end
105
+
106
+ end
50
107
 
51
108
  end
@@ -1,58 +1,54 @@
1
- class DevCli < Cli
1
+ module QuickTicker
2
2
 
3
- attr_accessor :main
3
+ class DevCli < QuickTicker::Cli
4
4
 
5
- def initialize(main_cli)
6
- super(main_cli)
7
- self.main = main_cli
8
- end
5
+ attr_accessor :main
9
6
 
10
- def option_menu
11
- puts "\nPlease select a fixture to load:"
12
- puts "1. Load MSFT.html"
13
- puts "2. Load IBM.html"
14
- puts "3. Load QQQ.html"
15
- puts "4. Load FBIOX.html"
16
- puts "Or enter any other key to return to"
17
- puts "your regularly scheduled program."
18
- input = gets.strip.gsub('.', '')
19
- path = File.expand_path(File.dirname(__FILE__)).chomp('bin') + '/fixtures/'
20
- if input == "1"
21
- valid = self.symbol_validation("MSFT", false, path + "MSFT.html")
22
- elsif input == "2"
23
- valid = self.symbol_validation("IBM", false, path + "IBM.html")
24
- elsif input == "3"
25
- valid = self.symbol_validation("QQQ", false, path + "QQQ.html")
26
- elsif input == "4"
27
- valid = self.symbol_validation("FBIOX", false, path + "FBIOX.html")
28
- else
29
- puts "Leaving Developer Mode and resuming program."
7
+ def initialize(main_cli)
8
+ super(main_cli)
9
+ self.main = main_cli
10
+ self.last_option_lambda = -> { self.call_dev_option_menu }
11
+ self.exit_message = "Leaving Developer Mode and resuming program."
30
12
  end
31
- return nil
32
- end
33
13
 
34
- def stock_option_menu(opt_1_string, opt_1_lambda)
35
- input = super(opt_1_string, opt_1_lambda)
36
- if input == "1"
37
- opt_1_lambda.()
38
- elsif input == "2"
39
- self.option_menu
40
- else
41
- puts "Leaving Developer Mode and resuming program."
42
- return nil
14
+ def call_dev_option_menu
15
+ process_dev_option_menu_input(display_dev_option_menu)
43
16
  end
44
- end
45
17
 
46
- def symbol_validation(symbol, valid, fixture_url = nil)
47
- valid_array = super(symbol, valid, fixture_url)
48
- if valid_array[1] # whether entered symbol was a mutual fund
49
- puts ""
50
- option_menu
18
+ def display_dev_option_menu
19
+ puts "\nPlease select a fixture to load:"
20
+ puts "1. Load MSFT.html"
21
+ puts "2. Load IBM.html"
22
+ puts "3. Load QQQ.html"
23
+ puts "4. Load FBIOX.html"
24
+ puts "Or enter any other key to return to"
25
+ puts "your regularly scheduled program."
26
+ gets.strip.gsub('.', '')
51
27
  end
52
- valid_array[0] # returns whether entered symbol was valid
53
- end
54
28
 
29
+ def process_dev_option_menu_input(input)
30
+ if input == "1"
31
+ valid = self.symbol_validation("MSFT", "http://lair001.github.io/fixtures/qticker/MSFT.html")
32
+ elsif input == "2"
33
+ valid = self.symbol_validation("IBM", "http://lair001.github.io/fixtures/qticker/IBM.html")
34
+ elsif input == "3"
35
+ valid = self.symbol_validation("QQQ", "http://lair001.github.io/fixtures/qticker/QQQ.html")
36
+ elsif input == "4"
37
+ valid = self.symbol_validation("FBIOX", "http://lair001.github.io/fixtures/qticker/FBIOX.html")
38
+ else
39
+ puts self.exit_message
40
+ end
41
+ return nil
42
+ end
55
43
 
44
+ def symbol_validation(symbol, fixture_url = nil)
45
+ valid_array = super(symbol, fixture_url)
46
+ if valid_array[1] # whether entered symbol was a mutual fund
47
+ call_dev_option_menu
48
+ end
49
+ valid_array[0] # returns whether entered symbol was valid
50
+ end
56
51
 
52
+ end
57
53
 
58
54
  end
@@ -1,39 +1,34 @@
1
- class MainCli < Cli
1
+ module QuickTicker
2
2
 
3
- attr_accessor :scraper, :dev
3
+ class MainCli < QuickTicker::Cli
4
4
 
5
- def initialize(cli = nil)
6
- super(cli)
7
- self.dev = DevCli.new(self)
8
- end
5
+ attr_accessor :dev
6
+
7
+ def initialize(cli = nil)
8
+ super(cli)
9
+ self.dev = QuickTicker::DevCli.new(self)
10
+ self.last_option_lambda = -> { self.ticker_symbol_prompt }
11
+ self.exit_message = "Thank you for using Quick Ticker!"
12
+ end
9
13
 
10
14
 
11
- def ticker_symbol_prompt
12
- valid = false
13
- while !valid do
14
- print "\nPlease enter a ticker symbol: "
15
- symbol = gets.strip.upcase
16
- if symbol == "DEV"
17
- self.welcome("Developer Mode", -> {self.dev.option_menu}) if symbol == "DEV"
18
- else
19
- valid = self.symbol_validation(symbol, valid)
15
+ def ticker_symbol_prompt
16
+ valid = false
17
+ while !valid do
18
+ print "\nPlease enter a ticker symbol: "
19
+ symbol = gets.strip.upcase
20
+ if symbol == "DEV"
21
+ self.dev.welcome("Developer Mode", -> {self.dev.call_dev_option_menu}) if symbol == "DEV"
22
+ else
23
+ valid = self.symbol_validation(symbol)
24
+ end
20
25
  end
21
26
  end
22
- end
23
27
 
24
- def stock_option_menu(opt_1_string, opt_1_lambda)
25
- input = super(opt_1_string, opt_1_lambda)
26
- if input == "1"
27
- opt_1_lambda.()
28
- elsif input == "2"
29
- self.ticker_symbol_prompt
30
- else
31
- return nil
28
+ def symbol_validation(symbol, fixture_url = nil)
29
+ super(symbol)[0] # returns whether entered symbol was valid
32
30
  end
33
- end
34
31
 
35
- def symbol_validation(symbol, valid, fixture_url = nil)
36
- super(symbol, valid)[0] # returns whether entered symbol was valid
37
32
  end
38
33
 
39
- end
34
+ end
@@ -1 +1,16 @@
1
- require_relative '../config/environment.rb'
1
+ require 'nokogiri'
2
+ require 'word_wrap'
3
+ require 'word_wrap/core_ext'
4
+ require 'watir'
5
+ require 'phantomjs'
6
+
7
+ require_relative '../lib/cli.rb'
8
+ require_relative '../lib/table.rb'
9
+ require_relative '../lib/stock_attribute.rb'
10
+ require_relative '../lib/stock_description.rb'
11
+ require_relative '../lib/dev_cli.rb'
12
+ require_relative '../lib/main_cli.rb'
13
+ require_relative '../lib/stock_quote.rb'
14
+ require_relative '../lib/stock_related_company.rb'
15
+ require_relative '../lib/scraper.rb'
16
+ require_relative '../lib/stock.rb'
@@ -1,82 +1,114 @@
1
- class Scraper
1
+ module QuickTicker
2
2
 
3
- attr_accessor :cli, :gfs_noko_html, :stock
3
+ class Scraper
4
4
 
5
- def initialize(cli)
6
- self.cli = cli
7
- end
5
+ attr_accessor :browser, :cli, :gfs_noko_html, :stock
8
6
 
9
- def gfs_url(symbol)
10
- "https://www.google.com/finance?q=" + symbol
11
- end
7
+ def initialize(cli)
8
+ self.cli = cli
9
+ self.browser = Watir::Browser.new(:phantomjs)
10
+ end
12
11
 
13
- def load_gfs_noko_html(url)
14
- self.gfs_noko_html = Nokogiri::HTML(open(url))
15
- end
12
+ def gfs_url(symbol)
13
+ "https://www.google.com/finance?q=" + symbol
14
+ end
16
15
 
17
- # Returns an array. Array[0] is a stock if one was succesfully created and nil otherwise.
18
- # Array[1] indicates whether the symbol cooresponds to a mutual fund.
19
- def load_gfs(symbol, fixture_url = nil)
20
- fixture_url.nil? ? load_gfs_noko_html(self.gfs_url(symbol)) : load_gfs_noko_html(fixture_url)
21
- return [nil, true] unless self.gfs_noko_html.text.match('\(MUTF:').nil?
22
- return [nil, false] if self.gfs_noko_html.css("span.pr").text.strip == "" # checks whether the page lists a price
23
- # return [nil, false] if self.gfs_noko_html.css("div.fjfe-content").text.include?("- produced no matches.")
24
- [self.create_stock(symbol), false]
25
- end
16
+ def load_gfs_noko_html(url)
17
+ browser.goto(url)
18
+ self.gfs_noko_html = Nokogiri::HTML(browser.html)
19
+ end
26
20
 
27
- def create_stock(symbol)
28
- data = scrape_stock(symbol)
29
- Stock.new(data)
30
- end
21
+ # Returns an array. Array[0] is a stock if one was succesfully created and nil otherwise.
22
+ # Array[1] indicates whether the symbol cooresponds to a mutual fund.
23
+ def load_gfs(symbol, fixture_url = nil)
24
+ fixture_url.nil? ? load_gfs_noko_html(self.gfs_url(symbol)) : load_gfs_noko_html(fixture_url)
25
+ return [nil, true] unless self.gfs_noko_html.text.match('\(MUTF:').nil?
26
+ return [nil, false] if self.gfs_noko_html.css("span.pr").text.strip == "" # checks whether the page lists a price
27
+ # return [nil, false] if self.gfs_noko_html.css("div.fjfe-content").text.include?("- produced no matches.")
28
+ [self.create_stock(symbol), false]
29
+ end
31
30
 
32
- def scrape_stock(symbol)
33
- data = { stock: {} }
34
- data[:stock][:symbol] = symbol
35
- begin
36
- data[:stock][:name] = self.gfs_noko_html.css("div.g-first a").text.match('(?<=All news for )[\w,.)() ]*(?= »)')[0]
37
- rescue NoMethodError
38
- data[:stock][:name] = ""
31
+ def create_stock(symbol)
32
+ data = package_stock(symbol)
33
+ QuickTicker::Stock.new(data)
39
34
  end
40
- data[:stock][:exchange] = self.gfs_noko_html.css("span.dis-large").text.split("\n")[0]
41
- data[:stock] = self.nil_to_empty_str(data[:stock])
42
- data[:quote] = self.scrape_quote
43
- data[:desc] = self.scrape_desc
44
- data
45
- end
46
35
 
47
- def scrape_quote
48
- data = {}
49
- data[:price] = self.gfs_noko_html.css("span.pr").text.strip
50
- data[:change] = self.gfs_noko_html.css("div.nwp span.bld").text.split("\n")[0]
51
- begin
52
- data[:change_pct] = self.gfs_noko_html.css("div.nwp span.bld").text.split("\n")[1].gsub(/[)(]/, '')
53
- rescue NoMethodError
54
- data[:change_pct] = ""
36
+ def package_stock(symbol)
37
+ data = {}
38
+ data[:stock] = self.scrape_stock(symbol)
39
+ data[:quote] = self.scrape_stock_quote
40
+ data[:description] = self.scrape_stock_description
41
+ data[:related_companies] = self.scrape_stock_related_companies
42
+ data
55
43
  end
56
- data[:range] = self.gfs_noko_html.css("td[data-snapfield='range']+td").text.strip
57
- data[:range_yr] = self.gfs_noko_html.css("td[data-snapfield='range_52week']+td").text.strip
58
- data[:open] = self.gfs_noko_html.css("td[data-snapfield='open']+td").text.strip
59
- data[:volume] = self.gfs_noko_html.css("td[data-snapfield='vol_and_avg']+td").text.strip.split("/")[0]
60
- data[:volume_avg] = self.gfs_noko_html.css("td[data-snapfield='vol_and_avg']+td").text.strip.split("/")[1]
61
- data[:mkt_cap] = self.gfs_noko_html.css("td[data-snapfield='market_cap']+td").text.strip
62
- data[:pe_ttm] = self.gfs_noko_html.css("td[data-snapfield='pe_ratio']+td").text.strip
63
- data[:div_yld] = self.gfs_noko_html.css("td[data-snapfield='latest_dividend-dividend_yield']+td").text.strip.split("/")[1]
64
- nil_to_empty_str(data)
65
- end
66
44
 
67
- def scrape_desc
68
- data = {}
69
- data[:sector] = self.gfs_noko_html.css("a#sector").text
70
- data[:industry] = self.gfs_noko_html.css("a#sector+a").text
71
- data[:summary] = self.gfs_noko_html.css("div.companySummary").text.gsub("More from Reuters »", "").strip
72
- nil_to_empty_str(data)
73
- end
45
+ def scrape_stock(symbol)
46
+ data = {}
47
+ data[:symbol] = symbol
48
+ begin
49
+ data[:name] = self.gfs_noko_html.css("div.g-first a").text.match('(?<=All news for )[\w,.)() ]*(?= »)')[0]
50
+ rescue NoMethodError
51
+ data[:name] = ""
52
+ end
53
+ data[:exchange] = self.gfs_noko_html.css("span.dis-large").text.split("\n")[0]
54
+ nil_to_empty_str(data)
55
+ end
56
+
57
+ def scrape_stock_quote
58
+ data = {}
59
+ data[:price] = self.gfs_noko_html.css("span.pr").text.strip
60
+ data[:change] = self.gfs_noko_html.css("div.nwp span.bld").text.split("\n")[0]
61
+ begin
62
+ data[:change_pct] = self.gfs_noko_html.css("div.nwp span.bld").text.split("\n")[1].gsub(/[)(]/, '').chomp("%")
63
+ rescue NoMethodError
64
+ data[:change_pct] = ""
65
+ end
66
+ data[:range] = self.gfs_noko_html.css("td[data-snapfield='range']+td").text.strip
67
+ data[:range_yr] = self.gfs_noko_html.css("td[data-snapfield='range_52week']+td").text.strip
68
+ data[:open] = self.gfs_noko_html.css("td[data-snapfield='open']+td").text.strip
69
+ data[:volume] = self.gfs_noko_html.css("td[data-snapfield='vol_and_avg']+td").text.strip.split("/")[0]
70
+ data[:volume_avg] = self.gfs_noko_html.css("td[data-snapfield='vol_and_avg']+td").text.strip.split("/")[1]
71
+ data[:mkt_cap] = self.gfs_noko_html.css("td[data-snapfield='market_cap']+td").text.strip
72
+ data[:pe_ttm] = self.gfs_noko_html.css("td[data-snapfield='pe_ratio']+td").text.strip
73
+ data[:div_yld] = self.gfs_noko_html.css("td[data-snapfield='latest_dividend-dividend_yield']+td").text.strip.split("/")[1]
74
+ nil_to_empty_str(data)
75
+ end
74
76
 
75
- # convert any nil values to empty strings to avoid exceptions
76
- def nil_to_empty_str(data_hash)
77
- data_hash.each do |key, value|
78
- data_hash[key] = "" if data_hash[key].nil?
77
+ def scrape_stock_description
78
+ data = {}
79
+ data[:sector] = self.gfs_noko_html.css("a#sector").text
80
+ data[:industry] = self.gfs_noko_html.css("a#sector+a").text
81
+ data[:summary] = self.gfs_noko_html.css("div.companySummary").text.gsub("More from Reuters »", "").strip
82
+ nil_to_empty_str(data)
79
83
  end
84
+
85
+ def scrape_stock_related_companies
86
+ data = []
87
+ for i in (0..10) do
88
+ begin
89
+ data << {
90
+ symbol: self.gfs_noko_html.css("table#cc-table td.ctsymbol")[i].text,
91
+ price: self.gfs_noko_html.css("table#cc-table td.ctsymbol+td+td")[i].text,
92
+ change: self.gfs_noko_html.css("table#cc-table td.ctsymbol+td+td+td")[i].text,
93
+ change_pct: self.gfs_noko_html.css("table#cc-table td.ctsymbol+td+td+td+td")[i].text.chomp("%"),
94
+ mkt_cap: self.gfs_noko_html.css("table#cc-table td.ctsymbol+td+td+td+td+td+td")[i].text
95
+ }
96
+ rescue NoMethodError
97
+ i = 11
98
+ end
99
+ end
100
+ data.collect do |related_company_hash|
101
+ nil_to_empty_str(related_company_hash)
102
+ end
103
+ end
104
+
105
+ # convert any nil values to empty strings to avoid exceptions
106
+ def nil_to_empty_str(data_hash)
107
+ data_hash.each do |key, value|
108
+ data_hash[key] = "" if data_hash[key].nil?
109
+ end
110
+ end
111
+
80
112
  end
81
113
 
82
114
  end