The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!/usr/bin/perl

use strict;
use File::Find;
use Getopt::Long;
use Storable qw(lock_retrieve lock_store);
use Data::Dumper;
use File::Spec;
use Cache::Memcached;
use Finance::Quant;
use IO::Capture::Stdout;
use XML::Simple;
use Statistics::Basic qw(mean);
use File::Basename;

my @modules;
my $CACHE_VERSION = 1;
my $VERSION = "1.03";
my $backtest = {};
my @tested=();

my$dir = File::Spec->tmpdir();

chdir($dir);
# return only matching modules
sub wanted { 


/longtrend_backtest[_](.*).data$/ && push @tested,[$1,$File::Find::name];
#   /^*ownload(.*)Smallcon.jpg$/ && push @tested,[$1,$File::Find::name];

}
my @looking = ["Finance::Quant","Finance::Optical","Finance::Google","Finance::NASDAQ"];
# look in the @IRC dirs that exist
find(\&wanted, grep { -r and -d } @INC);

#print Dumper @tested;

# nice printout
foreach (@tested) {
  $backtest->{$_->[0]}=[$_->[1]];
# print "$_->[0] $_->[1]\n";
}


my %opts = (
            timeout       => 24*60*60,
            max_cache_age => 1,
            backrole=>0,
            cache_file    => "~/.getquotes_cache",
            cache         => 1,
            backdate      => "",
            datelastbuys=>"",
            ob=>"",
           );

GetOptions (\%opts,
            "timeout=i",
            "cache!",
            "datelastbuys=s",
            "ob=s",
            "max_cache_age=i",
            "backrole=i",
            "backdate=s",
            "cache_file=s");


$opts{dayoffweek}=`date +"%u"`;


if($opts{dayoffweek} gt 5 || $opts{dayoffweek} eq 1){
    $opts{lastbuisday}="last friday";
    $opts{backrole} = 2;
}else{
    $opts{lastbuisday}="yesterday";
    $opts{backrole} = 1;
}
    $opts{backdate} = trim(`date --date=$opts{backrole}" days ago" +%Y-%m-%d`);


$opts{cache_file} = tildeexp($opts{cache_file});


my$capture = IO::Capture::Stdout->new();
my $memd = new Cache::Memcached {
'servers' => [ "127.0.0.1:11211"],
'debug' => 0,
'compress_threshold' => 10_000,
} or warn($@);

my @symbols = @ARGV;

  my $self = $memd->get("master-run");

#  $memd->set("symbols_key",[keys %{$self->{result}}]);

  @symbols =  @{$memd->get("master-run-SYMBOLS")};

  @symbols = map {uc} @symbols;

  $opts{ob} .= sprintf $#symbols;

my $cache = {};
eval {
  $cache = lock_retrieve($opts{cache_file});
};
#$cache = {};
$cache = {} unless
  $cache->{CACHE_VERSION} and $cache->{CACHE_VERSION} !~ m/\D/
  and $cache->{CACHE_VERSION} == $CACHE_VERSION;

refetch($cache, @symbols)
  unless $opts{cache} == 1 and
  check_cache($cache, @symbols);




#   0 Symbol
#   1 Company Name
#   2 Last Price
#   3 Last Trade Date
#   4 Last Trade Time
#   5 Change
#   6 Percent Change
#   7 Volume
#   8 Average Daily Vol
#   9 Bid
#  10 Ask
#  11 Previous Close
#  12 Today's Open
#  13 Day's Range
#  14 52-Week Range
#  15 Earnings per Share
#  16 P/E Ratio
#  17 Dividend Pay Date
#  18 Dividend per Share
#  19 Dividend Yield
#  20 Market Capitalization
#  21 Stock Exchange


      $opts{ob} .= sprintf("\n****************************************************************************************************************\n\nQUOTES\n\n****************************************************************************************************************\n");

my $coreData = "";

