Read and write stock prices to file using arrays

I’m trying to write a script that will take stock ticker symbols from a
file, pull the company name and current price and write it back to the
file while ignoring comments and blank lines so as to leave the rest of
the file intact.

I can get the stock prices etc. i’m after using the yahoofinance module

like so

require ‘rubygems’
require ‘yahoofinance’

quote_type = YahooFinance::StandardQuote

Set the symbols for which we want to retrieve quotes.

quote_symbols = [‘yhoo’,‘goog’]

Get the quotes from Yahoo! Finance. The get_quotes method call

returns a Hash containing one quote object of type “quote_type” for

each symbol in “quote_symbols”. If a block is given, it will be

called with the quote object (as in the example below).

YahooFinance.get_quotes( quote_type, quote_symbols ) do |qt|
puts “QUOTING: #{qt.symbol}”
puts “#{qt.name}”
puts “#{qt.lastTrade}”
end

I want to create an array of ticker symbols like quote_symbols above,
but from a file.

here’s a test data file i’m using

this is the comment line

that was a blank, below is a stock

YHOO
GOOG # this comment can be deleted

i would like the output to look like

this is the comment line

that was a blank, below is a stock

YHOO:Yahoo! Inc.:17.35
GOOG:Google Inc.:567.49

this is the code i’m having trouble with.
filename = ARGV[0]

File.open(filename, ‘r+’) do |f|
lines = f.readlines # load array of lines
lines.each do |line|
next if line =~ /^#/ # skip comments
next if line.chomp.empty? # skip empty lines
$ticker[lines] = line #***the problem
puts lines.index(line)
end
end

$ticker.each do |p|
puts “#{p}”
end

*** I’m trying to create the array $ticker that has all my stocks so i
can pass it on to the yahoofinance module.

The other stumbling block for me is how to keep the results of YHOO →
Yahoo! Inc. and the price 17.35 matched up with YHOO so i can put it
back on the same line.

Thanks
Peter

On Fri, Apr 9, 2010 at 2:34 AM, Peter M. [email protected] wrote:

require ‘yahoofinance’
YahooFinance.get_quotes( quote_type, quote_symbols ) do |qt|

end
end

If you are just reading the lines you can do:

lines = File.readlines(filename)

Then:

ticker = []
lines.each do |line|
next if line =~ /^#/ # skip comments
next if line.chomp.empty? # skip empty lines
ticker << line
end

p ticker

From your specs I think you are missing also the functionality of
removing everything after a #. If a line can contain only a ticker
symbol and optional things (after all a ticker symbol is just a word),
you can do:

ticker = []
lines.each do |line|
next if line =~ /^#/ # skip comments
next if line.chomp.empty? # skip empty lines
ticker << line.split(" ")[0] # or maybe line[/\w+/]
end

The other stumbling block for me is how to keep the results of YHOO →
Yahoo! Inc. and the price 17.35 matched up with YHOO so i can put it
back on the same line.

An idea would be to fill a hash with the data retrieved for each symbol:

ticker_data = {}

YahooFinance.get_quotes( quote_type, quote_symbols ) do |qt|
puts “QUOTING: #{qt.symbol}”
puts “#{qt.name}”
puts “#{qt.lastTrade}”
ticker_data[qt.symbol] = [qt.name, qt.lastTrade]
end

and then write to the file the data in ticker_data. I can see
additional complexity if you want to keep the file exactly as it is
(with the comments) but inserting the retrieved data in place. The
easiest would be to just write ticker_data:

File.open(filename, “w”) do |file|
ticker_data.each {|ticker, data| file.puts “#{ticker} #{data.join(”
“)}”}
end

If you want to keep the same order of the symbols:

File.open(filename, “w”) do |file|
ticker.each do |ticker|
file.puts “#{ticker} #{ticker_data[ticker].join(” “)}”}
end
end

Although this would remove the comments from the file.

Jesus.

ticker = []
lines.each do |line|
next if line =~ /^#/ # skip comments
next if line.chomp.empty? # skip empty lines
ticker << line
end

ticker << line.split(" ")[0] # or maybe line[/\w+/]

Thank you. The ticker << line is really helpful.

An idea would be to fill a hash with the data retrieved for each symbol:

ticker_data = {}

YahooFinance.get_quotes( quote_type, quote_symbols ) do |qt|
puts “QUOTING: #{qt.symbol}”
puts “#{qt.name}”
puts “#{qt.lastTrade}”
ticker_data[qt.symbol] = [qt.name, qt.lastTrade]
end

I didn’t realize a hash could point to more than one object. Awesome.

and then write to the file the data in ticker_data. I can see
additional complexity if you want to keep the file exactly as it is
(with the comments) but inserting the retrieved data in place. The
easiest would be to just write ticker_data:

this is why i was trying to do $ticker[lines] = line when storing the
data. I thought I could point the index of the ticker symbol, which
would tell me what line it was on, to my array $ticker. Then i wanted to
use that index to write back the ticker data to the same line it came
from.

i think i need to capture the tickers position in the file at this point
ticker << line
and then somehow use it when writing the file back.

I’m going to work on that, but if there is another approach, or my
thinking is flawed, a nudge in the right direction would be nice.

The hardest part of learning to code for me is thinking like a computer.

Thanks for the help Jesus.

On Fri, Apr 9, 2010 at 7:27 PM, Peter M. [email protected] wrote:

I didn’t realize a hash could point to more than one object. Awesome.

It can’t, but the one value a key points to can be an object of any
type, including arrays etc… For instance, a very common ruby idiom is
to group a set of objects by using a hash of arrays. For example,
let’s say you have a bunch of people, who belong to one of three
teams, red, blue or green.

data = [[‘john’, ‘red’],
[‘jane’, ‘green’],
[‘alice’, ‘red’],
[‘bob’, ‘blue’],
[‘tom’, ‘red’],
[‘dick’, ‘green’],
[‘harry’, ‘red’],
[‘bill’, ‘blue’]]

You can collect the teams like so:

teams = {}

data.each {|name, team|
teams[team] ||= [] # initialize with an empty array if this is the
first time you’ve seen the team
teams[team] << name # append the name to the team’s array
}

p teams

=> {“red”=>[“john”, “alice”, “tom”, “harry”], “green”=>[“jane”,

“dick”], “blue”=>[“bob”, “bill”]}

martin

On Fri, Apr 9, 2010 at 3:57 PM, Peter M. [email protected] wrote:

I didn’t realize a hash could point to more than one object. Awesome.

As Martin explained, it’s just one object. It happens to be an array
with many other objects inside, or it could be a struct, for example:

Quote = Struct.new(:symbol, :name, :last_trade)
YahooFinance.get_quotes( quote_type, quote_symbols ) do |qt|
ticker_data[qt.symbol] = Quote.new qt.symbol, qt.name, qt.last_trade
end

or another hash:

YahooFinance.get_quotes( quote_type, quote_symbols ) do |qt|
ticker_data[qt.symbol] = {:symbol => qt.symbol, :name => qt.name,
:last_trade => qt.last_trade}
end

depends on how you want to use it later.

from.
The problem is that you can’t really insert things “in the middle” of a
file.
If you write something in the current position of a file (the cursor),
it will overwrite what’s after, it doesn’t push the rest of the file
to make space for what you are writing.

So, the usual simple way is to read the file, modify it in memory and
write it all again. If you want to preserve everything, you could
split the file in chunks with the comments, and replace the other
lines with the result of your calculation. For that, you will have to
keep track of which lines represent ticker data. For example
(untested):

TickerData = Struct.new :symbol, :file_position, :name, :last_trade

lines = File.readlines(filename)
tickers = {}
lines.each_with_index do |line, i|
next if line =~ /^#/ # skip comments
next if line.chomp.empty? # skip empty lines
symbol = line[/\w+/]
tickers[symbol] = TickerData.new symbol, i #store the symbol and the
file position
end

YahooFinance.get_quotes( quote_type, tickers.keys ) do |qt|
next unless tickers[qt.symbol]
tickers[qt.symbol].name = qt.name
tickers[qt.symbol].last_trade = qt.last_trade
end

Now you have the original lines in an array and a hash with all the
data, including to which line it corresponds, you can sub the line
with what you want, and then write the file back:

tickers.each do |symbol, ticker|
lines[ticker.file_position] = “#{ticker.symbol} #{ticker.name}
#{ticker.last_trade}\n”
end

File.open(filename, “w”) {|file| file.puts lines.join}

I think this should work but I haven’t tested it.

Jesus.

On Apr 9, 2010, at 15:25 , Peter M. wrote:

next if line.chomp.empty? # skip empty lines

you might want line.strip.empty? if you encounter errant whitespace in
your data file.

well, I just got home from work and was going to hack away… but instead
i cut n paste and got a working script after a couple minor edits.
Thanks so much for such a complete solution. I wasn’t expecting that!

Here is the working code in full. It handles my file perfectly.

require ‘rubygems’
require ‘yahoofinance’

quote_type = YahooFinance::StandardQuote

filename = ARGV[0]

TickerData = Struct.new :symbol, :file_position, :name, :last_trade

lines = File.readlines(filename)
tickers = {}
lines.each_with_index do |line, i|
next if line =~ /^#/ # skip comments
next if line.chomp.empty? # skip empty lines
symbol = line[/\w+/].upcase
tickers[symbol] = TickerData.new symbol, i #store the symbol and file
position
end

YahooFinance.get_quotes( quote_type, tickers.keys ) do |qt|
next unless tickers[qt.symbol]
tickers[qt.symbol].name = qt.name
tickers[qt.symbol].last_trade = qt.lastTrade
end

tickers.each do |symbol, ticker|
lines[ticker.file_position] = “#{ticker.symbol} #{ticker.name}
#{ticker.last_trade}\n”
end

File.open(filename, “w”) {|file| file.puts lines.join}

Off to do some reading on structs
Thanks Martin and Jesus