2.13.7. Enterprise Risk Analysis

This section re-analyzes reinsurance structure alternatives introduced in Brehm et al. [2007], Enterprise Risk Analysis for Property & Liability Insurance Companies. This book is the ERM text on the syllabus for CAS Exam Part 7.

2.13.7.1. Reinsurance Example

This section analyzes the example given in Section 2.5 of Enterprise Risk Analysis.

Assumptions. ABCD writes 33M excess property and casualty business.

  • ABCD total gross

    • Loss ratio: 69.36%

    • Expense ratio: 23%

    • Combined ratio: 92.36%

    • Margin 2.52M

  • Casualty

    • 14M premium

    • 78% expected loss ratio

    • 5M limits

    • 4M xs 1M reinsurance, ceded premium 4.41M

  • Property

    • 19M premium

    • 63% expected loss ratio

    • 20M limits

    • 17M xs 3M per risk reinsurance, ceded premium 2.36M

    • 95% share of 24M xs 1M cat reinsurance, ceded premium 1.53M with 1@100%

    • Cat program designed to cover to 250-year event.

  • Reinsurance total

    • Average recoveries 5.08M

    • Ceded premium 8.3M

    • Net premium 24.7M

  • Mythical alternative program

    • Stop-loss, 20 xs 30, ceded premium 1.98

Additional assumptions. There are no details of the stochastic model, so we assume

  • Frequency and severity models per DecL below,

  • Split the property losses into cat and non-cat by assuming that cat premium equals 2M, non-cat premium 17M, at the same loss ratios (this is just a split of losses, the by line loss ratios are not used), and

  • Cat, non-cat and casualty are independent.

  • Free and unlimited reinstatements on the catastrophe protection. See REF for a discussion of reinstatements.

2.13.7.2. Stochastic Models and Baseline Analysis

Construct the gross and net portfolios. All amounts in millions.

2.13.7.2.1. Gross Portfolio

The 250-year cat PML is printed last, to compare with the 25M program.

In [1]: from aggregate import build, qd, mv

In [2]: import pandas as pd

In [3]: import matplotlib.pyplot as plt

In [4]: abcd = build('port ABCD '
   ...:          'agg Casualty 14.0 premium at 78% lr '
   ...:              '5 xs 0 '
   ...:              'sev lognorm 0.1 cv 10 '
   ...:              'mixed gamma 0.3 '
   ...:          'agg PropertyNC 17.0 premium at 63% lr '
   ...:              '25 xs 0 '
   ...:              'sev lognorm [0.1 1] cv [5 10] wts [.7 .3] '
   ...:              'mixed gamma 0.1 '
   ...:          'agg PropertyC 2.0 premium at 63% lr '
   ...:              '150 xs 0 '
   ...:              'sev 3 * pareto 2.375 - 3 '
   ...:              'poisson ', bs=1/128, approximation='exact')
   ...: 

In [5]: qd(abcd)

                    E[X] Est E[X]    Err E[X]   CV(X) Est CV(X)  Skew(X) Est Skew(X)
unit       X                                                                        
Casualty   Freq   125.93                      0.31296            0.60054            
           Sev  0.086717 0.086436  -0.0032438  4.0147    4.0286   9.5592      9.5552
           Agg     10.92   10.885  -0.0032438 0.47532   0.47626  0.88481     0.88565
PropertyNC Freq   34.918                      0.19657            0.24744            
           Sev   0.30672  0.30659 -0.00041535    4.91    4.9121   11.569      11.569
           Agg     10.71   10.706 -0.00041535 0.85384   0.85419   1.9358      1.9358
PropertyC  Freq   0.5801                       1.3129             1.3129            
           Sev     2.172    2.172 -9.2692e-07  2.0207    2.0207   11.511      11.511
           Agg      1.26     1.26 -9.2692e-07  2.9601    2.9601   12.398      12.398
total      Freq   161.42                      0.24785            0.57516            
           Sev    0.1418  0.14155  -0.0017419  5.8043             23.386            
           Agg     22.89    22.85  -0.0017419 0.48741   0.48813   1.6182      1.6193
log2 = 16, bandwidth = 1/128, validation: fails sev mean, agg mean.

