---
title: Using `price` and `evaluate`
subtitle: "Premiums from distortions and breakeven distortions from premiums on an Aggregate with price, price_pentagon and evaluate"
categories: [pricing]
date: last-modified
jupyter:
jupytext:
text_representation:
extension: .qmd
format_name: quarto
format_version: '1.0'
kernelspec:
display_name: Python 3 (ipykernel)
language: python
name: python3
---
Pricing and evaluation solve the same equation for different unknowns. With assets $a$ behind a loss $X$, a distortion $g$ and a premium $P$ are consistent when
$$
g[X \wedge a] = \int_0^a g(S(x))\,dx = P.
$$
Square brackets mark $g$ acting on a random variable, as a spectral risk measure, and parentheses mark $g$ acting on a probability. Pricing fixes $g$ and solves for $P$, which is what `Aggregate.price(p, g)` does, with $a$ the VaR at probability $p$ (see [Using `price`](price.qmd)). Evaluation fixes $P$ and solves, for each family of distortions, for the shape that satisfies the equation. The position $P - X \wedge a$ is acceptable to every distortion in the family up to that shape, so the **breakeven** distortion is the most stress the position survives, the acceptability level of Cherny and Madan. Its $\mathrm{gini}$ (see [Using distortions](distortions.qmd)) is an acceptability index on one scale for every family: the larger it is, the more stress the premium allows for.
Both answers come with the accounting of the **pentagon**: expected loss $L = \mathsf{P}(X \wedge a)$, margin $M = P - L$, premium $P$, capital $Q = a - P$, and assets $a$, with the ratios `LR` $= L/P$, `PQ` $= P/Q$, and `ROE` $= M/Q$. The method `Aggregate.price_pentagon` completes the pentagon from the assets, given by `p=` or `a=`, and one target, one of `P`, `M`, `Q`, `LR`, `PQ`, and `ROE`. No distortion is involved: the assets fix $L$, and the identities do the rest.
The method `Aggregate.evaluate(P, p=None, a=None)` returns an `EvaluationResult`, whose `evaluation_df` has one row per family. With `p=` or `a=` the position is $P - X \wedge a$ and the families are `ccoc`, `ph`, `wang`, `dual`, and `tvar`. Without either, the position is $P - X$ against the whole distribution, and `ccoc`, which needs an asset level, is left out. The argument `P` defaults to the premium declared in the DecL program. The examples use the same book as [Using `price`](price.qmd).
```decl
agg Book 25 claims 2000 xs 0 sev lognorm 100 cv 4 poisson
distortion Gentle ph 0.6
```
```{python}
import warnings
import pandas as pd
from aggregate import build, Distortion, __version__
from style import wprint, GT, refuse, figures
figures()
print(__version__)
book = build('agg Book 25 claims 2000 xs 0 sev lognorm 100 cv 4 poisson')
gentle = build('distortion Gentle ph 0.6')
```
## The premium and pentagon from a distortion
At $p = 0.995$ the assets are 6,438, and the method `price` with $g(s) = s^{0.6}$ gives a premium of 2,945.6, a return of 19.1% on capital. The method `price_pentagon` with the same assets and that premium returns the same pentagon from the identities alone. Given a cost of capital of 15% in place of the premium, it returns the premium that earns it, 2,822.4, which is $L + (a - L)\,r/(1+r)$ with $r = 0.15$.
```{python}
priced = book.price(0.995, gentle)
P, L = priced.loc['Book', ['P', 'L']]
pent = pd.concat([priced.rename(index={'Book': 'price'}),
book.price_pentagon(p=0.995, P=P).rename(index={'total': 'price_pentagon(P)'}),
book.price_pentagon(p=0.995, ROE=0.15).rename(index={'total': 'price_pentagon(ROE)'})])
GT(pent, float_format=',.1f', ratio_cols=['LR', 'PQ', 'ROE'])
```
## Breakeven distortions from a premium
Evaluating the premium of 2,945.6 at the same assets reverses the pricing calculation: the `ph` row recovers the shape $a = 0.6$, to a solver error of $10^{-10}$. The `ccoc` row recovers the return of 19.1%, since `ccoc` at return $r$ prices to exactly the premium that earns $r$ on capital. The other rows are the shapes of the other families that give the same premium. Their indexes run from 0.160 for `ccoc` to 0.386 for `tvar`: the families differ in how much stress the same premium allows for, and the index is lowest for the families that load the tail.
```{python}
ev = book.evaluate(P, p=0.995)
GT(ev.evaluation_df.loc['Book', ['gini', 'param_name', 'param', 'error', 'status']],
formatters={'gini': '{x:.3f}', 'param': '{x:.4f}', 'error': '{x:.1e}'})
```
Each breakeven distortion prices the book back to the premium. The check uses `Distortion.price` on the distribution of the book, at the same assets.
```{python}
ser = book.density_df.p_total
for k, row in ev.evaluation_df.loc['Book'].iterrows():
d = Distortion(k, **{row.param_name: row.param})
print(f'{d.label:12s} {d.price(ser, a=ev.a).ask:,.2f}')
```
The same equation is solved in three other places. The `tvar` breakeven level, 0.3857, is the threshold `p_star` of the `Bounds` class, which computes the envelope of every distortion that prices the book to the premium (see [Using `Bounds`](bounds.qmd) and [Using `PricingBounds`](pricing-bounds.qmd)). The method `calibrate_distortions(coc, p=)` solves the same equation with the premium set by a cost of capital. The method `PnL.evaluate` evaluates each step of a profit and loss walk, including one whose consideration is random, such as a swing-rated premium (see [Using pnl and xpnl](pnls.qmd)).
## Evaluation with and without assets
Without `p=` or `a=` the position is the premium against the unlimited loss, which has the tail beyond 6,438 to pay for, so the same premium allows for less stress. The `ph` breakeven rises from 0.600 to 0.618 and its index falls from 0.250 to 0.236. The panel `a = 6,438` is the one to compare with a calibration at the same assets.
```{python}
both = pd.concat({'a = 6,438': ev.evaluation_df.loc['Book', ['gini', 'param']],
'unlimited': book.evaluate(P).evaluation_df.loc['Book', ['gini', 'param']]},
axis=1)
GT(both, float_format='.4f')
```
## The Gini index across premiums
A premium set by a loss ratio, $P = L/\mathrm{LR}$, is evaluated below at loss ratios from 90% down to 70%. Every index rises as the loss ratio falls, and the families keep their order at every premium, from `ccoc` lowest to `tvar` highest. At a 75% loss ratio the indexes run from 0.183 to 0.432. The table puts premiums down and families across, with one index in each cell.
```{python}
scan = {f'{lr:.0%}': book.evaluate(L / lr, p=0.995).evaluation_df.loc['Book', 'gini']
for lr in [0.9, 0.85, 0.8, 0.75, 0.7]}
GT(pd.DataFrame(scan).T.rename_axis('LR'), float_format='.3f')
```
## A premium below the expected loss
A premium below the expected loss, 2,280.1, has no breakeven, since every concave distortion prices the loss at or above its mean, and so above the premium. The method `evaluate` returns NaN with the reason in `status`, and issues one `DegenerateEvaluationWarning` for the whole panel.
```{python}
with warnings.catch_warnings(record=True) as caught:
warnings.simplefilter('always')
low = book.evaluate(2200, p=0.995)
wprint(caught[0].message)
GT(low.evaluation_df.loc['Book', ['gini', 'param', 'status']])
```
## Refused calls
Four calls are refused: `evaluate` on a book with no premium declared and none passed; `p=1` on a book with unbounded losses, where it does not determine an asset level; both `p=` and `a=`; and `price_pentagon` with two targets.
```{python}
refuse(lambda: book.evaluate(),
lambda: book.evaluate(P, p=1),
lambda: book.evaluate(P, p=0.995, a=6438),
lambda: book.price_pentagon(p=0.995, P=P, ROE=0.15))
```