read and write stock prices to file using arrays

P

Peter Miller

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
http://www.transparentech.com/opensource/yahoofinance
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
 
J

Jesús Gabriel y Galán

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
http://www.transparentech.com/opensource/yahoofinance
like so

require 'rubygems'
require 'yahoofinance'

quote_type =3D YahooFinance::StandardQuote

# Set the symbols for which we want to retrieve quotes.
quote_symbols =3D ['yhoo','goog']

# Get the quotes from Yahoo! Finance. =A0The get_quotes method call
# returns a Hash containing one quote object of type "quote_type" for
# each symbol in "quote_symbols". =A0If 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|
=A0puts "QUOTING: #{qt.symbol}"
=A0puts "#{qt.name}"
=A0puts "#{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 =3D ARGV[0]

File.open(filename, 'r+') do |f|
=A0lines =3D f.readlines =A0 =A0 =A0 =A0 =A0 =A0# load array of lines
=A0lines.each do |line|
=A0 =A0next if line =3D~ /^#/ =A0 =A0 =A0 =A0 # skip comments
=A0 =A0next if line.chomp.empty? =A0 =A0# skip empty lines
=A0 =A0$ticker[lines] =3D line =A0 =A0 =A0 =A0#***the problem
=A0 =A0puts lines.index(line)
=A0end
end

If you are just reading the lines you can do:

lines =3D File.readlines(filename)

Then:

ticker =3D []
lines.each do |line|
next if line =3D~ /^#/ # 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 =3D []
lines.each do |line|
next if line =3D~ /^#/ # 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 =3D {}

YahooFinance.get_quotes( quote_type, quote_symbols ) do |qt|
puts "QUOTING: #{qt.symbol}"
puts "#{qt.name}"
puts "#{qt.lastTrade}"
ticker_data[qt.symbol] =3D [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.
 
P

Peter Miller

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

Martin DeMello

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
 
J

Jesús Gabriel y Galán

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

ticker_data =3D {}

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

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 =3D Struct.new:)symbol, :name, :last_trade)
YahooFinance.get_quotes( quote_type, quote_symbols ) do |qt|
ticker_data[qt.symbol] =3D 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] =3D {:symbol =3D> qt.symbol, :name =3D> qt.name,
:last_trade =3D> qt.last_trade}
end

depends on how you want to use it later.
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] =3D 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.

The problem is that you can't really insert things "in the middle" of a fil=
e.
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 =3D Struct.new :symbol, :file_position, :name, :last_trade

lines =3D File.readlines(filename)
tickers =3D {}
lines.each_with_index do |line, i|
next if line =3D~ /^#/ # skip comments
next if line.chomp.empty? # skip empty lines
symbol =3D line[/\w+/]
tickers[symbol] =3D 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 =3D qt.name
tickers[qt.symbol].last_trade =3D 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] =3D "#{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.
 
P

Peter Miller

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top