In [6]: mv(abcd)
mean     = 22.89
variance = 124.4768
std dev  = 11.1569

In [7]: print(abcd['PropertyC'].q(0.996))
22.859375

2.13.7.2.2. Net Portfolio

In [8]: abcd_net = build('port ABCD:Net '
   ...:          'agg Casualty 14.0 premium at 78% lr '
   ...:              '5 xs 0 '
   ...:              'sev lognorm 0.1 cv 10 '
   ...:              'occurrence net of 4 xs 1 '
   ...:              'mixed gamma 0.3 '
   ...:          'agg PropertyNC 17.0 premium at 63% lr '
   ...:              '25 xs 0 '
   ...:              'sev lognorm [0.1 1] cv [5 10] wts [.7 .3] '
   ...:              'occurrence net of 17 xs 3 '
   ...:              'mixed gamma 0.1 '
   ...:          'agg PropertyC 2.0 premium at 63% lr '
   ...:              '150 xs 0 '
   ...:              'sev 3 * pareto 2.375 - 3 '
   ...:              'occurrence net of 24 xs 1 '
   ...:              'poisson ', bs=1/128, approximation='exact')
   ...: 

In [9]: qd(abcd_net)

                    E[X] Est E[X] Err E[X]   CV(X) Est CV(X)  Skew(X) Est Skew(X)
unit       X                                                                     
Casualty   Freq   125.93                   0.31296            0.60054            
           Sev  0.086717 0.065564 -0.24393  4.0147    2.5318   9.5592      4.1714
           Agg     10.92   8.2563 -0.24393 0.47532    0.3858  0.88481     0.65534
PropertyNC Freq   34.918                   0.19657            0.24744            
           Sev   0.30672   0.2098 -0.31597    4.91    2.8604   11.569      6.1847
           Agg     10.71   7.3259 -0.31597 0.85384   0.52244   1.9358      1.0361
PropertyC  Freq   0.5801                    1.3129             1.3129            
           Sev     2.172  0.80417 -0.62976  2.0207    2.7345   11.511      36.228
           Agg      1.26   0.4665 -0.62976  2.9601    3.8228   12.398      40.649
total      Freq   161.42                   0.24785            0.57516            
           Sev    0.1418 0.099419 -0.29888  5.8043             23.386            
           Agg     22.89   16.049 -0.29888 0.48741   0.32957   1.6182      2.0938
log2 = 16, bandwidth = 1/128, validation: n/a, reinsurance.

In [10]: qd(abcd_net.est_sd)
5.2892

2.13.7.2.3. Ceded Portfolio

In [11]: abcd_ceded = build('port ABCD:Ceded '
   ....:          'agg Casualty 14.0 premium at 78% lr '
   ....:              '5 xs 0 '
   ....:              'sev lognorm 0.1 cv 10 '
   ....:              'occurrence ceded to 4 xs 1 '
   ....:              'mixed gamma 0.3 '
   ....:          'agg PropertyNC 17.0 premium at 63% lr '
   ....:              '25 xs 0 '
   ....:              'sev lognorm [0.1 1] cv [5 10] wts [.7 .3] '
   ....:              'occurrence ceded to 17 xs 3 '
   ....:              'mixed gamma 0.1 '
   ....:          'agg PropertyC 2.0 premium at 63% lr '
   ....:              '150 xs 0 '
   ....:              'sev 3 * pareto 2.375 - 3 '
   ....:              'occurrence ceded to 24 xs 1 '
   ....:              'poisson ', bs=1/128, approximation='exact')
   ....: 

In [12]: qd(abcd_ceded)

                    E[X] Est E[X] Err E[X]   CV(X) Est CV(X)  Skew(X) Est Skew(X)
unit       X                                                                     
Casualty   Freq   125.93                   0.31296            0.60054            
           Sev  0.086717 0.020872 -0.75931  4.0147    11.205   9.5592      14.019
           Agg     10.92   2.6283 -0.75931 0.47532    1.0464  0.88481      1.3572
PropertyNC Freq   34.918                   0.19657            0.24744            
           Sev   0.30672 0.096787 -0.68444    4.91    10.657   11.569       13.51
           Agg     10.71   3.3796 -0.68444 0.85384    1.8142   1.9358      2.3095
PropertyC  Freq   0.5801                    1.3129             1.3129            
           Sev     2.172   1.3679 -0.37024  2.0207     2.254   11.511      4.2512
           Agg      1.26   0.7935 -0.37024  2.9601    3.2375   12.398      5.6851
total      Freq   161.42                   0.24785            0.57516            
           Sev    0.1418 0.042134 -0.70286  5.8043             23.386            
           Agg     22.89   6.8014 -0.70286 0.48741    1.0577   1.6182      1.7643
log2 = 16, bandwidth = 1/128, validation: n/a, reinsurance.

In [13]: qd(abcd_ceded.est_sd)
7.1941

2.13.7.2.4. Reinsurance Summary

The bottom table shows expected losses, counts, severity, loss ratios and margins implicit in the given reinsurance structure, pricing, and the gross stochastic model. The non-cat property reinsurance has the highest ceded loss ratio and the cat program the lowest.

In [14]: re_all = pd.concat((a.reinsurance_occ_layer_df for a in abcd_net),
   ....:     keys=abcd_net.unit_names, names=['unit', 'share', 'limit', 'attach']); \
   ....: re_all = re_all.drop('gup', axis=0, level=3); \
   ....: qd(re_all, sparsify=False)
   ....: 

stat                              ex     ex      ex     cv     cv      cv      en severity     pct
view                           ceded    net subject  ceded    net subject   ceded    ceded   ceded
unit       share limit attach                                                                     
Casualty   1.0   4.0   1.0    2.6283 8.2563  10.885 11.205 2.5318  4.0286   2.007   1.3096 0.24147
PropertyNC 1.0   17.0  3.0    3.3796 7.3259  10.706 10.657 2.8604  4.9121 0.65611    5.151 0.31569
PropertyC  1.0   24.0  1.0    0.7935 0.4665    1.26  2.254 2.7345  2.0207 0.29294   2.7088 0.62976

In [15]: re_summary = re_all.iloc[:, [0, 3, 6, 7]]; \
   ....: re_summary.columns = ['ex', 'cv', 'en', 'severity']; \
   ....: re_summary['premium'] = [4.41, 2.36, 1.53]; \
   ....: re_summary['lr'] = re_summary.ex / re_summary.premium; \
   ....: re_summary['margin'] = re_summary.premium - re_summary.ex; \
   ....: qd(re_summary)
   ....: 

                                  ex     cv      en  severity  premium      lr  margin
unit       share limit attach                                                         
Casualty   1.0   4.0   1.0    2.6283 11.205   2.007    1.3096     4.41 0.59599  1.7817
PropertyNC 1.0   17.0  3.0    3.3796 10.657 0.65611     5.151     2.36   1.432 -1.0196
PropertyC  1.0   24.0  1.0    0.7935  2.254 0.29294    2.7088     1.53 0.51863  0.7365

2.13.7.2.5. Underwriting Result Distributions

Make the underwriting result distributions, including the proposed stop loss reinsurance (computed by hand). The dataframe compare accumulates the gross, ceded, and net probability mass functions. We use these to determine statistics and to plot.

In [16]: compare = abcd.density_df[['loss', 'p_total']]; \
   ....: compare.columns = ['loss', 'gross']; \
   ....: compare['gross_uw'] = 33 - compare.loss; \
   ....: compare['net_current'] = abcd_net.density_df.p_total; \
   ....: compare['net_current_uw'] = 33 - 4.41 - 2.36 - 1.53 - compare.loss;
   ....: 

In [17]: from aggregate import make_ceder_netter

In [18]: compare['net_stoploss'] = abcd.density_df.p_total; \
   ....: c, n = make_ceder_netter([(1, 20, 30)]); \
   ....: compare['nsll'] = n(compare.loss); \
   ....: g = compare.groupby('nsll').net_stoploss.sum(); \
   ....: compare['net_stoploss'] = 0.0; \
   ....: compare.loc[g.index, 'net_stoploss'] = g; \
   ....: compare['net_stoploss_uw'] = 33 - 1.98 - compare.loss;
   ....: 

2.13.7.2.6. Comparison with ERA Book Figures

Statistics summary, compare Figure 2.5.2.

In [19]: from aggregate import MomentWrangler

In [20]: from scipy.interpolate import interp1d

In [21]: ans = []; cdfs = []

In [22]: for xs, den in [(compare.gross_uw, compare.gross), (compare.net_current_uw, compare.net_current),
   ....:                  (compare.net_stoploss_uw, compare.net_stoploss)]:
   ....:     xd = xs * den
   ....:     ex1 = np.sum(xd)
   ....:     xd *= xs
   ....:     ex2 = np.sum(xd)
   ....:     ex3 = np.sum(xd * xs)
   ....:     mw = MomentWrangler()
   ....:     mw.noncentral = ex1, ex2, ex3
   ....:     ans.append(mw)
   ....:     cdfs.append(interp1d(den.cumsum(), xs))
   ....: 

In [23]: fig_252 = pd.concat([i.stats for i in ans], keys=['Gross', 'Current', 'StopLoss'], axis=1)

In [24]: for p in [0.01, 0.99]:
   ....:     fig_252.loc[f'q({p})'] = [float(i(p)) for i in cdfs]
   ....: 

In [25]: qd(fig_252)

          Gross  Current  StopLoss
ex        10.15   8.6513    10.021
var      124.41   27.975    58.777
sd       11.154   5.2892    7.6666
cv       1.0989  0.61137   0.76502
skew    -1.6193  -2.0938   -1.0088
q(0.01)   25.98   18.081        24
q(0.99)  -25.76  -5.6036   -7.7399

Plot of densities and distributions, compare Figure 2.5.3 and 2.5.4.

In [26]: fig, axs = plt.subplots(1, 3, figsize=(3 * 3.5, 2.45), constrained_layout=True)

In [27]: ax0, ax1, ax2 = axs.flat

In [28]: for ax in [ax0, ax1]:
   ....:     ax.plot(compare.gross_uw, compare.gross, label='Gross')
   ....:     ax.plot(compare.net_current_uw, compare.net_current, label='Net, current')
   ....:     yl = ax.get_ylim()
   ....:     ax.plot(compare.net_stoploss_uw, compare.net_stoploss, label='Net, stop loss')
   ....:     ax.legend(loc='upper left')
   ....:     ax.set(xlim=[-45, 30], ylim=yl)
   ....:     ax.axvline(0, lw=.25, c='C7')
   ....: 

In [29]: ax1.set(yscale='log', ylim=[1e-9, 1], title='Log density'); \
   ....: ax0.set(title='Mixed density/mass function');
   ....: 

In [30]: ax2.plot(compare.gross_uw, 1 - compare.gross.cumsum(), label='Gross'); \
   ....: ax2.plot(compare.net_current_uw, 1 - compare.net_current.cumsum(), label='Net, current'); \
   ....: ax2.plot(compare.net_stoploss_uw, 1 - compare.net_stoploss.cumsum(), label='Net, stop loss'); \
   ....: ax2.legend(loc='upper left'); \
   ....: ax2.set(xlim=[-45, 30], ylim=[-0.025, 1.025]);
   ....: 

In [31]: ax2.axvline(0, lw=.25, c='C7');
../../_images/gc253.png

Numerical distribution of underwriting results at various return points, compare Figure 2.5.5. Given there was no information about the stochastic model provided, and the model here is based on common benchmarks, the agreement between the two distributions is striking.

In [32]: fig_255 = pd.DataFrame(columns=['Gross', 'Current', 'StopLoss'], dtype=float)

In [33]: for p in [.0025, .005, 0.0075, .01, .0125, .015, .0175, .02,
   ....:           .04, .06, .08, .1, .12, .14, .16, .18, .2, .22, .24,
   ....:           .25, .26, .28, .3, .32, .34, .36, .38, .4, .42, .44,
   ....:           .46, .48, .5]:
   ....:     fig_255.loc[p] = [float(i(1-p)) for i in cdfs]
   ....: 

In [34]: fig_255.index.name = 'p'

In [35]: qd(fig_255, float_format=lambda x: f'{x:.3f}', max_rows=len(fig_255))

         Gross  Current  StopLoss
p                                
0.0025 -38.503  -10.257   -20.483
0.0050 -32.113   -7.826   -14.093
0.0075 -28.394   -6.514   -10.374
0.0100 -25.760   -5.604    -7.740
0.0125 -23.740   -4.902    -5.720
0.0150 -22.114   -4.328    -4.094
0.0175 -20.758   -3.842    -2.738
0.0200 -19.595   -3.420    -1.575
0.0400 -13.603   -1.182     1.021
0.0600  -9.962    0.182     1.021
0.0800  -7.211    1.186     1.022
0.1000  -4.936    1.991     1.023
0.1200  -2.978    2.669     1.024
0.1400  -1.263    3.259     1.025
0.1600   0.246    3.784     1.026
0.1800   1.575    4.259     1.027
0.2000   2.752    4.695     1.028
0.2200   3.801    5.100     1.821
0.2400   4.745    5.478     2.765
0.2500   5.183    5.659     3.203
0.2600   5.603    5.835     3.623
0.2800   6.391    6.173     4.411
0.3000   7.121    6.496     5.141
0.3200   7.802    6.805     5.822
0.3400   8.442    7.103     6.462
0.3600   9.047    7.390     7.067
0.3800   9.622    7.669     7.642
0.4000  10.171    7.941     8.191
0.4200  10.697    8.206     8.717
0.4400  11.203    8.465     9.223
0.4600  11.692    8.720     9.712
0.4800  12.167    8.970    10.187
0.5000  12.628    9.217    10.648

2.13.7.3. Modern Analysis

The first step is to analyze the pricing in the context of needed capital. Strip expenses out (at 23% across all units) to determine a net (of expenses) technical premium.

In [36]: er = 0.23

In [37]: df = pd.DataFrame({'unit': ['Casualty', 'PropertyNC', 'PropertyC'],
   ....:                           'prem': [14, 17, 2],
   ....:                             'gross_loss': [a.est_m for a in abcd]}).set_index('unit')
   ....: 

In [38]: df['ceded_prem'] = [4.41, 2.36, 1.53]; \
   ....: df['net_prem'] = df.prem - df.ceded_prem; \
   ....: df['tech_prem'] = df.prem * (1 - er); \
   ....: df['margin'] = df.tech_prem - df.gross_loss; \
   ....: df.loc['Total'] = df.sum(0); \
   ....: df['lr'] = df.gross_loss / df.prem; \
   ....: df['cr'] = df.lr + er; \
   ....: df['tech_lr'] = df.gross_loss / df.tech_prem;
   ....: 

In [39]: fp = lambda x: f'{x:.1%}';

In [40]: fc = lambda x: f'{x:.2f}'

In [41]: qd(df, float_format=fc, formatters={'lr':fp, 'cr': fp, 'tech_lr': fp})

            prem  gross_loss  ceded_prem  net_prem  tech_prem  margin    lr     cr tech_lr
unit                                                                                      
Casualty   14.00       10.88        4.41      9.59      10.78   -0.10 77.7% 100.7%  101.0%
PropertyNC 17.00       10.71        2.36     14.64      13.09    2.38 63.0%  86.0%   81.8%
PropertyC   2.00        1.26        1.53      0.47       1.54    0.28 63.0%  86.0%   81.8%
Total      33.00       22.85        8.30     24.70      25.41    2.56 69.2%  92.2%   89.9%

The example does not specify a capital standard. Let’s investigate the implied return on capital at different capital standards. The capital standard is expressed as a loss percentile. The next calculation produces a table of returns expressed as a cost of capital (coc). It also shows the expected policyholder deficit.

In [42]: tech_prem = df.loc['Total', 'tech_prem']; \
   ....: ps = [.99, .995, .996, .999]; \
   ....: As = [abcd.q(p) for  p in ps]; \
   ....: el = abcd.density_df.loc[As, 'exa_total']; \
   ....: margin = tech_prem - el; \
   ....: cocs = margin /  (As - tech_prem); \
   ....: summary = pd.DataFrame({'p': ps, 'a': As, 'prem':tech_prem, 'el': el,
   ....:                         'margin': margin, 'tech_lr': el / tech_prem, 'coc': cocs,
   ....:                        'epd': (abcd.est_m - el) / abcd.est_m}).set_index('p')
   ....: 

In [43]: summary.index = [fp(i) for i in summary.index]; \
   ....: summary.index.name = 'p'; \
   ....: qd(summary, float_format=fc, formatters={'coc': fp, 'tech_lr': fp, 'epd': fp})
   ....: 

          a  prem    el  margin tech_lr  coc  epd
p                                                
99.0% 58.77 25.41 22.75    2.66   89.5% 8.0% 0.5%
99.5% 65.12 25.41 22.79    2.62   89.7% 6.6% 0.3%
99.6% 67.16 25.41 22.80    2.61   89.7% 6.2% 0.2%
99.9% 80.90 25.41 22.83    2.58   89.8% 4.6% 0.1%

Based on this analysis, we assume a 99.5% (200-year) capital standard, which gives a reasonable 8% return on capital. 200-year capital is also the Solvency II standard.

From here, the analysis could proceed in many directions. The approach we select is

  1. Calibrate a set of distortions to total pricing on a gross basis with the 200-year capital standard.

  2. Analyze the pricing implied by these distortions on the net book and its natural allocation by unit.

  3. Compare the model value (implied ceded premium) with market reinsurance price.

The model value is the maximum amount that is consistent with pricing according to each distortion. Reinsurance cheaper than the model value is efficient: replacing traditional capital with reinsurance capital lowers the economic cost of bearing risk.

2.13.7.3.1. Calibrate Distortions

Extract the exact cost of capital implied by given gross pricing.

In [44]: coc = summary.loc['99.5%', 'coc']

In [45]: print(coc)
0.06591531668710866

Calibrate distortions to current pricing. Use five one-parameter distortion families

  1. constant cost of capital (CCoC),

  2. proportional hazard (PH)

  3. Wang,

  4. dual, and

  5. TVaR.

They are sorted from most tail-centric (expensive for tail risk) to cheapest. See Mildenhall and Major [2022].

The next dataframe shows the asset level and implied loss ratio, distortion name, survival probability (0.5%), expected loss, premium, premium to capital leverage (PQ), the cost of (return on) capital, the distortion family parameter, and the parameterization error. The calibrated premium matches the technical premium.

In [46]: abcd.calibrate_distortions(ROEs=[coc], Ps=[.995], strict='ordered');

In [47]: qd(abcd.distortion_df)

                                  S      L     P      PQ      Q      COC    param       error
a         LR       method                                                                    
65.117188 0.896997 ccoc   0.0049978 22.793 25.41 0.63993 39.707 0.065915 0.065915           0
                   ph     0.0049978 22.793 25.41 0.63993 39.707 0.065915   0.7966  4.7227e-06
                   wang   0.0049978 22.793 25.41 0.63993 39.707 0.065915  0.24044  1.1474e-06
                   dual   0.0049978 22.793 25.41 0.63993 39.707 0.065915   1.3749 -7.4227e-11
                   tvar   0.0049978 22.793 25.41 0.63993 39.707 0.065915  0.17864  9.4873e-06

The plot show this effect: COC is fattest on the left for small exceedance probabilities (high losses), whereas TVaR is fattest on the right.

In [48]: fig, axs = plt.subplots(1, 5, figsize=(10.0, 2.1), constrained_layout=True)

In [49]: for ax, (k, v) in zip(axs.flat, abcd.dists.items()):
   ....:     v.plot(ax=ax)
   ....: 

In [50]: fig.suptitle('Comparison of distortion functions giving current market premium in total')
Out[50]: Text(0.5, 0.98, 'Comparison of distortion functions giving current market premium in total')
../../_images/gc_dist.png

2.13.7.3.2. Analyze Implied Pricing

Apply the distortions to the net portfolio and analyze the resulting pricing using analyze_distortions(), which includes a by-unit margin allocation. The dataframe ans.comp_df contains a wealth of other information; we just focus on the premium. The last row, Technical, shows market reinsurance pricing.

In [51]: abcd_net.dists = abcd.dists

