#!/afs/tr/proj/tools/bin/perl # Written by Ted Anderson during 1995. # Read all the old transaction confirmation messages. Extract the actual # confirmation text and save it away, indexed by date. # Show signed YESes instead of NOs. $nonos = 1; # Account name of interest $whoami = 'ota@transarc.com'; # Details may vary depending on how mail is stored. @files = (); $readStdin = 0; undef($readAMS); undef($readTradeFiles); undef($readTransFiles); $verbose = 0; for ($i=0; $i<@ARGV; $i++) { $_ = $ARGV[$i]; if (!/^-/) { push (@files, $_); next; } if (/^-$/) { $readStdin = 1; } elsif (/^-verbose/) { $verbose = 1; } elsif (/^-notradefile/) { $readTradeFiles = 0; } elsif (/^-notransfile/) { $readTransFiles = 0; } elsif (/^-AMS/) { $readAMS = 1; } else { print STDERR "Usage: $0 [-notradefile] [-notransfile] [-AMS] [-] [*]\n"; die ("Unknown option: '$_'"); } } $explicitFiles = ((@files > 0) || $readStdin); $readTradeFiles = !$explicitFiles if (!defined($readTradeFiles)); $readTransFiles = !$explicitFiles if (!defined($readTransFiles)); $readAMS = !$explicitFiles if (!defined($readAMS)); push (@files, <./transactions.*[0-9a-zA-Z]>) if ($readTransFiles); push (@files, <./trades.confirm*.mail>, <./trades.confirm*.mail.Z>) if ($readTradeFiles); push (@files, <$ENV{HOME}/.MESSAGES/mail/idea-futures/trades/+*>) if ($readAMS); &ReadFile (*STDIN) if ($readStdin); foreach $file (@files) { $file = "zcat $file|" if ($file =~ /\.Z$/); open (IN, "$file") || die ("Couldn't open trade file $file: $!"); &ReadFile (*IN); close (IN); } # Open a tmp file for the report so we can output the account summary at the # beginning. Most recent trades will be at the bottom. open (TMP, "+>/tmp/trades.$$") || die ("Couldn't open tmp file: $!"); unlink ("/tmp/trades.$$") || die ("Couldn't unlink tmp file: $!"); select TMP; # Process trades in time order, writing report to TMP. foreach $date (sort keys(%file)) { &ReadTrades($date); } seek (TMP, 0, SEEK_SET); select STDOUT; # Now output most recent entry for each issue. Then follow it by the whole # trading history most recent last. print "Status of each issue:\n"; foreach $sym (sort(keys(%status))) { print $status{$sym}; } print "\nTrades:\n"; print while (); close (TMP) || die ("failed to close tmp file: $!"); exit 0; sub Push { local ($date, @info) = @_; (@info == 5) || die ("Wrong amount of info for $date: " . join(' ',@info)); $file{$date} .= (join(' ', @info) . "\n"); } sub ReadFile { local (*IN) = @_; local ($date, $symbol, $buy, $quant, $price); while () { if (($date) = (m=^The following transaction was made on your account at (9[4-9]/.*)=)) { while () { last if (/^\(signed\) IF/); ($symbol, $buy, $quant, $price) = /^\s*[-+]?\d+\.\d\d\s+(....) ([BS])(\d+)(\S+)/; next if (!$symbol); &Push ($date, $symbol, "held", $buy, $quant, $price); } } elsif (($date, $trans) = m=(\d\d/\d\d/\d\d \d\d:\d\d:\d\d)((\t\S+){11})\s*$=) { &ReadTransLog($date, $trans); } } } # a transaction log entry looks like: # date sym buyer(name cash held max) seller(name cash held max) quant price # need to improve detection of duplicates. sub ReadTransLog { local ($date, $trans) = @_; local (@info); local ($symbol, $quant, $price); local ($held, $n); # strip leading tab, then split. @info = split (/\t/, substr($trans, 1)); # see if we are either buyer or seller next unless ($info[1] eq $whoami || $info[5] eq $whoami); $symbol = shift(@info); $price = pop(@info); $quant = pop(@info); if ($info[0] eq $whoami) { # we are buyer $held = $info[2]; if (($quant != 0) && ($held < 0)) { # first close any short position $n = ((-$held >= $quant) ? $quant : -$held); &Push($date, $symbol, $held, 'B', $n, '@'.$price); &Push ($date, $symbol, "held", 'S', $n, 'P'); $quant -= $n; $held += $n; } if ($quant >= 0) { # buy the pairs we want &Push ($date, $symbol, $held, 'B', $quant, '@'.$price); } } else { # we are seller $held = $info[4+2]; if ($held > 0) { # first sell those we already hold $n = (($held > $quant) ? $quant : $held); &Push($date, $symbol, $held, 'S', $n, '@'.$price); $quant -= $n; $held -= $n; } if ($quant > 0) { # must go short, so buy pairs first. &Push ($date, $symbol, $held, 'B', $quant, 'P'); &Push ($date, $symbol, "held", 'S', $quant, '@'.$price); } } } # GetNextTrade -- gets the next trade of the same symbol from the @trades # array. The particulars are of the desired trade are returned as an # array. The line is saved in $_. # # NOTE -- @trades and $_ are free variables. sub GetNextTrade { local ($symbol) = @_; local (@next); do { return undef if (!@trades); if ($symbol) { return undef if ($symbol ne substr($trades[0],0,4)); } $_ = shift(@trades); @next = split; die ("no symbol: $_") if (@next != 5); if ($dup{join(' ', $next[0], @next[2..4])}++ > 0) { # warn ("On $date found trade $_ duplicated"); next; } } until (@next); @next; } # $time $sym $Buyer Buy Buyer Buy Seller Sell Seller Sell quant price # cash Held Max cash Held Max # 11:19:49 DMMS jamesm 51.03 0 53 morgan 99.40 0 1987 1 # 11:21:46 DMMS jamesm 50.08 1 52 morgan 99.35 -1 1986 1 # 12:33:29 Stew mnr 80.20 49 422 dbm 90.50 0 111 1 # 13:10:51 Terr jamesm 49.13 5 122 mnr 80.01 -20 133 10 # 13:14:43 CFsn ddhwtt 37.98 5 759 morgan 99.30 0 104 20 # 13:19:57 SLvl kk 50.00 0 166 mnr 74.01 42 165 10 # 13:23:43 DMMS morgan 80.30 -2 81 jamesm 45.13 2 4713 10 # 13:46:21 CFsn ddhwtt 36.98 25 1232 morgan 72.40 -20 74 10 # 13:46:21 SLvl mnr 77.01 32 513 ddhwtt 36.68 -67 43 10 # 13:46:21 Terr ddhwtt 28.18 10 70 mnr 75.51 -30 125 10 # ReadTrades -- takes a date string and uses it as a key into the associative # array %file. The corresponding valud is a string consisting of all the # trades that took place at that time. Process these trades to track the # account status for each issue. The status consists of number of YES or # NO coupons held, and the total cost to reach that position. The cost is # zeroed when the position is liquidated. # # Each line of the report gives the date/time, the issue's symbol, whether # yeses (Y) or pairs (P) were traded, the number bought () or # sold (-) and the price. This is followed by the new account status. If # either yes (Y) or no (N) coupons are held then the number is shown with # the average price/share to required to obtain them. If the position has # been liquidated the net profit (loss) is printed. If both yes and no # coupons are held then this is a transient situation and the status is # blank. sub ReadTrades { local ($date) = @_; local (@trades) = split("\n", $file{$date}); local ($symbol, $held, $buy, $quant, $price); local (%dup); local ($i, $q, @next); while (@trades) { ($symbol, $held, $buy, $quant, $price) = &GetNextTrade(undef); next if (!$symbol); $correction = 0; if ($held ne "held") { if ($held < 0) { $held = -$held; if (($held != $no{$symbol}) || $yes{$symbol}) { warn ("Inconsistent $symbol holding on $date: IF says -$held, I compute +$yes{$symbol}-$no{$symbol}") if ($verbose); if (($yes{$symbol} == 0) && ($no{$symbol} != 0)) { # try to scale the cost to avoid bias $cost{$symbol} = $cost{$symbol}/$no{$symbol}*$held; } $no{$symbol} = $held; $yes{$symbol} = 0; $correction = 1; } } elsif (($held != $yes{$symbol}) || $no{$symbol}) { warn ("Inconsistent $symbol holding on $date: IF says $held, I compute +$yes{$symbol}-$no{$symbol}") if ($verbose); if (($no{$symbol} == 0) && ($yes{$symbol} != 0)) { # try to scale the cost to avoid bias $cost{$symbol} = $cost{$symbol}/$yes{$symbol}*$held; } $yes{$symbol} = $held; $no{$symbol} = 0; $correction = 1; } } # just a status update next if (!$correction && $quant == 0); $quant *= (($buy eq 'B') ? 1 : -1); if ($price eq 'P') { $yes{$symbol} += $quant; $no{$symbol} += $quant; $cost{$symbol} += -$quant*100; (@next = &GetNextTrade($symbol)) || die ("trades after pair isn't same symbol: $_"); # ignore $held for now ($buy, $quant, $price) = @next[2..4]; $quant *= (($buy eq 'B') ? 1 : -1); ($price) = ($price =~ /^@(\d+)$/); defined($price) || die ("Bogus price: $price"); $yes{$symbol} += $quant; $cost{$symbol} += -$quant*$price; } else { ($price) = ($price =~ /^@(\d+)$/); defined($price) || die ("Bogus price: $price"); $yes{$symbol} += $quant; $cost{$symbol} += -$quant*$price; # see if next is be a pair trade, and use it to settle up if ($yes{$symbol} && $no{$symbol}) { (@next = &GetNextTrade($symbol)) || die ("trades after pair isn't same symbol: $_"); # ignore $held for now (($next[2] eq 'S') && ($next[4] eq 'P')) || die ("expected to be selling pairs: $_"); $q = -$next[3]; $yes{$symbol} += $q; $no{$symbol} += $q; $cost{$symbol} += -$q*100; } } $cost = $cost{$symbol}; if (!$yes{$symbol} && !$no{$symbol}) { # out position was liquidated $s = ($correction ? "~~" : "**"); if ($cost >= 0) { $status = sprintf(" $s\$%.2f**", $cost/100.0); } else { $status = sprintf(" $s(\$%.2f)**", -$cost/100.0); } $cost{$symbol} = 0; } elsif ($yes{$symbol} && $no{$symbol}) { # just bought pairs or we are about to sell pairs so supress # account status until we settle. $status = ""; } else { $s = ($correction ? "~~" : "--"); $shares = ($yes{$symbol} + $no{$symbol}); $yeses = $yes{$symbol}; if ($nonos && !$yeses) { $shares = -$shares; $yeses = 1; } $status = sprintf(" $s %4d%s", $shares, ($yeses ? "Y" : "N")); if ($cost < 0) { $costPer = -$cost/$shares; $costPer += 100 if ($shares < 0); if ($costPer < 0) { # we are in the RED on these shares to print net loss $status .= sprintf(" : (\$%.2f)", -$cost/100.0); } else { $status .= sprintf(" @ %.3g", $costPer); } } else { # we are in the black on these shares, so print net profit $status .= sprintf(" : \$%.2f", $cost/100.0); } } if ($what eq 'P') { # pairs $line = sprintf("$date $symbol $what %4d $status\n", $quant); } elsif ($quant > 0) { # buy $line = sprintf("$date $symbol $what %4d B@%-2d $status\n", $quant, $price); } else { # sell $line = sprintf("$date $symbol $what %4d S@%-2d $status\n", $quant, $price); } $status{$symbol} = $line; print $line; } }