Questo articolo è la seconda parte della serie di articoli che descrive come simulare l’andamento futuro del proprio modello o portafoglio di finanza personale in Python secondo specifiche variabili di input. Nell’articolo precedente abbiamo modellato alcuni flussi in ingresso e in uscita al patrimonio netto personale. Abbiamo applicato un aumento salariale annuale ai futuri flussi di reddito, oltre ad applicare varie aliquote fiscali a sia al reddito attivo (stipendio) che al reddito da investimenti.
Inoltre abbiamo introdotto un elemento stocastico nel modello per generare casualmente i rendimenti degli investimenti. Usando il rendimento mensile medio storico e la volatilità dell’indice S&P Total Return come proxy del “mercato”. Infine, abbiamo aggiunto un paio di righe di codice per verificare se il valore patrimoniale fosse ridotto a (o sotto) zero in qualsiasi momento durante il periodo di tempo simulato. In questo articolo vediamo come calcolare il “rischio di rovina”, ovvero calcolare le probabilità di ottenere un portafoglio con asset che valgono meno di un valore soglia (il valore per cui è inaccettabile scendere al di sotto) che deve essere maggiore di zero.
Il codice completo mostrato alla fine della Parte 1 è riportato di seguito, con alcune modifiche ai valori di inflows e outflows per riportandoli ai livelli originali.
import pandas as pd
import numpy as np
import random
import yfinance as yf
import datetime
import matplotlib as mpl
import matplotlib.pyplot as plt
# Imposta il random seed in modo da replicare i risultati
np.random.seed(seed=7)
start, end = "2000-12-31", "2020-01-01"
# download dei dati storici
sp = yf.download("^SP500TR", start=start, end=end)
sp_monthly_pct_return = sp.resample('M').last().pct_change().mean().values[0]
sp_monthly_std_dev = sp.resample('M').last().pct_change().std().values[0]
inflows = {'active_annual_income': 50_000,
'starting_assets': 250_000}
outflows = {'rent': 1500,
'credit_card_payment': 750,
'medical_insurance': 250,
'pension_contribution': 500,
'misc': 1500}
variables = {'start_date': "01/01/2020",
'years': 10,
'tax_on_active_income_gains': 0.25,
'avg_ann_income_raise': 0.05,
'avg_ann_inflation': 0.02,
'tax_on_investment_gains': 0.35,
'avg_monthly_market_returns': sp_monthly_pct_return,
'avg_monthly_market_volatility': sp_monthly_std_dev}
income_gains_storage = []
investment_gains_storage = []
assets_starting_list = [inflows['starting_assets']]
assets_ending_list = []
months = variables['years'] * 12
ruined = False
for month in range(months):
if assets_ending_list:
assets_starting_list.append(assets_ending_list[-1])
assets = assets_starting_list[-1]
assets -= (outflows['rent'] + outflows['credit_card_payment'] + \
outflows['medical_insurance'] + outflows['pension_contribution'] + \
outflows['misc'])
if assets <= 0:
inv_gain = 0
ruined = True
break
market_return = np.random.normal(variables['avg_monthly_market_returns'],
variables['avg_monthly_market_volatility'],
1)[0]
investment_return = (assets * market_return) * (1 - variables['tax_on_investment_gains'])
investment_gains_storage.append(investment_return)
assets += investment_return
income = (inflows['active_annual_income'] * (1 - variables['tax_on_active_income_gains'])) / 12
income_gains_storage.append(income)
if (month % 12 == 0):
inflows['active_annual_income'] *= (1 + (variables['avg_ann_income_raise']))
outflows['rent'] *= (1 + (variables['avg_ann_inflation']))
outflows['credit_card_payment'] *= (1 + (variables['avg_ann_inflation']))
outflows['medical_insurance'] *= (1 + (variables['avg_ann_inflation']))
outflows['pension_contribution'] *= (1 + (variables['avg_ann_inflation']))
outflows['misc'] *= (1 + (variables['avg_ann_inflation']))
assets += income
assets_ending = assets
assets_ending_list.append(assets_ending)
plt.plot(pd.Series(assets_ending_list))
plt.xlabel('Month')
plt.ylabel('Ending Asset Value')
plt.show()
Simulare il pensionamento
Vediamo ora come implementare una logica per fissare una data futura in cui prevediamo di andare in pensione e di cessare effettivamente di lavorare. Di conseguenza il nostro reddito attivo (cioè lo stipendio) diminuirà drasticamente, se non scomparirà del tutto. La pensione è una fase importante della vita da pianificare, per molte ragioni. Non sono influenzati solamente i nostri afflussi salariali, ma è probabile che cambierebbero anche molte delle nostre uscite. All’età pensionabile, non è raro che le persone abbiano saldato eventuali debiti ipotecari in sospeso sulle loro case. Quindi non ci saranno più deflussi mensili per i mutui sulla casa e offre altre opportunità come programmi di rilascio di azioni o “ridimensionamento” delle proprietà, ecc.
Della massima importanza è garantire che, al momento in cui andiamo in pensione, abbiamo costruito una base patrimoniale sufficientemente ampia da sostenere le tue esigenze e generare un reddito da investimenti sufficiente a coprire i costi della vita, per tutti gli anni in cui finirai per vivere in pensione.
Tenendo presente questo, iniziamo aggiungendo una data di pensionamento pianificata (inserita come anni da oggi fino al pensionamento). Consideriamo 25 anni nel futuro. Inoltre estendiamo il periodo di simulazione da 10 a 40 anni. Dobbiamo anche aggiungere un paio di righe per verificare se siamo prima o dopo la data di pensionamento prevista e adeguare di conseguenza il reddito mensile (cioè l’entrata dello stipendio).
np.random.seed(seed=7)
start, end = "2000-12-31", "2020-01-01"
sp = yf.download("^SP500TR", start=start, end=end)
sp_monthly_pct_return = sp.resample('M').last().pct_change().mean().values[0]
sp_monthly_std_dev = sp.resample('M').last().pct_change().std().values[0]
inflows = {'active_annual_income': 50_000,
'starting_assets': 250_000}
outflows = {'rent': 1500,
'credit_card_payment': 750,
'medical_insurance': 250,
'pension_contribution': 500,
'misc': 1500}
variables = {'start_date': "01/01/2020",
'years': 40,
'retirement_year': 25,
'tax_on_active_income_gains': 0.25,
'avg_ann_income_raise': 0.05,
'avg_ann_inflation': 0.02,
'tax_on_investment_gains': 0.35,
'avg_monthly_market_returns': sp_monthly_pct_return,
'avg_monthly_market_volatility': sp_monthly_std_dev}
income_gains_storage = []
investment_gains_storage = []
assets_starting_list = [inflows['starting_assets']]
assets_ending_list = []
months = variables['years'] * 12
ruined = False
for month in range(months):
if assets_ending_list:
assets_starting_list.append(assets_ending_list[-1])
assets = assets_starting_list[-1]
assets -= (outflows['rent'] + outflows['credit_card_payment'] + \
outflows['medical_insurance'] + outflows['pension_contribution'] + \
outflows['misc'])
if assets <= 0:
inv_gain = 0
ruined = True
break
market_return = np.random.normal(variables['avg_monthly_market_returns'],
variables['avg_monthly_market_volatility'],
1)[0]
investment_return = (assets * market_return) * (1 - variables['tax_on_investment_gains'])
investment_gains_storage.append(investment_return)
assets += investment_return
# Aggiunge la logica per impostare un salario a 0 quando si va in pensione
if month >= variables['retirement_year'] * 12:
income = 0
else:
income = (inflows['active_annual_income'] * (1 - variables['tax_on_active_income_gains'])) / 12
income_gains_storage.append(income)
if (month % 12 == 0):
inflows['active_annual_income'] *= (1 + (variables['avg_ann_income_raise']))
outflows['rent'] *= (1 + (variables['avg_ann_inflation']))
outflows['credit_card_payment'] *= (1 + (variables['avg_ann_inflation']))
outflows['medical_insurance'] *= (1 + (variables['avg_ann_inflation']))
outflows['pension_contribution'] *= (1 + (variables['avg_ann_inflation']))
outflows['misc'] *= (1 + (variables['avg_ann_inflation']))
assets += income
assets_ending = assets
assets_ending_list.append(assets_ending)
plt.plot(pd.Series(assets_ending_list))
plt.xlabel('Month')
plt.ylabel('Ending Asset Value')
plt.show()
Possiamo vedere chiaramente l’effetto che il calo salariale ha sul valore finale del nostro patrimonio dopo il pensionamento. Come è perfettamente logico, senza stipendio la nostra ricchezza personale e la nostra base patrimoniale diminuiscano nel tempo. Dobbiamo però ancora considerare che i nostri deflussi post-pensionamento sono solitamente molto diversi dalla situazione “normale”, non pensionistica.
Simulare diversi flussi in uscita
Supponiamo che dopo il pensionamento non pagheremo più l’affitto o le rate del mutuo, quindi l’attuale deflusso per l’affitto di 1.500 non si applicherà più una volta raggiunta la data di pensionamento. Potremmo anche immaginare che i costi della nostra assicurazione sanitaria e le spese mediche generali aumenterebbero in modo significativo e potremmo iniziare a ricevere un reddito pensionistico ogni mese. La situazione esatta varierà da individuo a individuo, quindi per ora applichiamo una logica semplice.
np.random.seed(seed=7)
start, end = "2000-12-31", "2020-01-01"
sp = yf.download("^SP500TR", start=start, end=end)
sp_monthly_pct_return = sp.resample('M').last().pct_change().mean().values[0]
sp_monthly_std_dev = sp.resample('M').last().pct_change().std().values[0]
inflows = {'active_annual_income': 50_000,
'starting_assets': 250_000,
'monthly_pension': 1500} # aggiunge l'importo mensile della pensione
outflows = {'rent': 1500,
'credit_card_payment': 750,
'medical_insurance': 250,
'pension_contribution': 500,
'misc': 1500,
'retirement_medical_expenses': 850, # aggiunge la spesa sanitaria dopo la pensione
'retirement_misc': 2000} # aggiunge costi generici dopo la pensione
variables = {'start_date': "01/01/2020",
'years': 40,
'retirement_year': 25,
'tax_on_active_income_gains': 0.25,
'avg_ann_income_raise': 0.05,
'avg_ann_inflation': 0.02,
'tax_on_investment_gains': 0.35,
'avg_monthly_market_returns': sp_monthly_pct_return,
'avg_monthly_market_volatility': sp_monthly_std_dev}
income_gains_storage = []
investment_gains_storage = []
assets_starting_list = [inflows['starting_assets']]
assets_ending_list = []
months = variables['years'] * 12
ruined = False
for month in range(months):
if assets_ending_list:
assets_starting_list.append(assets_ending_list[-1])
assets = assets_starting_list[-1]
# Aggiunge la logica per considerare diversi flussi in uscita dopo il pensionamento
if month >= variables['retirement_year'] * 12:
outflow = outflows['retirement_medical_expenses'] + outflows['retirement_misc']
else:
outflow = (outflows['rent'] + outflows['credit_card_payment'] + \
outflows['medical_insurance'] + outflows['pension_contribution'] + \
outflows['misc'])
assets -= outflow
if assets <= 0:
inv_gain = 0
ruined = True
break
market_return = np.random.normal(variables['avg_monthly_market_returns'],
variables['avg_monthly_market_volatility'],
1)[0]
investment_return = (assets * market_return) * (1 - variables['tax_on_investment_gains'])
investment_gains_storage.append(investment_return)
assets += investment_return
# Aggiunge la logica per impostare un salario a 0 quando si va in pensione
if month >= variables['retirement_year'] * 12:
income = inflows['monthly_pension']
else:
income = (inflows['active_annual_income'] * (1 - variables['tax_on_active_income_gains'])) / 12
income_gains_storage.append(income)
if (month % 12 == 0):
inflows['active_annual_income'] *= (1 + (variables['avg_ann_income_raise']))
outflows['rent'] *= (1 + (variables['avg_ann_inflation']))
outflows['credit_card_payment'] *= (1 + (variables['avg_ann_inflation']))
outflows['medical_insurance'] *= (1 + (variables['avg_ann_inflation']))
outflows['pension_contribution'] *= (1 + (variables['avg_ann_inflation']))
outflows['misc'] *= (1 + (variables['avg_ann_inflation']))
assets += income
assets_ending = assets
assets_ending_list.append(assets_ending)
plt.plot(pd.Series(assets_ending_list))
plt.xlabel('Month')
plt.ylabel('Ending Asset Value')
plt.show()
Struttura del codice
Il codice precedente implementa la logica descritta finora. Tuttavia, sta diventando un po’ disordinato e potrebbe essere ripulito e riordinato. Ci sono alcune parti dello script che potrebbero essere raggruppate come funzioni: quelle per il calcolo dei inflows e outflows in ogni periodo.
def calculate_outflows(month, outflows, variables):
# logica per considerare diversi flussi in uscita DOPO il pensionamento
if month >= variables['retirement_year'] * 12:
outflow = outflows['retirement_medical_expenses'] + outflows['retirement_misc']
else:
# logica per considerare diversi flussi in uscita PRIMA il pensionamento
outflow = (outflows['rent'] + outflows['credit_card_payment'] + \
outflows['medical_insurance'] + outflows['pension_contribution'] + \
outflows['misc'])
# ogni anno incrementa le uscite secondo il tasso d'inflazione
if (month % 12 == 0) and (month > 0):
outflows['rent'] *= (1 + (variables['avg_ann_inflation']))
outflows['credit_card_payment'] *= (1 + (variables['avg_ann_inflation']))
outflows['medical_insurance'] *= (1 + (variables['avg_ann_inflation']))
outflows['pension_contribution'] *= (1 + (variables['avg_ann_inflation']))
outflows['misc'] *= (1 + (variables['avg_ann_inflation']))
return outflow
def calculate_income(month, inflows, variables):
# logica per considerare diversi flussi in uscita DOPO il pensionamento
if month >= variables['retirement_year'] * 12:
income = inflows['monthly_pension']
else:
# logica per considerare diversi flussi in uscita PRIMA il pensionamento
income = (inflows['active_annual_income'] * (1 - variables['tax_on_active_income_gains'])) / 12
if (month % 12 == 0) and (month > 0):
inflows['active_annual_income'] *= (1 + (variables['avg_ann_income_raise']))
return income
Potremmo anche prendere il codice usato per calcolare il reddito mensile da investimenti e trasformarlo in una funzione. In questo modo possiamo aggiungere facilmente maggiore complessità alla logica, se necessario.
def calculate_investment_gains(assets, variables):
if assets <= 0:
inv_gains = 0
else:
market_return = np.random.normal(variables['avg_monthly_market_returns'],
variables['avg_monthly_market_volatility'],
1)[0]
inv_gains = assets * market_return
return inv_gains
Incorporiamo le funzioni nello script principale.
if __name__ == "__main__":
np.random.seed(seed=7)
start, end = "2000-12-31", "2020-01-01"
sp = yf.download("^SP500TR", start=start, end=end)
sp_monthly_pct_return = sp.resample('M').last().pct_change().mean().values[0]
sp_monthly_std_dev = sp.resample('M').last().pct_change().std().values[0]
inflows = {'active_annual_income': 50_000,
'starting_assets': 250_000,
'monthly_pension': 1500}
outflows = {'rent': 1500,
'credit_card_payment': 750,
'medical_insurance': 250,
'pension_contribution': 500,
'misc': 1500,
'retirement_medical_expenses': 850,
'retirement_misc': 2000}
variables = {'start_date': "01/01/2020",
'years': 40,
'retirement_year': 25,
'tax_on_active_income_gains': 0.25,
'avg_ann_income_raise': 0.05,
'avg_ann_inflation': 0.02,
'tax_on_investment_gains': 0.35,
'avg_monthly_market_returns': sp_monthly_pct_return,
'avg_monthly_market_volatility': sp_monthly_std_dev}
income_gains_storage = []
investment_gains_storage = []
assets_starting_list = [inflows['starting_assets']]
assets_ending_list = []
months = variables['years'] * 12
ruined = False
for month in range(months):
if assets_ending_list:
assets_starting_list.append(assets_ending_list[-1])
assets = assets_starting_list[-1]
# calola le uscite tramite la funzione
outflow = calculate_outflows(month, outflows, variables)
assets -= outflow
# Modificato il blocco "if" per includere la nuova funzione
if assets <= 0:
ruined = True
break
# usa la funzione per calcolare i rendimenti dell'investimento
investment_return = calculate_investment_gains(assets, variables)
investment_gains_storage.append(investment_return)
assets += investment_return
# calcola le entrate tramite la funzione
income = calculate_income(month, inflows, variables)
income_gains_storage.append(income)
assets += income
assets_ending = assets
assets_ending_list.append(assets_ending)
plt.plot(pd.Series(assets_ending_list))
plt.xlabel('Month')
plt.ylabel('Ending Asset Value')
plt.show()
Sottolineiamo che aver spostato il calcolo dei rendimenti mensili all’interno di una funzione senza includere l’impostazione del valore del seed di numpy all’interno di tale funzione significa che otterremo risultati leggermente diversi, ogni volta che la eseguiamo. Non abbiamo incluso il seed all’interno della funzione perchè in questo caso non vogliamo che il rendimento del mercato di ogni mese sia esattamente lo stesso.
Codice completo
In questo articolo abbiamo descritto come implementare un portafoglio di finanza personale in Python. Per il codice completo riportato in questo articolo, si può consultare il seguente repository di github:
https://github.com/datatrading-info/Asset_Management