[QUIZ] Stock Portfolios (#41)

(James Edward Gray II) #1

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

···

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

I'm a member of a local stock club. For those of you not familiar with such
clubs, they're just a group of people who pool resources (primarily knowledge,
research time, and finances) to purchase stocks.

My particular club makes use of a lot of technology for information and
communication. A recent request was a tool to practice investing. This is
especially helpful to inexperienced members. The idea is that they can pretend
to purchase a few stocks (preferably after studying their choices!) and then see
how they do over time.

This week's Ruby Quiz is to build this simple tool.

Here's a sample run to get the ideas flowing:

  $ ./portfolio
  Buy (symbol shares/dollars): PIXR $1000
  You purchased 23 shares of PIXR for $991.53.
  Buy (symbol shares/dollars): GOOG 3
  You purchased 3 shares of GOOG for $887.55.
  Buy (symbol shares/dollars):
  +--------+--------+-----------+----------------+---------------+-----------+
  > Symbol | Shares | Buy Price | Buy Date | Current Price | Gain/Loss |
  +--------+--------+-----------+----------------+---------------+-----------+
  > GOOG | 3 | $ 295.85 | 07/25/05 19:53 | $ 295.85 | $ 0.00 |
  +--------+--------+-----------+----------------+---------------+-----------+
  > PIXR | 23 | $ 43.11 | 07/25/05 19:53 | $ 43.11 | $ 0.00 |
  +--------+--------+-----------+----------------+---------------+-----------+

Later, I can see how my shares are doing:

  $ ./portfolio
  +--------+--------+-----------+----------------+---------------+-----------+
  > Symbol | Shares | Buy Price | Buy Date | Current Price | Gain/Loss |
  +--------+--------+-----------+----------------+---------------+-----------+
  > GOOG | 3 | $ 295.85 | 07/25/05 19:53 | $ 293.29 | $ -7.68 |
  +--------+--------+-----------+----------------+---------------+-----------+
  > PIXR | 23 | $ 43.11 | 07/25/05 19:53 | $ 43.44 | $ 7.59 |
  +--------+--------+-----------+----------------+---------------+-----------+

Don't feel tied to this exact display or interface. Some interesting ideas
might be to show historical data before asking a user to confirm their choice,
or plot changes over regular intervals instead of just showing the initial and
current prices. If you think of something else, go for it.

I think this is a great quiz for beginners wanting to learn more about Ruby's
standard library. If that describes you, I encourage you to give it a try.
(Hint: Many web sites offer stock data, all you need do is figure out how to
get it...)

(Peter C. Verhage) #2

Ruby Quiz wrote:

(Hint: Many web sites offer stock data, all you need do is figure out how to
get it...)

Possible "spoiler", but Yahoo makes it easy to get the stock data, see:
http://www.gummy-stuff.org/Yahoo-data.htm

Regards,

Peter

(James Edward Gray II) #3

Here's the code used to create the quiz:

#!/usr/local/bin/ruby

require "yaml"
require "soap/rpc/driver"

server = SOAP::RPC::Driver.new( "http://services.xmethods.com/soap",
                                 "urn:xmethods-delayed-quotes" )
server.add_method( "getQuote", "symbol" )

Struct.new("Shares", :symbol, :shares, :price, :date)

file_name = File.join(ENV["HOME"], ".portfolio.yaml")
portfolio = if File.exist? file_name
     File.open(file_name) { |file| YAML.load(file) }
else
     Hash.new
end

if portfolio.empty? or not ARGV.empty? and ARGV.first == "-p"
     loop do
         print "Buy (symbol shares/dollars): "
         line = $stdin.gets.strip
         break if line.empty?

         ticker, amount = line.split
         price = server.getQuote(ticker)
         if price <= 0
             puts "Unable to fetch a price for that ticker symbol."
             next
         end

         if amount =~ /^\$(\d+)$/
             dollars = Integer($1)
             shares = (dollars / price).to_i
         elsif amount =~ /^[1-9]\d*$/
             shares = Integer(amount)
         else
             puts "Unknown amount format."
             next
         end

         if shares.zero?
             puts "You can't purchase any shares for that amount."
             next
         else
             date = Time.now.strftime("%m/%d/%y %H:%M")
             buy = Struct::Shares.new(ticker, shares, price, date)
             portfolio[buy.symbol] = buy

             puts "You purchased #{buy.shares} shares of #{buy.symbol} " +
                  "for $#{"%.2f" % (buy.shares * price)}."
         end
     end

     File.open(file_name, "w") { |file| YAML.dump(portfolio, file) }
end

output = [%w{Symbol Shares Buy\ Price Buy\ Date Current\ Price Gain/Loss}]
portfolio.each do |ticker, details|
     current = server.getQuote(ticker)
     output << [ ticker, details.shares, details.price, details.date, current,
                 details.shares * current - details.shares * details.price ]
end

widths = Array.new(output.first.size, 0)
output.each do |row|
     row.each_index do |index|
         width = if row[index].is_a? Float
             ("%.2f" % row[index]).length + 1
         else
             row[index].to_s.length
         end
         widths[index] = width if width > widths[index]
     end
end

border = "+-" + widths.map { |width| "-" * width }.join("-+-") + "-+"
template = ("| " + output.last.map do |column|
     case column
     when Float
         "$%#{widths.shift - 1}.2f"
     when Integer
         "%#{widths.shift}d"
     else
         "%#{widths.shift}s"
     end
end.join(" | ") + " |").sub(/\A\| %/, "| %-")
puts border
puts template.gsub(/ \| \$?%(\d+)(?:\.\d+)?[sdf]/, " | %\\1s") % output.first
output[1..-1].sort.each do |row|
     puts border
     puts template % row
end
puts border

__END__

James Edward Gray II

···

On Aug 5, 2005, at 11:30 AM, Ruby Quiz wrote:

This week's Ruby Quiz is to build this simple tool.

(Adam Sanderson) #4

Here's my solution:
  http://staff.washington.edu/netghost/stocks.rb

The application bit is a little unpolished, but it has some neat
features:
-Lookup stocks:
  view goog, msft
-Buy/Sell multiple stocks:
  buy 10 goog, 20 msft
-Get a history of transactions:
  history goog

Anyways, this was pretty fun, and I even found a compelling use for
OpenStruct, neat.
  .adam

(James Edward Gray II) #5

Possible "spoiler", but Yahoo makes it easy to get the stock data, see:
http://www.gummy-stuff.org/Yahoo-data.htm

Thanks for sharing. I wasn't aware of that service and it is trivial to wrap:

$ ./stock_data -s -n -d1 -l1 -y -r AAPL GOOG PIXR RHAT
"AAPL","APPLE COMPUTER","8/9/2005",43.55,N/A,35.54
"GOOG","GOOGLE","8/9/2005",289.969,N/A,85.34
"PIXR","PIXAR","8/9/2005",43.65,N/A,26.73
"RHAT","RED HAT INC","8/9/2005",14.24,N/A,57.87
$ cat stock_data
#!/usr/local/bin/ruby -w

# Stock data via: http://www.gummy-stuff.org/Yahoo-data.htm

require "open-uri"

# parse options and symbols requested
data, stocks = ARGV.partition { |e| e[0] == ?- }
data = data.map { |tag| tag[/\w+/] }.join
stocks = stocks.join("+")

# fetch and print data
open "http://finance.yahoo.com/d/quotes.csv?s=#{stocks}&f=#{data}" do

csv>

         csv.each { |line| puts line }
end

__END__

James Edward Gray II

···

On Aug 5, 2005, at 12:34 PM, Peter C. Verhage wrote:

(David Tran) #6

James Edward Gray II wrote:

if portfolio.empty? or not ARGV.empty? and ARGV.first == "-p"

Typo error ?

The way you wrote:
if portfolio.empty? or not ARGV.empty? and ARGV.first == "-p"
Will treat like:
if (portfolio.empty? or not ARGV.empty?) and ARGV.first == "-p"

I guess you would like to say:
if portfolio.empty? or (not ARGV.empty? and ARGV.first == "-p")

Note:
&& has higher precedence than ||
but "or" "and" both are the same precedence.

(Jeff Moss) #7

I use a web service that uses yahoo, I always thought this was dirty though, I wanted a web service but not one that ripped it off some website.

require 'soap/rpc/driver'

driver = SOAP::RPC::Driver.new( 'http://services.xmethods.com/soap', 'urn:xmethods-delayed-quotes' )
driver.add_method( 'getQuote', 'a_string' )
driver.getQuote('GOOG')

-Jeff

···

----- Original Message ----- From: "James Edward Gray II" <james@grayproductions.net>
To: "ruby-talk ML" <ruby-talk@ruby-lang.org>
Sent: Tuesday, August 09, 2005 9:50 AM
Subject: Re: [SOLUTION] Stock Portfolios (#41)

On Aug 5, 2005, at 12:34 PM, Peter C. Verhage wrote:

Possible "spoiler", but Yahoo makes it easy to get the stock data, see:
http://www.gummy-stuff.org/Yahoo-data.htm

Thanks for sharing. I wasn't aware of that service and it is trivial to wrap:

$ ./stock_data -s -n -d1 -l1 -y -r AAPL GOOG PIXR RHAT
"AAPL","APPLE COMPUTER","8/9/2005",43.55,N/A,35.54
"GOOG","GOOGLE","8/9/2005",289.969,N/A,85.34
"PIXR","PIXAR","8/9/2005",43.65,N/A,26.73
"RHAT","RED HAT INC","8/9/2005",14.24,N/A,57.87
$ cat stock_data
#!/usr/local/bin/ruby -w

# Stock data via: http://www.gummy-stuff.org/Yahoo-data.htm

require "open-uri"

# parse options and symbols requested
data, stocks = ARGV.partition { |e| e[0] == ?- }
data = data.map { |tag| tag[/\w+/] }.join
stocks = stocks.join("+")

# fetch and print data
open "http://finance.yahoo.com/d/quotes.csv?s=#{stocks}&f=#{data}" do >csv>
        csv.each { |line| puts line }
end

__END__

James Edward Gray II

(James Edward Gray II) #8

You're right and I should know better. Thanks!

James Edward Gray II

···

On Aug 10, 2005, at 11:01 AM, email55555@gmail.com wrote:

James Edward Gray II wrote:

if portfolio.empty? or not ARGV.empty? and ARGV.first == "-p"

Typo error ?

The way you wrote:
if portfolio.empty? or not ARGV.empty? and ARGV.first == "-p"
Will treat like:
if (portfolio.empty? or not ARGV.empty?) and ARGV.first == "-p"

I guess you would like to say:
if portfolio.empty? or (not ARGV.empty? and ARGV.first == "-p")

Note:
&& has higher precedence than ||
but "or" "and" both are the same precedence.