#! /usr/bin/gawk -f # Last edited on 2015-02-21 17:27:25 by stolfilocal BEGIN { abort = -1; # Reads a file of transactions for a wallet, as produced # by {cleanup_wallet_data.gawk}. # Caller must define (with "-v") {wallet}, {iniDate}, {finDate} # Each input line represents # a transfer into or out of a specified wallet {WAL}, or a # transfer between addresses of the same wallet. The # fields in each input line are assumed to be # # {DAY} {TIME} | {TXID} | {IOINDEX} | {AMOUNT} | {IOWALLET} | {BALANCE} # # where {TXID} is a transaction ID, {IOINDEX} is a sequential number # of an input or output in that transaction (from 0), {AMOUNT} # is the amount transferred, {IOWALLET} is the ID of the # wallet at the other ends of the transfer, and {BALANCE} is # the claimed balance in {WAL} after the transaction. # The {DAY} and {TIME} need not be in order. # The output file will have one line for each day present in the input file. # The fields are # # {DAY} { {DNUM} {DBTC} {CNUM} {CBTC} }.. # # where the fields {DNUM} and {DBTC} are number of transactions and total amount of BTC # moved in the {DAY}, respectively, while {CNUM} and {CBTC} are the cumulative totals # up to the {DAY}, inclusive. These four fields are repeated 3 times; the three groups of four # summarize, in order, transfers out of the wallet (negative {AMOUNT}s), # internal shuffles (zero {AMOUNT}s), and transfers into the wallet (positive {AMOUNT}s). # Needs the wallet name, to check intra-wallet transactions: if (wallet == "") { arg_error(("must define {wallet}")); } if (iniDate == "") { arg_error(("must define {iniDate}")); } if (finDate == "") { arg_error(("must define {finDate}")); } # Make sure that conversions of numbers to strings and vice-versa preserve 8 decimals after point: OFMT = "%.8f"; CONVFMT = "%.8f"; # Statistics indexed by # {dy}: day the transactions were issued. # {kind}: -1 = send, +1 = receive, 0 = internal shuffle # {var}: 0 = count, 1 = BTC moved split("", stat); # Number of entries per day, indexed with {dy}: split("", nread_day); nread = 0; # Number of data records read from input. nused = 0; # Number of data records in date range. ndays = 0; # Number of distinct days seen. odt = "2009-01-03 00:00:00"; # Previous date and time. bal = "??"; # Balance up to previous line. } /^20[01][0-9]-[01][0-9]/ { nread++; if (NF != 12) { data_error(("invalid field count")); } for (j = 3; j <= NF; j += 2 ) { if ($(j) != "|") { data_error(("field " j "is not \"|\"")); } } dy = check_day($1); # Day, "{yyyy}-{mm}-{dd}". tm = check_time($2); # Time of day, "{HH}:{MM}:{SS}" (UTC). tx = $4; # Transaction ID. iotx = check_nat($6); # Input/output index in transaction. val_btc = check_amount($8); # Value received/sent from/to that input/output. iowal = $10; # Wallet identifier as per "http://www.walletexplorer.com". eb = check_amount($12); # Wallet balance, as per "http://www.walletexplorer.com". dt = (dy " " tm); # Check chronological order of day + hour (not essential): if (dt < odt) { data_warning(("times out of order")); } odt = dt; if ((dy < iniDate) || (dy > finDate)) { next; } # Check for new date: if (! (dy in nread_day)) { clear_stats(nread_day,stat,dy); ndays++; } # Determine the kind of operation by the sign of the value: if (val_btc+0 > 0) { kind = +1; } else if (val_btc+0 < 0) { kind = -1; } else { kind = 0; if (iowal != wallet) { data_error(("wrong wallet \"" iowal "\" in internal shuffle")); } } if (kind*val_btc < 0) { prog_error(("wrong {kind} " val_btc " --> " kind)); } # Check balance: bal += val_btc; if (sprintf("%.8f", eb + 0.0) != sprintf("%.8f", bal + 0.0)) { if (nread > 0) { data_warning(("computed balance = " bal " claimed = " eb)); } bal = eb; } # Accumulate day's statistics: stat[dy,kind,0] += 1; stat[dy,kind,1] += val_btc; nread_day[dy]++; nused++; next; } // { data_error(("invalid format")); } END { printf "read %d records \n", nread, iniDate, finDate > "/dev/stderr"; printf "found %d records in range %s .. %s\n", nused, iniDate, finDate > "/dev/stderr"; printf "found %d dates in that range\n", ndays > "/dev/stderr"; output_stats(ndays,nread_day,stat); } function check_nat(x) { if (x !~ /^[0-9]+$/) { data_error(("invalid nat \"" x "\"")); } return sprintf("%d", x + 0); } function check_amount(x) { if (x !~ /^[-+]?[0-9]+[.][0-9]+$/) { data_error(("invalid amount \"" x "\"")); } x = x + 0.0; if (x == 0) { return "00.0000000000"; } else { return sprintf("%+.8f", x + 0.0); } } function check_day(x) { if (x !~ /^20[01][0-9]-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/) { data_error(("invalid day \"" x "\"")); } return x; } function check_time(x) { if (x !~ /^([01][0-9]|2[0-3])[:][0-5][0-9][:]([0-5][0-9]|60)$/) { data_error(("invalid time of day \"" x "\"")); } return x; } function clear_stats(nread_day,stat,dy, kind,var) { nread_day[dy] = 0; for (kind = -1; kind <= +1; kind++) for (var = 0; var <= 1; var++) { stat[dy,kind,var] = 0; } } function output_stats(ndays,nread_day,stat, dy,kind,var,days,nd,k,stat_cum,tt) { # Sort the days in order, put in {days[1..nd]}: nd = asorti(nread_day,days); if (nd != ndays) { prog_error(("day count mismatch")); } split("",stat_cum); # Cumulative totals indexed by {kind,var}. for (k = 1; k <= nd; k++) { dy = days[k]; tt = stat[dy,-1,0] + stat[dy,0,0] + stat[dy,+1,0]; if (nread_day[dy] != tt) { prog_error(("day count = " nread_day[dy] " does not match stats = " stat[dy,-1,0] " + " stat[dy,0,0] " + " stat[dy,+1,0])); } printf "%s", dy; for (kind = -1; kind <= +1; kind++) { # Accumulate days stats: for (var = 0; var <= 1; var++) { stat_cum[kind,var] += stat[dy,kind,var]; } # Print day and cumulative statistics for day {dy}: printf " %8d", stat[dy,kind,0]; printf " %+18.8f", stat[dy,kind,1]; printf " %8d", stat_cum[kind,0]; printf " %+18.8f", stat_cum[kind,1]; } printf "\n"; } } function data_error(msg) { printf "%s:%s: ** %s\n", FILENAME, FNR, msg > "/dev/stderr"; printf " %s\n", $0 > "/dev/stderr"; abort = 1; exit(abort); } function data_warning(msg) { printf "%s:%s: !! %s\n", FILENAME, FNR, msg > "/dev/stderr"; printf " %s\n", $0 > "/dev/stderr"; } function arg_error(msg) { printf "** %s\n", msg > "/dev/stderr"; abort = 1; exit(abort); } function prog_error(msg) { printf "** PROGRAM ERROR: %s\n", msg > "/dev/stderr"; abort = 1; exit(abort); }