3.4. Portfolio Module

3.4.1. Portfolio Class

class aggregate.portfolio.Portfolio(name, spec_list, uw=None)[source]

Portfolio creates and manages a portfolio of Aggregate objects each modeling one unit of business. Applications include

  • Model a book of insurance

  • Model a large account with several sub lines

  • Model a reinsurance portfolio or large treaty

__init__(name, spec_list, uw=None)[source]

Create a new Portfolio object.

Parameters
  • name – The name of the portfolio. No spaces or underscores.

  • spec_list

    A list of

    1. dictionary: Aggregate object dictionary specifications or

    2. Aggregate: An actual aggregate objects or

    3. tuple (type, dict) as returned by uw[‘name’] or

    4. string: Names referencing objects in the optionally passed underwriter

    5. a single DataFrame: empirical samples (the total column, if present, is ignored); a p_total column is used for probabilities if present

Returns

new Portfolio object.

_make_var_tvar(ser)[source]

There is no severity version here, so this knows where to store the answer, cf Aggregate version.

_repr_html_()[source]

Updated to mimic Aggregate

accounting_economic_balance_sheet(a=0, p=0)[source]

story version assumes line 0 = reserves and 1 = prospective….other than that identical

usual a and p rules

add_exa(df, ft_nots=None)[source]

Use fft to add exeqa_XXX = E(X_i | X=a) to each dist

also add exlea = E(X_i | X <= a) = sum_{x<=a} exa(x)*f(x) where f is for the total ie. self.density_df[‘exlea_attrit’] = np.cumsum( self.density_df.exa_attrit * self.density_df.p_total) / self.density_df.F

and add exgta = E(X_i | X>a) since E(X) = E(X | X<= a)F(a) + E(X | X>a)S(a) we have exgta = (ex - exlea F) / S

and add the actual expected losses (not theoretical) the empirical amount: self.density_df[‘e_attrit’] = np.sum( self.density_df.p_attrit * self.density_df.loss)

Mid point adjustment is handled by the example creation routines self.density_df.loss = self.density_df.loss - bs/2

YOU CANNOT HAVE A LINE with a name starting t!!!

See LCA_Examples for original code

Alternative approach to exa: use UC=unconditional versions of exlea and exi_xgta:

  • exleaUC = np.cumsum(port.density_df[‘exeqa_’ + col] * port.density_df.p_total) # unconditional

  • exixgtaUC =np.cumsum( self.density_df.loc[::-1, ‘exeqa_’ + col] / self.density_df.loc[::-1, ‘loss’] * self.density_df.loc[::-1, ‘p_total’] )

  • exa = exleaUC + exixgtaUC * self.density_df.loss

Parameters
  • df – data frame to add to. Initially add_exa was only called by update and wrote to self.density_df. But now it is called by gradient too which writes to gradient_df, so we need to pass in this argument

  • ft_nots – FFTs of the not lines (computed in gradients) so you don’t round trip an FFT; gradients needs to recompute all the not lines each time around and it is stilly to do that twice

add_exa_details(df, eta_mu=False)[source]

From add_exa, details for epd functions and eta_mu flavors.

Note eta_mu=True is required for epd_2 functions.

add_exa_sample(sample, S_calculation='forwards')[source]

Computes a version of density_df using sample to compute E[Xi | X]. Then fill in the other ex…. variables using code from Portfolio.add_exa, stripped down to essentials.

If no p_total is given then samples are assumed equally likely. total is added if not given (sum across rows) total is then aligned to the bucket size self.bs using (total/bs).round(0)*bs. The other loss columns are then scaled so they sum to the adjusted total

Next, group by total, sum p_total and average the lines to create E[Xi|X]

This sample is merged into a stripped down density_df. Then the other ex… columns are added. Excludes eta mu columns.

Anticipated use: replace density_df with this, invalidate quantile function and then compute various allocation metrics.

The index on the input sample is ignored.

Formally extensions.samples.add_exa_sample.

analysis_collateral(line, c, a, debug=False)[source]

E(C(a,c)) expected value of line against not line with collateral c and assets a, c <= a

Parameters
  • line – line of business with collateral, analyzed against not line

  • c – collateral, c <= a required; c=0 reproduces exa, c=a reproduces lev

  • a – assets, assumed less than the max loss (i.e. within the square)

  • debug

Returns

analysis_priority(asset_spec, output='df')[source]

Create priority analysis report_ser. Can be called multiple times with different asset_specs asset_spec either a float used as an epd percentage or a dictionary. Entering an epd percentage generates the dictionary

base = {i: self.epd_2_assets[(‘not ‘ + i, 0)](asset_spec) for i in self.line_names}

Parameters
  • asset_spec – epd

  • output – df = pandas data frame; html = nice report, markdown = raw markdown text

Returns

analyze_distortion(dname, dshape=None, dr0=0.025, ddf=5.5, LR=None, ROE=None, p=None, kind='lower', A=None, use_self=False, plot=False, a_max_p=0.99999999, add_comps=True, efficient=True)[source]

