Skip to content

Instantly share code, notes, and snippets.

@pdavidsonFIA
Last active April 25, 2021 07:43
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save pdavidsonFIA/7ae9c4bafca5bfc69198f92db4c70f2a to your computer and use it in GitHub Desktop.
Save pdavidsonFIA/7ae9c4bafca5bfc69198f92db4c70f2a to your computer and use it in GitHub Desktop.
__author__ = """Peter Davidson FIA"""
__email__ = 'peter.davidson@wefox.com'
__version__ = '0.1'
__license__ = 'If you find this helpful, please say thank you'
import pandas as pd, numpy as np
def shear_array(df):
# This function just shears an array
# E.g. x = np.arange(9).reshape(3,3)
# shear_array(x)
rows, cols = df.shape
# Add padding on right of array to make space for shear
temp = np.pad(df, ((0,0), (0,max(rows-cols,rows-1))), mode='constant')
y = np.arange(rows)
y[y < 0] += temp.shape[1]
rx, colx = np.ogrid[:temp.shape[0], :temp.shape[1]]
colx = colx - y[:, np.newaxis]
return temp[rx, colx]
# Dictionary of the different models to illustrate:
models = {
0: {'title': 'Basic cashflows, no reserves',
'upr_fac': 0,
'resv_fac': 0,
'ibnr_fac': 0,
'dac_fac': 0
},
1: {'title': 'UPR to spread premium earnings',
'upr_fac': 1,
'resv_fac': 0,
'ibnr_fac': 0,
'dac_fac': 0
},
2: {'title': 'Reserves recognise loss when reported',
'upr_fac': 1,
'resv_fac': 1,
'ibnr_fac': 0,
'dac_fac': 0
},
3: {'title': 'IBNR recognises loss when incurred',
'upr_fac': 1,
'resv_fac': 1,
'ibnr_fac': 1,
'dac_fac': 0
},
4: {'title': 'DAC to spread commission',
'upr_fac': 1,
'resv_fac': 1,
'ibnr_fac': 1,
'dac_fac': 1
}
}
# Basic product information:
prod1 = {
'premium': 100,
'loss_ratio': 0.7,
'term': 12,
'pattern_payment': 18,
'pattern_reporting': 6,
'comm_rate': 0.10
}
# Cashflows for a model:
def model_cashflows(prod_data, upr_fac, resv_fac, ibnr_fac, dac_fac):
proj_horiz = range(30)
prod = pd.DataFrame(index=proj_horiz)
prod.at[0, 'gwp_i'] = prod_data['premium']
prod.gwp_i = prod.gwp_i.fillna(0)
prod['gwp'] = prod.gwp_i.cumsum()
# Earnings before factor:
prod.at[0, 'upr'] = -prod_data['premium']
prod.at[prod_data['term'], 'upr'] = 0
prod.upr = prod.upr.interpolate()
prod['d_upr'] = prod.upr.diff().fillna(prod.upr)
prod['gep_i'] = prod[['gwp_i', 'd_upr']].sum(axis=1)
prod['gep'] = prod.gep_i.cumsum()
prod['ult_i'] = - prod.gep_i * prod_data['loss_ratio']
# Earnings after factor:
prod.upr = prod.upr * upr_fac
prod['d_upr'] = prod.d_upr * upr_fac
prod['gep_i'] = prod[['gwp_i', 'd_upr']].sum(axis=1)
prod['gep'] = prod.gep_i.cumsum()
prod['comm_i'] = - prod.gwp_i * prod_data['comm_rate']
prod['commission'] = prod.comm_i.cumsum()
prod.at[0, 'dac'] = prod_data['premium'] * prod_data['comm_rate'] * dac_fac
prod.at[prod1['term'], 'dac'] = 0
prod.dac = prod.dac.interpolate()
prod['d_dac'] = prod.dac.diff().fillna(prod.dac)
patt_claims = np.ones(prod_data['pattern_payment']) / prod_data['pattern_payment']
claims = np.outer(prod.ult_i, patt_claims)
claims = shear_array(claims)
claims = pd.DataFrame(claims).melt()
claims.columns = ['proj', 'claims_i']
claims = claims.groupby(by='proj').sum().iloc[:len(proj_horiz)-1]
patt_inc = np.ones(prod_data['pattern_reporting']) / prod_data['pattern_reporting']
inc = np.outer(prod.ult_i, patt_inc)
inc = shear_array(inc)
inc = pd.DataFrame(inc).melt()
inc.columns = ['proj', 'inc_i']
inc = inc.groupby(by='proj').sum().iloc[:len(proj_horiz)-1]
prod = pd.concat((prod, claims, inc), axis=1)
prod = prod.fillna(0)
prod['claims'] = prod.claims_i.cumsum()
prod['inc'] = prod.inc_i.cumsum()
prod['ult'] = prod.ult_i.cumsum()
prod['reserves'] = (prod.inc - prod.claims) * resv_fac
prod['d_reserves'] = prod.reserves.diff().fillna(prod.reserves)
prod['ibnr'] = (prod.ult - prod.inc) * ibnr_fac
prod['d_ibnr'] = prod.ibnr.diff().fillna(prod.ibnr)
prod['profit'] = prod[['gwp', 'upr', 'commission', 'dac', 'claims', 'reserves', 'ibnr']].sum(axis=1)
prod['cash'] = prod[['gwp', 'commission', 'claims']].sum(axis=1)
profit = prod[['gwp', 'upr', 'gep', 'commission', 'dac', 'claims', 'reserves', 'ibnr', 'profit']].copy()
asset_positions = ['cash', 'dac']
liability_positions = ['upr', 'reserves', 'ibnr']
bs = prod[asset_positions + liability_positions].copy()
bs['equity'] = bs.sum(axis=1)
bs[liability_positions] = -1 * bs[liability_positions]
bs['liabilities'] = bs[liability_positions].sum(axis=1)
bs['assets'] = bs[asset_positions].sum(axis=1)
bs['dummy_zero'] = 0
# cashflow = prod[['gwp_i', 'comm_i', 'claims_i']].sum(axis=1)
cashflow = bs.cash
# profit_m = prod[['gwp_i', 'd_upr', 'comm_i', 'd_dac', 'claims_i', 'd_reserves', 'd_ibnr']].sum(axis=1)
profit_m = profit.profit
return cashflow, profit_m, bs, profit
# Run each model and save results:
cashflow_results = []
profit_m_results = []
bs_results = []
profit_results = []
for i in models:
graph_data = model_cashflows(
prod1,
models[i]['upr_fac'],
models[i]['resv_fac'],
models[i]['ibnr_fac'],
models[i]['dac_fac']
)
cashflow_results.append(graph_data[0])
profit_m_results.append(graph_data[1])
bs_results.append(graph_data[2])
profit_results.append(graph_data[3])
import matplotlib.pyplot as plt
import matplotlib._color_data as mcd
from matplotlib.animation import FuncAnimation
import matplotlib.animation as animation
def bar_chart_data(df, stocks=None):
if stocks is None:
stocks = []
cols = len(df)
bottom = df.copy()
bottom.iloc[:] = 0
tops = df.copy()
tops.iloc[:] = 0
for c in range(cols):
if c == 0 or df.index[c] in stocks:
bottom.iloc[c] = 0
tops.iloc[c] = max(0, df.iloc[c])
else:
bottom.iloc[c] = bottom.iloc[c - 1] + df.iloc[c - 1]
tops.iloc[c] = tops.iloc[c - 1] + min(0, df.iloc[c - 1]) + max(0, df.iloc[c])
return bottom, tops
def get_waterfall_colours(df, stocks=None):
"""Returns color palette for drawing waterfall bars"""
default_colours = mcd.XKCD_COLORS
index_rgb = ['xkcd:orangered', 'xkcd:green','xkcd:blue', 'xkcd:brown'] # Red, green, blue, brown
palette_subset = [default_colours[i] for i in index_rgb]
colour_palette = []
for index, value in df.items():
if index in stocks:
if value >= 0:
idx = 2 # Blue base
else:
idx = 3
elif value >= 0:
idx = 1 # Green uptick
else:
idx = 0 # Red downtick
colour_palette.append(palette_subset[idx])
return colour_palette
def colour_red_green(x):
return 'xkcd:green' if x >= 0 else 'xkcd:red'
# plt.style.use('ggplot')
plt.xkcd()
# Set up a plot space:
fig = plt.figure(figsize=(8, 8))
# Monthly cashflows, balance sheet and cumulative p&l:
ax_cash = plt.subplot(421)
ax_cash.set(xlim=(0, 30), ylim=(0, 100))
# ax_cash.set(xlim=(-10, 10))
ax_cash.yaxis.set_visible(True)
ax_pm = plt.subplot(423)
ax_pm.set(xlim=(0, 30), ylim=(-20, 70))
ax_pm.yaxis.set_visible(True)
ax_bs = plt.subplot(222)
ax_bs.set(ylim=(0, 120))
ax_pl = plt.subplot(212)
ax_pl.set( ylim=(-10, 100))
# Set titles for each of the plots:
ax_cash.set_title('cumulative cashflows')
ax_pm.set_title('cumulative profit')
ax_pl.set_title('profit and loss')
ax_bs.set_title('balance sheet')
# Initial commentary:
model_key = 0
fig.suptitle(models[model_key]['title'], fontsize=20)
start_time = 0
# Cashflow chart
cash_line, = ax_cash.plot(0, 0)
profit_line, = ax_pm.plot(0, 0)
# cf_colors = [colour_red_green(i) for i in cashflow_results[model_key]]
# rects_cf = ax_cash.barh(1, cashflow_results[model_key][start_time], color=cf_colors[start_time])
# pm_colors = [colour_red_green(i) for i in profit_m_results[model_key]]
# rects_pm = ax_pm.barh(1, profit_m_results[model_key][start_time], color=pm_colors[start_time])
# P&L chart:
labels = list(profit_results[model_key].columns)
bar_bottoms = bar_chart_data(profit_results[model_key].iloc[start_time], stocks=['gep', 'profit'])[0]
bar_colours = get_waterfall_colours(profit_results[4].iloc[1], stocks=['gep', 'profit'])
rects_pl = ax_pl.bar(labels,
height=[i for i in profit_results[model_key].iloc[start_time]],
width=0.35,
bottom=bar_bottoms,
color=bar_colours)
# Balance sheet chart:
bs_1 = bs_results[model_key][['cash', 'liabilities', 'upr']]
bs_2 = bs_results[model_key][['dac', 'equity', 'reserves']]
bs_3 = bs_results[model_key][['dummy_zero', 'dummy_zero', 'ibnr']]
bs1_start = [i for i in bs_1.iloc[start_time]]
bs2_start = [i for i in bs_2.iloc[start_time]]
bs3_start = [i for i in bs_3.iloc[start_time]]
bs3_bottoms_start = [bs1_start[i] + bs2_start[i] for i in range(3)]
x_labels = ['assets', 'equity', 'liabilities']
rects_bs1 = ax_bs.bar(
x_labels,
height=bs1_start,
width=0.35,
color=['xkcd:blue', 'xkcd:white', 'xkcd:indigo'],
label='Women')
rects_bs2 = ax_bs.bar(
x_labels,
height=bs2_start,
width=0.35,
color=['xkcd:violet', colour_red_green(bs2_start[1]), 'xkcd:magenta'],
bottom=bs1_start, label='Women')
rects_bs3 = ax_bs.bar(
x_labels,
height=bs3_start,
width=0.35,
color=['xkcd:blue', 'xkcd:white', 'xkcd:lavender'],
bottom=bs3_bottoms_start, label='Women')
# Initialize the annotations:
asset_labels = ['cash', 'dac']
liability_labels=['upr', 'reserves', 'ibnr']
an1 = ax_bs.annotate(asset_labels[0],
(0, rects_bs1[0]._height/2),
textcoords="offset points",
xytext=(0.2, 0))
an2 = ax_bs.annotate(asset_labels[1],
(0, rects_bs1[0]._height + rects_bs2[0]._height/ 2),
textcoords="offset points",
xytext=(0.2, 0))
an3 = ax_bs.annotate(liability_labels[0],
(2, rects_bs1[2]._height / 2),
textcoords="offset points",
xytext=(-0.2, 0))
an4 = ax_bs.annotate(liability_labels[1],
(2, rects_bs1[2]._height + rects_bs2[2]._height/ 2),
textcoords="offset points",
xytext=(-0.2, 0))
an5 = ax_bs.annotate(liability_labels[2],
(2, rects_bs1[2]._height + rects_bs2[2]._height +rects_bs3[2]._height/ 2),
textcoords="offset points",
xytext=(-0.2, 0))
def update_bs_annotations():
an1.xy = (0, rects_bs1[0]._height/2)
an2.xy = (0, rects_bs1[0]._height + rects_bs2[0]._height / 2)
an3.xy = (2, rects_bs1[2]._height / 2)
an4.xy = (2, rects_bs1[2]._height + rects_bs2[2]._height / 2)
an5.xy = (2, rects_bs1[2]._height + rects_bs2[2]._height + rects_bs3[2]._height / 2)
plt.tight_layout()
# Text box to show time interval:
time_text = ax_bs.text(0.3, -0.3, 'time=', transform=ax_bs.transAxes, size=16)
time_text.set_text('time= ' + str(start_time))
# Text on product info
product_text = ax_bs.text(-0.2, -0.5, 'Premium: 100, loss ratio: 70%, commission: 10%', transform=ax_bs.transAxes, size=12)
# ax.legend((eur_line, chf_line, gbp_line), ('eur', 'chf', 'gbp'), loc='lower right')
time_key = start_time
# Add pauses to start and end
pad_start = 10
pad_end = 10
proj_periods = 30
model_interval = pad_start + proj_periods + pad_end
def animate(i):
global bs_1, bs_2, bs_3
global model_key, time_key
global pad_start, proj_periods, model_interval
time_key = i % model_interval
time_key = min(max(time_key - pad_start, 0), proj_periods-1)
time_text.set_text('time: ' + str(time_key))
if i % model_interval == 0:
model_key = min(int(i/model_interval), len(models.keys())-1)
fig.suptitle(models[model_key]['title'], fontsize=20)
bs_1 = bs_results[model_key][['cash', 'liabilities', 'upr']]
bs_2 = bs_results[model_key][['dac', 'equity', 'reserves']]
bs_3 = bs_results[model_key][['dummy_zero', 'dummy_zero', 'ibnr']]
# update cashflow:
cash_line.set_data(range(time_key), cashflow_results[model_key][:time_key])
# rects_cf[0].set_width(cashflow_results[model_key][time_key])
# rects_cf[0].set_color(cf_colors[key])
profit_line.set_data(range(time_key), profit_m_results[model_key][:time_key])
# rects_pm[0].set_width(profit_m_results[model_key][time_key])
# rects_pm[0].set_color(pm_colors[key])
# update bs:
new_bs1 = [j for j in bs_1.iloc[time_key]]
new_bs2 = [j for j in bs_2.iloc[time_key]]
new_bs3 = [j for j in bs_3.iloc[time_key]]
new_bs3_bottoms = [new_bs1[j] + new_bs2[j] for j in range(3)]
for k in range(3):
rects_bs1[k].set_height(new_bs1[k])
rects_bs2[k].set_height(new_bs2[k])
rects_bs2[k].set_y(new_bs1[k])
rects_bs2[1].set_color(colour_red_green(new_bs2[1])) # Change colour of equity bar
rects_bs3[k].set_height(new_bs3[k])
rects_bs3[k].set_y(new_bs3_bottoms[k])
update_bs_annotations()
# Update p&l waterfall:
new_heights = [j for j in profit_results[model_key].iloc[time_key]]
new_bottoms = bar_chart_data(profit_results[model_key].iloc[time_key], stocks=['gep', 'profit'])[0]
for k in range(len(rects_pl)):
rects_pl[k].set_height(new_heights[k])
rects_pl[k].set_y(new_bottoms[k])
anim = FuncAnimation(
fig, animate, interval=100, frames=len(profit_results)*model_interval)
plt.draw()
plt.show()
fldr =r'C:\Users\peter\Desktop\\'
# anim.save(fldr + 'finance.gif', writer='imagemagick')
# Set up formatting for the movie files
Writer = animation.writers['ffmpeg']
writer = Writer(fps=8, metadata=dict(artist='Peter Davidson'), bitrate=1800)
anim.save(fldr + 'finance.mp4', writer=writer)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment