Files
rent-or-buy/rent-or-buy.ipynb

294 lines
12 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Rent or Buy?\n",
"\n",
"If you have a small pile of money that you are thinking of dropping on the downpayment of a house, should you? This document runs a small stochastic simulation that will help you decide whether it makes sense to buy a house or to rent. \n",
"\n",
"Conventional wisdom says that you will save money in the long term if you buy a house (the thinking being that you don't \"waste\" money on rent, and that you \"build equity\" in the value of the house). However, as this notebook will demonstrate, things are a little more complicated.\n",
"\n",
"The two cases this notebook contrasts are:\n",
"\n",
"* Putting a large lump sum down on the downpaymnet of a house, and then paying monthly payments to pay off the remainder of your debt. Once the house is paid off, those monthly payments will instead be invested in an index fund.\n",
"* Putting all the money that you would have put in a downpayment in an index fund, and then spending what you would have spent on monthly mortage payments on rent. \n",
"\n",
"Perhaps surprisngly, you will find that for many parameters, there is very little difference between the two scenarious, for the following simple reason:\n",
"\n",
"**Yes, the value of your house is going to go up. But the stock market is going to go up a lot faster**. \n",
"\n",
"This notebook allows you to play with the following parameters interactively:\n",
"\n",
"* **Starting Sum** This is the amount you have saved up. In the \"buying\" scenario, you put this all in your downpayment. In the \"renting\" scenario, you put this all in the stock market in an index fund. \n",
"* **Cost of the house** \n",
"* **Tax rate on the house**\n",
"* **Interest rate** On your mortage\n",
"* **Duration** Of your mortgage \n",
"* **Fraction of monthly payment you are willing to pay as rent** Assuming you are renting\n",
"* **Rent hike** Anticipated annual rent increase (percentage)"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"ename": "NameError",
"evalue": "name 'display' is not defined",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)",
"\u001b[0;32m<ipython-input-1-8fa7e24a99ac>\u001b[0m in \u001b[0;36m<module>\u001b[0;34m()\u001b[0m\n\u001b[1;32m 25\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 26\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 27\u001b[0;31m \u001b[0mdisplay\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mwidgets\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mHTML\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"<h3>Initial savings</h3>\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 28\u001b[0m \u001b[0mdisplay\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mwidgets\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mHTML\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mvalue\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m\"<p>This amount is the what you have saved up, that you're willing to put down as downpayment for a house</p>\"\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 29\u001b[0m \u001b[0mdisplay\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstarting_sum\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n",
"\u001b[0;31mNameError\u001b[0m: name 'display' is not defined"
]
}
],
"source": [
"\n",
"import math\n",
"import random\n",
"import time\n",
"\n",
"from IPython.display import display",
"from ipywidgets import *\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"\n",
"%matplotlib notebook\n",
"\n",
"time = np.linspace(2019, 2060,30)\n",
"\n",
"\n",
"# annual return of stock market\n",
"historical_yearly_return = [18.5,5.2,16.8,31.5,-3.1,\n",
" 30.5,7.6,10.1,1.3,37.6,\n",
" 23.1,33.4,28.6,21.0,-9.1,\n",
" -11.9,-22.1,28.7,10.9,4.9,\n",
" 15.8,5.5,-37.0,26.5,15.1,2.1,16.0,32.4,13.7,1.4,11.9];\n",
"\n",
"\n",
"house_appreciation = [5.5, 3.52, 5.3, 3.9]\n",
"\n",
"# income and initial condition parameters\n",
"starting_sum = widgets.FloatText(value=100000,min = 0, step = 10000, description = \"Starting $\")\n",
"\n",
"\n",
"display(widgets.HTML(value=\"<h3>Initial savings</h3>\"))\n",
"display(widgets.HTML(value=\"<p>This amount is the what you have saved up, that you're willing to put down as downpayment for a house</p>\"))\n",
"display(starting_sum)\n",
"\n",
"\n",
"\n",
"# buying \n",
"house_cost = widgets.IntText(value = 600000, min = 400000, step = 10000, description = \"Cost ($)\")\n",
"tax_rate = widgets.FloatSlider(value = 1, min = 1, step = .1,max=4, description = \"Tax (%)\")\n",
"interest_rate = widgets.FloatSlider(value = 3, min = 1, step = .5, description = \"Interest (%)\")\n",
"duration = widgets.IntSlider(value = 20, min = 5, max=30, step= 1, description = \"Duration (y)\")\n",
"\n",
"display(widgets.HTML(value=\"<h3>Buying</h3>\"))\n",
"box1 = widgets.HBox([house_cost, tax_rate])\n",
"box2 = widgets.HBox([interest_rate, duration])\n",
"display(widgets.VBox([box1,box2]))\n",
"\n",
"\n",
"\n",
"\n",
"\n",
"# assuming renting and investing\n",
"display(widgets.HTML(value=\"<h3>Renting and investing</h3>\"))\n",
"\n",
"\n",
"rent_fraction = widgets.IntSlider(value = 50, min = 0, max = 100, step = 5, description = \"Rent frac (%)\")\n",
"rent_growth = widgets.IntSlider(value = 1, min = 1, max = 30, step = 1, description = \"Rent hike (%)\")\n",
"rent_box = widgets.HBox([rent_fraction, rent_growth])\n",
"\n",
"\n",
"display(rent_box)\n",
"\n",
"\n",
"fig = plt.figure(figsize=(10, 7),num=None)\n",
"\n",
"# first plot shows mean asset value in the two cases. \n",
"ax_value = fig.add_subplot(2, 1, 1)\n",
"ax_value.set_ylabel('Mean Asset Value ($)')\n",
"rent_asset_mean, = ax_value.plot(time,time*0+1,'r-',linewidth=3)\n",
"\n",
"\n",
"buy_asset_mean, = ax_value.plot(time,time*0+1,'k-',linewidth=3)\n",
"\n",
"\n",
"# second plot shows anticipated housing costs for both renting and buying\n",
"# this does not include utilies, etc. which are common to both scenarios\n",
"ax_costs = fig.add_subplot(2,1,2)\n",
"ax_costs.set_xlabel('Year')\n",
"ax_costs.set_ylabel('Housing cost ($)')\n",
"\n",
"rent_cost, = ax_costs.plot(time, time*0 + 1, 'r-')\n",
"buy_cost, = ax_costs.plot(time, time*0 + 1, 'k-')\n",
"\n",
"\n",
"\n",
"fig.canvas.draw()\n",
"fig.canvas.draw()\n",
"fig.canvas.flush_events()\n",
"\n",
"\n",
"def income(growth_rate,starting_income):\n",
" predicted_income = 0*time + starting_income\n",
" for i in range(time.size):\n",
" predicted_income[i] = starting_income*math.pow(1 + growth_rate, i)\n",
" return predicted_income\n",
" \n",
"\n",
" \n",
"def estimateStockGrowth(annual_contrib,initial_contrib):\n",
" # how many samples to randomize across? \n",
" N = 100\n",
" \n",
" ensemble_returns = np.zeros((N,time.size))\n",
" \n",
" # initialize\n",
" ensemble_returns[:,0] = initial_contrib\n",
" \n",
" \n",
" for ti in np.arange(1,time.size):\n",
" for i in np.arange(0,N):\n",
" # pick a random yield rate\n",
" r = random.choice(historical_yearly_return)/100\n",
" ensemble_returns[i,ti] = ensemble_returns[i,ti-1]*(1+r) + annual_contrib[ti]\n",
" \n",
" return ensemble_returns\n",
" \n",
"\n",
"def updateAll(change):\n",
" # compute annual payments assuming buying a house\n",
" r = interest_rate.value/100\n",
" n = duration.value\n",
" annual_payment = (house_cost.value - starting_sum.value)*(r)*math.pow((1+r),n)/(math.pow(1+r,n) - 1)\n",
" temp = 0*time + (tax_rate.value/100)*house_cost.value\n",
" for i in range(0,n):\n",
" temp[i] = annual_payment\n",
" \n",
" # plot monthly housing cost\n",
" buy_cost.set_ydata(temp/12)\n",
" to_invest = temp[0] - temp\n",
" ax_costs.set_ylim([0, annual_payment*.15])\n",
" \n",
" # now compute how much rent we can afford\n",
" temp = 0*time + annual_payment/12*rent_fraction.value/100\n",
" \n",
" for i in range(1,time.size):\n",
" temp[i] = temp[i-1]*(1+rent_growth.value/100)\n",
" \n",
" \n",
" rent_cost.set_ydata(temp)\n",
" \n",
" # so now calculate the amount we expect to be able to invest if we rent\n",
" rent_invest_contrib = annual_payment/12 - temp\n",
" \n",
" ensemble_returns = estimateStockGrowth(rent_invest_contrib,starting_sum.value)\n",
" mean_stock = np.mean(ensemble_returns,axis=0)\n",
" std_stock = np.std(ensemble_returns,axis=0)\n",
" \n",
" \n",
" rent_asset_mean.set_ydata(mean_stock)\n",
" ax_value.collections.clear()\n",
" shade = ax_value.fill_between(time, mean_stock-std_stock, mean_stock + std_stock,color=[1,0.8,0.8],alpha=.2)\n",
" \n",
" ax_value.set_ylim([starting_sum.value, max(mean_stock+std_stock)])\n",
" \n",
" ax_value.set_yscale('log')\n",
" \n",
" \n",
" # now compute house appreciation over time\n",
" house_value = 0*time + house_cost.value\n",
" for i in range(1,time.size):\n",
" r = random.choice(house_appreciation)/100\n",
" house_value[i] = house_value[i-1]*(1+r)\n",
" \n",
" \n",
" # after the house is paid off, annual payments are diverted to investments\n",
" ensemble_returns = estimateStockGrowth(to_invest,0)\n",
" mean_stock = np.mean(ensemble_returns,axis=0) + house_value\n",
" std_stock = np.std(ensemble_returns,axis=0)\n",
" \n",
"\n",
" buy_asset_mean.set_ydata(mean_stock)\n",
"\n",
" shade = ax_value.fill_between(time, mean_stock-std_stock, mean_stock + std_stock,color=[.8,0.8,0.8],alpha=.2)\n",
" \n",
" \n",
" fig.canvas.draw()\n",
" fig.canvas.flush_events()\n",
" plt.show()\n",
" \n",
"\n",
"\n",
" \n",
" \n",
" \n",
"# listeners\n",
"house_cost.observe(updateAll)\n",
"tax_rate.observe(updateAll)\n",
"interest_rate.observe(updateAll)\n",
"duration.observe(updateAll)\n",
"rent_fraction.observe(updateAll)\n",
"rent_growth.observe(updateAll)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.1"
}
},
"nbformat": 4,
"nbformat_minor": 2
}