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
dictionary: Aggregate object dictionary specifications or
Aggregate: An actual aggregate objects or
tuple (type, dict) as returned by uw[‘name’] or
string: Names referencing objects in the optionally passed underwriter
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.
- 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 forepd_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 dictionarybase = {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.
marginal loss, lr, roe etc.
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:
basic: exag_sumparts, exag_total df.exa_total
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 ing_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
- 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_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
- 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
- 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
- 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
- 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
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
- 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
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 levelp
. 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
- 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
- sample(n, replace=True, desired_correlation=None, keep_total=True)[source]
Pull multivariate sample. Apply Iman Conover to induce correlation if required.
- 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
- show_enhanced_exhibits(fmt='{:.5g}')[source]
show all the exhibits created by enhanced_portfolio methods
- 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:
- 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 analysistilting: [@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_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.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_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()