tradelog/tradelog.sh
2021-04-07 00:04:06 +02:00

175 lines
4.1 KiB
Bash
Executable File

#!/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