Module: YahooFinance

Defined in:
lib/yahoofinance.rb

Overview

Module for retrieving quote data (‘live’ and ‘historical’) from YahooFinance!

This module is made up of 2 parts:

1. Retrieving 'live' quote data. 'Live' quotes are the current

data available for a given stock. Most often delayed 20mins.

2. Retrieving 'historical' quote data.  'Historical' quotes are

for past pricings of a given stock.

Defined Under Namespace

Classes: BaseQuote, ExtendedQuote, HistoricalQuote, RealTimeQuote, StandardQuote

Constant Summary collapse

STDHASH =

‘Live’ quote data retrieval.

{
  "s" => [ "symbol", "val" ],
  "n" => [ "name", "val" ],
  "l1" => [ "lastTrade", "val.to_f" ],
  "d1" => [ "date", "val" ],
  "t1" => [ "time", "val" ],
  "c" => [ "change", "val" ],
  "c1" => [ "changePoints", "val.to_f" ],
  "p2" => [ "changePercent", "val.to_f" ],
  "p" => [ "previousClose", "val.to_f" ],
  "o" => [ "open", "val.to_f" ],
  "h" => [ "dayHigh", "val.to_f" ],
  "g" => [ "dayLow", "val.to_f" ],
  "v" => [ "volume", "val.to_i" ],
  "m" => [ "dayRange", "val" ],
  "l" => [ "lastTradeWithTime", "val" ],
  "t7" => [ "tickerTrend", "convert(val)" ],
  "a2" => [ "averageDailyVolume", "val.to_i" ],
  "b" => [ "bid", "val.to_f" ],
  "a" => [ "ask", "val.to_f" ]
# These return integers like "1,000".  The CVS parser gets confused by this
# so I've removed them for the time being.
#    "b6" => [ "bidSize", "val" ],
#    "a5" => [ "askSize", "val" ]
#    "k3" => [ "lastTradeSize", "convert(val)" ],
}
EXTENDEDHASH =
{
  "s" => [ "symbol", "val" ],
  "n" => [ "name", "val" ],
  "w" => [ "weeks52Range", "val" ],
  "j5" => [ "weeks52ChangeFromLow", "val.to_f" ],
  "j6" => [ "weeks52ChangePercentFromLow", "val" ],
  "k4" => [ "weeks52ChangeFromHigh", "val.to_f" ],
  "k5" => [ "weeks52ChangePercentFromHigh", "val" ],
  "e" => [ "earningsPerShare", "val.to_f" ],
  "r" => [ "peRatio", "val.to_f" ],
  "s7" => [ "shortRatio", "val" ],
  "r1" => [ "dividendPayDate", "val" ],
  "q" => [ "exDividendDate", "val" ],
  "d" => [ "dividendPerShare", "convert(val)" ],
  "y" => [ "dividendYield", "convert(val)" ],
  "j1" => [ "marketCap", "convert(val)" ],
  "t8" => [ "oneYearTargetPrice", "val" ],
  "e7" => [ "epsEstimateCurrentYear", "val" ],
  "e8" => [ "epsEstimateNextYear", "val" ],
  "e9" => [ "epsEstimateNextQuarter", "val" ],
  "r6" => [ "pricePerEPSEstimateCurrentYear", "val" ],
  "r7" => [ "pricePerEPSEstimateNextYear", "val" ],
  "r5" => [ "pegRatio", "val.to_f" ],
  "b4" => [ "bookValue", "val.to_f" ],
  "p6" => [ "pricePerBook", "val.to_f" ],
  "p5" => [ "pricePerSales", "val.to_f" ],
  "j4" => [ "ebitda", "val" ],
  "m3" => [ "movingAve50days", "val" ],
  "m7" => [ "movingAve50daysChangeFrom", "val" ],
  "m8" => [ "movingAve50daysChangePercentFrom", "val" ],
  "m4" => [ "movingAve200days", "val" ],
  "m5" => [ "movingAve200daysChangeFrom", "val" ],
  "m6" => [ "movingAve200daysChangePercentFrom", "val" ],
  "s1" => [ "sharesOwned", "val" ],
  "p1" => [ "pricePaid", "val" ],
  "c3" => [ "commission", "val" ],
  "v1" => [ "holdingsValue", "val" ],
  "w1" => [ "dayValueChange", "val" ],
  "g1" => [ "holdingsGainPercent", "val" ],
  "g4" => [ "holdingsGain", "val" ],
  "d2" => [ "tradeDate", "val" ],
  "g3" => [ "annualizedGain", "val" ],
  "l2" => [ "highLimit", "val" ],
  "l3" => [ "lowLimit", "val" ],
  "n4" => [ "notes", "val" ],
  "x" => [ "stockExchange", "val" ]
# This returns an integer like "1,000,000".
# The CVS parser gets confused by this
# so I've removed it for the time being.
#    "f6" => [ "floatShares", "val" ],
}
REALTIMEHASH =
{ 
  "s" => [ "symbol", "val" ],
  "n" => [ "name" , "val" ],
  "b2" => [ "ask", "val.to_f" ],
  "b3" => [ "bid", "val.to_f" ],
  "k2" => [ "change", "val" ],
  "k1" => [ "lastTradeWithTime", "val" ],
  "c6" => [ "changePoints", "val.to_f" ],
  "m2" => [ "dayRange", "val" ],
  "j3" => [ "marketCap", "convert(val)" ],
#     "v7" => [ "holdingsValue", "val" ],
#     "w4" => [ "dayValueChange", "val" ],
#     "g5" => [ "holdingsGainPercent", "val" ],
#     "g6" => [ "holdingsGain", "val" ],
#     "r2" => [ "pe", "val" ],
#     "c8" => [ "afterHoursChange", "val" ],
#     "i5" => [ "orderBook", "val" ],
}
DEFAULT_READ_TIMEOUT =
5

