From f8efa34a681cda5d6ea5f19135b151a1d876b30d Mon Sep 17 00:00:00 2001 From: ericek111 Date: Wed, 7 Apr 2021 00:04:06 +0200 Subject: [PATCH] initial commit --- echo.awk | 33 ++++++++++ hist-ord.awk | 58 +++++++++++++++++ last-price.awk | 43 ++++++++++++ list-tick.awk | 35 ++++++++++ pos.awk | 70 ++++++++++++++++++++ profit.awk | 56 ++++++++++++++++ tradelog.sh | 174 +++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 469 insertions(+) create mode 100644 echo.awk create mode 100644 hist-ord.awk create mode 100644 last-price.awk create mode 100644 list-tick.awk create mode 100644 pos.awk create mode 100644 profit.awk create mode 100755 tradelog.sh diff --git a/echo.awk b/echo.awk new file mode 100644 index 0000000..d7f529b --- /dev/null +++ b/echo.awk @@ -0,0 +1,33 @@ +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 { + +} \ No newline at end of file diff --git a/hist-ord.awk b/hist-ord.awk new file mode 100644 index 0000000..9fc5290 --- /dev/null +++ b/hist-ord.awk @@ -0,0 +1,58 @@ +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 + } +} \ No newline at end of file diff --git a/last-price.awk b/last-price.awk new file mode 100644 index 0000000..d4fdd7d --- /dev/null +++ b/last-price.awk @@ -0,0 +1,43 @@ +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] + } +} \ No newline at end of file diff --git a/list-tick.awk b/list-tick.awk new file mode 100644 index 0000000..4841596 --- /dev/null +++ b/list-tick.awk @@ -0,0 +1,35 @@ +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 + } +} \ No newline at end of file diff --git a/pos.awk b/pos.awk new file mode 100644 index 0000000..f00ab6e --- /dev/null +++ b/pos.awk @@ -0,0 +1,70 @@ +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] + } +} \ No newline at end of file diff --git a/profit.awk b/profit.awk new file mode 100644 index 0000000..02d1770 --- /dev/null +++ b/profit.awk @@ -0,0 +1,56 @@ +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 +} \ No newline at end of file diff --git a/tradelog.sh b/tradelog.sh new file mode 100755 index 0000000..3cf5f17 --- /dev/null +++ b/tradelog.sh @@ -0,0 +1,174 @@ +#!/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" + + # 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 + list-tick ) + aftercmd="sort -dk1" + ;; + pos ) + aftercmd="sort -nk3 -r" + ;; + last-price ) + aftercmd="sort -dk1" + ;; + hist-ord ) + aftercmd="sort -dk1" + ;; + esac + + "$catcmd" "$1" | awk \ + -f "$ARG_COMMAND.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" \ + - | $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 + + + + + + +