1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
|
#!/usr/bin/env runhaskell
-- | Calculations for FIRE (financial independence, retire early)
module Main where
import Hledger
import Data.Decimal (DecimalRaw, roundTo)
import Data.Either (fromRight)
import Data.Time.Calendar (Day)
import Data.Time.Clock (UTCTime(utctDay), getCurrentTime)
import Data.Text (pack)
main = do
j <- getJournal
today <- getCurrentTime >>= return . utctDay
say ["savings rate:", show $ savingsRate j today]
say ["target fund:", show $ targetFund j today]
say ["when free:", show $ whenFreedom j today]
say = putStrLn . unwords
getJournal :: IO Journal
getJournal = do
jp <- defaultJournalPath
let opts = definputopts { auto_ = True }
ej <- readJournalFile opts jp
return $ fromRight undefined ej
-- | Helper for getting the total out of a balance report.
getTotal :: Journal -> Day -> String -> Quantity
getTotal j d q = head $ map aquantity $ total
where
opts = defreportopts { balancetype_ = CumulativeChange }
(query, _) = parseQuery d $ pack q
(_, (Mixed total)) = balanceReport opts query j
-- | 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. Basically take your savings and divide it by
-- your income on a monthly basis.
--
savingsRate :: Journal -> Day -> Quantity
savingsRate j d = roundTo 2 $ monthlySavings / monthlyIncome
where
monthlySavings = sum $ map (getTotal j d) $ map appendMonthly savingsAccounts
appendMonthly s = s ++ " --monthly"
monthlyIncome = getTotal j d "^in --monthly"
-- | The target fund is simply 25x your annual expenditure.
--
-- This is going to be incomplete until I have a full year of
-- expenses.. currently, I just use my most recent quarter times 4 as a proxy
-- for the yearly expenses.
--
-- Assumptions: 4% withdrawal rate, 3-5% return on investments.
--
targetFund :: Journal -> Day -> Quantity
targetFund j d = 25 * yearlyExpenses
where
yearlyExpenses = 4 * quarterlyExpenses
quarterlyExpenses = sum $ map aquantity $ total
(query, _) = parseQuery d $ pack "^ex -p thisquarter --forecast cur:USD"
(_, (Mixed total)) = balanceReport opts query j
opts = defreportopts
-- | 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 = sum $ map (getTotal j d) $ map appendMonthly savingsAccounts
appendMonthly s = s ++ " --monthly"
|