In [52]: ansn = abcd_net.analyze_distortions(p=0.996, add_comps=False); \
   ....: ans = abcd.analyze_distortions(p=0.996, add_comps=False); \
   ....: bit = pd.concat((ans.comp_df.xs('P', 0, 1), ansn.comp_df.xs('P', 0, 1),
   ....:                 ans.comp_df.xs('P', 0, 1) - ansn.comp_df.xs('P', 0, 1)),
   ....:                 axis=1, keys=['gross', 'net', 'ceded']); \
   ....: bit = bit.iloc[[0, 2,-1, 1, -2]]; \
   ....: bit.loc['Technical'] = 0.0; \
   ....: bit.loc['Technical', 'gross'] = df.tech_prem.sort_index().values; \
   ....: bit.loc['Technical', 'ceded'] = df.ceded_prem.sort_index().values; \
   ....: bit.loc['Technical', 'net'] = df.net_prem.sort_index().values; \
   ....: qd(bit, sparsify=False, line_width=50)
   ....: 

             gross     gross      gross  gross  \
line      Casualty PropertyC PropertyNC  total   
Method                                           
Dist ccoc   10.371    4.6237      10.55 25.545   
Dist ph     11.347    1.5483     12.542 25.438   
Dist wang   11.514    1.4754     12.439 25.428   
Dist dual   11.698    1.4415     12.283 25.423   
Dist tvar   11.908    1.4298     12.084 25.421   
Technical    10.78      1.54      13.09  25.41   

               net       net        net    net  \
line      Casualty PropertyC PropertyNC  total   
Method                                           
Dist ccoc   7.8043    2.3449     6.9327 17.082   
Dist ph     8.6519   0.53481      7.977 17.164   
Dist wang   8.7296   0.49483     8.0137 17.238   
Dist dual   8.8065   0.47931     8.0416 17.327   
Dist tvar   8.8941   0.47972     8.0693 17.443   
Technical     9.59      0.47      14.64   24.7   

             ceded     ceded      ceded  ceded  
line      Casualty PropertyC PropertyNC  total  
Method                                          
Dist ccoc   2.5669    2.2788     3.6172 8.4632  
Dist ph     2.6952    1.0135     4.5651 8.2738  
Dist wang    2.784   0.98056     4.4253 8.1898  
Dist dual   2.8917   0.96217     4.2413 8.0952  
Dist tvar   3.0137   0.95009     4.0143 7.9781  
Technical     4.41      1.53       2.36    8.3  

2.13.7.3.3. Compare Model Value and Market Price

Focus on the last block above, under ceded. The rows Dist ... show the model value of reinsurance according to each distortion. The row Technical shows the market price. The market suggests to buy when the value is greater than the price.

The analysis provides a clear answer only for casualty, where the model value of reinsurance is much lower than the market price for all distortions: don’t buy the reinsurance.

For property cat, CCoC, the most tail-centric distortion, sees a lot of value in the reinsurance — hardly surprising. All the other less tail-centric distortions do not see it as adding value overall (lower value than market price). The order of the distortions and their assessment of the value of cat reinsurance are perfectly aligned, as they were for casualty albeit in the opposite order.

For property non-cat, the PH and Wang distortions see value, the others do not, though dual is close. This is the most interesting case because the ranking does not agree with the distortion ordering (as it does for the other two units). Property non-cat contributes to volatility and tail-risk, and so is more nuanced. Management often struggles with property risk reinsurance because tail-centric measures understate the value it provides. Actuaries stuggle to find analytic methods that capture its management-perceived value. The range of distortions considered covers the two views well.

In total the program is not seen as good value by any of the distortions. Since they span the reasonable range of risk preferences, this is a robust result.

Management often cares about more than just tail risk and they generally rejects the findings from CCoC. Whether or not they see value in reinsurance is sensitive to their exact risk appetite. These findings are consistent with the fact that each company tends to structure its reinsurance differently, tailored to their own risk appetite. Difference in risk appetite have a material impact on decision making.

2.13.7.3.4. Analysis for Stop Loss Reinsurance

Here is the analysis for the stop loss reinsurance. This analysis is manual, because the net of stop loss distribution for a Portfolio is not currently built-in. We have to extract the relevant distributions and apply the distortions, estimate a_stoploss the net asset requirement at p=0.995 (rounded to be a multiple of bs), determine the net expected loss and the model value. Recall compare.net_stoploss is the density of the net of stop-loss loss outcome. S1 is used to create its survival function, to which the distortion is applied to determine pricing. exa and exag are the objective and risk adjusted losses (model value) given an asset level a, computed as \(\int_0^a S\) and \(\int_0^a g (S)\) respectively (see PIR REF). We then select the relevant row and assemble the answer.

In [53]: S0 = pd.Series(compare.net_stoploss, index=compare.loss); \
   ....: S0.name = 'S'; \
   ....: S1 = S0[::-1].shift(1, fill_value=0).cumsum(); \
   ....: a0 = float(interp1d(S0.cumsum(), S0.index)(0.995)); \
   ....: a_stoploss = abcd.snap(a0); \
   ....: print(f'Net of stoploss assets {a_stoploss:.3f}');
   ....: 
Net of stoploss assets 45.109

In [54]: net_el_stoploss_unlim = (compare.loss * compare.net_stoploss).sum(); \
   ....: net_el_stoploss = (np.minimum(compare.loss, a_stoploss) * compare.net_stoploss).sum(); \
   ....: epd = 1 - net_el_stoploss / net_el_stoploss_unlim; \
   ....: qd(pd.Series([net_el_stoploss_unlim, net_el_stoploss, epd], index=['unlimited net loss', 'net loss limited by assets', 'epd']));
   ....: 

unlimited net loss              20.999
net loss limited by assets      20.941
epd                          0.0027373

In [55]: pricer = S1.to_frame().sort_index();

In [56]: for nm, dist in abcd.dists.items():
   ....:     pricer[f'{nm}_exa'] = pricer['S'].shift(1, fill_value=0).cumsum() * abcd.bs
   ....:     pricer[f'{nm}_gS'] = dist.g(pricer.S)
   ....:     pricer[f'{nm}_exag'] = pricer[f'{nm}_gS'].shift(1, fill_value=0).cumsum() * abcd.bs
   ....:     pricer = pricer.sort_index()
   ....: 

In [57]: try:
   ....:     pricer = pricer.loc[[a_stoploss]]; \
   ....:     pricer.columns = pricer.columns.str.split('_', expand=True); \
   ....:     comp = pricer.stack(0).droplevel(0,0); \
   ....:     comp.loc['Technical'] = [net_el_stoploss, tech_prem - 1.98, np.nan]; \
   ....:     comp['stoploss_value'] = tech_prem - comp.exag; \
   ....:     comp = comp.sort_values('stoploss_value', ascending=False); \
   ....:     qd(comp)
   ....: except:
   ....:     print('Unspecfied error: TODO investigate.')
   ....: 
Unspecfied error: TODO investigate.

The output table reveals that the stop loss value is greater than its market price for the CCoC, PH, and Wang distortions, but less for the dual and TVaR. Thus, management averse to tail risk regard it as beneficial, but those more concerned with volatility and body risk do not see it as worthwhile.

A note of caution is in order on this analysis. Stop loss structures are a broker favorite, but are generally not liked by reinsurers. Aggregate features are hard to underwrite and price, and the lower premium is not attractive. A treaty similar to the proposed stop loss would be very hard to find in the market.

2.13.7.4. Visualizing Risk

The next figure shows the kappa functions, a handy way to visualize which units are contributing to total risk across the loss spectrum (see REF). Here the horizontal axis is total loss. The middle plot shows the reinsurance is quite effective at lowering the risk from Property NC (green line), but less effective at altering the risk profile of the other two lines. In particular, cat (red line) still dominates the tail risk.

In [58]: fig, axs = plt.subplots(1, 3, figsize=(3 * 3.5, 2.55), constrained_layout=True)

In [59]: for ax, a in zip(axs.flat, [abcd, abcd_net, abcd_ceded]):
   ....:     mx = a.q(0.9999)
   ....:     a.density_df.filter(regex='exeqa_[CPt]').plot(ax=ax,
   ....:         xlim=[0, mx], ylim=[0, mx], title=a.name);
   ....:     ax.set(xlabel='loss, $x$');
   ....: 

In [60]: axs.flat[0].set(ylabel='$E[X_unit | X=x]$');

In [61]: fig.suptitle('Conditional loss as a function of x for each unit');
../../_images/gc_kappa.png