###########################################
package CachedQuote; 
# Cache stock closing prices 
# Mike Schilli, 2007 (m@perlmeister.com) 
########################################### 
use strict; 
use warnings; 
use Cache::Historical; 
use Log::Log4perl qw(:easy); 
use Finance::QuoteHist::Yahoo; 

########################################### 
sub new { 
########################################### 
  my($class, %options) = @_; 

  my $self = { 
    file => "/tmp/cached-quote.dat", 
    %options, 
  }; 

  $self->{cache} = Cache::Historical->new( 
        sqlite_file => $self->{file}); 

  bless $self, $class; 
} 

########################################### 
sub quote { 
########################################### 
  my($self, $date, $key) = @_; 

  my $quote = $self->{cache}->get( 
          $date, $key); 

  return $quote if defined $quote; 
  $self->quote_refresh( $date, $key ); 

  return $self->{cache}->get_interpolated( 
            $date, $key); 
} 

########################################### 
sub quote_refresh { 
########################################### 
  my($self, $date, $symbol) = @_; 

  my($from, $to) = 
    $self->{cache}->time_range($symbol); 

  my $upd = $self->{cache}-> 
             since_last_update($symbol); 

    # Date available, no refresh 
  if(defined $to and defined $from and 
     $date <= $to and $date >= $from) { 
      DEBUG "Date within, no refresh"; 
      return 1; 
  } 

  if(defined $date and defined $to and 
     defined $upd and $date > $to and 
     $upd->delta_days < 1) { 
      DEBUG "Date ($date) above cached", 
       " range ($from-$to), but cache ", 
       "is up-to-date."; 
      return 1; 
  } 

  my $start = $date->clone->subtract( 
                            years => 1 ); 
  if(defined $start and defined $from and 
     $start > $from and $to > $start) { 
        # no need to refresh old data 
      $start = $to; 
  } 

  $self->quotes_fetch( 
    $start, 
    DateTime->today(), 
    $symbol); 
} 

########################################### 
sub quotes_fetch { 
########################################### 
  my($self, $start, $end, $symbol) = @_; 

  DEBUG "Refreshing $symbol ", 
        "($start - $end)"; 

  my $q = Finance::QuoteHist::Yahoo->new( 
    symbols    => [$symbol], 
    start_date => date_format($start), 
    end_date   => date_format($end), 
  ); 

  foreach my $row ($q->quotes()) { 
    my($symbol, $date, $open, $high, $low, 
       $close, $volume) = @$row; 

    $self->{cache}->set( dt_parse($date), 
                  $symbol, $close ); 
  } 
} 

########################################### 
sub date_format { 
########################################### 
  my($dt) = @_; 
  return $dt->strftime("%m/%d/%Y"); 
} 

########################################### 
sub dt_parse { 
########################################### 
  my($string) = @_; 
  my $fmt = 
      DateTime::Format::Strptime->new( 
        pattern => "%Y/%m/%d"); 
  $fmt->parse_datetime($string); 
} 

1;
