Skip to content

Instantly share code, notes, and snippets.

@ufizo
Created March 10, 2013 15:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ufizo/5128956 to your computer and use it in GitHub Desktop.
Save ufizo/5128956 to your computer and use it in GitHub Desktop.
Queries Google Finance to get current stock rates for NSE and NYSE.
=head1 NAME
stock4.pl No fancy name yet
=head1 DESCRIPTION
Queries Google Finance to get current stock rates for NSE and NYSE.
=head1 DEPENDENCIES
Parallel::ForkManager
HTTP::Tiny
Time::HiRes
=head1 BUGS AND LIMITATIONS
We are basically looking at running this 24x7, with parallel processes.
Shared hosting servers may not like this.
Timeout should be tuned to avoid overlap of jobs.
May need to explicitly specify mysql_socket (Needed with XAMPP on ArchLinux)
mysql_socket=/opt/lampp/var/mysql/mysql.sock:$dbhost
Shared hosting also limits MAX_USER_CONNECTIONS to 15. With forked process, it is very easy to hit that limit very quickly, specially even if the script is closed, the mySQL sometimes might still stay open. Workaround can be killing the perl script externally, or dumping all the data into a matrix, and updating the database with just one mySQL connection, instead of creating separate connections for each forked process.
=head1 AUTHOR
Arpit Singh `me@arpitsingh.in`
=head1 CREDITS
Abhay Rana `capt.n3m0@gmail.com` (For his update script in php)
=cut
use warnings;
use lib 'Parallel';
use DBI;
use Parallel::ForkManager;
use HTTP::Tiny qw();
use Time::HiRes qw(gettimeofday);
# Script VARS
my $n = 90; # Numbers of symbols to query to google at once.
# Google allows upto 100.
my $threads = 15; # Number of parallel connections.
# More may be faster, but will also use more resources.
my $timeout = 55; # Timeout in seconds. Default 55, to allow 1 min CRON jobs.
# DB VARS
my $dbname = "";
my $dbhost = "localhost";
my $dbuser = "";
my $dbpass = "";
my $gUrl = 'http://www.google.com/finance/info?infotype=infoquoteall&q=';
my $market_open = 1; # To terminate the script immediately if markets are closed.
sub doItdoIt(){
# Set the exchange
my @t = gmtime(time);
# Dirty convert to IST
my $hr = $t[2]+5; if ($hr > 23) {$hr = $hr - 24;}
my $min = $t[1]+30; if ($min > 59) {$min = $min - 60; $hr++;}
my $nyse = 0, my $nse = 0;
my $exchange, my @links;
if ( ($hr > 9)&&($hr < 15)||
($hr == 9)&&($min >= 15)||
($hr == 15)&&($min <= 30)
){$nse = 1;}
if ( ($hr >= 20)&&($hr<=24)||
($hr >= 0)&&($hr<2)||
($hr == 2)&&($min <= 30)
){$nyse = 1;}
if($nyse == 0 && $nse == 0){
print "$hr:$min, ";
print "MC";
$market_open = 0;
}
else{
if($nyse){$exchange = 'NYSE';}
if($nse){$exchange = 'NSE';}
}
# Prepare the list to update stocks for
if ($exchange){
# Get total symbols, and divide into chunks
my $dbh = DBI->connect("DBI:mysql:$dbname;$dbhost","$dbuser","$dbpass") || die "Database connection not made: $DBI::errstr";
my $sth = $dbh->prepare("SELECT COUNT(*) FROM stocks WHERE exchange='$exchange'");
$sth->execute();
my $data = $sth->fetchall_arrayref();
$sth->finish();
my $total_syms = $$data[0][0];
my $n_parts = int($total_syms/$n);
# Make the list of google links
my $start, my $query, my @data, my $gquery, my $url;
for (0..$n_parts){
$start = $_*$n;
$query = "SELECT symbol FROM stocks WHERE exchange='$exchange' LIMIT $start,$n";
@data = @{$dbh->selectcol_arrayref($query)};
$gquery = join(',',@data);
$url = $gUrl.$gquery;
#print $url."\n";
push (@links,$url);
#print $_*$n." to ".(($_+1)*$n-1)."\n";
}
$dbh->disconnect or warn "Disconnection error: $DBI::errstr\n";
undef($dbh);
}
# Fetch and update stocks
my $pm = Parallel::ForkManager->new($threads);
foreach my $link (@links) {
$pm->start and next; # do the fork
refresh_stocks($link);
$pm->finish; # do the exit in the child process
}
$pm->wait_all_children;
}
sub refresh_stocks(){
#print "Getting @_\n";
my $newstocks = HTTP::Tiny->new->get(@_);
if ($newstocks->{success}) {
my $json_text = $newstocks->{content}; $json_text =~ s|\\x26||;
my @jsons = ( $json_text =~ m|({.*?})|sig );
for my $js (@jsons){
my $e1, my $t1, my $symbol, my $current, my $change, my $cp, my $mc, my $opening, my $high, my $low;
if ($js =~ m|"e"\W\:\W"(.*?)"|){$e1 = $1;}
if ($js =~ m|"t"\W\:\W"(.*?)"|){$t1 = $1;}
$symbol = $e1.':'.$t1;
if ($js =~ m|"l"\W\:\W"(.*?)"|){$current = $1; $current =~ s|\,||;}
if ($js =~ m|"c"\W\:\W"(.*?)"|){$change = $1; $change =~ s|\,||;}
if ($js =~ m|"cp"\W\:\W"(.*?)"|){$cp = $1; $cp =~ s|\,||;}
if ($js =~ m|"mc"\W\:\W"(.*?)"|){$mc = $1; $mc =~ s|\,||;}
if ($js =~ m|"op"\W\:\W"(.*?)"|){$opening = $1; $opening =~ s|\,||;}
if ($js =~ m|"hi"\W\:\W"(.*?)"|){$high = $1; $high =~ s|\,||;}
if ($js =~ m|"lo"\W\:\W"(.*?)"|){$low = $1; $low =~ s|\,||;}
#print "$symbol is at $current \n";
my $dbh = DBI->connect("DBI:mysql:$dbname;$dbhost","$dbuser","$dbpass") || die "Database connection not made: $DBI::errstr";
my $sth = $dbh->prepare("UPDATE stocks SET current='$current', `change`='$change', change_percent = '$cp',market_cap='$mc',opening='$opening',high='$high',low='$low' WHERE symbol='$symbol'");
$sth->execute();
$sth->finish();
$dbh->disconnect or warn "Disconnection error: $DBI::errstr\n";
undef($dbh);
}
}
}
my $t1, my $t2;
$SIG{ALRM} = sub { die "Time out\n" };
eval {
alarm($timeout);
while($market_open){
$t1 = [Time::HiRes::gettimeofday()];
doItdoIt();
# Current prices of stocks have been updated, Update networths, and run the trigger job.
HTTP::Tiny->new->get('http://cognistreet.cognizance.org.in/update/networth');
HTTP::Tiny->new->get('http://cognistreet.cognizance.org.in/update/triggers');
$t2 = Time::HiRes::tv_interval($t1);
print "$t2\n";
}
alarm(0);
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment