#!/usr/local/bin/perl # trade.pl. This program allows users to interact # with the WWWWS system. Set these variables # appropriately before use. $dataPath = "/CHANGE/THIS/PATH/wwwws"; $programUrl = "/CHANGE/THIS/URL/cgi-bin/trade.pl"; require "cgi-lib.pl"; require "chart.pl"; require "ctime.pl"; $historySize = 30; srand(); $accountName = $ENV{'REMOTE_USER'}; if ($accountName eq "") { # This shouldn't happen! But it will happen # if the program is not installed according # to the instructions, or if the server # has bugs regarding user authentication. print "Pragma: no-cache\n"; print "Content-type: text/html\n\n"; print "\n"; print "User Not Authenticated\n"; print "

User Not Authenticated

\n"; print "The trade program was not installed in\n"; print "a password-authenticated directory, or\n"; print "there is a bug in the web server.\n"; print "Please report this problem to the\n"; print "site administrator.\n"; print "\n"; exit 1; } $pathInfo = $ENV{'PATH_INFO'}; $_ = $pathInfo; #Perl is very, very good at this sort of pattern matching if (/\/trade/) { exit &Trade; } elsif (/\/history\/(\w+)/) { exit &History($1); } elsif (/\/historychart\/(\w+)\/image\d+\.gif/) { exit &HistoryChart($1); } elsif (/\/transactions/) { exit &Transactions; } elsif (/\/newspaper/) { exit &Newspaper; } else { exit &Portfolio; } sub Trade { local($symbol, $shares, $attr, $funds, $s); $s = $dataPath . "/database"; if (!open(TRADEIN, $s)) { &DatabaseMissing(); return 0; } &StartTransaction; &ReadParse(*input); if ($input{'newbuy symbol'} ne "") { $symbol = $input{'newbuy symbol'}; $shares = $input{'newbuy shares'}; if ($shares) { &stockBuy($symbol, $shares); } } while() { @fields = split(/\s/, $_); $symbol = $fields[0]; if ($symbol eq "") { last; } $attr = $symbol . " shares"; $shares = $input{$attr}; if ($shares > 0) { local($direction); $attr = $symbol . " direction"; $direction = $input{$attr}; if ($direction eq "buy") { stockBuy($symbol, $shares); } elsif ($direction eq "sell") { stockSell($symbol, $shares); } else { # Not a complete submission. # Don't guess! There's serious # play money at stake here. */ &LogTransaction( "Please specify buy or sell for " . $symbol . "No transaction performed."); EndTransaction(); return 0; } } elsif ($shares == 0) { # OK, do nothing } elsif ($shares < 0) { &LogTransaction($symbol . ": negative numbers of shares are not accepted for trades. Enter a positive number and select buy or sell."); } } close(TRADEIN); &EndTransaction; return &Portfolio; } sub stockBuy { local($symbol, $shares) = @_; local($purchasePrice, $price, $funds); # A purchase. Can we afford it? if (! &getFunds($funds)) { &LogTransaction( "Unable to access your account. Please contact the site administrator."); return; } if (! &getStockPrice($symbol, $price)) { &LogTransaction("Stock symbol " . $symbol . "is not in the database."); return; } $purchasePrice = $shares * $price; if ($purchasePrice > $funds) { # No &LogTransaction( "Insufficient funds to purchase " . $shares . " shares of " . $symbol . "!"); } else { local($currentShares); &getStockShares($symbol, $currentShares); # An ideal universe with no commissions! $currentShares += $shares; $funds -= $purchasePrice; &setStockShares($symbol, $currentShares); &LogTransaction("Purchased " . $shares . " shares of " . $symbol . " at " . $price . "."); } &setFunds($funds); } sub stockSell { local($symbol, $shares) = @_; # A sale. Do we have that many shares? local($salePrice, $currentShares, $price, $funds); if (! &getFunds($funds)) { &LogTransaction("Unable to access your account. Please contact the site administrator."); return; } if (! &getStockPrice($symbol, $price)) { &LogTransaction("Stock symbol " . $symbol . " is not in the database."); return; } $salePrice = $shares * $price; &getStockShares($symbol, $currentShares); if ($currentShares < $shares) { # No &LogTransaction("Can't sell " . $shares . " shares of " . $symbol . ": you only have " . $currentShares . "shares!"); } else { # An ideal universe with no commissions! $currentShares -= $shares; $funds += $salePrice; &setStockShares($symbol, $currentShares); &LogTransaction("Sold " . $shares . " shares of " . $symbol . " at " . $price . "."); } &setFunds($funds); } sub DatabaseMissing { print "Pragma: no-cache\n"; print "Content-type: text/html\n\n"; print "Database Missing\n"; print "

Database Missing

\n"; print "Please contact the site administrator.\n"; print "\n"; } sub StartTransaction { $path = $dataPath . "/" . $accountName . ".cur"; open(CURRENT, ">" . $path); $path = $dataPath . "/" . $accountName . ".log"; open(PERMANENT, ">>" . $path); } sub EndTransaction { print CURRENT "End of transaction.

\n"; close(CURRENT); close(PERMANENT); } sub LogTransaction { local($text) = @_; print CURRENT $text, "

\n"; print PERMANENT $text, " Time: ", &ctime(time), "

\n"; } sub History { local($symbol) = @_; local(@history); print "Pragma: no-cache\n"; print "Content-type: text/html\n\n"; print "\n"; print "Price History: ", $symbol, "\n"; print "

Price History: ", $symbol, "

\n"; if (! &getStockHistory($symbol, *history)) { print "

There is no stock with that ticker symbol.

\n"; } else { # Add a random component to the URL in order to # prevent web browsers from caching the chart # when it is out of date. print "

\n"; } print "Prices for the past 30 days are shown.\n"; print "


Portfolio \n"; print "Newspaper \n"; print "Past Transactions \n"; print "\n"; return 0; } sub HistoryChart { local($symbol) = @_; local(@history, @labels, $i, $im); $im = new GD::Image(400, 200); &getStockHistory($symbol, *history); for ($i = 0; ($i < int(@history)); $i++) { $labels[$i] = int(@history) - $i - 1; } &chartDraw($im, $chartBar | $chartCross | $chartLine, *labels, "Stock Price", *history); print "Pragma: no-cache\n"; print "Content-type: image/gif\n\n"; print $im->gif; } sub Transactions { $path = $dataPath . "/" . $accountName . ".log"; print "Pragma: no-cache\n"; print "Content-type: text/html\n\n"; print "\n"; print "Past Transactions\n"; print "

Past Transactions

\n"; if (!open(TRANSACTIONSIN, $path)) { print "

No transactions to date.

\n"; } else { while() { print $_; } close(TRANSACTIONSIN); } print "
Portfolio \n"; print "Newspaper \n"; print "\n"; return 0; } sub Newspaper { print "Pragma: no-cache\n"; print "Content-type: text/html\n\n"; print "\n"; print "Financial News\n"; print "

Financial News

\n"; $path = $dataPath . "/database"; open(NEWSPAPERIN, $path) || return 0; while() { local(@fields, $symbol, $price); s/\s+$//g; @fields = split(/\s/, $_); $symbol = $fields[0]; if ($symbol eq "") { last; } $price = $fields[$#fields]; if ($price eq $symbol) { print $symbol, ": no price set

\n"; } else { print $symbol, ": trading at ", $price; if (int(@fields) > 2) { $move = $fields[$#fields] - $fields[$#fields - 1]; $move = sprintf("%.4f", $move); if ($move == 0.0) { print " unchanged"; } elsif ($move > 0.0) { print " UP ", $move; } else { print " DOWN ", $move; } } print "\n"; print "See History

\n"; } } close(NEWSPAPERIN); print "


Portfolio \n"; print "Past Transactions \n"; print "\n"; return 0; } sub Portfolio { local($funds, $total, $path, $stocks); $total = 0; $stocks = 0; print "Pragma: no-cache\n"; print "Content-type: text/html\n\n"; print "\n"; print "Portfolio\n"; print "

Portfolio

\n"; $path = $dataPath . "/" . $accountName . ".cur"; if (open(PORTFOLIOIN, $path)) { print "

Transaction Report

\n"; while () { print $_; } close(PORTFOLIOIN); unlink($path); } $path = $dataPath . "/" . $accountName . ".dat"; if (!open(PORTFOLIOIN, $path)) { # Bad, bad news print "

