summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xhledger-allocation21
-rwxr-xr-xhledger-date6
-rwxr-xr-xhledger-fadd70
-rwxr-xr-xhledger-level134
-rwxr-xr-xhledger-networth7
-rwxr-xr-xhledger-overview.hs362
6 files changed, 0 insertions, 600 deletions
diff --git a/hledger-allocation b/hledger-allocation
deleted file mode 100755
index 077e1b8..0000000
--- a/hledger-allocation
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env bash
-
-function cleanup() {
- tail -n1 $1 | tr -d [:alpha:] | tr -d [:blank:] | tr -d [=,=]
-}
-
-networth=$(hledger bal ^as ^li --value=now,USD --auto --output-format=csv \
- | tail -n1 \
- | tr -d [totalUSD,\"])
-
-#commodities=$(hledger commodities)
-commodities=("BTC|sat|SAT|GBTC" GROQ USD ETH BCH LTC XLM)
-
-printf "networth: %.2f USD\n\n" $networth
-
-for com in ${commodities[@]}
-do
- n=$(hledger bal ^as ^li cur:"$com" --value=now,USD | cleanup)
- pct=$(bc -l <<< "$n/$networth*100")
- printf "%s: %.1f%%\n" $com $pct
-done
diff --git a/hledger-date b/hledger-date
deleted file mode 100755
index 1f524d4..0000000
--- a/hledger-date
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/usr/bin/env bash
-for n in $(seq 0 31); do date -d"$n days ago" "+%Y/%m/%d (%a)"; done \
- | fzf --height 50% --reverse --delimiter '/' \
- --preview="gcal -H yes %{1}{2}{3} && hledger --color=yes reg date:\$(cut -f1 -d' ' <<< {1}-{2}-{3})" \
- --preview-window 'noborder,right,80%' \
- --header-first --header "Today is $(date '+%Y/%m/%d (%a)')"
diff --git a/hledger-fadd b/hledger-fadd
deleted file mode 100755
index 36e762a..0000000
--- a/hledger-fadd
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/usr/bin/env bash
-
-function fz {
- readarray -t lines < <(fzf \
- --height 50% --reverse --expect alt-enter --print-query "$@" \
- --bind "pgup:preview-page-up" --bind "pgdn:preview-page-down"
- )
- query=${lines[0]}
- enter=${lines[1]}
- match=${lines[2]}
- if [[ "$enter" == "alt-enter" ]]
- then
- echo "$query"
- else
- echo "$match"
- fi
-}
-
-accounts=$(cache hledger accounts)
-payees=$(cache hledger payees)
-
-date=$(for n in $(seq 0 31); do date -d"$n days ago" "+%Y/%m/%d (%a)"; done \
- | fzf --height 50% --reverse --delimiter '/' \
- --preview="gcal -H yes %{1}{2}{3}" \
- --preview-window 'noborder,top,8' \
- --header-first --header "Today is $(date '+%Y/%m/%d (%a)')" \
- | cut -d ' ' -f1
-)
-echo "date: $date"
-
-payee=$(fz --prompt="payee: " --preview='hledger print payee:{..}' <<< $payees)
-echo "payee: $payee"
-
-# store past txs in a file, in the background, so I don't have to wait for the
-# command to re-run each time
-past_txs=$(mktemp)
-hledger print payee:"$payee" > $past_txs &
-
-read -rep "to amount: " to_amount
-to_account=$(fz --prompt="to account: " --preview="cat $past_txs" <<< $accounts)
-echo "to account: $to_account"
-
-
-from_account=$(fz --prompt="from account: " --preview="cat $past_txs" <<< $accounts)
-echo "from account: $from_account"
-read -rep "from amount: " -i "-$to_amount" from_amount
-
-
-read -r -d '' TX <<EOF
-$date * $payee
- $to_account $to_amount USD
- $from_account $from_amount USD
-EOF
-
-tmp=$(mktemp)
-printf "\n%s\n" "$TX" >> "$tmp"
-cat "$tmp"
-
-read -rep "all good? Y/e/n " ok
-case $ok in
- n)
- exit 1
- ;;
- e)
- vim "$tmp" && cat "$tmp" >> ~/org/fund/ledger.journal
- ;;
- *)
- cat "$tmp" >> ~/org/fund/ledger.journal
- ;;
-esac
diff --git a/hledger-level b/hledger-level
deleted file mode 100755
index 98dd09c..0000000
--- a/hledger-level
+++ /dev/null
@@ -1,134 +0,0 @@
-#!/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 --value=now --infer-value \
- | tail -n1 \
- | awk '{ print $1 }' \
- | tr -d ','
-)
-level=$(log10 $networth)
-flevel=$(floor $level)
-
-data=$(mktemp --suffix=-data)
-hledger reg $query --value=end,USD --infer-value -O csv \
- | python3 -c "$process_row" \
- > $data
-
-plot=$(mktemp --suffix=-plot)
-
-# here's the actual plotting code
-cat <<EOF > $plot
-set datafile separator ","
-
-set terminal png size 2560,1920
-#set term dumb
-
-set multiplot
-set size 1, 0.5
-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.5
-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.0
-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
deleted file mode 100755
index 3eabcc2..0000000
--- a/hledger-networth
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/usr/bin/env bash
-#
-# pipe to tplot for a quick plot
-hledger reg ^as ^li -V -XUSD \
- | awk -F'[[:space:]][[:space:]]+' '{print $4}' \
- | cut -d' ' -f1 \
- | tr -d ','
diff --git a/hledger-overview.hs b/hledger-overview.hs
deleted file mode 100755
index 0c4bad7..0000000
--- a/hledger-overview.hs
+++ /dev/null
@@ -1,362 +0,0 @@
-#!/usr/bin/env runhaskell
-
-{-# LANGUAGE FlexibleInstances #-}
-{-# LANGUAGE LambdaCase #-}
-{-# LANGUAGE NumericUnderscores #-}
-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE TypeSynonymInstances #-}
-
--- | Calculates and displays an overview of my finances.
---
--- TODO: bip metrics
--- - calculate how many bips my monthly nut is, should be <100
--- - put runway in terms of bips? what else can I calculate in bips?
--- - add --bips flag for all conversions?
-module Main where
-
-import Data.Decimal (Decimal (..), DecimalRaw (..), divide, realFracToDecimal, roundTo, roundTo')
-import Data.Either (fromRight)
-import Data.Function ((&))
-import qualified Data.List as List
-import qualified Data.Map as Map
-import Data.Maybe (fromJust, fromMaybe)
-import Data.Text (Text, pack)
-import qualified Data.Text as T
-import qualified Data.Text.IO as IO
-import Data.Time.Calendar (Day, fromGregorian, toGregorian)
-import Data.Time.Clock (UTCTime (..), diffTimeToPicoseconds, diffUTCTime, getCurrentTime)
-import Hledger
-import Hledger.Data.Types
-import Rainbow
-import System.Environment (getArgs)
-import qualified System.Process as Process
-
-today :: IO Day
-today = getCurrentTime >>= return . utctDay
-
--- | My impl of 'defaultJournal' with auto postings enabled
-getJournal :: IO Journal
-getJournal = defaultJournalPath >>= readJournalFile inputopts >>= either error' return
- where inputopts = definputopts { auto_ = True }
-
--- | For running stuff in ghci
-run :: (Journal -> Day -> a) -> IO a
-run f = do
- j <- getJournal
- t <- today
- return $ f j t
-
-janj :: CommoditySymbol -> Maybe ValuationType
-janj = Just . AtNow . Just
-
-main = do
- let banner txt = Process.callProcess "figlet" ["-f", "small", txt ]
- -- cur :: a -> Tagged a
- (cur, value_) <-
- getArgs
- >>= \case
- ["sat"] -> banner "sats" >> pure (SAT, janj "sat")
- ["sats"] -> banner "sats" >> pure (SAT, janj "sat")
- ["btc"] -> banner "bitcoin" >> pure (BTC, janj "BTC")
- _ -> banner "fiat" >> pure (USD, janj "USD")
-
- let reportopts = defreportopts {value_ = value_}
- j <- getJournal
- t <- today
- let bal = getTotal j t reportopts
- sec "balances"
- row " cashapp" (cur $ bal "^as:me:cash:cashapp status:! status:*") Nothing
- row " wallet" (cur $ bal "^as:me:cash:wallet") Nothing
- row " cse" (cur $ bal "^as:me:cash:cse status:*") Nothing
- row " disc" (cur $ bal "^li:me:cred:discover status:*") Nothing
- row " citi" (cur $ bal "^li:me:cred:citi status:*") Nothing
-
- sec "allowances"
- row " ben" (cur $ bal "^li:me:allowance:ben cur:USD") Nothing
- row " kate" (cur $ bal "^li:me:allowance:kate cur:USD") Nothing
- row " tithe" (cur $ bal "^li:me:church:tithe cur:USD") Nothing
- row "nick rent" (cur $ bal "^li:me:rent:nick cur:USD") Nothing
-
- let (year, month, _) = toGregorian t
- let expectedLevel = fromJust $ Map.lookup (roundTo 2 $ (fromIntegral year + fromIntegral month / 12) - (1992 + 7 / 12)) $ levelSchedule cur
- let expectedNetWorth = unlevel expectedLevel
- let monthlyNut = nut t $ bal "^ex:me:want ^ex:..:need"
- let thisMonth = bal "^ex:me:want ^ex:..:need date:thismonth"
-
- sec "metrics"
- let balCategorize = bal "^ex:us:to-categorize"
- row " attn" (Target 0 balCategorize) $ Just $ "todo: count number of to-categorize"
- -- row " in-ex" (Limit 0 $ bal "^in ^ex:me:want ^ex:..:need" / monthsSinceBeginning t) $ Just "keep this negative to make progress"
- row " li:as" (Percent_ $ 100 * (- bal "^li") / bal "^as") Nothing
- -- let lastyear = 2020
- -- row " in:net" (- getTotal j t (defreportopts {value_ = value_, period_ = YearPeriod 2020}) "^in:me") Nothing
- -- net cash is limited to USD because that is what I can effectively spend
- let netCash = bal "^as:me:cash ^li:me:cred cur:USD"
- row " net cash" (Target 0 netCash) $ Just "credit spending minus cash. keep it positive"
- row "month nut" (Limit monthlyNut thisMonth) $ Just $ "avg: " <> (display $ Diff $ monthlyNut - thisMonth)
- let netWorth = bal "^as ^li"
- row " bip" (roundTo 2 $ trivial * netWorth) Nothing
- -- ideally: ramen 12 mo, runway 4 yrs.
- let (_, _, runwayMo) = runway j t reportopts
- row " runway" (Target 12 runwayMo) $ Just "want: 12 months"
- let (ramenNut, _, ramenMo) = ramen j t reportopts
- row " ramen" (Target 3 ramenMo) $ Just $ "want: 3 months" <> gap <> "nut: " <> display ramenNut
- let (thisyear, thismonth, _) = toGregorian t
- let age = fromInteger thisyear - 1992 + fromIntegral (thismonth-7)/12 -- offset for birthmonth
- let n = whenFreedom j t reportopts
- let ageFree = roundTo 1 $ (n / 12) + age :: Decimal
- row "fire rate" (Percent_ $ savingsRate j t reportopts) Nothing
- let fireFund = targetFund j t reportopts
- row "fire fund" fireFund $ Just $ "plan: "
- <> (display $ fromJust $ Map.lookup (level fireFund) $ levelScheduleRev cur)
- row "when free" (Months_ n) $ Just $ "I'll be " <> pr ageFree <> " years old"
-
- sec $ "plan [" <> show (roundTo 2 $ age) <> "]"
- row "net worth" (Target expectedNetWorth $ netWorth) $ Just $ "plan: " <> display (cur expectedNetWorth)
- row " level" (Target expectedLevel $ level netWorth)
- $ Just $ "plan: " <> display expectedLevel
- let maturity = fromJust $ Map.lookup (level netWorth) $ levelScheduleRev cur
- row " maturity" (Target age maturity) $ Just $ "diff: " <> (display $ Diff (maturity - age))
- let levelup n = level netWorth & (+ n) & roundTo' floor 1 & unlevel & \target -> target - netWorth
- let nextLevel incr = let n = (roundTo' floor 1 $ level netWorth + incr)
- in display n <> ", age: " <> display (fromJust $ Map.lookup n $ levelScheduleRev cur)
- row " next" (levelup 0.1) $ Just $ nextLevel 0.1
- row " nnext" (levelup 0.2) $ Just $ nextLevel 0.2
- row " nnnext" (levelup 0.3) $ Just $ nextLevel 0.3
- let satbal = getTotal j t (defreportopts {value_ = janj "sat"}) "^as cur:'BTC|sat|sats'"
- row "real sats" (Target 450000000 satbal) Nothing
-
--- | TODO: extend this to 'Tagged', not just 'Quantity'.
-data Metric
- = Target { expected :: Quantity, actual :: Quantity }
- -- ^ actual should not be less than expected
- | Limit { expected :: Quantity, actual :: Quantity }
- -- ^ actual should not be more than expected
-
--- | Tag displayable things so I can change how things print, e.g. add a percent
--- sign, or some coloring, etc.
-class Display a where
- display :: a -> Chunk
-
-instance Display Chunk where
- display c = c
-
--- green = achieved
--- yellow = within 5% of achieving
--- red = work to do
-instance Display Metric where
- display = \case
- Target expected actual -> color actual (>=) expected $ display actual
- Limit expected actual -> color actual (<=) expected $ display actual
- where
- color :: Quantity -> (Quantity -> Quantity -> Bool) -> Quantity -> Chunk -> Chunk
- color actual cmp expected
- | actual `cmp` expected = fore green
- | actual `cmp` (expected * 0.95) = fore yellow
- | otherwise = fore red
-
--- | Tag numbers for different kinds of displays
-data Tagged a
- = Months_ a
- | Percent_ a
- | Diff a
- | USD a
- | SAT a
- | BTC a
- | Months a
-
-instance (Num a, Ord a, Display a) => Display (Tagged a) where
- display (USD q) = display q <> chunk " USD"
- display (SAT q) = display q <> chunk " sat"
- display (BTC q) = display q <> chunk " BTC"
- display (Months_ q) = display q <> " months"
- display (Percent_ p) = display p <> "%"
- display (Diff n)
- | n > 0 = "+" <> display n
- | n < 0 = display n
- | n == 0 = "=="
-
-instance Display Text where
- display t = chunk t
-
--- | Pretty-print a number. From https://stackoverflow.com/a/61070523/1146898
-instance Display Quantity where
- display d = chunk $
- T.intercalate "." $ case T.splitOn "." $ T.pack $ show $ roundTo 2 d of
- x : xs -> (T.pack . reverse . go . reverse . T.unpack) x : xs
- xs -> xs
- where
- go (x : y : z : []) = x : y : z : []
- go (x : y : z : ['-']) = x : y : z : ['-']
- go (x : y : z : xs) = x : y : z : ',' : go xs
- go xs = xs
-
-instance Display Int where
- display d = chunk $ T.pack $ show d
-
-sec :: String -> IO ()
-sec label = putStrLn $ "\n=== " <> label <> " ==="
-
-pr :: Show s => s -> Chunk
-pr = chunk . pack . show
-
-row :: (Display a) => Chunk -> a -> Maybe Chunk -> IO ()
-row label value note =
- putChunkLn $ gap <> label <> ":" <> gap <> display value <> " " <> rest
- where
- rest = case note of
- Nothing -> ""
- Just a -> gap <> "\t(" <> a <> ")"
-
-gap :: Chunk
-gap = " "
-
--- | 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 -> 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
-
-levelAtAge cur age = fromJust $ Map.lookup (roundTo 2 age) $ levelSchedule cur
-
-unlevelAtAge cur = unlevel . levelAtAge cur
-
--- Shows the steps between levels
-steps start = zip (map realToFrac lvls) (zipWith (-) (ls ++ [0]) (0 : ls))
- where
- lvls = [start, start + 0.01 .. 8.0]
- ls = map (realToFrac . unlevel) lvls
-
--- | Map of level to age.
-levelScheduleRev :: (Quantity -> Tagged Decimal) -> Map.Map Decimal Decimal
-levelScheduleRev cur = Map.fromList $ zip lvls ages
- where (ages, lvls) = levelSchedule' cur
-
--- | Map of age to level. Age is year + month as a decimal.
-levelSchedule :: (Quantity -> Tagged Decimal) -> Map.Map Decimal Decimal
-levelSchedule cur = Map.fromList $ zip ages lvls
- where (ages, lvls) = levelSchedule' cur
-
--- | Helper for above fns
-levelSchedule' :: (Quantity -> Tagged Decimal) -> ([Decimal], [Decimal])
-levelSchedule' cur = (ages++[70], lvls++[goal])
- where
- ultimateGoal = cur 1_000_000_000 -- thats a billion usd
- (start, goal) = case ultimateGoal of
- USD n -> (5.0, level n)
- -- normalize btc/sat levels to comparative usd levels, given exchange rate
- -- assumptions below
- BTC n -> (level $ usdToBtc $ unlevel 5, level $ usdToBtc n)
- SAT n -> (level $ usdToSat $ unlevel 5, level $ usdToSat n)
- step = (goal - start) / 600
- ages = map (roundTo 2) [20, 20 + 1 / 12 .. 70]
- lvls = map (roundTo 2) [start, start + step .. goal]
-
--- This is a bit speculative, but I'm assuming the value of a bitcoin is
--- 100,000 USD. Eventually I should change this to actually use the USD/BTC
--- exchange rate, but it changes so much that it would be hard to have a
--- concrete plan when dealing in BTC/sats. So I figure 100k is a good price
--- target for the next year or so.
-usdBtcExchangeRate = 100_000
-usdToBtc usd = usd / usdBtcExchangeRate
-btcToUsd btc = btc * usdBtcExchangeRate
-usdToSat usd = btcToSat $ usdToBtc usd
-
--- These are constants, should never change.
-btcToSat btc = btc * 100_000_000
-satToBtc sat = sat / 100_000_000
-
--- | A trivial decision is one that is 0.01% of the total, or 1 basis point.
--- From <https://ofdollarsanddata.com/climbing-the-wealth-ladder/>.
-trivial :: Quantity
-trivial = 0.0001
-
-getTotal :: Journal -> Day -> ReportOpts -> String -> Quantity
-getTotal j t o q = sum . map aquantity $ getTotalAmounts j t o q
-
-getTotalAmounts :: Journal -> Day -> ReportOpts -> String -> [Amount]
-getTotalAmounts j d opts q = Map.elems totals
- where
- (_, (Mixed totals)) = balanceReport (ReportSpec opts d query []) j
- Right (query, _) = parseQuery d $ pack q
-
--- | These are the accounts that I consider a part of my savings and not my
--- cash-spending accounts.
-savingsAccounts :: [String]
-savingsAccounts =
- ["as:me:save", "as:me:vest"]
-
--- | Savings rate is a FIRE staple: (Income - Expenses) / Income * 100
-savingsRate :: Journal -> Day -> ReportOpts -> Quantity
-savingsRate j d opts = roundTo 2 $ 100 * (income - expenses) / income
- where
- savings = getTotal j d opts query
- query = List.intercalate " " $ savingsAccounts
- income = - getTotal j d opts "^in"
- expenses = getTotal j d opts "^ex:me:want ^ex:..:need"
-
--- | The target fund is simply 25x your annual expenditure.
---
--- Assumptions: 4% withdrawal rate, 3-5% return on investments.
-targetFund :: Journal -> Day -> ReportOpts -> Quantity
-targetFund j d opts = 25 * yearlyExpenses
- where
- yearlyExpenses = expenses / yearsSinceBeginning d
- expenses = sum $ map aquantity $ Map.elems total
- Right (query, _) = parseQuery d $ pack "^ex:me:want ^ex:..:need"
- (_, (Mixed total)) = balanceReport (ReportSpec opts d query []) j
-
--- | I have expense data going back to 2019.10. Use this for calculating
--- averages per month.
-monthsSinceBeginning :: Day -> Quantity
-monthsSinceBeginning d =
- diffUTCTime (UTCTime d 0) start
- & secondsToMonths
- & mkDecimal
- where
- mkDecimal n = fromRational $ toRational n :: Decimal
- secondsToMonths s = s / 60 / 60 / 24 / 7 / 4
- start = UTCTime (fromGregorian 2019 10 1) 0
-
-yearsSinceBeginning :: Day -> Quantity
-yearsSinceBeginning d = monthsSinceBeginning d / 12
-
--- | How long until I can live off of my savings and investment returns?
---
--- Return integer is number of months until I'm free.
-whenFreedom :: Journal -> Day -> ReportOpts -> Quantity
-whenFreedom j d opts = roundTo 1 $ targetFund j d opts / monthlySavings j d opts
-
--- | This is the current value of my savings accounts, divided by the months
--- since starting. Because this takes the /current/ value of the accounts, the
--- capital gains are already priced in, so as long as my investments continue to
--- return at a similar rate, then this number is accurate.
-monthlySavings :: Journal -> Day -> ReportOpts -> Quantity
-monthlySavings j d opts =
- savingsAccounts
- & map (getTotal j d opts)
- & sum
- & \n -> (n / monthsSinceBeginning d)
-
--- | How many months I could sustain myself with my cash and savings, given my
--- current expenses.
-runway :: Journal -> Day -> ReportOpts -> (Quantity, Quantity, Quantity)
-runway j d opts = (nut d total, cash, cash / nut d total)
- where
- total = getTotal j d opts "^ex:..:need ^ex:me:want"
- cash = getTotal j d opts "^as:me:save ^as:me:cash ^li:me:cred"
-
--- | Ramen profitability. Like 'runway', except let's say I live on /only/ the
--- necessities, and don't spend my bitcoin. So cash flow in my checking account
--- is primary.
-ramen :: Journal -> Day -> ReportOpts -> (Quantity, Quantity, Quantity)
-ramen j d opts = (nut d total, cash, cash / nut d total)
- where
- total = getTotal j d opts "^ex:..:need"
- cash = getTotal j d opts "^as:me:cash ^li:me:cred"
-
-nut :: Day -> Quantity -> Quantity
-nut d total = total / monthsSinceBeginning d