risksim
Portfolio-level loss simulation and aggregate reinsurance modeling.
Overview
risksim composes one or more stochastic loss models into a portfolio, simulates
the portfolio's aggregate loss, applies aggregate reinsurance structures, and
summarizes the gross, ceded, and retained results. It is intentionally
model-agnostic: anything that exposes a .sample(size) method can be a portfolio
component, so risksim handles composition, contracts, and simulation summaries
while the loss generation lives wherever you like.
It pairs naturally with lossmodels — build a collective-risk model (or any
severity/frequency-based model) there, drop it into a risksim portfolio here —
but it has no hard dependency on it; its only runtime requirement is numpy.
Highlights
- Portfolios — combine multiple simulated loss components, each with an optional weight, into a single portfolio.
- Aggregate reinsurance — aggregate annual layers (attachment, limit, share) and multi-layer contract programs.
- One simulation, every view — a single run yields gross, ceded, retained, per-component, and per-layer losses.
- Risk measures — mean, variance / standard deviation, VaR, TVaR, exceedance probabilities, and a one-call summary, on any loss vector.
- Model-agnostic — any object implementing
.sample(size)works; components that also expose.mean()unlock closed-form portfolio moments.
Installation
pip install risksim
From source:
pip install -e .
Requires Python >=3.10; the only runtime dependency is numpy. The examples
below use lossmodels for the component models — install it too if you want to
run them as written.
Package structure
risksim/
├── portfolio.py # Portfolio and PortfolioItem
├── contracts.py # AggregateLayer, ContractProgram, apply_contract
├── results.py # SimulationResult (gross / ceded / retained / component / layer views)
├── metrics.py # mean, variance, std, var, tvar, prob_exceeding, summary
└── protocols.py # SupportsSample, SupportsMoments
Quick start
from lossmodels import Poisson, Lognormal, CollectiveRiskModel
from risksim import Portfolio, PortfolioItem, AggregateLayer
# two lines of business, each a collective-risk model (any .sample object works)
line_a = CollectiveRiskModel(Poisson(3.0), Lognormal(8.0, 0.9))
line_b = CollectiveRiskModel(Poisson(1.5), Lognormal(9.0, 0.7))
portfolio = Portfolio([
PortfolioItem("line_a", line_a),
PortfolioItem("line_b", line_b, weight=1.25), # optional exposure weight
])
# simulate the portfolio's aggregate annual loss, net of an aggregate layer
layer = AggregateLayer(attachment=250_000, limit=500_000, share=1.0)
result = portfolio.simulate(100_000, contract=layer)
print("gross mean :", result.gross_mean)
print("ceded mean :", result.ceded_mean)
print("retained mean:", result.retained_mean)
print("retained 99% VaR :", result.var(0.99))
print(result.summary())
Portfolios
A PortfolioItem wraps a sampleable loss model with a name and an optional
weight (a multiplier applied to that component's sampled losses). A Portfolio
is a sequence of items.
from risksim import Portfolio, PortfolioItem
port = Portfolio([PortfolioItem("a", model_a), PortfolioItem("b", model_b, weight=0.5)])
gross = port.sample(50_000) # total portfolio loss per simulation
by_component = port.sample_components(50_000) # per-component loss matrix
print(port.component_names())
When every component also exposes .mean() / .variance() (the SupportsMoments
protocol — which lossmodels models satisfy), the portfolio reports closed-form
moments without simulating:
port.mean() # sum of component means (times weights)
port.std()
port.variance()
simulate(size, contract=None) runs the portfolio and returns a
SimulationResult; sample(size) returns just the gross loss vector.
Aggregate reinsurance
An AggregateLayer is an aggregate annual structure defined by three numbers:
attachment— the aggregate retention; losses below it are not ceded.limit— the width of the layer above the attachment (None= unlimited).share— the ceded proportion of the layer (e.g.0.8for an 80% placement).
For a gross annual loss L, the ceded amount is
share × min(max(L − attachment, 0), limit).
from risksim import AggregateLayer, ContractProgram, apply_contract
# a single layer: 80% of the 500k aggregate layer above a 250k retention
layer = AggregateLayer(attachment=250_000, limit=500_000, share=0.8)
# stack layers into a program (e.g. a working layer plus a higher layer)
program = ContractProgram([
AggregateLayer(attachment=250_000, limit=500_000, share=1.0, name="working"),
AggregateLayer(attachment=750_000, limit=1_000_000, share=0.5, name="higher"),
])
# apply either to a loss vector directly
ceded, retained = apply_contract(gross_losses, program)
apply_contract(losses, contract) returns (ceded, retained) arrays aligned with
the input, where ceded + retained == losses. Passing a contract to
Portfolio.simulate(..., contract=program) does the same inside the simulation and
records the per-layer detail.
The simulation result
SimulationResult holds every view from a single run and computes risk measures on
demand:
| Attribute / method | Meaning |
|---|---|
gross_losses, ceded_losses, retained_losses |
aligned loss vectors |
component_losses, component_names |
per-component loss matrix |
layer_losses, layer_names |
per-layer ceded amounts (with a contract) |
gross_mean, ceded_mean, retained_mean |
mean of each view |
component_means, layer_means |
per-component / per-layer means |
losses |
the retained vector (the portfolio's net position) |
mean(), std(), variance() |
moments of the net loss |
var(q), tvar(q) |
Value-at-Risk and Tail-VaR of the net loss |
prob_exceeding(threshold) |
P(net loss > threshold) |
summary() |
a dict of headline statistics |
n_sims |
the number of simulations |
Risk measures
The metrics module operates on any loss vector (a NumPy array or list), so you
can use it independently of the portfolio machinery:
from risksim import metrics
metrics.mean(losses)
metrics.std(losses) # metrics.variance(...) also available
metrics.var(losses, 0.99) # 99% VaR
metrics.tvar(losses, 0.99) # 99% TVaR
metrics.prob_exceeding(losses, 1_000_000)
metrics.summary(losses, quantiles=(0.95, 0.99, 0.995))
The SupportsSample protocol
Any object implementing sample(size: int) -> np.ndarray can be a portfolio
component — a lossmodels collective-risk model or severity, a custom class, or a
thin wrapper around a sampler. Components that additionally implement mean() /
variance() (SupportsMoments) enable the closed-form Portfolio.mean() /
.std() / .variance() shortcuts.
Testing
pytest -q
License
MIT License
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file risksim-0.3.1.tar.gz.
File metadata
- Download URL: risksim-0.3.1.tar.gz
- Upload date:
- Size: 22.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
680923b1a60911f498540c987d865a2b59ddb412b1a0368d575ac112b09444b8
|
|
| MD5 |
da47f9a438e9caf8a69faeeaea4d58ab
|
|
| BLAKE2b-256 |
48e315bc570aa00f75c928bfb70f0f0352c8e8109745ee031bf9740bcd0e60bc
|
File details
Details for the file risksim-0.3.1-py3-none-any.whl.
File metadata
- Download URL: risksim-0.3.1-py3-none-any.whl
- Upload date:
- Size: 12.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2f2ab8de70a2cba761a863a86213009e5d28cfb736b52b880343eca7ddf3e6ae
|
|
| MD5 |
14f8e87ef532388aed11e69191863809
|
|
| BLAKE2b-256 |
7d80ad3a385b541f4353dcbfc2d2a8317261fc26f8cc613b48fed988dabe1981
|