Graphic and summary DataFrame for one distortion showing results that vary by asset level. such as increasing or decreasing cumulative premium.

Characterized by the need to know an asset level, vs. apply_distortion that produced values for all asset levels.

Returns DataFrame with values upto the input asset level…differentiates from apply_distortion graphics that cover the full range.

analyze_pricing will then zoom in and only look at one asset level for micro-dynamics…

Logic of arguments:

if data_in == 'self' use self.augmented_df; this implies a distortion self.distortion

else need to build the distortion and apply it
    if dname is a distortion use it
    else built one calibrated to input data

LR/ROE/a/p:
    if p then a=q(p, kind) else p = MESSY
    if LR then P and ROE; if ROE then Q to P to LR
    these are used to calibrate distortion

A newly made distortion is run through apply_distortion with no plot

Logic to determine assets similar to calibrate_distortions.

Can pass in a pre-calibrated distortion in dname

Must pass LR or ROE to determine profit

Must pass p or A to determine assets

Output is an Answer class object containing

Answer(augmented_df=deets, trinity_df=df, distortion=dist, fig1=f1 if plot else None,
      fig2=f2 if plot else None, pricing=pricing, exhibit=exhibit, roe_compare=exhibit2,
      audit_df=audit_df)

Originally example_factory.

example_factory_exhibits included:

do the work to extract the pricing, exhibit and exhibit 2 DataFrames from deets Can also accept an ans object with an augmented_df element (how to call from outside) POINT: re-run exhibits at different p/a thresholds without recalibrating add relevant items to audit_df a = q(p) if both given; if not both given derived as usual

Figures show

Parameters
  • dname – name of distortion

  • dshape – if input use dshape and dr0 to make the distortion

  • dr0

  • ddf – r0 and df params for distortion

  • LR – otherwise use loss ratio and p or a loss ratio

  • ROE

  • p – p value to determine capital.

  • kind – type of VaR, upper or lower

  • A

  • use_self – if true use self.augmented and self.distortion…else recompute

  • plot

  • a_max_p – percentile to use to set the right hand end of plots

  • add_comps – add old-fashioned method comparables (included = True as default to make backwards comp.)

  • efficient

Returns

various dataframes in an Answer class object

analyze_distortion_add_comps(ans, a_cal, p, kind, ROE)[source]

make exhibit with comparison to old-fashioned methods: equal risk var/tvar, scaled var/tvar, stand-alone var/tvar, merton perold, co-TVaR. Not all of these methods is additive.

covar method = proportion of variance (additive)

Other methods could be added, e.g. a volatility method?

Note on calculation

Each method computes allocated assets a_i (which it calls Q_i) = Li + Mi + Qi All methods are constant ROEs for capital We have Li in exhibit. Hence:

L = Li P = (Li + ROE ai) / (1 + ROE) = v Li + d ai Q = a - P M = P - L ratios

In most cases, ai is computed directly, e.g. as a scaled proportion of total assets etc.

The covariance method is slightly different.

Mi = vi M, vi = Cov(Xi, X) / Var(X) Pi = Li + Mi Qi = Mi / ROE ai = Pi + Qi

and sum ai = sum Li + sum Mi + sum Qi = L + M + M/ROE = L + M + Q = a as required. To fit it in the same scheme as all other methods we compute qi = Li + Mi + Qi = Li + vi M + vi M / ROE = li + viM(1+1/ROE) = Li + vi M/d, d=ROE/(1+ROE)

Parameters
  • ans – answer containing dist and augmented_df elements

  • a_cal

  • p

  • kind

  • LR

  • ROE

Returns

ans Answer object with updated elements

analyze_distortion_plots(ans, dist, a_cal, p, a_max, ROE, LR)[source]

Create plots from an analyze_distortion ans class note: this only looks at distortion related items…it doesn’t use anything from the comps

Parameters
  • ans

  • dist

  • a_cal

  • p

  • a_max

  • ROE

  • LR

Returns

analyze_distortions(a=0, p=0, kind='lower', efficient=True, augmented_dfs=None, regex='', add_comps=True)[source]

Run analyze_distortion on self.dists

Parameters
  • a

  • p – the percentile of capital that the distortions are calibrated to

  • kind – var, upper var, tvar, epd

  • efficient

  • augmented_dfs – input pre-computed augmented_dfs (distortions applied)

  • regex – apply only distortion names matching regex

  • add_comps – add traditional pricing comps to the answer

Returns

apply_distortion(dist, view='ask', plots=None, df_in=None, create_augmented=True, S_calculation='forwards', efficient=True)[source]

Apply the distortion, make a copy of density_df and append various columns to create augmented_df.

augmented_df depends on the distortion but only includes variables that work for all asset levels, e.g.

  1. marginal loss, lr, roe etc.

  2. bottom up totals

Top down total depend on where the “top” is and do not work in general. They are handled in analyze_distortions where you explicitly provide a top.

Does not touch density_df: that is independent of distortions

Optionally produce graphics of results controlled by plots a list containing none or more of:

  1. basic: exag_sumparts, exag_total df.exa_total

  2. extended: the full original set