$cache->{ranking}={};
for my $symbol (@symbols) {
  my $q = $cache->{$symbol}->{data};
  my $core = $cache->{$symbol}->{core};
  

#       next unless (defined($core->{result}[0]) && $core->{result}[0] eq"");
      my $r = @{$symbol,$core->{quote}->{result}}[0];
      
      
      my @r = split("\n",$r);
      
      foreach(@r){
#        print "\n$_" unless($_ !~/[\[]*[\]].*2012-02-09/);
         $cache->{$symbol}->{ordering}=$_ unless($_ !~/^[\[]1[\]]/);
      }
      @r = split(" ",$r[$#r]);
      
      $cache->{ranking}->{$r[2]}=$symbol unless($r[2] <10000);
#      $coreData .= "\n$symbol=". Dumper $core;


      #print "No symbol $symbol\n" and next unless ($q);
      $opts{ob} .= sprintf "%-5s %6.2f %6.2f %6.2f%% - %10s %10s\t%10s\t(%10s)\t(%s)\n", $q->[0], $q->[2], $q->[5], $q->[6], $q->[3], $q->[4] , $core->{position}, lc $q->[1],"<BACKTEST>";
  }


      my @keys = reverse sort { $a <=> $b  }  keys %{$cache->{ranking}};
      
 #   print Dumper @keys;  
      
      $opts{ob} .= sprintf("\n********************************************************\********************************************************\n\nORDERS %s\n\n****************************\************************************************************************************\n",$opts{backdate});
      
 foreach (@keys) { 
 
    my $sym = $cache->{ranking}->{$_};
    
    $cache->{$sym}->{ordering} = "" unless ($cache->{$sym}->{ordering});
   
    next unless ($cache->{$sym}->{ordering}=~ m/$opts{backdate}/);
   
    if($cache->{$sym}->{ordering} =~ m/$sym -/){
    $cache->{$sym}->{ordering} = "SELL>".$cache->{$sym}->{ordering};
    }elsif($cache->{$sym}->{ordering} =~ m/$sym/){
    
      $cache->{$sym}->{ordering} = "BUY>".$cache->{$sym}->{ordering};
    }else{
          $cache->{$sym}->{ordering} = ">".$cache->{$sym}->{ordering};
    }
#     
         $opts{ob} .= sprintf("\n%d %s %s  %s %s %s %s",$_,$sym,
                                          $cache->{$sym}->{core}->{position},
                                          $cache->{$sym}->{core}->{"guru-sum"},
                                          $cache->{$sym}->{core}->{"momentum"},$cache->{$sym}->{ordering}); 
                                          
  
#      }                                          
                                          
}


sub trim
{
	my $string = shift;

  $string = "" unless($string);
	$string =~ s/^\s+//;
	$string =~ s/\s+$//;
	return $string;
}


# return only matching modules
sub wantedx { 


   /^F*(Market|Quant|Quant*Home|NASDAQ|Optical).*\.pm$/ && push @modules, $File::Find::name;
  
}


sub searchModule{

    my @return = ();
    # look in the @IRC dirs that exist
    find(\&wantedx, grep { -r and -d } @INC);



    # de-dupe
    my %saw;
    @modules = grep(!$saw{$_}++, @modules);

    # strip extraneous directories off the modules
    for my $prefix ( sort { length $b <=> length $a } @INC ) {
      for (@modules) { 
          next unless($_ =~ /Finance/);
          next if s/^\Q$prefix// 
       }
    }

    
    return  \@return;
}



sub DirContent ($ $)
{
    my ( $path,$id ) = @_;

     warn "$id: $path\n";

    local *DIR;

    unless ( opendir DIR, $path )
    {
    	print "$id: can't read $path $@";
    }

    my @tmp = readdir DIR;
    closedir DIR;

    my @results = ();
    foreach(@tmp){
          next if($_ eq "." || $_ eq "..");
          
         if($_ =~ /$id/){
           push @results,$_;
         }
         
    }

    return @results;
}


sub refetch {
  my ($cache, @symbols) = @_;
  eval {
    local $^W = 0;  # because Finance::YahooQuote doesn't pass
                    # warnings with 5.6.0.
    require Finance::YahooQuote;
    import  Finance::YahooQuote;
    $Finance::YahooQuote::TIMEOUT = $Finance::YahooQuote::TIMEOUT = $opts{timeout};
    
  };

  die qq[\nYou need to install the Finance::YahooQuote module\n\nTry\n\n  perl -MCPAN -e 'install "Finance::YahooQuote"'\n\nas root\n\n]
        if $@ =~ /locate Finance/;
  die $@ if $@;

  my @q = getquote(@symbols);
  for my $q (@q) {
    my $symbol = $q->[0];
    if ($q->[1] eq $symbol) {
      $q = undef;
    } else {
      $q->[6] =~ s/%$//;
    }
    $cache->{$symbol}->{time} = time;
    $cache->{$symbol}->{data} = $q;
    
    
my $quote = {};


$quote = {"symbol"	=>	$q->[0],
"CompanyName"	=>	$q->[1],
"LastPrice"	=>	$q->[2],
"LastTradeDate"	=>	$q->[3],
"LastTradeTime"	=>	$q->[4],
"PercentChange"	=>	$q->[5],
"Volume"	=>	$q->[7],
"AverageDailyVol"	=>	$q->[8],
"PreviousClose"	=>	$q->[11],
"Open"	=>	$q->[12],
"DayRange"	=>	$q->[13],
"52-WeekRange"	=>	$q->[14],
"MarketCapitalization"	=>	$q->[20],
"result"	=>	[readFile(@{$backtest->{$q->[0]}})]
};

    $cache->{$symbol}->{core} = getCore($symbol,$quote);
    
    
    
  }
  $cache->{CACHE_VERSION} = $CACHE_VERSION;
  lock_store($cache, $opts{cache_file});
}

sub check_cache {
  my ($cache, @symbols) = @_;
  # check that all symbols are fresh enough
  for my $symbol (@symbols) {
    unless ($cache->{$symbol}->{time}
            and $cache->{$symbol}->{time} > time-$opts{max_cache_age}) {

      # XXX .. cache cleaning should work
     # for my $symbol (keys %{$cache}) {
        #if ($cache->{$symbol}->{time} < time-($opts{max_cache_age}*20)) {
        #  delete $cache->{$symbol};
       # }
     # }

      return 0;
    }
  }
  return 1;
}




sub getCore{

  my ($symbol,$data) = @_;
  my $homex = $self->{result}->{$symbol}->{extended};
     $homex->{quote}=$data;
     
      my @tested = ();
      $symbol = "" unless($symbol);

     /longtrend_backtest[_]$symbol.data$/ && push @tested,[$1,$File::Find::name];
  
  return $homex;

}


sub readFile {
  my($filename) = @_;
  my(@lines);

  my $details = {};
  my $ret = "";
  if(!defined($filename) ) {
    return [];

  }
  my @array=();
  
  open(IN, $filename) or die "Error: couldn't open file $filename : $!\n";
  @lines = <IN>;
  
     foreach my $line(@lines){
        
        next unless($line =~/Net|Tx|2012-02/);
        $ret .= $line;
        push @array,$line;
        
   
    }
  
  close(IN);
   
  return $ret;#@array
}


sub tildeexp {
  my $path = shift;
  $path =~ s{^~([^/]*)} {  
          $1 
                ? (getpwnam($1))[7] 
                : ( $ENV{HOME} || $ENV{LOGDIR} || (getpwuid($>))[7])
          }ex;
  return $path;
}



#my $xml = XML::Simple::XMLout($cache);

#open (XML,">a");
#print XML $xml;
#close (XML);

      $opts{ob} .= sprintf $coreData;

      printf $opts{ob};

=head1 NAME

getquotes - get quotes from Yahoo Finance.

=head1 SYNOPSIS

  getquotes
       (gets the default quotes)

  getquotes YHOO VCLK ANDN 
       (get quotes for Yahoo, ValueClick and Andover Net)

  getquotes --nocache
       (don't use the cache)

  getquotes --max_cache_age=600 YHOO
       (cache is valid for 10 minutes (default is 5*60 seconds))

  getquotes --cache_file=/tmp/cache
       (alternate cache file (default ~/.getquotes.cache))

  getquotes --timeout 10
       (timeout after 10 seconds instead of the default 3)


=head1 TODO

More clued documentation. :)

--help option.

Cache cleanup.

=head1 COPYRIGHT