No database record available

\n"; print "Your database entry is not available.\n"; print "Please contact the site administrator.\n"; print "\n"; return 0; } $funds = ; $funds =~ s/\s+$//; print "

Current Holdings

\n"; print "

Funds Available: ", $funds, "

\n"; print "
\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; print "\n"; while () { local($symbol, $shares, $price, $net); s/\s+$//g; ($symbol, $shares) = split(/ /); &getStockPrice($symbol, $price); print ""; $net = sprintf("%.4f", $shares * $price); print "\n"; $total += $net; print "\n"; print "\n"; print "\n"; print "\n"; $stocks++; } if (!$stocks) { print "\n"; } close(PORTFOLIOIN); print "\n"; $total = sprintf("%.4f", $total); print "\n"; print "
Stock TickerShares HeldCurrent PriceTotal ValueShares to Trade
", $symbol, "", $shares, "", $price, "", $net, "\n"; print "\n"; print "buysell
No current holdings.
Total Value", $total, "
\n"; print "Acquire new stock: \n"; print "\n"; print "Shares:

\n"; print "\n"; print "

\n"; print "Newspaper \n"; print "Past Transactions \n"; print "\n"; return 0; } sub getStockPrice { local($sym, $s) = @_; local(@fields, $s); $s = $dataPath . "/database"; if (!open(PRICEIN, $s)) { return 0; } while() { s/\s+$//; @fields = split(/ /); if ($fields[0] eq $sym) { if ($#fields) { $_[1] = $fields[$#fields]; return 1; } } } close(PRICEIN); return 0; } sub getStockHistory { local($sym, *history) = @_; local($i, $s, @fields); $s = $dataPath . "/database"; if (!open(HISTORYIN, $s)) { return 0; } while() { s/\s+$//; @fields = split(/ /); if ($fields[0] eq $sym) { for ($i = 0; ($i < ($#fields - 1)); $i++) { $history[$i] = $fields[$i + 1]; } $#history = $#fields - 2; return 1; } } close(HISTORYIN); return 0; } sub getFunds { local($s, $funds); $s = $dataPath . "/" . $accountName . ".dat"; if (!open(FUNDSIN, $s)) { return 0; } $funds = ; $funds =~ s/\s+$//; $_[0] = $funds; close(FUNDSIN); return 1; } sub setFunds { local($funds) = @_; local($sold, $snew, $oldFunds); $sold = $dataPath . "/" . $accountName . ".dat"; if (!open(FUNDSIN, $sold)) { return 0; } $snew = $dataPath . "/" . $accountName . ".new"; if (!open(FUNDSOUT, ">" . $snew)) { close(FUNDSIN); return 0; } $oldFunds = ; print FUNDSOUT $funds, "\n"; while () { print FUNDSOUT $_; } close(FUNDSIN); close(FUNDSOUT); rename($snew, $sold); return 1; } sub getStockShares { local($sym) = @_; local(@fields); $s = $dataPath . "/" . $accountName . ".dat"; if (!open(SHARESIN, $s)) { return 0; } while() { local($symbol, $shares); s/\s+$//; ($symbol, $shares) = split(/ /, $_, 2); if ($symbol eq $sym) { $_[1] = $shares; close(SHARESIN); return 1; } } close(SHARESIN); return 0; } sub setStockShares { local($sym, $shares) = @_; local($sold, $snew, $funds, $found); $sold = $dataPath . "/" . $accountName . ".dat"; if (!open(SHARESIN, $sold)) { return 0; } $snew = $dataPath . "/" . $accountName . ".new"; if (!open(SHARESOUT, ">" . $snew)) { close(SHARESIN); return 0; } $funds = ; $funds =~ s/\s+$//; print SHARESOUT $funds, "\n"; $found = 0; while () { local($symbol, $oldShares); s/\s+$//; ($symbol, $oldShares) = split(/ /, $_, 2); if ($symbol eq $sym) { $found = 1; $oldShares = $shares; } print SHARESOUT $symbol, " ", $oldShares, "\n"; } if (!$found) { print SHARESOUT $sym, " ", $shares, "\n"; } close(SHARESIN); close(SHARESOUT); rename($snew, $sold); return 1; }