Simulare un portafoglio di finanza personale in Python – Parte 2

Simulare un portafoglio di finanza personale in Python – Parte 2

Sommario

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 un portafoglio di finanza personale in Python – Parte 2

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()
				
			
Ottimizzazione-Portfolio-valore-finale-asset-pensione

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()
				
			
Ottimizzazione-Portfolio-valore-finale-asset-pensione-outflows

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

Benvenuto su DataTrading!

Sono Gianluca, ingegnere software e data scientist. Sono appassionato di coding, finanza e trading. Leggi la mia storia.

Ho creato DataTrading per aiutare le altre persone ad utilizzare nuovi approcci e nuovi strumenti, ed applicarli correttamente al mondo del trading.

DataTrading vuole essere un punto di ritrovo per scambiare esperienze, opinioni ed idee.

SCRIVIMI SU TELEGRAM

Per informazioni, suggerimenti, collaborazioni...

Torna in alto
Scroll to Top