Created
April 6, 2021 21:55
-
-
Save ericek111/7b39a1b606d8391fd251a5f20c5bf7a0 to your computer and use it in GitHub Desktop.
Tradelog
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| export LC_ALL=C | |
| export POSIXLY_CORRECT=yes | |
| printUsage() { | |
| cat <<-EOF | |
| )" Usage: $(basename "$0") [-h|--help] [FILTER] [COMMAND] [LOG [LOG2 [...]] | |
| COMMAND: | |
| list-tick list all tickers mentioned in the logs | |
| profit final product of all entries (negative for a net loss) | |
| pos currently held positions ordered asc. by value | |
| last-price last | |
| hist-ord histogram of transactions grouped by tickers | |
| graph-pos graph positions for each ticker | |
| [ommited] entries are echoed according to the provided filters (if any) | |
| FILTER: | |
| -a DATETIME only records AFTER (exclusive) this date are considered | |
| -b DATETIME only records BEFORE (exclusive) this date are considered | |
| format: YYYY-MM-DD HH:MM:SS or in POSIX: %Y-%m-%d %H:%M:%S | |
| -t TICKER count records for this TICKER, can be repeated | |
| -w WIDTH sets the width (in columns) of the longest line for commands | |
| hist-ord - if ommited, one block = one transaction | |
| graph-pos - if ommited, one block = 1000 units of currency | |
| Note: When printing blocks of the graph, division products are floored | |
| (rounded towards zero). So for \`graph-pos -w 6\` with 1234 (equal | |
| to 6 blocks) as the maximum value, 1233.99 will be shown as #####. | |
| Format: | |
| DATUM A CAS;TICKER;TYP TRANSAKCE;JEDNOTKOVA CENA;MENA;OBJEM;ID | |
| Exit codes: | |
| 0 Success. | |
| 1 Invalid date provided for the -a or -b filter. | |
| 2 Duplicated parameter. | |
| EOF | |
| exit 1 | |
| } | |
| ARG_DATE_AFTER="0000-01-01 00:00:00" | |
| ARG_DATE_BEFORE="9999-12-31 23:59:59" | |
| ARG_TICKER="" | |
| ARG_WIDTH="0" | |
| ARG_COMMAND="echo" | |
| # needs spaces around all quotes | |
| ACCEPTED_COMMANDS=" list-tick profit pos last-price hist-ord graph-pos echo " | |
| # validateDateFormat (date) | |
| # Returns 0 if the provided string follows YYYY-MM-DD HH:MM:SS. | |
| # Note: This is a veeeeeeeeeeery basic check, allows e. g. "2020-12-24 15" | |
| function validateDateFormat { | |
| # accepts 60 for leap seconds | |
| awkOut="$(echo "$1" | awk -v FS='[-: ]' -v RS='\0' ' { | |
| print (match($0, /^[[:digit:]]{4,}-[[:digit:]]{2,}-[[:digit:]]{2,} [[:digit:]]{2,}:[[:digit:]]{2,}:[[:digit:]]{2,}\n?$/)) ? "good" : "bad" | |
| }')" | |
| [ "$awkOut" = "good" ]; | |
| } | |
| # processFile (file) | |
| function processFile { | |
| catcmd="cat" | |
| if [ "${1: -3}" == ".gz" ]; then | |
| catcmd="zcat" | |
| fi | |
| aftercmd="cat" | |
| awkProg="" | |
| # it could be done in awk, but so far we managed without GNU extensions, | |
| # hence we'll bite our tongue and just do this | |
| case "$ARG_COMMAND" in | |
| echo ) | |
| awkProg="$(cat <<'EOF' | |
| BEGIN { | |
| RS="\n" | |
| FS=";" | |
| split(ARG_TICKER, _tmp_tickers, ";") | |
| for (i in _tmp_tickers) { | |
| tickers[_tmp_tickers[i]] = "" | |
| hasTickers = 1 | |
| } | |
| gsub(/[-: ]/, "", ARG_DATE_AFTER); | |
| gsub(/[-: ]/, "", ARG_DATE_BEFORE); | |
| } | |
| (hasTickers == 1) && ($2 in tickers == 0) { next } | |
| { | |
| fieldDate=$1 | |
| gsub(/[-: ]/, "", fieldDate); | |
| if (ARG_DATE_AFTER != "" && ARG_DATE_AFTER > fieldDate) { next } | |
| if (ARG_DATE_BEFORE != "" && ARG_DATE_BEFORE < fieldDate) { next } | |
| } | |
| { | |
| print $0 | |
| } | |
| END { | |
| } | |
| EOF | |
| )" ;; | |
| list-tick ) | |
| aftercmd="sort -dk1" | |
| awkProg="$(cat <<'EOF' | |
| BEGIN { | |
| RS="\n" | |
| FS=";" | |
| split(ARG_TICKER, _tmp_tickers, ";") | |
| for (i in _tmp_tickers) { | |
| tickers[_tmp_tickers[i]] = "" | |
| hasTickers = 1 | |
| } | |
| gsub(/[-: ]/, "", ARG_DATE_AFTER); | |
| gsub(/[-: ]/, "", ARG_DATE_BEFORE); | |
| } | |
| (hasTickers == 1) && ($2 in tickers == 0) { next } | |
| { | |
| fieldDate=$1 | |
| gsub(/[-: ]/, "", fieldDate); | |
| if (ARG_DATE_AFTER != "" && ARG_DATE_AFTER > fieldDate) { next } | |
| if (ARG_DATE_BEFORE != "" && ARG_DATE_BEFORE < fieldDate) { next } | |
| } | |
| { | |
| if ($2 in unique_tickers == 0) { | |
| unique_tickers[$2] = "" | |
| } | |
| } | |
| END { | |
| for (i in unique_tickers) { | |
| print i | |
| } | |
| } | |
| EOF | |
| )" ;; | |
| profit ) | |
| awkProg="$(cat <<'EOF' | |
| function sumItUp(units, cpu) { | |
| # avoid floating point arithmetic AT ALL COST! | |
| partsLen = split(cpu, parts, ".") | |
| if (partsLen == 1) { | |
| cost = parts[1] * 100 | |
| return cost * units | |
| } | |
| dec = parts[2] | |
| dec = substr(dec, 0, 2) | |
| for (i = length(dec); i < 2; i++) { | |
| dec = dec"0" | |
| } | |
| cost = parts[1] * 100 + dec | |
| return cost * units | |
| } | |
| BEGIN { | |
| RS="\n" | |
| FS=";" | |
| split(ARG_TICKER, _tmp_tickers, ";") | |
| for (i in _tmp_tickers) { | |
| tickers[_tmp_tickers[i]] = "" | |
| hasTickers = 1 | |
| } | |
| gsub(/[-: ]/, "", ARG_DATE_AFTER); | |
| gsub(/[-: ]/, "", ARG_DATE_BEFORE); | |
| profit = 0 | |
| } | |
| (hasTickers == 1) && ($2 in tickers == 0) { next } | |
| { | |
| fieldDate=$1 | |
| gsub(/[-: ]/, "", fieldDate); | |
| if (ARG_DATE_AFTER != "" && ARG_DATE_AFTER > fieldDate) { next } | |
| if (ARG_DATE_BEFORE != "" && ARG_DATE_BEFORE < fieldDate) { next } | |
| } | |
| { | |
| if ($3 == "buy") { | |
| profit -= sumItUp($6, $4) | |
| } else if ($3 == "sell") { | |
| profit += sumItUp($6, $4) | |
| } | |
| } | |
| END { | |
| # taking the lazy way here, not cool! | |
| profit /= 100 | |
| printf "%.2f\n", profit | |
| } | |
| EOF | |
| )" ;; | |
| pos ) | |
| awkProg="$(cat <<'EOF' | |
| function sumItUp(units, cpu) { | |
| # avoid floating point arithmetic AT ALL COST! | |
| partsLen = split(cpu, parts, ".") | |
| if (partsLen == 1) { | |
| cost = parts[1] * 100 | |
| return cost * units | |
| } | |
| dec = parts[2] | |
| dec = substr(dec, 0, 2) | |
| for (i = length(dec); i < 2; i++) { | |
| dec = dec"0" | |
| } | |
| cost = parts[1] * 100 + dec | |
| return cost * units | |
| } | |
| BEGIN { | |
| RS="\n" | |
| FS=";" | |
| split(ARG_TICKER, _tmp_tickers, ";") | |
| for (i in _tmp_tickers) { | |
| tickers[_tmp_tickers[i]] = "" | |
| hasTickers = 1 | |
| } | |
| gsub(/[-: ]/, "", ARG_DATE_AFTER); | |
| gsub(/[-: ]/, "", ARG_DATE_BEFORE); | |
| } | |
| (hasTickers == 1) && ($2 in tickers == 0) { next } | |
| { | |
| fieldDate=$1 | |
| gsub(/[-: ]/, "", fieldDate); | |
| if (ARG_DATE_AFTER != "" && ARG_DATE_AFTER > fieldDate) { next } | |
| if (ARG_DATE_BEFORE != "" && ARG_DATE_BEFORE < fieldDate) { next } | |
| } | |
| { | |
| if ($3 == "buy") { | |
| profit[$2] += $6 | |
| } else if ($3 == "sell") { | |
| profit[$2] -= $6 | |
| } | |
| lastPrice[$2] = $4 | |
| } | |
| END { | |
| widestNum = 0 | |
| for (tic in profit) { | |
| profit[tic] = sumItUp(profit[tic], lastPrice[tic]) | |
| profit[tic] /= 100 | |
| str = sprintf("%.2f", profit[tic]) | |
| strLen = length(str) | |
| if (strLen > widestNum) { | |
| widestNum = strLen | |
| } | |
| } | |
| for (tic in profit) { | |
| printf "%-9s : %"widestNum".2f\n", tic, profit[tic] | |
| } | |
| } | |
| EOF | |
| )" aftercmd="sort -nk3 -r" | |
| ;; | |
| last-price ) | |
| awkProg="$(cat <<'EOF' | |
| BEGIN { | |
| RS="\n" | |
| FS=";" | |
| split(ARG_TICKER, _tmp_tickers, ";") | |
| for (i in _tmp_tickers) { | |
| tickers[_tmp_tickers[i]] = "" | |
| hasTickers = 1 | |
| } | |
| gsub(/[-: ]/, "", ARG_DATE_AFTER); | |
| gsub(/[-: ]/, "", ARG_DATE_BEFORE); | |
| } | |
| (hasTickers == 1) && ($2 in tickers == 0) { next } | |
| { | |
| fieldDate=$1 | |
| gsub(/[-: ]/, "", fieldDate); | |
| if (ARG_DATE_AFTER != "" && ARG_DATE_AFTER > fieldDate) { next } | |
| if (ARG_DATE_BEFORE != "" && ARG_DATE_BEFORE < fieldDate) { next } | |
| } | |
| { | |
| lastPrice[$2] = $4 | |
| } | |
| END { | |
| widestNum = 0 | |
| for (tic in lastPrice) { | |
| str = sprintf("%.2f", lastPrice[tic]) | |
| strLen = length(str) | |
| if (strLen > widestNum) { | |
| widestNum = strLen | |
| } | |
| } | |
| for (tic in lastPrice) { | |
| printf "%-9s : %"widestNum".2f\n", tic, lastPrice[tic] | |
| } | |
| } | |
| EOF | |
| )" aftercmd="sort -dk1" | |
| ;; | |
| hist-ord ) | |
| awkProg="$(cat <<'EOF' | |
| BEGIN { | |
| RS="\n" | |
| FS=";" | |
| split(ARG_TICKER, _tmp_tickers, ";") | |
| for (i in _tmp_tickers) { | |
| tickers[_tmp_tickers[i]] = "" | |
| hasTickers = 1 | |
| } | |
| gsub(/[-: ]/, "", ARG_DATE_AFTER); | |
| gsub(/[-: ]/, "", ARG_DATE_BEFORE); | |
| } | |
| (hasTickers == 1) && ($2 in tickers == 0) { next } | |
| { | |
| fieldDate=$1 | |
| gsub(/[-: ]/, "", fieldDate); | |
| if (ARG_DATE_AFTER != "" && ARG_DATE_AFTER > fieldDate) { next } | |
| if (ARG_DATE_BEFORE != "" && ARG_DATE_BEFORE < fieldDate) { next } | |
| } | |
| { | |
| totalTrans[$2]++ | |
| } | |
| END { | |
| mostTrans = 0 | |
| for (tic in totalTrans) { | |
| if (mostTrans < totalTrans[tic]) { | |
| mostTrans = totalTrans[tic] | |
| } | |
| } | |
| if (ARG_WIDTH < 1) { | |
| ARG_WIDTH = 1 | |
| } | |
| for (tic in totalTrans) { | |
| if (ARG_WIDTH == 1) { | |
| cols = totalTrans[tic] | |
| } else { | |
| cols = (totalTrans[tic] / mostTrans) * ARG_WIDTH | |
| } | |
| row = "" | |
| for (i = 1; i <= cols; i++) { | |
| row = row"#" | |
| } | |
| printf "%-9s : %s\n", tic, row | |
| } | |
| } | |
| EOF | |
| )" aftercmd="sort -dk1" | |
| ;; | |
| esac | |
| # dis betr | |
| # -f "$ARG_COMMAND.awk" \ | |
| "$catcmd" "$1" | awk \ | |
| -v ARG_TICKER="$ARG_TICKER" \ | |
| -v ARG_DATE_AFTER="$ARG_DATE_AFTER" \ | |
| -v ARG_DATE_BEFORE="$ARG_DATE_BEFORE" \ | |
| -v ARG_WIDTH="$ARG_WIDTH" \ | |
| "$awkProg" \ | |
| - | $aftercmd | |
| # Note: Oh wouldn't it be great to use the @include keyword | |
| # from GNU awk and avoid all that duplicated code. Oh well. | |
| } | |
| # Transform long options into short ones | |
| for arg in "$@"; do | |
| shift | |
| case "$arg" in | |
| "--help") set -- "$@" "-h" ;; | |
| *) set -- "$@" "$arg" | |
| esac | |
| done | |
| _hasSpecifiedWidth=0 | |
| if [ ! $1 ]; then | |
| cat - | |
| exit 0 | |
| fi | |
| while getopts "ha:b:t:w:" o; do | |
| case "$o" in | |
| a) | |
| ARG_DATE_AFTER="$OPTARG" | |
| if ! validateDateFormat "$OPTARG"; then | |
| echo "Invalid after date." 1>&2 | |
| exit 1 | |
| fi | |
| ;; | |
| b) | |
| ARG_DATE_BEFORE="$OPTARG" | |
| if ! validateDateFormat "$OPTARG"; then | |
| echo "Invalid before date." 1>&2 | |
| exit 1 | |
| fi | |
| p="$OPTARG" | |
| ;; | |
| t) | |
| ARG_TICKER="$ARG_TICKER;$OPTARG" | |
| # if [ ! "$OPTARG" ]; then echo "Missing argument for -t" ; exit 2 ; fi | |
| ;; | |
| w) | |
| ARG_WIDTH="$OPTARG" | |
| if [ $_hasSpecifiedWidth = "1" ]; then | |
| echo "Width specified twice!" 1>&2 | |
| exit 2 | |
| fi | |
| _hasSpecifiedWidth=1 | |
| ;; | |
| h) | |
| printUsage | |
| ;; | |
| *) | |
| ;; | |
| esac | |
| done | |
| shift "$((OPTIND-1))" | |
| # otherwise fails with "binary operator expected" ?!?!?! | |
| _tmpArgs="$@" | |
| if [ -z "$_tmpArgs" ]; then | |
| processFile - | |
| fi | |
| for arg in "$@"; do | |
| if [ -z "${ACCEPTED_COMMANDS##* $arg *}" ]; then | |
| ARG_COMMAND="$arg" | |
| shift | |
| fi | |
| done | |
| for arg in "$@"; do | |
| processFile "$arg" | |
| done | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment