[QUIZ] Stock Portfolios (#41) [SOLUTION]

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.

Well I fit that description, so fresh from reading the pickaxe, here's
my (lengthy!) attempt.

···

-------------------------------------------------

require 'date'
require 'open-uri'

class PriceFetcher
  PriceURL = "http://finance.yahoo.com/q?s="

  def PriceFetcher.getPrice(ticker)
    open(PriceURL + ticker) do |p|
      #Last Trade:</td><td class="yfnc_tabledata1"><big><b>35.90</b>
      p.read.scan(/(Last Trade:<\/td><td
class="[a-z_0-9]*"><big><b>)([0-9]+.[0-9]+)<\/b>/)
      return $2
    end
    return nil
  end
end

class TablePrinter
  def initialize(spacing, titles)
    @spacing = spacing
    @titles = titles
  end

  def printDivider
    line = '+'
    @spacing.each do |x|
      line += '-'*x + '+'
    end
    puts line
  end

  def printHeader
    printDivider
    printDataRow_impl(@titles)
    printDivider
  end

  def printDataRow(data)
    printDataRow_impl(data)
    printDivider
  end

  protected
  def printDataRow_impl(data)
    line, counter = '+', 0
    @spacing.each do |x|
      line += ' '
      wordSize = 0
      if(counter < data.size)
        word = data[counter].to_s
        wordSize = word.size
        line += word
      end
      line += ' '*(x-wordSize-1) + '|'
      counter += 1
    end
    puts line
  end
end

class Portfolio
  class Holding
    def initialize(ticker, shares, cash)
      @ticker = ticker.upcase
      @price_per_share = PriceFetcher.getPrice(ticker)
      @datetime = DateTime.now

      @shares = shares
      if(!@shares) then @shares = (cash.to_f/@price_per_share.to_f).floor end
    end
    attr_reader :shares, :price_per_share, :ticker, :datetime

    def to_s
      return "#{@shares} shares of #{@ticker} for
#{@shares.to_f*@price_per_share.to_f}"
    end

    def calculateProfit
      return shares.to_f*PriceFetcher.getPrice(ticker).to_f -
shares.to_f*price_per_share.to_f
    end
  end

  def initialize
    @holdings =
  end

  def getCurrentPrice(ticker)
    return PriceFetcher.getPrice(ticker)
  end

  def addShares(ticker, shares)
    @holdings.push(Holding.new(ticker, shares, nil))
    @holdings.last
  end

  def addDollars(ticker, dollars)
    @holdings.push(Holding.new(ticker, nil, dollars))
    @holdings.last
  end

  def printHoldings
    spacing = [10, 10, 12, 20, 15, 20]
    titles = ['Symbol', 'Shares', 'Buy Price', 'Buy Date', 'Value',
'Profit to date']
    tp = TablePrinter.new(spacing, titles)
    tp.printHeader
    @holdings.each do |h|
      tp.printDataRow([h.ticker, h.shares, h.price_per_share,
h.datetime.strftime('%m/%d/%Y %H:%M'),
                        h.shares.to_f*h.price_per_share.to_f,
h.calculateProfit])
    end
  end
end

puts "Welcome to Stock Trader v0.0001"
puts "Type quit when you're finished"
SerialFileName = "portfolio.serial"
port = Portfolio.new

begin
  File.open(SerialFileName) do |f|
    port = Marshal.load(f)

    puts "Current Holdings:"
    port.printHoldings
  end
rescue
  puts "No Saved data found, creating new portfolio..."
end

while true do
  printf "Buy (symbol shares/dollars):"
  input = gets.chomp
  if(input == "quit")
    break
  elsif(input =~ /([a-zA-Z]*) (\$?[0-9.]+)/)
    holding = 0
    if($2[0,1] == '$') then holding = port.addDollars($1, $2[1, $2.size-1])
    else holding = port.addShares($1, $2)
    end
    if holding then puts "You purchased #{holding.to_s}" end
  end
end

port.printHoldings

File.open(SerialFileName, "w+") do |f|
  Marshal.dump(port, f)
end

-------------------------------------------------

Thanks for the quiz!

Owen