The ultimate goal of the sstModel package is to build an instance of the Swiss Solvency Test (SST) model in order to be able to simulate from it and compute solvency figures. At the core of the sstModel package lies the idea that a binding occurs between risk-factor spaces (market risks, life risks, health risks, non-life risks, scenario risks, participation risks) and a portfolio characterizing the balance sheet.
In this document, we will follow step-by-step the process of defining a specific instance of sstModel and then computing it to get final solvency figures. Please consider that this document is only intended to present the API, meaning that we do not assume that it provides a comprehension on the model assumptions nor that the data values (chosen for the sake of API presentation) are representative of a real-world example.
As an example, we consider a portfolio subject to all types of risks implemented in the R-package:
In order to define a marketRisk, we first need to define a vocabulary of base market risk factors. In this special example we will consider that we hold a portfolio reporting in Swiss currency an that the market is composed of two currencies:
For the Swiss market we will consider the following base risk factors:
MSCI_CHF
(equity in CHF)Rued_Blass
(commercial real estate in CHF)SNB_IND
(spread in CHF)2Y_CHF
, 10Y_CHF
, 30Y_CHF
the interests rate in CHF for 2, 10 and 30 years.For the European market we will consider:
MSCI_EUR
(equity in EUR)AA_EUR_Spread
(spread in EUR)2Y_EUR
, 10Y_EUR
, 30Y_EUR
the interests rate in EUR for 2, 10 and 30 years.Additionally, since we have two markets, we should also consider the FX risk factor:
EURCHF
.We define this vocabulary as follows:
# define the market vocabulary
name <- c("EURCHF", # currency risk
"MSCI_CHF", "MSCI_EUR", # equity risks
"2Y_CHF", "10Y_CHF", "30Y_CHF", # interest rate risks in CHF
"2Y_EUR", "10Y_EUR", "30Y_EUR", # interest rate risks in EUR
"SNB_IND", "AA_EUR_Spread", # the spread risks
"Rued_Blass") # real estate
As the distribution of base market risk factor changes is assumed to be multivariate normal according to the SST standard model, we need to provide information on the covariance matrix of these risk factor changes.
# define the correlation matrix of base market risk factor changes
corr.mat <- matrix(c(
1,0.34161024,0.14725368,0.32600072,0.23891105,0.15829093,
0.33053628,0.2120393,0.22398321,-0.238861,-0.1888347,0.00871576, # EURCHF
0.34161024,1,0.80390297,0.36492975,0.2296897,0.14789193,
0.39493016,0.22665597,0.16277712,-0.4181629,-0.4711875,0.14542951, # MSCI_CHF
0.14725368,0.80390297,1,0.25113843,0.2195543,0.21116258,
0.50130346,0.30194514,0.23652861,-0.4573374,-0.497173,0.222605, # MSCI_EUR
0.32600072,0.36492975,0.25113843,1,0.54235597,0.34141452,
0.63987172,0.42851556,0.26727693,-0.3639534,-0.2760417,-0.0754334, # 2Y_CHF
0.23891105,0.2296897,0.2195543,0.54235597,1,0.85134549,
0.53410699,0.79149277,0.69474163,-0.4059395,-0.2140168,-0.0550199, # 10Y_CHF
0.15829093,0.14789193,0.21116258,0.34141452,0.85134549,1,
0.40118418,0.74668904,0.72843346,-0.1943058,-0.2089049,-0.1116269, # 30Y_CHF
0.33053628,0.39493016,0.50130346,0.63987172,0.53410699,0.40118418,
1,0.63153892,0.45730512,-0.4195621,-0.3505668,0.08683017, # 2Y_EUR
0.2120393,0.22665597,0.30194514,0.42851556,0.79149277,0.74668904,
0.63153892,1,0.90211566,-0.2619321,-0.3121842,-0.0739692, # 10Y_EUR
0.22398321,0.16277712,0.23652861,0.26727693,0.69474163,0.72843346,
0.45730512,0.90211566,1,-0.1365209,-0.2398577,-0.0437529, # 20Y_EUR_3
-0.238861,-0.4181629,-0.4573374,-0.3639534,-0.4059395,-0.1943058,
-0.4195621,-0.2619321,-0.1365209,1,0.31807203,-0.1240017, # SNB_IND
-0.1888347,-0.4711875,-0.497173,-0.2760417,-0.2140168,-0.2089049,
-0.3505668,-0.3121842,-0.2398577,0.31807203,1,-0.187089, # AA_EUR_Spread
0.00871576,0.14542951,0.222605,-0.0754334,-0.0550199,-0.1116269,
0.08683017,-0.0739692,-0.0437529,-0.1240017,-0.187089,1 # Rued_Blass
), byrow = T, ncol = length(name))
# name the columns and rows of this correlation matrix
colnames(corr.mat) <- name
rownames(corr.mat) <- name
# define the associated volatilities
volatility <- c(0.002473, # fx EURCHF
0.1281, 0.1674, # stocks (CHF, EUR)
0.0048876, 0.004994, 0.005203, # rate CHF
0.006247, 0.006575, 0.007183, # rate EUR
0.002979, 0.003496, # spreads
0.0739) # commercial real estate
# compute thr covariance matrix
cov.mat <- diag(volatility, length(volatility), length(volatility)) %*% corr.mat %*% diag(volatility, length(volatility), length(volatility))
# add the names to the columns and rows
colnames(cov.mat) <- rownames(cov.mat) <- colnames(corr.mat)
# we additionally should specify the base currency (equal indeed to the reporting currency) in which the covariance matrix is expressed as an attribute of the covariance matrix.
attr(cov.mat, "base.currency") <- "CHF"
Now we have a vocabulary of base market risk factors, but we still do not have a clear mapping to these quantitiy toward economical (-financial) meaningful metrics subject to risk (that we call riskFactor
). The next step thus consists in defining what we call a mappingTable, i.e., a coherent mapping of base market risk factors towards economical (-financial) measures (riskFactor
).
For that, we have the following constructors:
currency
: FX echange rate risk.equity
: asset (with direct market price) risk.spread
: spread risk (for cashflows).rate
: interest rates risk.pcRate
: principal component of interest rate curves (in case of principal component modelling of rates).Each of this constructor basically contains three type of information:
name
: a mapping towards the base market vocabulary.scale
: the scaling that should be applied to the base market risk factor to represent the riskFactor
change that we want to construct.riskFactor
. name
and scale
together imply that a change in the riskFactor
that we are defining will be represent by \(scale \times name\) (extended to linear combinations in case of principal component modeling of rates). Beware that this representation is strong (probabilistically speaking, there is equality almost surely and not only in distribution).
In our specific case we define:
# the exchange rates (here only one), always assuming that exchange rates should map into the reporting (base currency) and be coherent with the covariance matrix of base market risk factors.
list.currency <- list(currency(name = "EURCHF",
from = "EUR",
to = "CHF"))
# the asset risks (with direct market price).
list.equity <- list(equity(name = "MSCI_CHF",
type = "equity",
currency = "CHF"),
equity(name = "MSCI_EUR",
type = "equity",
currency = "EUR"))
# the real estates, here we assume that private real estate is a scaling
# of commercial real estate (implying a perfect correlation of riskFactor changes in the Monte-Carlo simulations).
list.real.estate <- list(equity(name = "Rued_Blass",
type = "commercial real estate",
currency = "CHF"),
equity(name = "Rued_Blass",
type = "private real estate",
currency = "CHF",
scale = 0.4694626))
# the interests rates (specififying the mapping)
list.rates <- list(rate(name = "2Y_CHF",
currency = "CHF",
horizon = "k"),
rate(name = "10Y_CHF",
currency = "CHF",
horizon = "m"),
rate(name = "30Y_CHF",
currency = "CHF",
horizon = "l"),
rate(name = "2Y_EUR",
currency = "EUR",
horizon = "k"),
rate(name = "10Y_EUR",
currency = "EUR",
horizon = "m"),
rate(name = "30Y_EUR",
currency = "EUR",
horizon = "l"))
# the spread risks (attached with a rating)
list.spread <- list(spread(name = "SNB_IND",
currency = "CHF",
rating = "AAA"),
spread(name = "AA_EUR_Spread",
currency = "EUR",
rating = "AA"))
# we finally bind all these risks in a mapping table of market risks
mapping.table <- mappingTable(c(list.currency, list.equity,
list.real.estate, list.rates,
list.spread),
list.arg = T)
We need to provide initial values (\(t = 0\)) for rates (rate
) and FX rates (currency
) as well as time-to-maturity projections.
# initial values list
initial.values <- list()
# initial fx values
initial.values$initial.fx <- initialFX(from = c("EUR"),
to = c("CHF"),
fx = c(1.07209))
# initial rates values, please note that initial.rates.values are defined in the document but not shown on the PDF version due to its length.
initial.values$initial.rate <- initialRate(time = c(c(1:10, 11:20, 21:50),
c(1:10, 11:20, 21:50)),
currency = c(rep("CHF", 50),
rep("EUR", 50)),
rate = initial.rates.values)
# define the time-to-maturity projections for rates
mapping.time <- mappingTime(time = c(1:10, 11:20, 21:50),
mapping = c(rep("k", 10), rep("m", 10), rep("l", 30)))
We are now in shape of constructing a marketRisk
instance.
# symmetrize the covariance matrix (to avoid numerical errors)
cov.mat <- (cov.mat + t(cov.mat)) / 2
# we build the full market risk
market.risk <- marketRisk(cov.mat = cov.mat,
mapping.table = mapping.table,
initial.values = initial.values,
base.currency = "CHF",
mapping.time = mapping.time)
As before, we first need to define a base vocabulary of life insurance risk factors:
# define the names of life insurance risk-factors.
life.risk.name <- c("Sterblichkeit", "Langlebigkeit", "Invaliditat",
"Reaktivierung","Kosten", "Storno", "Kapitaloption",
"KostenBVG", "StornoBVG")
Next we define the correlation matrix of life risk-factors (due to multivariate normal assumption) and probability values (sensitivites beeing understood as quantiles at one minus these probability values of centered Normal random variables):
# define the life correlation matrix
life.corr.mat <- matrix(c(1,-0.75,0.25,0,0,0,0,0,0,
-0.75,1,0,0,0,0,0.25,0,0,
0.25,0,1,-0.75,0.25,0,0,0.25,0,
0,0,-0.75,1,0,0,0,0,0,
0,0,0.25,0,1,0.5,0,0.5,0.5,
0,0,0,0,0.5,1,0,0.5,0.5,
0,0.25,0,0,0,0,1,0,-0.5,
0,0,0.25,0,0.5,0.5,0,1,0.5,
0,0,0,0,0.5,0.5,-0.5,0.5,1), ncol = 9, byrow = T)
# add names
colnames(life.corr.mat) <- life.risk.name
rownames(life.corr.mat) <- life.risk.name
# choose the life quantiles
life.quantiles <- rep(0.995, 9)
# define a lifeRisk
life.risk <- lifeRisk(corr.mat = life.corr.mat,
quantile = life.quantiles)
As before, we first need to define a base vocabulary of health insurance risk factors:
# define the names of health insurance risk-factors.
health.risk.name <- c("Langzeit Verpflichtungen", "Kollektiv Taggeld")
Next we define the correlation matrix of health risk factors (due to multivariate normal assumption) and quantiles values (sensitivites beeing understood as volatilities of centered Normal random variables):
# define the life correlation matrix
health.corr.mat <- diag(1, 2)
# add names
colnames(health.corr.mat) <- health.risk.name
rownames(health.corr.mat) <- health.risk.name
# define a healthRisk
health.risk <- healthRisk(corr.mat = health.corr.mat)
There are three types of inputs for nonLifeRisk supported by the package:
In this example we will assume log-normal simulation inputs:
# define a nonLifeRisk
nonlife.risk <- nonLifeRisk(type = "log-normal",
param = list(mu = 10, sigma = 2),
currency = "CHF")
In order to define a participation risk we provide the volatility information:
# define a participarion risk
participation.risk <- participationRisk(volatility = 0.25)
In order to define scenario risks, we need to provide information on:
In our case, we consider two scenarios:
# define a scenario risk
scenario.risk <- scenarioRisk(name = c("terrorism", "financial distress"),
probability = c(0.05, 0.01),
currency = c("CHF", "CHF"),
effect = c(-2*10^6, -10^6))
Once all risk-factors have been defined, we can proceed to the portfolio definition. We consider, in coherence with the risk factors, four types of items:
Please note that there is no non-life item, since all sufficient information for non life risk is contained in nonLifeRisk.
Let us incrementally add market items of all existing types:
# cashflows items (please note that spread is the initial spread value for this cashflow)
list.fixed.income <- list( # cashflow in CHF and EUR
cashflow(time = 1L, currency = "CHF",
rating = "AAA", spread = 0.05, value = 10^6),
cashflow(time = 5L, currency = "CHF",
rating = "AAA", spread = 0.03, value = 10^6),
cashflow(time = 1L, currency = "EUR",
rating = "AA", spread = -0.05, value = 10^6),
cashflow(time = 5L, currency = "EUR",
rating = "AA", spread = -0.03, value = 10^6),
# liabilities in CHF
liability(time = 2L, currency = "CHF", value = 20*10^6))
# asset items
list.assets <- list( # stock assets
asset(type = "equity",
currency = "CHF", value = 30*10^6),
asset(type = "equity",
currency = "EUR", value = 20*10^6),
# real estate assets
asset(type = "commercial real estate",
currency = "CHF", value = 10*10^6),
asset(type = "private real estate",
currency = "CHF", value = 5*10^6))
# assetForwards and fxForwards
list.forwards <- list( # asset forward
assetForward(type = "equity",
currency = "CHF",
time = 2L,
exposure = 2*10^6,
price = 10^6,
position ="short"),
# fx forward
fxForward(domestic = "CHF", foreign = "EUR",
time = 1L, nominal = 10^6, rate = 1.06,
position ="short"))
# delta remainder
list.delta <- list(delta(name = c("MSCI_CHF", "MSCI_EUR"),
currency = c("CHF","CHF"),
sensitivity = c(10^5, -10^5)))
We then add the insurance items:
life.item <- life(name = c("Sterblichkeit", "Langlebigkeit", "Invaliditat",
"Reaktivierung", "Kosten", "Storno", "Kapitaloption",
"KostenBVG", "StornoBVG"),
currency = rep("CHF",9),
sensitivity = rep(-10^5, 9))
health.item <- health(name = c("Langzeit Verpflichtungen", "Kollektiv Taggeld"), currency = rep("CHF",2), sensitivity = rep(10^5, 2))
We also need to provide the exposure to the participation:
participation.item <- participation(currency = "CHF", value = 10^6)
Once all market items and insurance items have been defined, we can bind all positions in a portfolio. Note that portfolio-specific values are also added as a parameter named portfolio.parameters
.
pf <- portfolio(market.items = c(list.fixed.income, list.assets,
list.forwards, list.delta),
base.currency = "CHF",
life.item = life.item,
health.item = health.item,
participation.item = participation.item,
portfolio.parameters = list(mvm = list(mvm.life = 10^5,
mvm.health = 10^5,
mvm.nonlife = 10^5),
credit.risk = 10^5,
correction.term = 10^4,
expected.financial.result = 3*10^6,
expected.insurance.result = 2*10^6,
rtkg = 20*10^6,
rtkr = 18*10^6))
In addition, you can also create macro-economic scenarios. Meaning you can valuate the portfolio (market items including participation if any) for specific values taken by the change in risk factors (with non-centered valuation formulas).
Here we define two scenarios:
# define here a macro economic scenario
# values taken by the change in market risk-factors (last in participation).
eco <- matrix(rnorm(2*13), nrow = 2)
# give names to economic scenarios and risk-factors (including participation)
colnames(eco) <- c(name, "participation")
rownames(eco) <- c("scenario_1", "scenario_2")
# create the maro.economic scenarios
macro.economic.scenarios <- macroEconomicScenarios(macro.economic.scenario.table = eco)
Once all type of risks and the portfolio have be defined, we can proceed to the sstModel definition, for that we will need to provide the aggregation copula for the risks:
the R-package supports conditional reordering with stressed Gaussian Copulas (as well as simple reordering with base Gaussian copula):
# providing a list of correlation matrices (the first one called "base" is used for ranks generation and the following for conditional scenarios).
list.correlation.matrix <- list(base = matrix(c(1,0.15,0.075,0.15,
0.15,1,0.25,0.25,
0.075,0.25,1,0.15,
0.15,0.25,0.15,1), ncol=4, byrow = T),
scenario1 = matrix(c(1,1,1,0.35,
1,1,1,0.35,
1,1,1,0.35,
0.35,0.35,0.35,1), ncol=4, byrow = T),
scenario2 = matrix(c(1,0.6,0.5,0.25,
0.6,1,0.8,0.35,
0.5,0.8,1,0.35,
0.25,0.35,0.35,1), ncol=4, byrow = T),
scenario3 = matrix(c(1,0.25,0.25,0.5,
0.25,1,0.25,0.25,
0.25,0.25,1,0.25,
0.5,0.25,0.25,1), ncol=4, byrow = T))
# we provide names for the correlation matrices
list.correlation.matrix <- lapply(list.correlation.matrix, function(corr) {rownames(corr) <- colnames(corr) <- c("market", "life","health","nonlife"); corr})
# define the region boundaries (i.e. the thresholds t under which we should reorder)
region.boundaries <- matrix(c(0.2,0.3,0.3,0.5,
0.5,0.2,0.2,0.8,
0.6,0.8,0.8,0.2), nrow=3, byrow = T)
# providing names for the regions boundaries and scenarios
colnames(region.boundaries) <- c("market", "life","health","nonlife")
rownames(region.boundaries) <- c("scenario1", "scenario2", "scenario3")
# scenario and region probabilities
scenario.probability = c(0.01, 0.01, 0.01)
region.probability = c(0.023, 0.034, 0.107)
Now we are in shape of binding the portfolio with all the types risks by creating a model instance:
# create a model instance
sst <- sstModel(portfolio = pf,
market.risk = market.risk,
life.risk = life.risk,
health.risk = health.risk,
participation.risk = participation.risk,
scenario.risk = scenario.risk,
macro.economic.scenarios = macro.economic.scenarios,
nhmr = 0.06,
reordering.parameters = list(list.correlation.matrix = list.correlation.matrix,
region.boundaries = region.boundaries,
region.probability = region.probability,
scenario.probability = scenario.probability))
Once an instance of the sstModel has been defined, we compute the model via Monte-Carlo simulations. The following R call agglomerates the core computations of the R-package. Please not that we provide a small number of simulations here (recommended \(10^6\)). Note also that the option nested.market.computation = T
is set to have more granularity details on the market simulations:
output <- compute(sst,
nsim = as.integer(10^4),
nested.market.computations = T)
The object object
output is of class sstOutput
. The idea is that once the computations have been made we can gather from this structure all type of summaries/sst figures that we are interested in. Here is the internal structure of the output:
str(output)
#> List of 15
#> $ simulations :Classes 'data.table' and 'data.frame': 10000 obs. of 15 variables:
#> ..$ marketRisk : num [1:10000] 882874 14300815 -3419543 1873604 -3360203 ...
#> ..$ cashflow : num [1:10000] -2016 56132 9327 58406 2472 ...
#> ..$ liability : num [1:10000] 258878 -108755 -89220 -340098 -23501 ...
#> ..$ asset : num [1:10000] 773630 14833456 -3705723 2224795 -3326423 ...
#> ..$ assetForward : num [1:10000] -147163 -485679 384918 -75935 -12619 ...
#> ..$ fxForward : num [1:10000] -8624 -539 6042 1444 -9687 ...
#> ..$ delta : num [1:10000] 8169 6199 -24887 4991 9556 ...
#> ..$ lifeRisk : num [1:10000] 107221 -161291 -109869 253654 196761 ...
#> ..$ healthRisk : num [1:10000] 281392 -72166 -83661 -6925 -207421 ...
#> ..$ participation : num [1:10000] -186478 346633 -318699 -58797 94067 ...
#> ..$ marketParticipationRisk: num [1:10000] 9328153 -3930547 -12107616 -10423842 5841706 ...
#> ..$ insuranceRisk : num [1:10000] 388613 -233457 -193530 246730 -10660 ...
#> ..$ drbc : num [1:10000] 7716766 -6164003 -14301146 -12177112 3831047 ...
#> ..$ scenarioRisk : num [1:10000] 0e+00 0e+00 -2e+06 0e+00 0e+00 0e+00 -2e+06 0e+00 0e+00 0e+00 ...
#> ..$ drbc.scenarioRisk : num [1:10000] 7716766 -6164003 -16301146 -12177112 3831047 ...
#> ..- attr(*, ".internal.selfref")=<externalptr>
#> $ mvm : num 3e+05
#> $ mvm.list :List of 3
#> ..$ mvm.life : num 1e+05
#> ..$ mvm.health : num 1e+05
#> ..$ mvm.nonlife: num 1e+05
#> $ rtkg : num 2e+07
#> $ rtkr : num 1.8e+07
#> $ credit.risk : num 1e+05
#> $ nhmr : num 0.06
#> $ correction.term : num 10000
#> $ expected.financial.result: num 3e+06
#> $ expected.insurance.result: num 2e+06
#> $ reference.currency : chr "CHF"
#> $ life.standalones : Named num [1:9] -103470 -103470 -103470 -103470 -103470 ...
#> ..- attr(*, "names")= chr [1:9] "Sterblichkeit" "Langlebigkeit" "Invaliditat" "Reaktivierung" ...
#> $ health.standalones : Named num [1:2] -266521 -266521
#> ..- attr(*, "names")= chr [1:2] "Langzeit Verpflichtungen" "Kollektiv Taggeld"
#> $ scenario.risk :Classes 'scenarioRisk', 'risk' and 'data.frame': 2 obs. of 4 variables:
#> ..$ name : chr [1:2] "terrorism" "financial distress"
#> ..$ probability: num [1:2] 0.05 0.01
#> ..$ currency : chr [1:2] "CHF" "CHF"
#> ..$ effect : num [1:2] -2e+06 -1e+06
#> $ macro.economic.scenarios :Classes 'data.table' and 'data.frame': 1 obs. of 2 variables:
#> ..$ scenario_1: num 4.94e+10
#> ..$ scenario_2: num -2.33e+08
#> ..- attr(*, ".internal.selfref")=<externalptr>
#> - attr(*, "class")= chr [1:2] "sstOutput" "list"
Let us first have a look at the the simulation values:
head(output$simulations)
#> marketRisk cashflow liability asset assetForward fxForward
#> 1: 882874 -2016.293 258878.10 773629.9 -147162.81 -8623.9707
#> 2: 14300815 56132.477 -108754.58 14833455.6 -485678.68 -538.7012
#> 3: -3419543 9327.034 -89219.51 -3705723.4 384917.86 6042.1542
#> 4: 1873604 58405.912 -340097.52 2224794.8 -75934.54 1444.2319
#> 5: -3360203 2471.569 -23501.37 -3326423.3 -12618.68 -9686.6872
#> 6: -4081443 51982.441 -110830.70 -4190220.7 175006.12 -2815.3698
#> delta lifeRisk healthRisk participation marketParticipationRisk
#> 1: 8168.988 107221.20 281391.95 -186477.66 9328153
#> 2: 6199.306 -161290.57 -72166.13 346632.81 -3930547
#> 3: -24887.406 -109869.20 -83660.92 -318698.95 -12107616
#> 4: 4990.979 253654.39 -6924.69 -58797.26 -10423842
#> 5: 9555.730 196760.94 -207420.54 94066.63 5841706
#> 6: -4564.435 50513.47 -126554.54 436829.75 -3040367
#> insuranceRisk drbc scenarioRisk drbc.scenarioRisk
#> 1: 388613.15 7716766 0e+00 7716766
#> 2: -233456.70 -6164003 0e+00 -6164003
#> 3: -193530.12 -14301146 -2e+06 -16301146
#> 4: 246729.70 -12177112 0e+00 -12177112
#> 5: -10659.60 3831047 0e+00 3831047
#> 6: -76041.07 -5116408 0e+00 -5116408
Then we can consult a bunch of solvency figures:
# market value margin
marketValueMargin(output)
#> [1] 1318066
# one year risk capital without scenarios
riskCapital(object = output, with.scenario = F)
#> [1] 14218415
# one year risk capital with scenarios
riskCapital(object = output, with.scenario = T)
#> [1] 14366917
# without scenarios
targetCapital(output, with.scenario = F)
#> [1] 15536481
# with scenarios
targetCapital(output, with.scenario = T)
#> [1] 15684983
# sst ratio without scenarios
sstRatio(object = output, with.scenario = F)
#> [1] 1.313925
# sst ratio with scenarios
sstRatio(object = output, with.scenario = T)
#> [1] 1.300344
Finally the output can be saved into a formated Excel output using the function write.sstOutput
.