summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Sima <ben@bsima.me>2020-12-30 15:27:31 -0500
committerBen Sima <ben@bsima.me>2020-12-30 15:27:31 -0500
commita7508431554f817d07963f1feed071dfe5fbcf28 (patch)
treec4a6d280fa9e5a6119bf4d58b2726ed1cf3a07f8
parent85f23894a6ef696af4d0f27368e757cdca887ce2 (diff)
hledger levels, graph, and networth
-rwxr-xr-xhledger-level133
-rwxr-xr-xhledger-networth4
-rwxr-xr-xhledger-overview.hs20
3 files changed, 153 insertions, 4 deletions
diff --git a/hledger-level b/hledger-level
new file mode 100755
index 0000000..4c2643b
--- /dev/null
+++ b/hledger-level
@@ -0,0 +1,133 @@
+#!/usr/bin/env bash
+set -exo pipefail
+
+query='^as ^li'
+
+function floor {
+ printf %.f $1
+}
+
+function ceil {
+ printf %.f $(bc <<< "$1+1")
+}
+
+function dec {
+ bc <<< "$1-1"
+}
+
+function inc {
+ bc <<< "$1+1"
+}
+
+function exp10 {
+ bc <<< "10^$1"
+}
+
+function log10 {
+ bc -l <<< "l($1)/l(10)"
+}
+
+function add {
+ bc -l <<< "$1 + $2"
+}
+
+# colors
+purple="#6961ff"
+green="#61ff69"
+red="#ff6961"
+
+datefmt="%Y-%m-%d"
+today=$(date +$datefmt)
+xstart=$(date -d"2 years ago" +$datefmt)
+
+# basically just calculates the log10 and removes some columns
+process_row=$(cat <<EOF
+import csv, sys, math
+from datetime import datetime
+data = sys.stdin.readlines()
+rdr = csv.reader(data)
+next(rdr, None) # skip header
+prev=None
+for row in rdr:
+ date = row[1]
+ networth = float(row[6][:-3].replace(",", "").strip())
+ if prev:
+ x1 = datetime.strptime(prev[0], "$datefmt")
+ x2 = datetime.strptime(date, "$datefmt")
+ y1 = prev[1]
+ y2 = networth
+ change = (y1-y2) / max((x1-x2).days, 1)
+ change = ((networth / prev[1]) - 1) * 100
+ else:
+ change = 1
+ level = math.log10(networth)
+ print(date)
+ print(f"{date},{level:.2f},{networth:.2f},{change}")
+ prev = (date, networth)
+EOF
+)
+
+networth=$(hledger bal $query --depth=1 -V --infer-value \
+ | tail -n1 \
+ | awk '{ print $1 }' \
+ | tr -d ','
+)
+level=$(log10 $networth)
+flevel=$(floor $level)
+
+data=$(mktemp --suffix=-data)
+hledger reg $query -V --infer-value -O csv \
+ | python -c "$process_row" \
+ > $data
+
+plot=$(mktemp --suffix=-plot)
+
+# here's the actual plotting code
+cat <<EOF > $plot
+set datafile separator ","
+
+#set term dumb
+
+set multiplot
+set size 1, 0.3
+set grid
+
+# x axis
+set xdata time
+set timefmt '$datefmt'
+set xrange ['$xstart':'$today']
+set format x '$datefmt'
+set xtics rotate by 60 offset 0,-1.5 out nomirror
+
+# Level
+set origin 0, 0.6
+set ytics 0.2
+set yrange [$(floor $(dec $level)):$(ceil $level)]
+set ylabel "Overall Level" textcolor rgb "green"
+plot '$data' using 1:2 linecolor rgb "red" smooth bezier with lines title "Level ($level)", \
+ $(inc $flevel) linecolor rgb "$purple" with lines title "Level $(inc $flevel)", \
+ $flevel linecolor rgb "$green" with lines title "Level $flevel", \
+ $(dec $flevel) linecolor rgb "$red" with lines title "Level $(dec $flevel)"
+
+
+# Net worth, within current level
+set origin 0, 0.3
+set ytics $(exp10 $level)
+set yrange [$(exp10 $(dec $flevel)):$(exp10 $(inc $flevel))]
+set ylabel "Net worth within current level" textcolor rgb "green"
+plot '$data' using 1:3 linecolor rgb "green" with filledcurves x1 title "Net Worth ($networth)", \
+ $(exp10 $(inc $flevel)) linecolor rgb "$purple" with lines title "Level $(inc $flevel)", \
+ $(exp10 $flevel) linecolor rgb "$green" with lines title "Level $flevel", \
+ $(exp10 $(dec $flevel)) linecolor rgb "$red" with lines title "Level $(dec $flevel)"
+
+# Rate of change
+set origin 0, 0
+set ytics 10
+set yrange [-100:100]
+set ylabel "Rate of change"
+plot '$data' using 1:4 linecolor rgb "green" smooth csplines with lines title "rate of change"
+
+unset multiplot
+EOF
+
+gnuplot -p $plot
diff --git a/hledger-networth b/hledger-networth
new file mode 100755
index 0000000..67c8aa8
--- /dev/null
+++ b/hledger-networth
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+#
+# pipe to tplot for a quick plot
+hledger reg ^as ^li -V | awk -F'[[:space:]][[:space:]]+' '{print $4}' | cut -d' ' -f1 | tr -d ','
diff --git a/hledger-overview.hs b/hledger-overview.hs
index 46a4f70..2d7e8e8 100755
--- a/hledger-overview.hs
+++ b/hledger-overview.hs
@@ -5,7 +5,7 @@
-- | Calculates and displays an overview of my finances.
module Main where
-import Data.Decimal (Decimal (..), DecimalRaw (..), divide, roundTo)
+import Data.Decimal (Decimal (..), DecimalRaw (..), divide, roundTo, roundTo', realFracToDecimal)
import Data.Either (fromRight)
import Data.Function ((&))
import qualified Data.List as List
@@ -46,7 +46,11 @@ main = do
row " in - ex" (prn $ bal "^in ^ex cur:USD") $ Just "keep this negative to make progress"
row "cred load" (prn netLiquid) $ Just "net liquid: credit spending minus puren cash assets. keep it positive"
row "net worth" (prn netWorth) Nothing
- row " level" (pr $ level netWorth) Nothing
+ row " level" (pr $ level netWorth) (Just $ "+" <> (prn $ netWorth - (unlevel $ roundTo' floor 1 $ level netWorth)))
+ let levelup n = level netWorth & (+n) & roundTo' floor 1 & unlevel & \target -> target - netWorth
+ row " next" (prn $ levelup 0.1) (Just $ prn $ roundTo' floor 1 $ level netWorth + 0.1)
+ row " nnext" (prn $ levelup 0.2) (Just $ prn $ roundTo' floor 1 $ level netWorth + 0.2)
+ row " nnnext" (prn $ levelup 0.3) (Just $ prn $ roundTo' floor 1 $ level netWorth + 0.3)
sec "trivials"
let trivialWorth = roundTo 2 $ trivial * netWorth
@@ -101,8 +105,16 @@ prn d = T.intercalate "." $ case T.splitOn "." $ T.pack $ show $ roundTo 2 d of
-- | There's levels to life, a proxy metric for what level you're at is your net
-- worth rounded down to the nearest power of 10.
-level :: Decimal -> Integer
-level = floor . logBase 10 . realToFrac
+level :: Decimal -> Decimal
+level = realFracToDecimal 2 . logBase 10 . realToFrac
+
+-- | Given a level, return the net worth required to achieve that level.
+unlevel :: Decimal -> Decimal
+unlevel = realFracToDecimal 2 . (10**) . realToFrac
+
+-- Shows the steps between levels
+steps = let lvls = [5.0, 5.2 .. 7.0]; ls = map (realToFrac . unlevel) lvls in zip (map realToFrac lvls) (zipWith (-) (ls++[0]) (0:ls))
+
-- | A trivial decision is one that is between 0.01% of the total.
--