Branch: master
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
268 lines (220 sloc) 10.5 KB
{:title "Years to financial independence"
(fn [{:keys [slug] :as page}]
(let [interest 5
max-interest 10
min-interest 0
withdraw-rate 4
max-withdraw-rate 4
min-withdraw-rate 1
sliders (fn [style]
[:div {:class style}
[:label.label "Interest Over Inflation (" [:span {:name "interest"} interest "%"] ")"]
{:name "interest"
:type "range"
:min min-interest
:max max-interest
:step 1
:value interest
:oninput "fin.core.onchange(this)"}]]
[ "How much interest you earn after inflation. If you earn 11% interest, but inflation is 5%, then your interest over inflation is 6%"]]
[:label.label "Withdrawal Rate (" [:span {:name "withdraw-rate"} withdraw-rate "%"] ")"]
{:name "withdraw-rate"
:type "range"
:min min-withdraw-rate
:max max-withdraw-rate
:step 1
:value withdraw-rate
:oninput "fin.core.onchange(this)"}]]
[ "The % of your money that you can withdraw yearly from your retirement savings, without your money ever running out"]]])]
[:h3 "Years to Financial Independence"]
"This calculator, given your spending habits and using the [4% rule](, estimates how many years you are from financial independence (aka being able to retire).
The goal of this calculator is to show that:
* The most important financial principle is to spend less than you earn and save the rest
* With enough discipline, you can easily become financially independent
* Serve as a wake up call if you are not on track with your retirement savings")
(sneakycode.render/snippet :source page)]
[:label.label "Monthly Spend"]
[:input.input {:type "number" :name "expenses"
:onkeyup "fin.core.onchange(this)"}]]
[ "This should exclude any savings toward retirement. Include everything else you spend in a month. For more accuracy try averaging your yearly spend"]]
[:label.label "Monthly Savings For Retirement"]
[:input.input {:type "number" :name "savings"
:onkeyup "fin.core.onchange(this)"}]]
[ "How much money do you save for your retirement every month?"]]
[:label.label "Net worth"]
[:input.input {:type "number" :name "net-worth"
:onkeyup "fin.core.onchange(this)"}]]
[ "How much money/assets have you already saved? Assets that costs you money should be excluded (like the house you live in or the car you drive)"]]
[:td "Target Retire Amount"]
[:th.has-text-right {:name "retire-amount" :style {:white-space "nowrap"}}]]
[:td "Current Savings Rate"]
[:th.has-text-right {:name "savings-rate"}]]
[:td "Years Left"]
[:th.has-text-right {:name "years-left"}]]]]
(sliders "is-hidden-mobile")
[:thead [:tr [:th "Savings Rate"] [:th "Years Left"]]]
[:tbody {:name "years"}]]
(sliders "is-hidden-tablet")]
true slug "fin.core"
(ns fin.core
(:require [clojure.string :as string]))
(defn update-nodes [name f]
(let [nodes (js/document.getElementsByName name)]
(doseq [i (range (.-length nodes))]
(let [el (aget nodes i)]
(f el)))))
(defn set-node-value [v]
(fn [el]
(set! (.-value el) v)))
(defn set-node-html [v]
(fn [el]
(set! (.-innerHTML el) v)))
(defn update-calculated-value
([state k] (update-calculated-value state k identity))
([state k transform]
(when-let [n (get state k)]
(update-nodes (name k) (set-node-html (transform n))))))
(defn nice-number [n]
(loop [s (str n)
result '()]
(if (empty? s)
(string/join " " result)
(string/join "" (drop-last 3 s))
(conj result (string/join "" (take-last 3 s)))))))
(defn usable? [n] (> n 0))
(defn calc-savings-rate [expenses savings]
(max 0 (js/Math.round (* 100 (- 1 (/ expenses (+ expenses savings)))))))
(defn years-till-financially-independent
"Implements the spreadsheet found here"
[net-worth take-home-pay return-after-inflation saving-rate withdrawal-rate]
(let [years-of-take-home-pay (/ net-worth (* take-home-pay 12))
return-after-inflation (/ return-after-inflation 100)
saving-rate (/ saving-rate 100)]
[year 0
investment-gains 0
years-of-take-home-income years-of-take-home-pay
stash-relative-to-spending 0
withdraw-spend-percent 0]
(if (or (>= year 1000) (>= withdraw-spend-percent 100))
(let [year' (inc year)
years-of-take-home-income' (+ (+ years-of-take-home-income investment-gains') saving-rate)
stash-relative-to-spending' (/ years-of-take-home-income' (- 1 saving-rate))
withdraw-spend-percent' (* stash-relative-to-spending' withdrawal-rate)
year' investment-gains' years-of-take-home-income'
stash-relative-to-spending' withdraw-spend-percent'))))))
;;;; STATE
(def *state (atom {:withdraw-rate 4
:interest 5}))
(defn update-state [name value]
(swap! *state assoc (keyword name) (js/parseInt value)))
(defn recalculate []
(let [{:keys [expenses savings net-worth interest withdraw-rate]} @*state
(cond-> @*state
(usable? expenses)
(assoc :retire-amount (* 300 expenses))
(and (usable? expenses) (usable? savings))
(let [savings-rate (calc-savings-rate expenses savings)
income (+ expenses savings)
net-worth (if (usable? net-worth) net-worth 0)
years-left (years-till-financially-independent net-worth income interest savings-rate withdraw-rate)
years (->> [5 10 15 20 30 40 50 60 70]
(map (fn [y]
[y (years-till-financially-independent
net-worth income interest y withdraw-rate)])))]
{:savings-rate savings-rate
:years-left years-left
:years (into years [[savings-rate years-left :this-is-you]])})))]
(reset! *state next)
(prn @*state)))
;;;; VIEWS
(defn update-years-table [state]
(when-let [years (:years state)]
(let [html (->> years
(sort-by first)
(map (fn [[rate years you?]]
"<tr style='background-color: "
you? "#a3c7ff"
(<= years 10) "#83de83"
(<= years 20) "#fdfdac"
(<= years 30) "#fdd283"
(<= years 40) "#ffa1a1"
(> years 40) "#ff7171") "'>"
"<td>" rate "%</td>"
"<td>" years (if you? " (this is you)" "") "</td>"
(string/join ""))]
(update-nodes "years" (set-node-html html)))))
(defn update-views []
(let [state @*state
percentage #(str % "%")]
(update-calculated-value state :interest percentage)
(update-calculated-value state :withdraw-rate percentage)
(update-calculated-value state :retire-amount nice-number)
(update-calculated-value state :savings-rate percentage)
(update-calculated-value state :years-left #(if (> % 1000) "> 1000" %))
(update-years-table state)))
(defn ^:export onchange [el]
(let [name (.-name el)
value (.-value el)]
(when-not (string/blank? value)
(update-nodes name (set-node-value value))
(update-state name value)