my-markdown-library 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/F24LS_md/ Lecture 4 - Public.md +347 -0
- data/F24LS_md/Lecture 1 - Introduction and Overview.md +327 -0
- data/F24LS_md/Lecture 10 - Development_.md +631 -0
- data/F24LS_md/Lecture 11 - Econometrics.md +345 -0
- data/F24LS_md/Lecture 12 - Finance.md +692 -0
- data/F24LS_md/Lecture 13 - Environmental Economics.md +299 -0
- data/F24LS_md/Lecture 15 - Conclusion.md +272 -0
- data/F24LS_md/Lecture 2 - Demand.md +349 -0
- data/F24LS_md/Lecture 3 - Supply.md +329 -0
- data/F24LS_md/Lecture 5 - Production C-D.md +291 -0
- data/F24LS_md/Lecture 6 - Utility and Latex.md +440 -0
- data/F24LS_md/Lecture 7 - Inequality.md +607 -0
- data/F24LS_md/Lecture 8 - Macroeconomics.md +704 -0
- data/F24LS_md/Lecture 8 - Macro.md +700 -0
- data/F24LS_md/Lecture 9 - Game Theory_.md +436 -0
- data/F24LS_md/summary.yaml +105 -0
- data/F24Lec_MD/LecNB_summary.yaml +206 -0
- data/F24Lec_MD/lec01/lec01.md +267 -0
- data/F24Lec_MD/lec02/Avocados_demand.md +425 -0
- data/F24Lec_MD/lec02/Demand_Steps_24.md +126 -0
- data/F24Lec_MD/lec02/PriceElasticity.md +83 -0
- data/F24Lec_MD/lec02/ScannerData_Beer.md +171 -0
- data/F24Lec_MD/lec02/demand-curve-Fa24.md +213 -0
- data/F24Lec_MD/lec03/3.0-CubicCostCurve.md +239 -0
- data/F24Lec_MD/lec03/3.1-Supply.md +274 -0
- data/F24Lec_MD/lec03/3.2-sympy.md +332 -0
- data/F24Lec_MD/lec03/3.3a-california-energy.md +120 -0
- data/F24Lec_MD/lec03/3.3b-a-really-hot-tuesday.md +121 -0
- data/F24Lec_MD/lec04/lec04-CSfromSurvey-closed.md +335 -0
- data/F24Lec_MD/lec04/lec04-CSfromSurvey.md +331 -0
- data/F24Lec_MD/lec04/lec04-Supply-Demand-closed.md +519 -0
- data/F24Lec_MD/lec04/lec04-Supply-Demand.md +514 -0
- data/F24Lec_MD/lec04/lec04-four-plot-24.md +34 -0
- data/F24Lec_MD/lec04/lec04-four-plot.md +34 -0
- data/F24Lec_MD/lec05/Lec5-Cobb-Douglas.md +131 -0
- data/F24Lec_MD/lec05/Lec5-CobbD-AER1928.md +283 -0
- data/F24Lec_MD/lec06/6.1-Sympy-Differentiation.md +253 -0
- data/F24Lec_MD/lec06/6.2-3D-utility.md +287 -0
- data/F24Lec_MD/lec06/6.3-QuantEcon-Optimization.md +399 -0
- data/F24Lec_MD/lec06/6.4-latex.md +138 -0
- data/F24Lec_MD/lec06/6.5-Edgeworth.md +269 -0
- data/F24Lec_MD/lec07/7.1-inequality.md +283 -0
- data/F24Lec_MD/lec07/7.2-historical-inequality.md +237 -0
- data/F24Lec_MD/lec08/macro-fred-api.md +313 -0
- data/F24Lec_MD/lec09/lecNB-prisoners-dilemma.md +88 -0
- data/F24Lec_MD/lec10/Lec10.2-waterguard.md +401 -0
- data/F24Lec_MD/lec10/lec10.1-mapping.md +199 -0
- data/F24Lec_MD/lec11/11.1-slr.md +305 -0
- data/F24Lec_MD/lec11/11.2-mlr.md +171 -0
- data/F24Lec_MD/lec12/Lec12-4-PersonalFinance.md +590 -0
- data/F24Lec_MD/lec12/lec12-1_Interest_Payments.md +267 -0
- data/F24Lec_MD/lec12/lec12-2-stocks-options.md +235 -0
- data/F24Lec_MD/lec13/Co2_ClimateChange.md +139 -0
- data/F24Lec_MD/lec13/ConstructingMAC.md +213 -0
- data/F24Lec_MD/lec13/EmissionsTracker.md +170 -0
- data/F24Lec_MD/lec13/KuznetsHypothesis.md +219 -0
- data/F24Lec_MD/lec13/RoslingPlots.md +217 -0
- data/F24Lec_MD/lec15/vibecession.md +485 -0
- data/F24Textbook_MD/00-intro/index.md +292 -0
- data/F24Textbook_MD/01-demand/01-demand.md +152 -0
- data/F24Textbook_MD/01-demand/02-example.md +131 -0
- data/F24Textbook_MD/01-demand/03-log-log.md +284 -0
- data/F24Textbook_MD/01-demand/04-elasticity.md +248 -0
- data/F24Textbook_MD/01-demand/index.md +15 -0
- data/F24Textbook_MD/02-supply/01-supply.md +203 -0
- data/F24Textbook_MD/02-supply/02-eep147-example.md +86 -0
- data/F24Textbook_MD/02-supply/03-sympy.md +138 -0
- data/F24Textbook_MD/02-supply/04-market-equilibria.md +204 -0
- data/F24Textbook_MD/02-supply/index.md +16 -0
- data/F24Textbook_MD/03-public/govt-intervention.md +73 -0
- data/F24Textbook_MD/03-public/index.md +10 -0
- data/F24Textbook_MD/03-public/surplus.md +351 -0
- data/F24Textbook_MD/03-public/taxes-subsidies.md +282 -0
- data/F24Textbook_MD/04-production/index.md +15 -0
- data/F24Textbook_MD/04-production/production.md +178 -0
- data/F24Textbook_MD/04-production/shifts.md +296 -0
- data/F24Textbook_MD/05-utility/budget-constraints.md +166 -0
- data/F24Textbook_MD/05-utility/index.md +15 -0
- data/F24Textbook_MD/05-utility/utility.md +136 -0
- data/F24Textbook_MD/06-inequality/historical-inequality.md +253 -0
- data/F24Textbook_MD/06-inequality/index.md +15 -0
- data/F24Textbook_MD/06-inequality/inequality.md +226 -0
- data/F24Textbook_MD/07-game-theory/bertrand.md +257 -0
- data/F24Textbook_MD/07-game-theory/cournot.md +333 -0
- data/F24Textbook_MD/07-game-theory/equilibria-oligopolies.md +96 -0
- data/F24Textbook_MD/07-game-theory/expected-utility.md +61 -0
- data/F24Textbook_MD/07-game-theory/index.md +19 -0
- data/F24Textbook_MD/07-game-theory/python-classes.md +340 -0
- data/F24Textbook_MD/08-development/index.md +35 -0
- data/F24Textbook_MD/09-macro/CentralBanks.md +101 -0
- data/F24Textbook_MD/09-macro/Indicators.md +77 -0
- data/F24Textbook_MD/09-macro/fiscal_policy.md +36 -0
- data/F24Textbook_MD/09-macro/index.md +14 -0
- data/F24Textbook_MD/09-macro/is_curve.md +76 -0
- data/F24Textbook_MD/09-macro/phillips_curve.md +70 -0
- data/F24Textbook_MD/10-finance/index.md +10 -0
- data/F24Textbook_MD/10-finance/options.md +178 -0
- data/F24Textbook_MD/10-finance/value-interest.md +60 -0
- data/F24Textbook_MD/11-econometrics/index.md +16 -0
- data/F24Textbook_MD/11-econometrics/multivariable.md +218 -0
- data/F24Textbook_MD/11-econometrics/reading-econ-papers.md +25 -0
- data/F24Textbook_MD/11-econometrics/single-variable.md +483 -0
- data/F24Textbook_MD/11-econometrics/statsmodels.md +58 -0
- data/F24Textbook_MD/12-environmental/KuznetsHypothesis-Copy1.md +187 -0
- data/F24Textbook_MD/12-environmental/KuznetsHypothesis.md +187 -0
- data/F24Textbook_MD/12-environmental/MAC.md +254 -0
- data/F24Textbook_MD/12-environmental/index.md +36 -0
- data/F24Textbook_MD/LICENSE.md +11 -0
- data/F24Textbook_MD/intro.md +26 -0
- data/F24Textbook_MD/references.md +25 -0
- data/F24Textbook_MD/summary.yaml +414 -0
- metadata +155 -0
@@ -0,0 +1,590 @@
|
|
1
|
+
---
|
2
|
+
title: "Lec12-4-PersonalFinance"
|
3
|
+
type: lecture-notebook
|
4
|
+
week: 12
|
5
|
+
source_path: "/Users/ericvandusen/Documents/Data88E-ForTraining/F24Lec_NBs/lec12/Lec12-4-PersonalFinance.ipynb"
|
6
|
+
---
|
7
|
+
|
8
|
+
<table style="width: 100%;" id="nb-header">
|
9
|
+
<tr style="background-color: transparent;"><td>
|
10
|
+
<img src="https://data-88e.github.io/assets/images/blue_text.png" width="250px" style="margin-left: 0;" />
|
11
|
+
</td><td>
|
12
|
+
<p style="text-align: right; font-size: 10pt;"><strong>Economic Models</strong>, Fall 24 <br>
|
13
|
+
Dr. Eric Van Dusen
|
14
|
+
<br>
|
15
|
+
</table>
|
16
|
+
|
17
|
+
```python
|
18
|
+
from datascience import Table
|
19
|
+
import numpy as np
|
20
|
+
import matplotlib.pyplot as plt
|
21
|
+
import ipywidgets as widgets
|
22
|
+
from IPython.display import display
|
23
|
+
%matplotlib inline
|
24
|
+
```
|
25
|
+
|
26
|
+
```python
|
27
|
+
try: import yfinance as yf
|
28
|
+
except:
|
29
|
+
!pip install yfinance
|
30
|
+
import yfinance as yf
|
31
|
+
```
|
32
|
+
|
33
|
+
## Personal Finance Demo
|
34
|
+
|
35
|
+
### This notebook will demonstrate some basic concepts in personal finance, over time, using Python.
|
36
|
+
- Rate of Savings
|
37
|
+
- Impact of Fees on Returns over Time
|
38
|
+
- Monte Carlo Simulation of Retirement Savings
|
39
|
+
- Historical Returns of Stocks and Bonds
|
40
|
+
|
41
|
+
|
42
|
+
The notebook will also motivate the use of widgets in Jupyter notebooks to interact with the data and see the impact of different parameters on the results.
|
43
|
+
|
44
|
+
### Part 1: Rate of Savings
|
45
|
+
|
46
|
+
```python
|
47
|
+
annual_income_widget = widgets.FloatText(
|
48
|
+
value=50000,
|
49
|
+
description='Annual Income:',
|
50
|
+
style={'description_width': 'initial'}
|
51
|
+
)
|
52
|
+
|
53
|
+
savings_rate_widget = widgets.FloatSlider(
|
54
|
+
value=0.2,
|
55
|
+
min=0,
|
56
|
+
max=1,
|
57
|
+
step=0.01,
|
58
|
+
description='Savings Rate (%):',
|
59
|
+
style={'description_width': 'initial'}
|
60
|
+
)
|
61
|
+
|
62
|
+
years_widget = widgets.IntSlider(
|
63
|
+
value=30,
|
64
|
+
min=1,
|
65
|
+
max=50,
|
66
|
+
step=1,
|
67
|
+
description='Years:',
|
68
|
+
style={'description_width': 'initial'}
|
69
|
+
)
|
70
|
+
|
71
|
+
annual_return_widget = widgets.FloatSlider(
|
72
|
+
value=0.05,
|
73
|
+
min=0,
|
74
|
+
max=0.2,
|
75
|
+
step=0.01,
|
76
|
+
description='Annual Return (%):',
|
77
|
+
style={'description_width': 'initial'}
|
78
|
+
)
|
79
|
+
display(annual_income_widget, savings_rate_widget, years_widget, annual_return_widget)
|
80
|
+
```
|
81
|
+
|
82
|
+
Save these values as variables
|
83
|
+
|
84
|
+
```python
|
85
|
+
annual_income = annual_income_widget.value
|
86
|
+
savings_rate = savings_rate_widget.value
|
87
|
+
years = years_widget.value
|
88
|
+
annual_return = annual_return_widget.value
|
89
|
+
```
|
90
|
+
|
91
|
+
Set a formula for the rate of savings
|
92
|
+
|
93
|
+
```python
|
94
|
+
annual_savings = annual_income * savings_rate
|
95
|
+
```
|
96
|
+
|
97
|
+
Create an array of years
|
98
|
+
|
99
|
+
```python
|
100
|
+
years_array = np.arange(1, years + 1)
|
101
|
+
savings_growth = []
|
102
|
+
```
|
103
|
+
|
104
|
+
Calculate the savings for each year, create total savings appending each year's savings to the previous year's savings
|
105
|
+
|
106
|
+
```python
|
107
|
+
total_savings = 0
|
108
|
+
for year in years_array:
|
109
|
+
# Add annual savings and apply growth
|
110
|
+
total_savings += annual_savings
|
111
|
+
total_savings *= (1 + annual_return)
|
112
|
+
savings_growth.append(total_savings)
|
113
|
+
```
|
114
|
+
|
115
|
+
Create a Table to store the results
|
116
|
+
|
117
|
+
```python
|
118
|
+
savings_table = Table().with_columns(
|
119
|
+
"Year", years_array,
|
120
|
+
"Total Savings ($)", savings_growth
|
121
|
+
)
|
122
|
+
|
123
|
+
savings_table
|
124
|
+
```
|
125
|
+
|
126
|
+
```python
|
127
|
+
|
128
|
+
plt.figure(figsize=(10, 6))
|
129
|
+
plt.plot(years_array, savings_growth, marker='o')
|
130
|
+
plt.title("Savings Growth Over Time")
|
131
|
+
plt.xlabel("Years")
|
132
|
+
plt.ylabel("Total Savings ($)")
|
133
|
+
plt.grid(True)
|
134
|
+
plt.show()
|
135
|
+
```
|
136
|
+
|
137
|
+
## Part 2 - Retirement Planning & Fees on Investments
|
138
|
+
|
139
|
+
```python
|
140
|
+
initial_investment_widget = widgets.FloatText(
|
141
|
+
value=10000,
|
142
|
+
description='Initial Investment ($):',
|
143
|
+
style={'description_width': 'initial'}
|
144
|
+
)
|
145
|
+
|
146
|
+
annual_return_widget_fee = widgets.FloatSlider(
|
147
|
+
value=0.07,
|
148
|
+
min=0,
|
149
|
+
max=0.2,
|
150
|
+
step=0.01,
|
151
|
+
description='Annual Return (%):',
|
152
|
+
style={'description_width': 'initial'}
|
153
|
+
)
|
154
|
+
|
155
|
+
years_widget_fee = widgets.IntSlider(
|
156
|
+
value=30,
|
157
|
+
min=1,
|
158
|
+
max=50,
|
159
|
+
step=1,
|
160
|
+
description='Investment Duration (Years):',
|
161
|
+
style={'description_width': 'initial'}
|
162
|
+
)
|
163
|
+
|
164
|
+
fee_percentage_widget = widgets.FloatSlider(
|
165
|
+
value=0.01,
|
166
|
+
min=0,
|
167
|
+
max=0.05,
|
168
|
+
step=0.005,
|
169
|
+
description='Annual Fee (%):',
|
170
|
+
style={'description_width': 'initial'}
|
171
|
+
)
|
172
|
+
second_fee_percentage_widget = widgets.FloatSlider(
|
173
|
+
value=0.02,
|
174
|
+
min=0,
|
175
|
+
max=0.05,
|
176
|
+
step=0.005,
|
177
|
+
description='Annual Fee Comparison (%):',
|
178
|
+
style={'description_width': 'initial'}
|
179
|
+
)
|
180
|
+
|
181
|
+
display(initial_investment_widget, annual_return_widget_fee, years_widget_fee, fee_percentage_widget, second_fee_percentage_widget)
|
182
|
+
```
|
183
|
+
|
184
|
+
Retrieve widget values for calculations
|
185
|
+
|
186
|
+
```python
|
187
|
+
initial_investment = initial_investment_widget.value
|
188
|
+
annual_return = annual_return_widget_fee.value
|
189
|
+
years = years_widget_fee.value
|
190
|
+
annual_fee = fee_percentage_widget.value
|
191
|
+
annual_fee_2 = second_fee_percentage_widget.value
|
192
|
+
```
|
193
|
+
|
194
|
+
Initialize arrays for growth with and without fees
|
195
|
+
|
196
|
+
Set starting values for investments
|
197
|
+
|
198
|
+
```python
|
199
|
+
years_array = np.arange(1, years + 1)
|
200
|
+
growth_without_fees = []
|
201
|
+
growth_with_fee_1 = []
|
202
|
+
growth_with_fee_2 = []
|
203
|
+
|
204
|
+
investment_without_fees = initial_investment
|
205
|
+
investment_with_fee_1 = initial_investment
|
206
|
+
investment_with_fee_2 = initial_investment
|
207
|
+
```
|
208
|
+
|
209
|
+
Build a growth path for the three options
|
210
|
+
|
211
|
+
```python
|
212
|
+
for year in years_array:
|
213
|
+
# Growth without fees
|
214
|
+
investment_without_fees *= (1 + annual_return)
|
215
|
+
growth_without_fees.append(investment_without_fees)
|
216
|
+
|
217
|
+
# Growth with fee 1
|
218
|
+
net_return_1 = annual_return - annual_fee
|
219
|
+
investment_with_fee_1 *= (1 + net_return_1)
|
220
|
+
growth_with_fee_1.append(investment_with_fee_1)
|
221
|
+
|
222
|
+
# Growth with fee 2
|
223
|
+
net_return_2 = annual_return - annual_fee_2
|
224
|
+
investment_with_fee_2 *= (1 + net_return_2)
|
225
|
+
growth_with_fee_2.append(investment_with_fee_2)
|
226
|
+
```
|
227
|
+
|
228
|
+
Create a Table to store the results
|
229
|
+
|
230
|
+
```python
|
231
|
+
fees_table = Table().with_columns(
|
232
|
+
"Year", years_array,
|
233
|
+
"Growth Without Fees ($)", growth_without_fees,
|
234
|
+
"Growth With Fees ($)", growth_with_fee_1,
|
235
|
+
"Growth With Fees 2 ($)", growth_with_fee_2
|
236
|
+
)
|
237
|
+
|
238
|
+
|
239
|
+
fees_table.show()
|
240
|
+
```
|
241
|
+
|
242
|
+
```python
|
243
|
+
# Plot the growth over time
|
244
|
+
plt.figure(figsize=(10, 6))
|
245
|
+
plt.plot(years_array, growth_without_fees, label="Without Fees", marker='o')
|
246
|
+
plt.plot(years_array, growth_with_fee_1, label=f"With Fees ({annual_fee*100:.2f}%)", marker='o', linestyle='--')
|
247
|
+
plt.plot(years_array, growth_with_fee_2, label=f"With Fee 2 ({annual_fee_2*100:.2f}%)", marker='o', linestyle=':')
|
248
|
+
plt.title("Investment Growth Over Time with and without Fees")
|
249
|
+
plt.xlabel("Years")
|
250
|
+
plt.ylabel("Investment Value ($)")
|
251
|
+
plt.legend()
|
252
|
+
plt.grid(True)
|
253
|
+
plt.show()
|
254
|
+
```
|
255
|
+
|
256
|
+
```python
|
257
|
+
|
258
|
+
```
|
259
|
+
|
260
|
+
## Part 3: Allocation of Assets
|
261
|
+
|
262
|
+
Let's simulate the impact of different asset allocations on the growth of investments over time.
|
263
|
+
|
264
|
+
In this example we will consider two asset classes: Stocks and Bonds.
|
265
|
+
|
266
|
+
We will use a simple model of the growth of investments in each asset class over time but with variable returns in each time period.
|
267
|
+
|
268
|
+
This model will be for a one time initial investment, but we will simulate the growth over multiple time periods.
|
269
|
+
|
270
|
+
Set up the widgets
|
271
|
+
|
272
|
+
```python
|
273
|
+
initial_investment_widget_alloc = widgets.FloatText(
|
274
|
+
value=10000,
|
275
|
+
description='Initial Investment ($):',
|
276
|
+
style={'description_width': 'initial'}
|
277
|
+
)
|
278
|
+
|
279
|
+
years_widget_alloc = widgets.IntSlider(
|
280
|
+
value=30,
|
281
|
+
min=1,
|
282
|
+
max=50,
|
283
|
+
step=1,
|
284
|
+
description='Investment Duration (Years):',
|
285
|
+
style={'description_width': 'initial'}
|
286
|
+
)
|
287
|
+
|
288
|
+
stock_allocation_widget = widgets.FloatSlider(
|
289
|
+
value=0.6,
|
290
|
+
min=0,
|
291
|
+
max=1,
|
292
|
+
step=0.05,
|
293
|
+
description='Stock Allocation (%):',
|
294
|
+
style={'description_width': 'initial'}
|
295
|
+
)
|
296
|
+
|
297
|
+
|
298
|
+
display(initial_investment_widget_alloc, years_widget_alloc, stock_allocation_widget)
|
299
|
+
```
|
300
|
+
|
301
|
+
Set up average returns and standard deviations for stocks and bonds
|
302
|
+
|
303
|
+
( these are hard coded for now - but could be widgets as well)
|
304
|
+
|
305
|
+
```python
|
306
|
+
average_stock_return = 0.08 # 8% average annual return for stocks
|
307
|
+
average_bond_return = 0.03 # 3% average annual return for bonds
|
308
|
+
std_dev_stock = 0.15 # 15% annual standard deviation for stocks
|
309
|
+
std_dev_bond = 0.05 # 5% annual standard deviation for bonds
|
310
|
+
```
|
311
|
+
|
312
|
+
```python
|
313
|
+
initial_investment = initial_investment_widget_alloc.value
|
314
|
+
years = years_widget_alloc.value
|
315
|
+
stock_allocation = stock_allocation_widget.value
|
316
|
+
bond_allocation = 1 - stock_allocation
|
317
|
+
```
|
318
|
+
|
319
|
+
```python
|
320
|
+
years_array = np.arange(1, years + 1)
|
321
|
+
growth_allocation = []
|
322
|
+
```
|
323
|
+
|
324
|
+
Calculate growth based on random returns derived from asset allocation
|
325
|
+
|
326
|
+
This will use the command 'np.random.normal' to generate random returns based on the average and standard deviation of returns for stocks and bonds
|
327
|
+
|
328
|
+
The arguments to 'np.random.normal' are the average return, the standard deviation of returns, that we hard coded above.
|
329
|
+
|
330
|
+
Think about it as each year the return is a random number drawn from a normal distribution with the average return and standard deviation we set above.
|
331
|
+
|
332
|
+
This will create a random path of returns for each year, as we rerun the cell, we will get a different path of returns. We could set a seed to get the same path of returns each time.
|
333
|
+
|
334
|
+
```python
|
335
|
+
#set_seed(42)
|
336
|
+
investment_value = initial_investment
|
337
|
+
for year in years_array:
|
338
|
+
stock_return = np.random.normal(average_stock_return, std_dev_stock)
|
339
|
+
bond_return = np.random.normal(average_bond_return, std_dev_bond)
|
340
|
+
|
341
|
+
# Calculate the portfolio's annual return based on the allocation
|
342
|
+
portfolio_return = (stock_allocation * stock_return) + (bond_allocation * bond_return)
|
343
|
+
|
344
|
+
# Update the investment value based on the portfolio return
|
345
|
+
investment_value *= (1 + portfolio_return)
|
346
|
+
growth_allocation.append(investment_value)
|
347
|
+
```
|
348
|
+
|
349
|
+
Create a Table to store the results
|
350
|
+
|
351
|
+
```python
|
352
|
+
allocation_table = Table().with_columns(
|
353
|
+
"Year", years_array,
|
354
|
+
f"Growth with {stock_allocation*100:.0f}% Stocks / {bond_allocation*100:.0f}% Bonds ($)", growth_allocation
|
355
|
+
)
|
356
|
+
|
357
|
+
# Display the table
|
358
|
+
allocation_table.show()
|
359
|
+
```
|
360
|
+
|
361
|
+
```python
|
362
|
+
|
363
|
+
plt.figure(figsize=(10, 6))
|
364
|
+
plt.plot(years_array, growth_allocation, label=f"{stock_allocation*100:.0f}% Stocks / {bond_allocation*100:.0f}% Bonds", marker='o')
|
365
|
+
plt.title("Investment Growth Over Time with Asset Allocation (Including Volatility)")
|
366
|
+
plt.xlabel("Years")
|
367
|
+
plt.ylabel("Investment Value ($)")
|
368
|
+
plt.legend()
|
369
|
+
plt.grid(True)
|
370
|
+
plt.show()
|
371
|
+
```
|
372
|
+
|
373
|
+
## Part 4 Monte Carlo Simulation
|
374
|
+
What is the role of uncertainty in financial planning?
|
375
|
+
|
376
|
+
In the previous example, we have a single path for the growth of investments. However, in reality, the growth of investments is more complicated!
|
377
|
+
|
378
|
+
We can use Monte Carlo simulation to simulate the growth of investments over time. This would allow us to see the range of possible outcomes for our investments. Think of it as generating many paths of returns and seeing the range of outcomes. This is a powerful tool for financial planning.
|
379
|
+
|
380
|
+
Step 1 - Set up the widgets
|
381
|
+
|
382
|
+
We can add a widget for the number of simulations we want to run. This will allow us to see the range of outcomes for our investments.
|
383
|
+
|
384
|
+
```python
|
385
|
+
initial_investment_widget_alloc = widgets.FloatText(
|
386
|
+
value=10000,
|
387
|
+
description='Initial Investment ($):',
|
388
|
+
style={'description_width': 'initial'}
|
389
|
+
)
|
390
|
+
|
391
|
+
years_widget_alloc = widgets.IntSlider(
|
392
|
+
value=30,
|
393
|
+
min=1,
|
394
|
+
max=50,
|
395
|
+
step=1,
|
396
|
+
description='Investment Duration (Years):',
|
397
|
+
style={'description_width': 'initial'}
|
398
|
+
)
|
399
|
+
|
400
|
+
stock_allocation_widget = widgets.FloatSlider(
|
401
|
+
value=0.6,
|
402
|
+
min=0,
|
403
|
+
max=1,
|
404
|
+
step=0.05,
|
405
|
+
description='Stock Allocation (%):',
|
406
|
+
style={'description_width': 'initial'}
|
407
|
+
)
|
408
|
+
|
409
|
+
simulations_widget = widgets.IntSlider(
|
410
|
+
value=1000,
|
411
|
+
min=100,
|
412
|
+
max=5000,
|
413
|
+
step=100,
|
414
|
+
description='Number of Simulations:',
|
415
|
+
style={'description_width': 'initial'}
|
416
|
+
)
|
417
|
+
|
418
|
+
# Display the widgets
|
419
|
+
display(initial_investment_widget_alloc, years_widget_alloc, stock_allocation_widget, simulations_widget)
|
420
|
+
```
|
421
|
+
|
422
|
+
Again we will hard code the average returns and standard deviations for stocks and bonds. ( for a future project we could make these widgets as well! )
|
423
|
+
|
424
|
+
```python
|
425
|
+
average_stock_return = 0.08
|
426
|
+
average_bond_return = 0.03
|
427
|
+
std_dev_stock = 0.15 # 15% annual standard deviation for stocks
|
428
|
+
std_dev_bond = 0.05 # 5% annual standard deviation for bonds
|
429
|
+
```
|
430
|
+
|
431
|
+
```python
|
432
|
+
# Retrieve widget values for calculations
|
433
|
+
initial_investment = initial_investment_widget_alloc.value
|
434
|
+
years = years_widget_alloc.value
|
435
|
+
stock_allocation = stock_allocation_widget.value
|
436
|
+
bond_allocation = 1 - stock_allocation
|
437
|
+
num_simulations = simulations_widget.value
|
438
|
+
|
439
|
+
# Initialize a list to store the final investment values of each simulation
|
440
|
+
final_values = []
|
441
|
+
```
|
442
|
+
|
443
|
+
Build a loop to simulate the growth of investments over time. Running the simulaton multiple times is called a *Monte Carlo Simulation*.
|
444
|
+
|
445
|
+
This will be similar to the previous example, but we will run the simulation multiple times to see the range of outcomes. The inner loop is the same as the previous example, but in the outer loop we will run the simulation multiple times.
|
446
|
+
|
447
|
+
Create a Table to store the results `final-values`
|
448
|
+
|
449
|
+
Calculate the average and standard deviation of the results `final-values`
|
450
|
+
|
451
|
+
This entire function is wrapped in a plot function that will plot the results of each run of the Monte Carlo simulation. Think of it as 1000 different plots that map out the range of outcomes for the investments.
|
452
|
+
|
453
|
+
In the plot we have set the alpha to 0.1 so that we can see the overlap of the different paths. Alpha is a parameter that sets the transparency of the plot. So, the lower the alpha, the more transparent the plot.
|
454
|
+
|
455
|
+
|
456
|
+
We will also plot the average and standard deviation of the results. This will give us a sense of the range of outcomes for our investments.
|
457
|
+
|
458
|
+
```python
|
459
|
+
|
460
|
+
plt.figure(figsize=(12, 8))
|
461
|
+
# Outer loop for running multiple simulations
|
462
|
+
for _ in range(num_simulations):
|
463
|
+
investment_value = initial_investment
|
464
|
+
growth_path = []
|
465
|
+
|
466
|
+
# Inner loop for each simulation
|
467
|
+
for year in range(years):
|
468
|
+
# Generate random returns for stocks and bonds based on mean and standard deviation
|
469
|
+
stock_return = np.random.normal(average_stock_return, std_dev_stock)
|
470
|
+
bond_return = np.random.normal(average_bond_return, std_dev_bond)
|
471
|
+
|
472
|
+
portfolio_return = (stock_allocation * stock_return) + (bond_allocation * bond_return)
|
473
|
+
|
474
|
+
investment_value *= (1 + portfolio_return)
|
475
|
+
growth_path.append(investment_value)
|
476
|
+
|
477
|
+
# Store the final investment value for this simulation
|
478
|
+
final_values.append(investment_value)
|
479
|
+
|
480
|
+
# Plot this simulation's growth path
|
481
|
+
plt.plot(range(1, years + 1), growth_path, color='blue', alpha=0.05) # alpha is the transparency level
|
482
|
+
|
483
|
+
# Calculate summary statistics of the final values across all simulations
|
484
|
+
mean_final_value = np.mean(final_values)
|
485
|
+
median_final_value = np.median(final_values)
|
486
|
+
percentile_10 = np.percentile(final_values, 10)
|
487
|
+
percentile_90 = np.percentile(final_values, 90)
|
488
|
+
|
489
|
+
# Plot the summary statistics on the chart
|
490
|
+
plt.title("Monte Carlo Simulation of Investment Growth with Asset Allocation")
|
491
|
+
plt.xlabel("Years")
|
492
|
+
plt.ylabel("Investment Value ($)")
|
493
|
+
plt.grid(True)
|
494
|
+
plt.axhline(mean_final_value, color='green', linestyle='--', label=f"Mean Final Value: ${mean_final_value:,.2f}")
|
495
|
+
plt.axhline(median_final_value, color='orange', linestyle='--', label=f"Median Final Value: ${median_final_value:,.2f}")
|
496
|
+
plt.axhline(percentile_10, color='red', linestyle='--', label=f"10th Percentile: ${percentile_10:,.2f}")
|
497
|
+
plt.axhline(percentile_90, color='purple', linestyle='--', label=f"90th Percentile: ${percentile_90:,.2f}")
|
498
|
+
plt.legend()
|
499
|
+
plt.show()
|
500
|
+
```
|
501
|
+
|
502
|
+
## Appendix (Part 4) - get the parameters for return and sd from the markets
|
503
|
+
|
504
|
+
We can get the average returns and standard deviations for stocks and bonds from the markets. This will allow us to use real data in our simulations.
|
505
|
+
|
506
|
+
We have hard coded the values for now, but we could use the market values to update our hard coded values above This would allow us to use real data in our simulations.
|
507
|
+
|
508
|
+
We will use the `yfinance` package to get the data from the markets. This package allows us to get data from Yahoo Finance in a simple way, no API key, and in the format we want.
|
509
|
+
|
510
|
+
We will use the `yfinance` package to get the data for the **S&P 500** and the **10 year treasury** bond. We will call the tickers for these assets `^GSPC` and `^TNX` respectively.
|
511
|
+
|
512
|
+
We will use the data to calculate the average annual returns and standard deviations for stocks and bonds. Using annual returns and standard deviations is clearly one limitation of this model, but it is a simple way to get a sense of the range of outcomes for our investments.
|
513
|
+
|
514
|
+
There are two important choices in the following code:
|
515
|
+
- The time period for the data
|
516
|
+
- The index for the data
|
517
|
+
|
518
|
+
*Note for Data 88E: There is a little Pandas manipulaton in the code to calculate the average annual returns and standard deviations for stocks and bonds. We will use the `ffill` and `resample` methods to fill in any missing values and to resample the data to get the annual returns and standard deviations. We will use the `dropna` method to remove any missing values from the data.*
|
519
|
+
|
520
|
+
```python
|
521
|
+
start_date = "2000-01-01"
|
522
|
+
end_date = "2023-01-01"
|
523
|
+
```
|
524
|
+
|
525
|
+
```python
|
526
|
+
# Fetch historical data for S&P 500 (stocks) and TLT (bonds) using yfinance
|
527
|
+
stock_data = yf.download('^GSPC', start=start_date, end=end_date)
|
528
|
+
bond_data = yf.download('TLT', start=start_date, end=end_date)
|
529
|
+
|
530
|
+
# Convert to annual adjusted close prices by resampling to year-end and forward-filling
|
531
|
+
stock_data = stock_data['Adj Close'].resample('Y').ffill()
|
532
|
+
bond_data = bond_data['Adj Close'].resample('Y').ffill()
|
533
|
+
|
534
|
+
# Calculate annual returns as percentage change for stocks and bonds
|
535
|
+
stock_returns = stock_data.pct_change().dropna()
|
536
|
+
bond_returns = bond_data.pct_change().dropna()
|
537
|
+
```
|
538
|
+
|
539
|
+
```python
|
540
|
+
# Create datascience Tables for annual returns
|
541
|
+
stock_table = Table().with_columns("Year", stock_data.index.year[1:], "Stock Returns", stock_returns.values)
|
542
|
+
bond_table = Table().with_columns("Year", bond_data.index.year[1:], "Bond Returns", bond_returns.values)
|
543
|
+
```
|
544
|
+
|
545
|
+
```python
|
546
|
+
# Calculate mean and standard deviation using the Table methods
|
547
|
+
average_stock_return_data = stock_table.column("Stock Returns").mean()
|
548
|
+
std_dev_stock_data = stock_table.column("Stock Returns").std()
|
549
|
+
average_bond_return_data = bond_table.column("Bond Returns").mean()
|
550
|
+
std_dev_bond_data = bond_table.column("Bond Returns").std()
|
551
|
+
```
|
552
|
+
|
553
|
+
```python
|
554
|
+
# Display the results
|
555
|
+
print(f"Average Stock Return (Market Data): {average_stock_return_data * 100:.2f}%")
|
556
|
+
print(f"Stock Return Standard Deviation (Market Data): {std_dev_stock_data * 100:.2f}%")
|
557
|
+
print(f"Average Bond Return (Market Data): {average_bond_return_data * 100:.2f}%")
|
558
|
+
print(f"Bond Return Standard Deviation (Market Data): {std_dev_bond_data * 100:.2f}%")
|
559
|
+
```
|
560
|
+
|
561
|
+
## Updating the Monte Carlo Simulation with Market Data !?
|
562
|
+
|
563
|
+
- How do these results compare to the parameters that we hard coded above?
|
564
|
+
- What are the implications for financial planning?
|
565
|
+
- How do the results change if we use real data from the markets?
|
566
|
+
|
567
|
+
```python
|
568
|
+
print( " Actual - Data")
|
569
|
+
print(f"Difference in Average Stock Return: {(average_stock_return - average_stock_return_data) * 100:.2f}%")
|
570
|
+
print(f"Difference in Stock Return Standard Deviation: {(std_dev_stock - std_dev_stock_data) * 100:.2f}%")
|
571
|
+
print(f"Difference in Average Bond Return: {(average_bond_return - average_bond_return_data) * 100:.2f}%")
|
572
|
+
print(f"Difference in Bond Return Standard Deviation: {(std_dev_bond - std_dev_bond_data) * 100:.2f}%")
|
573
|
+
```
|
574
|
+
|
575
|
+
Looks like our hard-coded values were a bit optimistic?
|
576
|
+
- We overestimanted returns
|
577
|
+
- We underestimated volatility
|
578
|
+
|
579
|
+
```python
|
580
|
+
|
581
|
+
```
|
582
|
+
|
583
|
+
```python
|
584
|
+
|
585
|
+
```
|
586
|
+
|
587
|
+
```python
|
588
|
+
|
589
|
+
```
|
590
|
+
|