Class Method Summary collapse

Class Method Details

.get(symbols, format, timeout = DEFAULT_READ_TIMEOUT) ⇒ Object

Return a string containing the results retrieved from YahooFinance. If there was an error during the execution of this method, the string “” is returned. In practice, this means that no *Quote objects will be created (empty hashes will be returned from get_*_quotes methods).



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/yahoofinance.rb', line 164

def YahooFinance.get( symbols, format, timeout=DEFAULT_READ_TIMEOUT )
  return "" if symbols == nil
  symbols = symbols.join( "," ) if symbols.class == Array
  symbols.strip!
  return "" if symbols == ""

  # Catch any exceptions that might occur here.  Possible exceptions
  # are the read_timeout and...
  #

  #
  # Don't catch any exceptions!  Just let them be thrown.
  #
#    begin
    Net::HTTP.start( "finance.yahoo.com", 80 ) { |http|
      http.read_timeout = timeout
      response = nil

#        begin
#          Timeout.timeout( 1 ) { 
          response = http.get( "/d/quotes.csv?s=#{symbols}&f=#{format}&e=.csv" )
#          }
#        rescue Exception => ex
#          puts "inner timeout got: #{ex.inspect}"
#          response = nil
#        end

      return "" if !response

      response.body.chomp
    }
#    rescue Exception => ex
    # This can often be a Timeout::Error.
#      puts "YahooFinance::get - #{ex.inspect}"
#      return ""
#    end

end

.get_extended_quotes(symbols) ⇒ Object



228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/yahoofinance.rb', line 228

def YahooFinance.get_extended_quotes( symbols )
  csvquotes = YahooFinance::get( symbols, EXTENDEDHASH.keys.join )
  ret = Hash.new
  CSV.parse( csvquotes ) do |row|
    qt = ExtendedQuote.new( row )
    if block_given?
      yield qt
    end
    ret[qt.symbol] = qt
  end
  ret
end

.get_historical_quotes(symbol, startDate, endDate) ⇒ Object



445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
# File 'lib/yahoofinance.rb', line 445

def YahooFinance.get_historical_quotes( symbol, startDate, endDate )
  rows = []
  rowct = 0
  gotchunk = false

  #
  # Yahoo is only able to provide 200 data points at a time for Some
  # "international" markets.  We want to download all of the data
  # points between the startDate and endDate, regardless of how many
  # 200 data point chunks it comes in.  In this section we download
  # as many chunks as needed to get all of the data points.
  #
  begin
    gotchunk = false
    r = YahooFinance.retrieve_raw_historical_quotes( symbol, 
                                                     startDate, endDate )
    if block_given?
      # If we were given a block, yield to it for every row of data
      # downloaded.
      r.each { |row| yield row }
    else
      rows += r
    end

    # Is this a chunk?  
    if r.length == 200
      # Adjust the endDate for when we retrieve the next chunk.
      endDate = HistoricalQuote.parse_date_to_Date( r.last[0] ) - 1
      # Marke this as a chunk so do the download again with the new endDate.
      gotchunk = true
    end

  end while gotchunk

  if block_given?
    # If we have already yielded to every row, just return nil.
    return nil
  else
    # Otherwise return the big array of arrays.
    rows
  end

