From 421e86e7ce32e63f71ea0dd91eaab174d3283bed Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Fri, 5 Feb 2021 11:38:50 -0500 Subject: hledger-overview fixing calculations Turns out my monthsSinceBeginning was wrong, and this was throwing off my runway and fire freedom. Bad news is my runway is shorter, good news is my time until freedom is also shorter. --- hledger-overview.hs | 68 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 28 deletions(-) diff --git a/hledger-overview.hs b/hledger-overview.hs index 701492a..1cb8c93 100755 --- a/hledger-overview.hs +++ b/hledger-overview.hs @@ -12,8 +12,8 @@ import qualified Data.List as List import Data.Text (Text, pack) import qualified Data.Text as T import qualified Data.Text.IO as IO -import Data.Time.Calendar (Day, toGregorian) -import Data.Time.Clock (UTCTime (utctDay), getCurrentTime) +import Data.Time.Calendar (Day, toGregorian, fromGregorian) +import Data.Time.Clock (UTCTime (..), getCurrentTime, diffUTCTime, diffTimeToPicoseconds) import Hledger today :: IO Day @@ -41,11 +41,15 @@ main = do let btcBalUSD q = sum . map aquantity $ getTotalAmounts j t (defreportopts {value_ = inUsdNow}) q row " btc" (prn $ btcBal "^as cur:BTC") (Just $ prn $ btcBalUSD "^as cur:BTC") + -- TODO: these metrics should have targets. if >target, print red, else print + -- green. Or limit: if (prn $ netWorth - (unlevel $ roundTo' floor 1 $ level netWorth))) let levelup n = level netWorth & (+n) & roundTo' floor 1 & unlevel & \target -> target - netWorth @@ -152,18 +156,19 @@ savingsAccounts :: [String] savingsAccounts = ["as:me:save", "as:me:vest"] --- | Savings rate is a FIRE staple. Basically take your savings and divide it by --- your income on a monthly basis. --- --- I think this is wronge because I need to take the monthly ammounts, but this --- gives total amounts +-- | Savings rate is a FIRE staple: (Income - Expenses) / Income * 100 savingsRate :: Journal -> Day -> Quantity -savingsRate j d = roundTo 2 $ allSavings / allIncome +savingsRate j d = roundTo 2 $ 100 * (income - expenses) / income + -- I used to do just savings/income, but this is wrong because it also + -- includes capital gains, which are not technically part of the savings rate. + --roundTo 2 $ savings / income where - allSavings = getTotal j d (defreportopts {value_ = inUsdNow}) query + opts = defreportopts {value_ = inUsdNow} + savings = getTotal j d opts query query = List.intercalate " " $ savingsAccounts -- gotta flip the sign because income is negative - allIncome = - getTotal j d (defreportopts {value_ = inUsdNow}) "^in" + income = - getTotal j d opts "^in" + expenses = getTotal j d opts "^ex" -- | The target fund is simply 25x your annual expenditure. -- @@ -175,43 +180,50 @@ savingsRate j d = roundTo 2 $ allSavings / allIncome targetFund :: Journal -> Day -> Quantity targetFund j d = 25 * yearlyExpenses where - yearlyExpenses = sum $ map aquantity $ total + yearlyExpenses = expenses / yearsSinceBeginning d + expenses = sum $ map aquantity $ total Right (query, _) = parseQuery d $ pack "^ex" (_, (Mixed total)) = balanceReport opts query j opts = defreportopts - { -- idk what the '2020 4' is for, but this actually results in the yearly - -- report for some reason - period_ = QuarterPeriod 2020 4, - value_ = Just $ AtNow $ Just "USD", + { value_ = inUsdNow, today_ = Just d } --- | I have data going back to 2018.12. Use this for calculating averages per --- month. +-- | I have expense data going back to 2019.10. Use this for calculating +-- averages per month. monthsSinceBeginning :: Day -> Quantity -monthsSinceBeginning d = fromInteger $ (year - 2019) * 12 + toInteger month + 1 +monthsSinceBeginning d = + diffUTCTime (UTCTime d 0) start + & secondsToMonths + & mkDecimal where - (year, month, _) = toGregorian d + 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 -> Quantity -whenFreedom j d = roundTo 1 $ targetFund j d / monthlySavings - where - monthlySavings = - savingsAccounts - & map (getTotal j d (defreportopts {value_ = inUsdNow, period_ = MonthPeriod 2020 10})) - & sum - & \n -> (n / monthsSinceBeginning d) +whenFreedom j d = roundTo 1 $ targetFund j d / monthlySavings j d + +monthlySavings :: Journal -> Day -> Quantity +monthlySavings j d = + savingsAccounts + & map (getTotal j d (defreportopts {value_ = inUsdNow})) + & sum + & \n -> (n / monthsSinceBeginning d) -- | How many months I could sustain myself with my cash and savings, given my -- current expenses. runway :: Journal -> Day -> (Quantity, Quantity, Quantity) runway j d = (nut, cash, cash / nut) where - nut = (sum $ map aquantity total) / monthsSinceBeginning d + nut = (sum $ map aquantity $ filter (\a -> acommodity a == "USD") total) / monthsSinceBeginning d (_, (Mixed total)) = monthlyBalance j d "^ex:me" cash = @@ -223,7 +235,7 @@ runway j d = (nut, cash, cash / nut) ramen :: Journal -> Day -> (Quantity, Quantity, Quantity) ramen j d = (nut, cash, cash / nut) where - nut = (sum $ map aquantity total) / monthsSinceBeginning d + nut = (sum $ map aquantity $ filter (\a -> acommodity a == "USD") total) / monthsSinceBeginning d (_, (Mixed total)) = monthlyBalance j d "^ex:me:need" cash = getTotal j d (defreportopts {value_ = inUsdNow}) "^as:me:cash ^li:me:cred" -- cgit v1.2.3