Per 0.11.0: no mass at 0 allowed. If you want to use a distortion with mass at 0 you must use a close approximation.

Parameters
  • dist – agg.Distortion

  • view – bid or ask price

  • plots – iterable of plot types

  • df_in – when called from gradient you want to pass in gradient_df and use that; otherwise use self.density_df

  • create_augmented (object) – store the output in self.augmented_df

  • S_calculation – if forwards, recompute S summing p_total forwards…this gets the tail right; the old method was backwards, which does not change S

  • efficient – just compute the bare minimum (T. series, not M. series) and return

Returns

density_df with extra columns appended

apply_distortions(dist_dict, As=None, Ps=None, kind='lower', efficient=True)[source]

Apply a list of distortions, summarize pricing and produce graphical output show loss values where \(s_ub > S(loss) > s_lb\) by jump

Parameters
  • kind

  • dist_dict – dictionary of Distortion objects

  • As – input asset levels to consider OR

  • Ps – input probs (near 1) converted to assets using self.q()

Returns

approximate(approx_type='slognorm', output='scipy')[source]

Create an approximation to self using method of moments matching.

Returns a dictionary specification of the portfolio aggregate_project. If updated uses empirical moments, otherwise uses theoretic moments

Parameters
  • approx_type – slognorm | sgamma | normal

  • output – return a dict or agg language specification

Returns

as_severity(limit=inf, attachment=0, conditional=False)[source]

Convert portfolio into a severity without recomputing.

Throws an error if self not updated.

Parameters
  • limit

  • attachment

  • conditional

Returns

audits(kind='all', **kwargs)[source]

produce audit plots to assess accuracy of outputs.

Currently only exeqa available

Parameters
  • kind

  • kwargs – passed to pandas plot, e.g. set xlim

Returns

best_bucket(log2=16, recommend_p=0.99999)[source]

Recommend the best bucket. Rounded recommended bucket for log2 points.

TODO: Is this really the best approach?!

Parameters
  • log2

  • recommend_p

Returns

biv_contour_plot(fig, ax, min_loss, max_loss, jump, log=True, cmap='Greys', min_density=1e-15, levels=30, lines=None, linecolor='w', colorbar=False, normalize=False, **kwargs)[source]

Make contour plot of line A vs line B. Assumes port only has two lines.

Works with an extract density_df.loc[np.arange(min_loss, max_loss, jump), densities] (i.e., jump is the stride). Jump = 100 * bs is not bad…just think about how big the outer product will get!

Parameters
  • fig

  • ax

  • min_loss – the density for each line is sampled at min_loss:max_loss:jump

  • max_loss

  • jump

  • log

  • cmap

  • min_density – smallest density to show on underlying log region; not used if log

  • levels – number of contours or the actual contours if you like

  • lines – iterable giving specific values of k to plot X+Y=k

  • linecolor

  • colorbar – show color bar

  • normalize – if true replace Z with Z / sum(Z)

  • kwargs – passed to contourf (e.g., use for corner_mask=False, vmin,vmax)

Returns

bodoff(*, p=0.99, a=0)[source]

Determine Bodoff layer asset allocation at asset level a or VaR percentile p, one of which must be provided. Uses formula 14.42 on p. 284 of Pricing Insurance Risk.

Parameters
  • p – VaR percentile

  • a – asset level

Returns

Bodoff layer asset allocation by unit

calibrate_blends(a, premium, s_values, gs_values=None, spread_values=None, debug=False)[source]

Input s values and gs values or (market) yield or spread.

A bond with prob s (small) of default is quoted with a yield (to maturity) of r over risk free (e.g., a cat bond spread, or a corporate bond spread over the appropriate Treasury). As a discount bond, the price is v = 1 - d.

B(s) = bid price for 1(U<s) (bond residual value) A(s) = ask price for 1(U<s) (insurance policy)

By no arb A(s) + B(1-s) = 1. By definition g(s) = A(s) (using LI so the particular U doesn’t matter. Applied to U = F(X)).

Let v = 1 / (1 + r) and d = 1 - v be the usual theory of interest quantities.

Hence B(1-s) = v = 1 - A(s) = 1 - g(s) and therefore g(s) = 1 - v = d.

The rate of risk discount δ and risk discount factor (nu) ν are defined so that B(1-s) = ν * (1 - s), it is the extra discount applied to the actuarial value that is bid for the bond. It is a function of s. Therefore ν = (1 - d) / (1 - s) = price of bond / actuarial value of payment.

Then, g(s) = 1 - B(1-s) = 1 - ν (1 - s) = ν s + δ.

Thus, if return (i.e., market yield spreads) are input, they convert to discount factors to define g points.

Blend can be defined by extrapolating the last points in a credit curve. If that fails, increase the return on the highest s point and fill in with a constant return to 1.

The ROE on the investment is not the promised return, because the latter does not allow for default.

Set up to be a function of the Portfolio = self. Calibrated to hit premium at asset level a. a must be in the index.

a = self.pricing_summary.at[‘a’, kind] premium = self.pricing_summary.at[‘P’, kind]

method = extend or roe

Input

blend_d0 is the Book’s blend, with roe above the equity point blend_d is calibrated to the same premium as the other distortions

method = extend if f_blend_extend or ccoc

ccoc = pick and equity point and back into its required roe. Results in a poor fit to the calibration data

extend = extrapolate out the last slope from calibrtion data

Initially tried interpolating the bond yield curve up, but that doesn’t work. (The slope is too flat and it interpolates too far. Does not look like a blend distortion.) Now, adding the next point off the credit yield curve as the “equity” point and solving for ROE.

If debug, returns more output, for diagnostics.

calibrate_distortion(name, r0=0.0, df=[0.0, 0.9], premium_target=0.0, roe=0.0, assets=0.0, p=0.0, kind='lower', S_column='S', S_calc='cumsum')[source]

Find transform to hit a premium target given assets of assets. Fills in the values in g_spec and returns params and diagnostics…so you can use it either way…more convenient

Parameters
  • name – name of distortion

  • r0 – fixed parameter if applicable

  • df – t-distribution degrees of freedom

  • premium_target – target premium

  • roe – or ROE

  • assets – asset level

  • p

  • kind

  • S_column – column of density_df to use for calibration (allows routine to be used in other contexts; if so used must input a premium_target directly. If assets they are used; else max assets used

Returns

calibrate_distortions(LRs=None, COCs=None, ROEs=None, As=None, Ps=None, kind='lower', r0=0.03, df=5.5, strict='ordered', S_calc='cumsum')[source]

Calibrate assets a to loss ratios LRs and asset levels As (iterables) ro for LY, it \(ro/(1+ro)\) corresponds to a minimum rate online

Parameters
  • LRs – LR or ROEs given

  • ROEs – ROEs override LRs

  • COCs – CoCs override LRs, preferred terms to ROE; ROE maintained for backwards compatibility.

  • As – Assets or probs given

  • Ps – probability levels for quantiles

  • kind

  • r0 – for distortions that have a min ROL

  • df – for tt

  • strict – if==’ordered’ then use the book nice ordering else if True only use distortions with no mass at zero, otherwise use anything reasonable for pricing

  • S_calc

Returns

cdf(x)[source]

distribution function

Parameters

x

Returns

collapse(approx_type='slognorm')[source]

Returns new Portfolio with the fit

Deprecated…prefer uw.write(self.fit()) to go through the agg language approach.

Parameters

approx_type – slognorm | sgamma

Returns

cotvar(p)[source]

Compute the p co-tvar asset allocation using ISA. Asset alloc = exgta = tail expected value, treating TVaR like a pricing variable.

static create_from_sample(name, sample_df, bs, log2=16, **kwargs)[source]

Create from a multivariate sample, update with bs, execute switcheroo, and return new Portfolio object.

OED: switcheroo, n. a change of position or an exchange, esp. one intended to surprise or deceive; a reversal or turn-about; spec. an unexpected change or ‘twist’ in a story. Also attributive, reversible, reversed.

density_sample(n=20, reg='loss|p_|exeqa_')[source]

sample of equally likely points from density_df with interesting columns reg - regex to select the columns

property describe

Theoretic and empirical stats. Used in _repr_html_. Leverage Aggregate object stats; same format

property distortion_df

Nicely formatted version of self.dist_ans (that exhibited several bad choices!).

ROE returned as COC in modern parlance.

property epd_2_assets

Make epd to assets and vice versa Note that the Merton Perold method requies the eta_mu fields, hence set True

equal_risk_epd(a)[source]

determine the common epd threshold so sum sa equals a

equal_risk_var_tvar(p_v, p_t)[source]

solve for equal risk var and tvar: find pv and pt such that sum of individual line VaR/TVaR at pv/pt equals the VaR(p) or TVaR(p_t)

these won’t return elements in the index because you have to interpolate hence using kind=middle

explain_validation()[source]

Explain the validation result. Can pass in if already calculated.

static from_DataFrame(name, df)[source]

create portfolio from pandas dataframe uses columns with appropriate names

Can be fed the agg output of uw.write_test( agg_program )

Parameters
  • name

  • df

Returns

static from_Excel(name, ffn, sheet_name, **kwargs)[source]

read in from Excel

works via a Pandas dataframe; kwargs passed through to pd.read_excel drops all blank columns (mostly for auditing purposes) delegates to from_dataFrame

Parameters
  • name

  • ffn – full file name, including path

  • sheet_name

  • kwargs

Returns

static from_dict_of_aggs(prefix, agg_dict, sub_ports=None, uw=None, bs=0, log2=0, padding=2, **kwargs)[source]

Create a portfolio from any iterable with values aggregate code snippets

e.g. agg_dict = {label: agg_snippet }

will create all the portfolios specified in subsets, or all if subsets==’all’

labels for subports are concat of keys in agg_dict, so recommend you use A:, B: etc. as the snippet names. Portfolio names are prefix_[concat element names]

agg_snippet is line agg blah without the tab or newline

Parameters
  • prefix

  • agg_dict

  • sub_ports

  • kwargs (bs, log2, padding,) – passed through to update; update if bs * log2 > 0

Returns

ft(x, tilt=None)[source]

FT of x with padding and tilt applied

gamma(a=None, p=None, kind='lower', compute_stand_alone=False, axs=None, plot_mode='return')[source]

Return the vector gamma_a(x), the conditional layer effectiveness given assets a. Assets specified by percentile level and type (you need a in the index) gamma can be created with no base and no calibration - it does not depend on a distortion. It only depends on total losses.

Returns the total and by layer versions, see “Main Result for Conditional Layer Effectiveness; Piano Diagram” in OneNote

In total gamma_a(x) = E[ (a ^ X) / X | X > x] is the average rate of reimbursement for losses above x given capital a. It satisfies int_0^infty gamma_a(x) S(x) dx = E[a ^ X]. Notice the integral is to infinity, regardless of the amount of capital a.

By line gamma_{a,i}(x) = E[ E[X_i | X] / X {(X ^ a) / X} 1_{X>x} ] / E[ {E[X_i | X] / X} 1_{X>x} ].

The denominator equals total weights. It is the line-i recovery weighted layer effectiveness. It equals alpha_i(x) S(x).

Now we have

E[X_i(a)] = int_0^infty gamma_{a,i}(x) alpha_i(x) S(x) dx

Note that you need upper and lower q’s in aggs now too.

Nov 2020: added arguments for plots; revised axes, separate plots by line

Parameters
  • a – input a or p and kind as usual

  • p – asset level percentile

  • kind – lower or upper

  • compute_stand_alone – add stand-alone evaluation of gamma

  • axs – enough axes; only plot if not None

  • plot_mode – return or linear scale for y axis

Returns

gradient(epsilon=0.0078125, kind='homog', method='forward', distortion=None, remove_fuzz=True, extra_columns=None, do_swap=True)[source]

Compute the gradient of various quantities relative to a change in the volume of each portfolio component.

Focus is on the quantities used in rate calculations: S, gS, p_total, exa, exag, exi_xgta, exi_xeqq, exeqa, exgta etc.

homog:

inhomog:

Parameters
  • epsilon – the increment to use; scale is 1+epsilon

  • kind – homog[ogeneous] or inhomog: homog computes impact of f((1+epsilon)X_i)-f(X_i). Inhomog scales the frequency and recomputes. Note inhomog will have a slight scale issues with E[Severity]

  • method – forward, central (using epsilon/2) or backwards

  • distortion – if included derivatives of statistics using the distortion, such as exag are also computed

  • extra_columns – extra columns to compute dervs of. Note there is virtually no overhead of adding additional columns

  • do_swap – force the step to replace line with line+epsilon in all not line2’s line2!=line1; whether you need this or not depends on what variables you to be differentiated. E.g. if you ask for exa_total only you don’t need to swap. But if you want exa_A, exa_B you do, otherwise the d/dA exa_B won’t be correct. TODO: replace with code!

Returns

DataFrame of gradients and audit_df in an Answer class

ift(x, tilt=None)[source]

IFT of x with padding and tilt applied

json(stream=None)[source]

write object as json

Parameters

stream

Returns

stream or text

limits(stat='range', kind='linear', zero_mass='include')[source]

Suggest sensible plotting limits for kind=range, density, .. (same as Aggregate).

Should optionally return a locator for plots?

Called by ploting routines. Single point of failure!

Must work without q function when not computed (apply_reins_work for occ reins…uses report_ser instead).

Parameters
  • stat – range or density or logy (for log density/survival function…ensure consistency)

  • kind – linear or log (this is the y-axis, not log of range…that is rarely plotted)

  • zero_mass – include exclude, for densities

Returns

property line_renamer

plausible defaults for nicer looking names

replaces . or : with space and capitalizes (generally don’t use . because it messes with analyze distortion….

leaves : alone

converts X1 to tex

converts XM1 to tex with minus (for reserves)

Returns

make_all(p=0, a=0, As=None)[source]

make all exhibits with sensible defaults if not entered, paid line is selected as the LAST line

make_audit_df(columns, theoretical_stats=None)[source]

Add or update the audit_df.

merton_perold(p, kind='lower')[source]

Compute Merton-Perold capital allocation at VaR(p) capital using VaR as risk measure.

TODO TVaR version of Merton Perold

more(regex)[source]

More information about methods and properties matching regex

multi_premium_capital(As, keys=None)[source]

concatenate multiple prem_capital exhibits

natural_profit_segment_plot(ax, p, line_names, colors, translations)[source]

Plot the natural allocations between 1-p and p th percentiles and optionally translate line(s). Works with augmented_df, no input distortion. User must ensure the correct distortion has been applied.

Parameters
  • ax

  • p

  • line_names

  • colors

  • translations

Returns

nice_program(wrap_col=90)[source]

return wrapped version of port program :return:

pdf(x)[source]

probability density function, assuming a continuous approximation of the bucketed density :param x: :return:

percentiles(pvalues=None)[source]

report_ser on percentiles and large losses. Uses interpolation, audit_df uses nearest.

Parameters

pvalues – optional vector of log values to use. If None sensible defaults provided

Returns

DataFrame of percentiles indexed by line and log

plot(axd=None, figsize=(7.0, 2.45))[source]

Defualt plot of density, survival functions (linear and log)

Parameters
  • axd – dictionary with plots A and B for density and log density

  • figsize – arguments passed to make_mosaic_figure if no axd

Returns

pmf(x)[source]

Probability mass function, treating aggregate as discrete x must be in the index (?)

property pprogram

pretty print the program to html

property pprogram_html

pretty print the program to html

premium_capital(a=0, p=0)[source]

at a if given else p level of capital

pricing story allows two asset levels…handle that with a concat

was premium_capital_detail

price(p, distortion=None, *, allocation='lifted', view='ask', efficient=True)[source]

Price using regulatory capital and pricing distortion functions.

Compute E_price (X wedge E_reg(X) ) where E_price uses the pricing distortion and E_reg uses the regulatory distortion derived from p. p can be input as a probability level converted to assets using kind, a level of assets directly (snapped to index).

Regulatory capital distortion is applied on unlimited basis.

Do not attempt to use with a weight_df dataframe from Bounds. For that, use the bounds object logic directly which is much more efficient.

The argument kind has been dropped, it is always 'var'. If that is not the case, convert your asset level to a VaR threshold.

Updated: May 2023 Turns out, really awkward to return the dictionary. In most calls there is just one distortion passed in. The result of the last distortion are reported in ans.price, and there is a new price_dict for the price of each distortion. T

Parameters
  • p – float; if >1 assets if <1 a prob converted to quantile

  • distortion – a distortion, list or dictionary (name: dist) of distortions. If None then self.dists dictionary is used.

  • allocation – ‘lifted’ (default for legacy reasons) or ‘linear’: treatment in default scenarios. See PIR.

  • view – bid or ask

  • efficient – for apply_distortion, lifted only

Returns

PricingResult namedtuple with ‘price’, ‘assets’, ‘reg_p’, ‘distortion’, ‘df’

price_ccoc(p, ccoc)[source]

Convenience function to price with a constant cost of captial equal ccoc at VaR level p. Does not invoke a Distortion. Returns standard DataFrame format.

pricing_bounds(premium, a=0, p=0, n_tps=64, kind='tail', slow=False, verbose=250)[source]

Compute the natural allocation premium ranges by unit consistent with total premium at asset level a or p (one of which must be provided).

Unlike typical case with even s values, this is run at the actual S values of the Portfolio.

Visualize:

from pandas.plotting import scatter_matrix
ans = port.pricing_bounds(premium, p=0.98)
scatter_matrix(ans.allocs, marker='.', s=5, alpha=1,
               figsize=(10, 10), diagonal='kde' )
profit_segment_plot(ax, p, line_names, dist_name, colors=None, translations=None)[source]

Lee diagram for each requested line on a stand-alone basis, loss and risk adj premium using the dist_name distortion. Optionally specify colors, using C{n}. Optionally specify translations applied to each line. Generally, this applies to shift the cat line up by E[non cat] losses to show it overlays the total.

For a Portfolio with line names CAT and NC:

port.gross.profit_segment_plot(ax, 0.99999, ['total', 'CAT', 'NC'],
                    'wang', [2,0,1])

add translation to cat line:

port.gross.profit_segment_plot(ax, 0.99999, ['total', 'CAT', 'NC'],
                    'wang', [2,0,1], [0, E[NC], 0])
Parameters
  • ax – axis on which to render

  • p – probability level to set upper and lower y axis limits (p and 1-p quantiles)

  • line_names

  • dist_name

  • colors

  • translations

Returns

q(p, kind='lower')[source]

Return quantile function of density_df.p_total.

Definition 2.1 (Quantiles) x(α) = qα(X) = inf{x ∈ R : P[X ≤ x] ≥ α} is the lower α-quantile of X x(α) = qα(X) = inf{x ∈ R : P[X ≤ x] > α} is the upper α-quantile of X.

kind=='middle' has been removed.

Parameters
  • p

  • kind – ‘lower’ or ‘upper’.

Returns

recommend_bucket()[source]

Data to help estimate a good bucket size.

Returns

remove_fuzz(df=None, eps=0, force=False, log='')[source]

remove fuzz at threshold eps. if not passed use np.finfo(float).eps.

Apply to self.density_df unless df is not None

Only apply if self.remove_fuzz or force :param eps: :param df: apply to dataframe df, default = self.density_df :param force: do regardless of self.remove_fuzz :return:

property renamer

write a sensible renamer for the columns to use thusly

self.density_df.rename(columns=renamer)

write a tex version separately Create once per item…assume lines etc. never change

Returns

dictionary that can be used to rename columns

report(report_list='quick')[source]
Parameters

report_list

Returns

sample(n, replace=True, desired_correlation=None, keep_total=True)[source]

Pull multivariate sample. Apply Iman Conover to induce correlation if required.

sample_compare(ax=None)[source]

Compare the sample sum to the independent sum of the marginals.

sample_density_compare(fuzz=0)[source]

Compare from density_df

save(filename='', mode='a')[source]

persist to json in filename; if none save to user.json

Parameters
  • filename

  • mode – for file open

Returns

scatter(marker='.', s=5, alpha=1, figsize=(10, 10), diagonal='kde', **kwargs)[source]

Create a scatter plot of marginals against one another, using pandas.plotting scatter_matrix.

Designed for use with samples. Plots exeqa columns

set_a_p(a, p)[source]

sort out arguments for assets and prob level and make them consistent neither => set defaults a only set p p only set a both do nothing

sf(x)[source]

survival function

Parameters

x

Returns

show_enhanced_exhibits(fmt='{:.5g}')[source]

show all the exhibits created by enhanced_portfolio methods

snap(x)[source]

snap value x to the index of density_df

Parameters

x

Returns

property spec

Get the dictionary specification.

Returns

property spec_ex

All relevant info.

Returns

stand_alone_pricing(dist, p=0, kind='var', S_calc='cumsum')[source]

Run distortion pricing, use it to determine and ROE and then compute traditional and default pricing, then consolidate the answer

Parameters
  • self

  • roe

  • p

  • kind

Returns

from common_scripts.py

stand_alone_pricing_work(dist, p, kind, roe, S_calc='cumsum')[source]

Apply dist to the individual lines of self, with capital standard determined by a, p, kind=VaR, TVaR, etc. Return usual data frame with L LR M P PQ Q ROE, and a

Dist can be a distortion, traditional, or defaut pricing modes. For latter two you have to input an ROE. ROE not required for a distortion.

Parameters
  • self – a portfolio object

  • dist – “traditional”, “default”, or a distortion (already calibrated)

  • p – probability level for assets

  • kind – var (or lower, upper), tvar or epd (note, p should be small for EPD, to pander, if p is large we use 1-p)

  • roe – for traditional methods input roe

Returns

exhibit is copied and augmented with the stand-alone statistics

from common_scripts.py

property statistics

Same as statistics df, to be consistent with Aggregate objects :return:

property tm_renamer

rename exa -> TL, exag -> TP etc. :return:

trim_df()[source]

Trim out unwanted columns from density_df

epd used in graphics

Returns

tvar(p, kind='')[source]

Compute the tail value at risk at threshold p. Revised June 2023.

Really this function returns ES, CVaR, but in modern terminology this is called TVaR.

Definition 2.6 (Tail mean and Expected Shortfall) Assume E[X−] < ∞. Then x¯(α) = TM_α(X) = α^{−1}E[X 1{X≤x(α)}] + x(α) (α − P[X ≤ x(α)]) is α-tail mean at level α the of X. Acerbi and Tasche (2002)

McNeil etc. p66-70 - this follows from def of ES as an integral of the quantile function

Parameters
  • p

  • kind – No longer neeed as the new method is exact (equals the old

tail) and about 1000x faster. :return:

tvar_threshold(p, kind)[source]

Find the value pt such that TVaR(pt) = VaR(p) using Bisection method. Will fail if p=0 because signs are the same.

twelve_plot(fig, axs, p=0.999, p2=0.9999, xmax=0, ymax2=0, biv_log=True, legend_font=0, contour_scale=10, sort_order=None, kind='two', cmap='viridis')[source]

Twelve-up plot for ASTIN paper and book, by rc index:

Greys for grey color map

11 density 12 log density 13 biv density plot

21 kappa 22 alpha (from alpha beta plot 4) 23 beta (?with alpha)

row 3 = line A, row 4 = line B from alpha beta four 2

1 S, gS, aS, bgS

32 margin 33 shift margin 42 cumul margin 43 natural profit compare

Args

self = portfolio or enhanced portfolio object p control xlim of plots via quantile; used if xmax=0 p2 controls ylim for 33 and 34: stand alone M and natural M; used if ymax2=0 biv_log - bivariate plot on log scale legend_font - fine tune legend font size if necessary sort_order = plot sorts by column and then .iloc[:, sort_order], if None [1,2,0]

from common_scripts.py

uat(As=None, Ps=[0.98], LRs=[0.965], r0=0.03, num_plots=1, verbose=False)[source]

Reconcile apply_distortion(s) with price and calibrate

Parameters
  • As – Asset levels

  • Ps (object) – probability levels used to determine asset levels using quantile function

  • LRs – loss ratios used to determine profitability

  • r0 – r0 level for distortions

  • verbose – controls level of output

Returns

uat_differential(line)[source]

Check the numerical and theoretical derivatives of exa agree for given line

Parameters

line

Returns

uat_interpolation_functions(a0, e0)[source]

Perform quick audit of interpolation functions

Parameters
  • a0 – base assets

  • e0 – base epd

Returns

update(log2, bs, approx_freq_ge=100, approx_type='slognorm', remove_fuzz=False, sev_calc='discrete', discretization_calc='survival', normalize=True, padding=1, tilt_amount=0, trim_df=False, add_exa=True, force_severity=True, recommend_p=0.99999, approximation=None, debug=False)[source]

TODO: currently debug doesn’t do anything…

Create density_df, performs convolution. optionally adds additional information if add_exa=True for allocation and priority analysis

tilting: [@Grubel1999]: Computation of Compound Distributions I: Aliasing Errors and Exponential Tilting (ASTIN 1999) tilt x numbuck < 20 is recommended log. 210 num buckets and max loss from bucket size

Aggregate reinsurance in parser has replaced the aggregate_cession_function (a function of a Portfolio object that adjusts individual line densities; applied after line aggs created but before creating not-lines; actual statistics do not reflect impact.) Agg re by unit is now applied in the Aggregate object.

TODO: consider aggregate covers at the portfolio level…Where in parse - at the top!

Parameters
  • log2

  • bs – bucket size

  • approx_freq_ge – use method of moments if frequency is larger than approx_freq_ge

  • approx_type – type of method of moments approx to use (slognorm or sgamma)

  • remove_fuzz – remove machine noise elements from FFT

  • sev_calc – how to calculate the severity, discrete (point masses as xs) or continuous (uniform between xs points)

  • discretization_calc – survival or distribution (accurate on right or left tails)

  • normalize – if true, normalize the severity so sum probs = 1. This is generally what you want; but

  • padding – for fft 1 = double, 2 = quadruple

  • tilt_amount – for tiling methodology - see notes on density for suggested parameters

  • epds – epd points for priority analysis; if None-> sensible defaults

  • trim_df – remove unnecessary columns from density_df before returning

  • add_exa – run add_exa to append additional allocation information needed for pricing; if add_exa also add epd info

  • force_severity – force computation of severities for aggregate components even when approximating

  • recommend_p – percentile to use for bucket recommendation.

  • approximation – if not None, use these instructions (‘exact’)

  • debug – if True, print debug information

Returns

property valid

Check if the model appears valid. See documentation for Aggregate.valid.

An answer of True does not guarantee the model is valid, but False means it is definitely suspect. (Similar to the null hypothesis in a statistical test). Called and reported automatically by qd for Aggregate objects.

Checks the relative errors (from self.describe) for:

  • severity mean < eps

  • severity cv < 10 * eps

  • severity skew < 100 * eps (skewness is more difficult to estimate)

  • aggregate mean < eps and < 2 * severity mean relative error (larger values indicate possibility of aliasing and that bs is too small).

  • aggregate cv < 10 * eps

  • aggregate skew < 100 * esp

eps = 1e-3 by default; change in validation_eps attribute.

Test only applied for CV and skewness when they are > 0.

Returns

True if all tests are passed, else False.

var(p)[source]

value at risk = alias for quantile function

Parameters

p

Returns

var_dict(p, kind='lower', total='total', snap=False)[source]

make a dictionary of value at risks for each line and the whole portfolio.

Returns: {line : var(p, kind)} and includes the total as self.name line

if p near 1 and epd uses 1-p.

Example:

for p, arg in zip([.996, .996, .996, .985, .01], [‘var’, ‘lower’, ‘upper’, ‘tvar’, ‘epd’]):

print(port.var_dict(p, arg, snap=True))

Parameters
  • p

  • kind – var (defaults to lower), upper, lower, tvar, epd

  • total – name for total: total==’name’ gives total name self.name

  • snap – snap tvars to index

Returns

3.4.2. Other Portfolio functions

aggregate.portfolio.check01(s)[source]

add 0 1 at start end

aggregate.portfolio.convex_points(s, gs)[source]

Extract the points that make the convex envelope, including 0 1

Testers:

%%sf 1 1 5 5

s_values, gs_values = [.001,.0011, .002,.003, 0.005, .008, .01], [0.002,.02, .03, .035, 0.036, .045, 0.05]
s_values, gs_values = [.001, .002,.003, .009, .011, 1],  [0.02, .03, .035, .05, 0.05, 1]
s_values, gs_values = [.001, .002,.003, .009, .01, 1],  [0.02, .03, .035, .0351, 0.05, 1]
s_values, gs_values = [0.01, 0.04], [0.03, 0.07]

points = make_array(s_values, gs_values)
ax.plot(points[:, 0], points[:, 1], 'x')

s_values, gs_values = convex_points(s_values, gs_values)
ax.plot(s_values, gs_values, 'r+')

ax.set(xlim=[-0.0025, .1], ylim=[-0.0025, .1])

hull = ConvexHull(points)
for simplex in hull.simplices:
    ax.plot(points[simplex, 0], points[simplex, 1], 'k-', lw=.25)
aggregate.portfolio.make_array(s, gs)[source]

convert to np array and pad with 0 1

aggregate.portfolio.make_awkward(log2, scale=False)[source]

Decompose a uniform random variable on range(2**log2) into two parts using Eamonn Long’s base 4 method.

Usage:

awk = make_awkward(16)
awk.density_df.filter(regex='p_[ABt]').cumsum().plot()
awk.density_df.filter(regex='exeqa_[AB]|loss').plot()