end

.get_historical_quotes_days(symbol, days, &block) ⇒ Object



489
490
491
492
493
# File 'lib/yahoofinance.rb', line 489

def YahooFinance.get_historical_quotes_days( symbol, days, &block )
  endDate = Date.today()
  startDate = Date.today() - days
  YahooFinance.get_historical_quotes( symbol, startDate, endDate, &block )
end

.get_HistoricalQuotes(symbol, startDate, endDate) ⇒ Object



495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
# File 'lib/yahoofinance.rb', line 495

def YahooFinance.get_HistoricalQuotes( symbol, startDate, endDate ) 
  ret = []
  YahooFinance.get_historical_quotes( symbol, startDate, endDate ) { |row|
    if block_given?
      yield HistoricalQuote.new( symbol, row )
    else
      ret << HistoricalQuote.new( symbol, row )
    end
  }
  if block_given?
    return nil
  else
    return ret
  end
end

.get_HistoricalQuotes_days(symbol, days, &block) ⇒ Object



511
512
513
514
515
# File 'lib/yahoofinance.rb', line 511

def YahooFinance.get_HistoricalQuotes_days( symbol, days, &block )
  endDate = Date.today()
  startDate = Date.today() - days
  YahooFinance.get_HistoricalQuotes( symbol, startDate, endDate, &block )
end

.get_quotes(quote_class, symbols, &block) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
# File 'lib/yahoofinance.rb', line 203

def YahooFinance.get_quotes( quote_class, symbols, &block )
  if quote_class == YahooFinance::StandardQuote
    return get_standard_quotes( symbols, &block )
  elsif quote_class == YahooFinance::ExtendedQuote
    return get_extended_quotes( symbols, &block )
  elsif quote_class == YahooFinance::RealTimeQuote
    return get_realtime_quotes( symbols, &block )
  else
    # Use the standard quote if the given quote_class was not recoginized.
    return get_standard_quotes( symbols, &block )
  end
end

.get_realtime_quotes(symbols) ⇒ Object



216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/yahoofinance.rb', line 216

def YahooFinance.get_realtime_quotes( symbols )
  csvquotes = YahooFinance::get( symbols, REALTIMEHASH.keys.join )
  ret = Hash.new
  CSV.parse( csvquotes ) do |row|
    qt = RealTimeQuote.new( row )
    if block_given?
      yield qt
    end
    ret[qt.symbol] = qt
  end
  ret
end

.get_standard_quotes(symbols) ⇒ Object



240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/yahoofinance.rb', line 240

def YahooFinance.get_standard_quotes( symbols )
  csvquotes = YahooFinance::get( symbols, STDHASH.keys.join )
  ret = Hash.new
  CSV.parse( csvquotes ) do |row|
    qt = StandardQuote.new( row )
    if block_given?
      yield qt
    end
    ret[qt.symbol] = qt
  end
  ret
end

.retrieve_raw_historical_quotes(symbol, startDate, endDate) ⇒ Object



416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
# File 'lib/yahoofinance.rb', line 416

def YahooFinance.retrieve_raw_historical_quotes( symbol, startDate, endDate )

  # Don't try to download anything if the starting date is before
  # the end date.
  return [] if startDate > endDate

  Net::HTTP.start( "itable.finance.yahoo.com", 80 ) { |http|
    query = "/table.csv?s=#{symbol}&g=d" +
      "&a=#{startDate.month-1}&b=#{startDate.mday}&c=#{startDate.year}" + 
      "&d=#{endDate.month-1}&e=#{endDate.mday}&f=#{endDate.year.to_s}"
    #puts "#{query}"
    response = http.get( query )
    #puts "#{response.body}"
    body = response.body.chomp

    # If we don't get the first line like this, there was something
    # wrong with the data (404 error, new data formet, etc).
    return [] if body !~ /Date,Open,High,Low,Close,Volume,Adj Close/

    # Parse into an array of arrays.
    rows = CSV.parse( body )
    # Remove the first array since it is just the field headers.
    rows.shift
    #puts "#{rows.length}"

    return rows
  }
end