1: #!/usr/bin/perl
   2: 
   3: =head1 NAME
   4: 
   5: ticker - A stock monitoring client written for Peep: The Network
   6: Auralizer.
   7: 
   8: Peep takes virtual events and turns them into unobtrusive sounds like
   9: birds chirping, rain falling, crickets doing whatever crickets do,
  10: etc.  It is frequently used for network and systems monitoring because it
  11: is well suited for that task, but I recently added an API
  12: to Net::Peep that allows customized clients to be built with very
  13: little code.
  14: 
  15: Since there are some stocks I follow I decided to make my first custom
  16: client a stock ticker.  Thanks to Finance::Quote, it took me about a
  17: half-hour to write :-) A rooster crow or doorbell chime tells me that
  18: something is happening with my stocks.  And at the end of the day, it
  19: e-mails me a report on any questionable activity.
  20: 
  21: Though it is heavily commented, the code itself is quite short.
  22: 
  23: =head1 USAGE
  24: 
  25:   ./ticker --help
  26: 
  27:   ./ticker --noautodiscovery --server=localhost --port=2001 --nodaemon
  28: 
  29: If you have any problems, try turning on debugging output with
  30: something like --debug=9.
  31: 
  32: =head1 CONFIGURATION
  33: 
  34: To use this client, include a section like the following in peep.conf:
  35: 
  36:   client ticker
  37:     class home
  38:     port 1999
  39:     config
  40:       # Stock symbol  Stock exchange    Event             Max      Min  
  41:       RHAT            nasdaq            red-hat           4.0      3.6
  42:       SUNW            nasdaq            sun-microsystems  9.0      8.0
  43:     end config
  44:     notification
  45:       notification-hosts localhost
  46:       notification-recipients bogus.user@bogusdomain.com
  47:       notification-level warn
  48:     end notification
  49:   end client ticker
  50: 
  51: and another section in the events block with something like
  52: 
  53:   events
  54:   #Event Type      |          Path to Sound File           | # of sounds to load
  55:   ...
  56:   red-hat            /usr/local/share/peep/sounds/misc/events/rooster.*        1
  57:   sun-microsystems   /usr/local/share/peep/sounds/misc/events/doorbell.*       1
  58:   end events
  59: 
  60: =head1 AUTHOR
  61: 
  62: Collin Starkweather <collin.starkweather@colorado.edu> Copyright (C) 2001
  63: 
  64: =head1 SEE ALSO 
  65: 
  66: perl(1), peepd(1), Net::Peep, Net::Peep::Client, Net::Peep::BC,
  67: Net::Peep::Notifier, Net::Peep::Notification, Finance::Quote
  68: 
  69: http://peep.sourceforge.net
  70: 
  71: =cut
  72: 
  73: # Always use strict :-)
  74: use strict;
  75: use Net::Peep::BC;
  76: use Net::Peep::Log;
  77: use Net::Peep::Client;
  78: use Net::Peep::Notifier;
  79: use Net::Peep::Notification;
  80: use Finance::Quote;
  81: use vars qw{ %config $logger $client $quoter $conf };
  82: 
  83: # The Net::Peep::Log object will allow us to print out some things in
  84: # a nicely formatted way so we can tell ourselves what we're doing ...
  85: $logger = new Net::Peep::Log;
  86: 
  87: # Instantiate a Peep client object.  The client object handles most of
  88: # the dirty work for us so we don't have to worry about things such as
  89: # forking in daemon mode or parsing the command-line options or the
  90: # Peep configuration file.
  91: $client = new Net::Peep::Client;
  92: $quoter = new Finance::Quote;
  93: 
  94: # First we have to give the client a name
  95: $client->name('ticker');
  96: 
  97: # Now we initialize the client.  If the initialization returns a false
  98: # value, we display documentation for the script and exit.
  99: $client->initialize() || $client->pods();
 100: 
 101: # Now we assign a parser that will parse the 'ticker' section of the
 102: # Peep configuration file
 103: $client->parser( \&parse );
 104: 
 105: # Now we tell the client to get the information from the configuration
 106: # file.  It returns a  Net::Peep::Conf, the Peep configuration object,
 107: # which contains information from the configuration file.
 108: $conf = $client->configure();
 109: 
 110: # Register a callback which will be executed every 60 seconds.  The
 111: # callback is simply a function which checks the price of the stock
 112: # and peeps every time it exceeds the maximum or minimum price that
 113: # has been set.
 114: $client->callback( \&loop );
 115: 
 116: # Start looping.  The callback will be executed every 60 seconds ...
 117: $client->MainLoop(60);
 118: 
 119: sub parse {
 120: 
 121:     # Parse the config section for the ticker client in the Peep
 122:     # configuration file
 123: 
 124:     # We'll use a regular expression to extract the ticker information
 125:     # and stuff it into a data structure (the global variable %config)
 126: 
 127:     # This subroutine will be used to parse lines from peep.conf such
 128:     # as the following and store the information in %config:
 129:     #   RHAT            nasdaq            red-hat           4.0      3.6
 130:     #   SUNW            nasdaq            sun-microsystems  9.0      8.0
 131:     for my $line (@_) {
 132: 	if ($line =~ /^\s*([A-Z]+)\s+(\w+)\s+([\w\-]+)\s+([\d\.]+)\s+([\d\.]+)/) {
 133: 	    my ($symbol,$exchange,$event,$max,$min) = ($1,$2,$3,$4,$5,$6);
 134: 	    $config{$symbol} = { event=>$event, exchange=>$exchange, max=>$max, min=>$min };
 135: 	}
 136:     }
 137: 
 138: } # end sub parse
 139: 
 140: sub loop {
 141: 
 142:     for my $key (sort keys %config) {
 143: 	$logger->log("Checking the price of [$key] ...");
 144: 	# Fetch some information about the stock including the price
 145: 	my %results = $quoter->fetch($config{$key}->{'exchange'},$key);
 146: 	my $price = $results{$key,'price'};
 147: 	$logger->log("\tThe price of [$key] is [$price].");
 148: 	if ($price > $config{$key}->{'max'} or $price < $config{$key}->{'min'}) {
 149: 	    $logger->log("\tThe price is out of bounds!  Sending notification ....");
 150: 	    # The price is out of bounds!  We'll start peeping ...
 151: 	    my $broadcast = Net::Peep::BC->new('ticker',$conf);
 152: 	    $broadcast->send('ticker',
 153: 			     type=>0, 
 154: 			     sound=>$config{$key}->{'event'},
 155: 			     location=>128, 
 156: 			     priority=>0, 
 157: 			     volume=>255);
 158: 	    # In case we're away from our desk, we'll also send out an
 159: 	    # e-mail notification.  Don't want to miss the action!
 160: 	    my $notifier = new Net::Peep::Notifier;
 161: 	    my $notification = new Net::Peep::Notification;
 162: 	    $notification->client('ticker');
 163: 	    $notification->status('crit');
 164: 	    $notification->datetime(time());
 165: 	    $notification->message("The price of $key is $price!");
 166: 	    $notifier->notify($notification);
 167: 	}
 168:     }
 169: 
 170: } # end sub loop
 171: 
 172: __END__