diff --git a/R/HelperFunctions.R b/R/HelperFunctions.R index f825d40e9a9b6ebde2d1abf81ed78091b0ae3bcc..75d525b75f90e0b0954cf23fdca66510c2301507 100644 --- a/R/HelperFunctions.R +++ b/R/HelperFunctions.R @@ -1,4 +1,4 @@ -calculatePVSurvival = function(q, advance, arrears, ..., m=1, mCorrection = list(alpha=1, beta=0), v=1) { +calculatePVSurvival = function(q, advance, arrears=c(0), ..., m=1, mCorrection = list(alpha=1, beta=0), v=1) { # assuming advance and arrears have the same dimensions... init = advance[1]*0; l = max(length(q), length(advance), length(arrears)); diff --git a/R/InsuranceContract.R b/R/InsuranceContract.R index b2717b944244eb7838c1e7572a58009de67e277a..989379c680938baaa75d66f4e6bcb0b7e2a24457 100644 --- a/R/InsuranceContract.R +++ b/R/InsuranceContract.R @@ -8,21 +8,23 @@ InsuranceContract = R6Class( tarif = NA, #### Contract settings - sumInsured = 1, - YOB = NA, - age = NA, - policyPeriod = Inf, - premiumPeriod = 1, - deferral = 0, - guaranteed = 0, + params = list( + sumInsured = 1, + YOB = NA, + age = NA, + policyPeriod = Inf, + premiumPeriod = 1, + deferral = 0, + guaranteed = 0, - premiumPayments = PaymentTimeEnum("in advance"), - benefitPayments = PaymentTimeEnum("in advance"), + premiumPayments = PaymentTimeEnum("in advance"), + benefitPayments = PaymentTimeEnum("in advance"), - premiumFrequency = 1, - benefitFrequency = 1, # Only for annuities! + premiumFrequency = 1, + benefitFrequency = 1, # Only for annuities! - loadings = list(), # Allow overriding the tariff-defined loadings (see the InsuranceTariff class for all possible names) + loadings = list() # Allow overriding the tariff-defined loadings (see the InsuranceTariff class for all possible names) + ), #### Caching values for this contract, initialized/calculated when the object is created transitionProbabilities = NA, @@ -35,8 +37,11 @@ InsuranceContract = R6Class( presentValues = NA, presentValuesCosts = NA, - premiums = NA, premiumCoefficients = NA, + premiums = NA, + absCashFlows = NA, + absPresentValues = NA, + reserves = NA, premiumComposition = NA, @@ -53,22 +58,22 @@ InsuranceContract = R6Class( premiumFrequency = 1, benefitFrequency = 1, deferral = 0, YOB = 1975) { self$tarif = tarif; - self$age = age; - self$policyPeriod = policyPeriod; - if (missing(premiumPeriod) && !is.na(self$tarif$defaultPremiumPeriod)) { - self$premiumPeriod = self$tarif$defaultPremiumPeriod; + self$params$age = age; + self$params$policyPeriod = policyPeriod; + if (missing(premiumPeriod) && !is.null(self$tarif$defaultPremiumPeriod)) { + self$params$premiumPeriod = self$tarif$defaultPremiumPeriod; } else { - self$premiumPeriod = premiumPeriod; + self$params$premiumPeriod = premiumPeriod; } - self$sumInsured = sumInsured; - if (!missing(deferral)) self$deferral = deferral; - if (!missing(YOB)) self$YOB = YOB; - if (!missing(premiumPayments)) self$premiumPayments = premiumPayments; - if (!missing(benefitPayments)) self$benefitPayments = benefitPayments; - if (!missing(premiumFrequency)) self$premiumFrequency = premiumFrequency; - if (!missing(benefitFrequency)) self$benefitFrequency = benefitFrequency; - if (!missing(guaranteed)) self$guaranteed = guaranteed; - if (!missing(loadings)) self$loadings = loadings; + self$params$sumInsured = sumInsured; + if (!missing(deferral)) self$params$deferral = deferral; + if (!missing(YOB)) self$params$YOB = YOB; + if (!missing(premiumPayments)) self$params$premiumPayments = premiumPayments; + if (!missing(benefitPayments)) self$params$benefitPayments = benefitPayments; + if (!missing(premiumFrequency)) self$params$premiumFrequency = premiumFrequency; + if (!missing(benefitFrequency)) self$params$benefitFrequency = benefitFrequency; + if (!missing(guaranteed)) self$params$guaranteed = guaranteed; + if (!missing(loadings)) self$params$loadings = loadings; self$recalculate(); }, @@ -78,291 +83,104 @@ InsuranceContract = R6Class( self$determineCashFlows(); self$calculatePresentValues(); self$calculatePremiums(); - self$calculatePresentValuesAllBenefits(); + self$updatePresentValues(); # Update the cash flows and present values with the values of the premium + + self$calculateAbsCashFlows(); + self$calculateAbsPresentValues(); self$calculateReserves(); self$premiumAnalysis(); - }, - exportExcel = function(filename) { - ### - nrrows = dim(self$cashFlows)[[1]]; # Some vectors are longer (e.g. qx), so determine the max nr or rows - - ################################################ - # Style information - ################################################ - headerStyle = createStyle(border="TopLeftRight", borderColour="#DA9694", borderStyle="medium", bgFill="#C0504D", fontColour="#FFFFFF", halign="center", valign="center", textDecoration="bold"); - tableHeaderStyle = createStyle(halign="center", valign="center", textDecoration="bold"); - hide0Style = createStyle(numFmt="General; General; \"\""); - cost0Style = createStyle(numFmt="0.000%; 0.000%; \"\""); - wrapStyle = createStyle(wrapText=TRUE); - centerStyle = createStyle(halign="center", valign="center"); - - ################################################ - # Helper Functions - ################################################ - writeAgeQTable = function (sheet, crow=1, ccol=1) { - writeData(wb, sheet, "Sterblichkeiten", startCol = ccol+2, startRow = crow); - addStyle(wb, sheet, style=headerStyle, rows=crow, cols = ccol+2, stack=TRUE); - mergeCells(wb, sheet, rows=crow, cols=(ccol+2):(ccol+3)) - writeDataTable(wb, sheet, self$transitionProbabilities[1:nrrows,], - startRow=crow+1, startCol = ccol, colNames = TRUE, rowNames = TRUE, - tableStyle = "TableStyleMedium3", withFilter = FALSE, headerStyle = tableHeaderStyle); - freezePane(wb, sheet, firstActiveRow=crow+2, firstActiveCol = ccol+2) - addStyle(wb, sheet, style=centerStyle, rows=(crow+2):(crow+1+nrrows), cols=ccol:(ccol+1), gridExpand = TRUE, stack=TRUE); - dim(self$transitionProbabilities)[[2]] + 2; - }; - writeValuesTable = function (sheet, values, caption=NULL, crow=1, ccol=1, rowNames=FALSE, tableStyle="TableStyleMedium3", tableName=NULL, withFilter=FALSE, headerStyle=tableHeaderStyle, valueStyle=NULL) { - nrrow = dim(values)[[1]]; - nrcol = dim(values)[[2]]; - addcol = if (rowNames) 1 else 0; - ecol = ccol + addcol + nrcol - 1; - if (!missing(caption)) { - writeData(wb, sheet, caption, startCol = ccol+addcol, startRow = crow); - addStyle(wb, sheet, style=headerStyle, rows=crow, cols = ccol+addcol, stack=TRUE); - mergeCells(wb, sheet, rows=crow, cols=(ccol+addcol):ecol); - } - writeDataTable(wb, sheet, values, startRow=crow+1, startCol=ccol, colNames=TRUE, - rowNames=rowNames, tableStyle=tableStyle, - tableName=tableName, withFilter = withFilter, headerStyle = headerStyle) - if (!missing(valueStyle)) { - addStyle(wb, sheet, style=valueStyle, rows=(crow+2):(crow+nrrow+1), cols=(ccol+addcol):ecol, gridExpand = TRUE, stack = TRUE); - } - # width of table is the return value - nrcol + addcol - }; - - writePremiumCoefficients = function(sheet, values, type="benefits", crow=crow, ccol=ccol) { - writeData(wb, sheet, matrix(c( - "Nettoprämie", "", "Zillmerprämie", "", "Bruttoprämie", "", - "rel. zu VS", "rel. zu Prämie", "rel. zu VS", "rel. zu Prämie", "rel. zu VS", "rel. zu Prämie"), 6, 2 - ), startCol = ccol, startRow = crow, colNames = FALSE, borders = "rows", borderColour = "gray5", borderStyle = "thin"); - mergeCells(wb, sheet, cols = ccol, rows = crow:(crow+1)); - mergeCells(wb, sheet, cols = ccol, rows = (crow+2):(crow+3)); - mergeCells(wb, sheet, cols = ccol, rows = (crow+4):(crow+5)); - addStyle(wb, sheet, style=createStyle(valign = "center", borderColour = "gray5", border = "LeftBottomTop", borderStyle = "thin"), rows = crow:(crow+5), cols = ccol); - mod = function(a) { as.data.frame(t(a)) }; - if (type=="costs") { - mod = function(vals) { - newvals=vals; - dim(newvals) = c(1, dim(vals)); - dimnames(newvals) = c(list("Coeff"), dimnames(vals)); - as.data.frame(self$tarif$costValuesAsMatrix(newvals)) - }; - } - - writeData(wb, sheet, mod(values[["net"]][["SumInsured"]][[type]]), startCol = ccol+2, startRow = crow, colNames=FALSE, borders="surrounding", borderColour="gray5", borderStyle="thin"); - writeData(wb, sheet, mod(values[["net"]][["Premium"]][[type]]), startCol = ccol+2, startRow = crow+1, colNames=FALSE, borders="surrounding", borderColour="gray5", borderStyle="thin"); - writeData(wb, sheet, mod(values[["Zillmer"]][["SumInsured"]][[type]]), startCol = ccol+2, startRow = crow+2, colNames=FALSE, borders="surrounding", borderColour="gray5", borderStyle="thin"); - writeData(wb, sheet, mod(values[["Zillmer"]][["Premium"]][[type]]), startCol = ccol+2, startRow = crow+3, colNames=FALSE, borders="surrounding", borderColour="gray5", borderStyle="thin"); - writeData(wb, sheet, mod(values[["gross"]][["SumInsured"]][[type]]), startCol = ccol+2, startRow = crow+4, colNames=FALSE, borders="surrounding", borderColour="gray5", borderStyle="thin"); - writeData(wb, sheet, mod(values[["gross"]][["Premium"]][[type]]), startCol = ccol+2, startRow = crow+5, colNames=FALSE, borders="surrounding", borderColour="gray5", borderStyle="thin"); - } - # costPV = as.data.frame(self$tarif$costValuesAsMatrix(self$presentValuesCosts)); - # ccol = 1; - # crow = 4; - # # We add six lines before the present values to show the coefficients for the premium calculation - # ccol = ccol + writeAgeQTable("Barwerte", crow=crow+6, ccol=1); - # - # ccol = ccol + writeValuesTable("Barwerte", as.data.frame(self$presentValues), - # crow=crow+6, ccol=ccol, tableName="PresentValues_Benefits", - # caption = "Leistungsbarwerte", valueStyle=hide0Style) + 1; - # - # writePremiumCoefficients("Barwerte", self$premiumCoefficients, type="costs", crow=crow, ccol=ccol-1); - # ccol = ccol + writeValuesTable("Barwerte", as.data.frame(costPV), - - - ################################################ - # General Workbook setup - ################################################ - wb = openxlsx::createWorkbook(); - addWorksheet(wb, "Tarifinformationen"); - addWorksheet(wb, "Reserven"); - addWorksheet(wb, "Barwerte"); - addWorksheet(wb, "Cash-Flows"); - - # Print out general Contract and Tariff information, including results - crow = 1; - writeData(wb, "Tarifinformationen", matrix(c( - "Tarif:", self$tarif$tarif, - "Tarifname:", self$tarif$name, - "Description:", self$tarif$desc - ), 3, 2, byrow = TRUE), startCol=1, startRow=1, colNames=FALSE, rowNames=FALSE); - mergeCells(wb, "Tarifinformationen", cols=2:10, rows=1); - mergeCells(wb, "Tarifinformationen", cols=2:10, rows=2); - mergeCells(wb, "Tarifinformationen", cols=2:10, rows=3); - addStyle(wb, "Tarifinformationen", style=wrapStyle, rows=3, cols=2:10, stack=TRUE); - addStyle(wb, "Tarifinformationen", style=createStyle(valign="top"), rows=1:3, cols=1:10, gridExpand=TRUE, stack=TRUE); - - crow = crow+4; - - ################################################ - # Basic parameters - ################################################ - values=c( - "Sum insured"=self$sumInsured, - "Mortality table"=self$tarif$mortalityTable@name, - "YOB"=self$YOB, - "Age"=self$age, - "Policy duration"=self$policyPeriod, - "Premium period"=self$premiumPeriod, - "Deferral"=self$deferral, - "Guaranteed payments"=self$guaranteed, - i=self$tarif$i); - - writeData(wb, "Tarifinformationen", "Basisdaten des Vertrags und Tarifs", startCol=1, startRow=crow); - mergeCells(wb, "Tarifinformationen", cols=1:length(values), rows=crow:crow); - writeDataTable(wb, "Tarifinformationen", as.data.frame(t(values)), - startCol=1, startRow=crow+1, colNames=TRUE, rowNames=FALSE, - tableStyle="TableStyleMedium3", withFilter = FALSE, headerStyle = tableHeaderStyle); - crow = crow + 4; - - # Premiums - writeData(wb, "Tarifinformationen", "Prämien", startCol=1, startRow=crow); - mergeCells(wb, "Tarifinformationen", cols=1:length(self$premiums), rows=crow:crow); - writeDataTable(wb, "Tarifinformationen", as.data.frame(t(self$premiums)), - startCol=1, startRow=crow+1, colNames=TRUE, rowNames=FALSE, - tableStyle="TableStyleMedium3", withFilter = FALSE, headerStyle = tableHeaderStyle); - crow = crow + 4; - - - ################################################ - # Print out Reserves and premium decomposition - ################################################ - - # Age, death and survival probabilities - ccol = 1; - crow = 4; - ccol = ccol + writeAgeQTable("Reserven", crow=crow, ccol=1); - ccol = ccol + writeValuesTable("Reserven", as.data.frame(self$reserves), - crow=crow, ccol=ccol, tableName="Reserves", - caption="Reserven", valueStyle=hide0Style) + 1; - ccol = ccol + writeValuesTable("Reserven", as.data.frame(self$premiumComposition), - crow=crow, ccol=ccol, tableName="Premium_Decomposition", - caption = "Prämienzerlegung", valueStyle=hide0Style) + 1; - - - ################################################ - # Print out present values - ################################################ - - # Age, death and survival probabilities - costPV = as.data.frame(self$tarif$costValuesAsMatrix(self$presentValuesCosts)); - ccol = 1; - crow = 4; - # We add six lines before the present values to show the coefficients for the premium calculation - ccol = ccol + writeAgeQTable("Barwerte", crow=crow+6, ccol=1); - - writePremiumCoefficients("Barwerte", self$premiumCoefficients, type="benefits", crow=crow, ccol=ccol-1); - ccol = ccol + writeValuesTable("Barwerte", as.data.frame(self$presentValues), - crow=crow+6, ccol=ccol, tableName="PresentValues_Benefits", - caption = "Leistungsbarwerte", valueStyle=hide0Style) + 1; - - writePremiumCoefficients("Barwerte", self$premiumCoefficients, type="costs", crow=crow, ccol=ccol-2); - ccol = ccol + writeValuesTable("Barwerte", as.data.frame(costPV), - crow=crow+6, ccol=ccol, tableName="PresentValues_Costs", - caption = "Kostenbarwerte", valueStyle=cost0Style) + 1; - - - ################################################ - # Print out cash flows - ################################################ - - # Age, death and survival probabilities - costCF = as.data.frame(self$tarif$costValuesAsMatrix(self$cashFlowsCosts)); - ccol = 1; - crow = 4; - ccol = ccol + writeAgeQTable("Cash-Flows", crow=crow, ccol=1); - ccol = ccol + writeValuesTable("Cash-Flows", self$cashFlows, - crow=crow, ccol=ccol, tableName="CashFlows_Benefits", - caption="Leistungscashflows", withFilter=TRUE, valueStyle=hide0Style) + 1; - ccol = ccol + writeValuesTable("Cash-Flows", costCF, - crow=crow, ccol=ccol, tableName="CashFlows_Costs", - caption="Kostencashflows", withFilter=TRUE, valueStyle=cost0Style) + 1; - - - openxlsx::saveWorkbook(wb, filename, overwrite = TRUE) - - - # #### Contract - # premiumPayments = PaymentTimeEnum("in advance"), - # benefitPayments = PaymentTimeEnum("in advance"), - # premiumFrequency = 1, - # benefitFrequency = 1, # Only for annuities! - # loadings = list(), # Allow overriding the tariff-defined loadings (see the InsuranceTariff class for all possible names) - # premiumSum = 0, - # - # - # #### TARIF: - # tariffType = TariffTypeEnum("wholelife"), # possible values: annuity, wholelife, endowment, pureendowment, terme-fix - # premiumFrequencyOrder = 0, - # benefitFrequencyOrder = 0, - # widowFactor = 0, - # premiumRefund = 0, - # premiumRefundLoading = 0, # Mindesttodesfallrisiko soll damit erreicht werden, z.B. 105% der einbezahlten Prämien - # costs = list(), - # benefitFrequencyLoading = list("1" = 0.0, "2" = 0.0, "4" = 0.0, "12" = 0.0), # TODO: Properly implement this - # premiumFrequencyLoading = list("1" = 0.0, "2" = 0.0, "4" = 0.0, "12" = 0.0), # TODO: Implement this - # loadings = list( # Loadings can also be function(sumInsured, premiums) # TODO: Add other possible arguments - # "ongoingAlphaGrossPremium" = 0, # Acquisition cost that increase the gross premium - # "tax" = 0.04, # insurance tax, factor on each premium paid - # "unitcosts" = 0, # annual unit cost for each policy (Stückkosten), absolute value - # "security" = 0, # Additional security loading on all benefit payments, factor on all benefits - # "noMedicalExam" = 0, # Loading when no medicial exam is done, % of SumInsured - # "noMedicalExamRelative" = 0, # Loading when no medicial exam is done, % of gross premium - # "sumRebate" = 0, # gross premium reduction for large premiums, % of SumInsured - # "premiumRebate" = 0, # gross premium reduction for large premiums, % of gross premium # TODO - # "advanceProfitParticipation" = 0, # Vorweggewinnbeteiligung (%-Satz der Bruttoprämie) - # "advanceProfitParticipationInclUnitCost" = 0, # Vorweggewinnbeteiligung (%-Satz der Prämie mit Zu-/Abschlägen, insbesondere nach Stückkosten) - # "partnerRebate" = 0 # Partnerrabatt auf Prämie mit Zu-/Abschlägen, wenn mehr als 1 Vertrag gleichzeitig abgeschlossen wird, additiv mit advanceBonusInclUnitCost and premiumRebate - # ), - - }, determineTransitionProbabilities = function() { - self$transitionProbabilities = self$tarif$getTransitionProbabilities(YOB = self$YOB, age = self$age); + self$transitionProbabilities = do.call(self$tarif$getTransitionProbabilities, self$params); self$transitionProbabilities }, determineCashFlows = function() { - self$cashFlowsBasic = self$tarif$getBasicCashFlows(YOB = self$YOB, age = self$age, guaranteed = self$guaranteed, deferral = self$deferral, policyPeriod = self$policyPeriod, premiumPeriod = self$premiumPeriod); - self$cashFlows = self$tarif$getCashFlows(age = self$age, premiumPayments = self$premiumPayments, benefitPayments = self$benefitPayments, policyPeriod = self$policyPeriod, guaranteed = self$guaranteed, deferral = self$deferral, premiumPaymentPeriod = self$premiumPeriod, basicCashFlows = self$cashFlowsBasic); + self$cashFlowsBasic = do.call(self$tarif$getBasicCashFlows, self$params); + self$cashFlows = do.call(self$tarif$getCashFlows, c(self$params, "basicCashFlows" = self$cashFlowsBasic)); self$premiumSum = sum(self$cashFlows$premiums_advance + self$cashFlows$premiums_arrears); - self$cashFlowsCosts = self$tarif$getCashFlowsCosts(YOB = self$YOB, age = self$age, deferral = self$deferral, guaranteed = self$guaranteed, premiumPaymentPeriod = self$premiumPeriod, policyPeriod = self$policyPeriod); + self$cashFlowsCosts = do.call(self$tarif$getCashFlowsCosts, self$params); list("benefits"= self$cashFlows, "costs"=self$cashFlowCosts, "premiumSum" = self$premiumSum) }, calculatePresentValues = function() { - self$presentValues = self$tarif$presentValueCashFlows(self$cashFlows, age = self$age, YOB = self$YOB, premiumFrequency = self$premiumFrequency, benefitFrequency = self$benefitFrequency, loadings = self$loadings); - self$presentValuesCosts = self$tarif$presentValueCashFlowsCosts(self$cashFlowsCosts, age = self$age, YOB = self$YOB); + self$presentValues = do.call(self$tarif$presentValueCashFlows, + c(list("cashflows"=self$cashFlows), self$params)); + self$presentValuesCosts = do.call(self$tarif$presentValueCashFlowsCosts, + c(list("cashflows"=self$cashFlowsCosts), self$params)); list("benefits" = self$presentValues, "costs" = self$presentValuesCosts) }, - # Add total benefits present value to the PV array. This can only be done after premium calculation, because e.g. premium refund depends on gross premium! - calculatePresentValuesAllBenefits = function() { - pvAllBenefits = self$tarif$presentValueBenefits(presentValues = self$presentValues, presentValuesCosts = self$presentValuesCosts, premiums = self$premiums, sumInsured = self$sumInsured, premiumSum = self$premiumSum ); + calculatePremiums = function() { + # the premiumCalculation function returns the premiums AND the cofficients, + # so we have to extract the coefficients and store them in a separate variable + res = do.call(self$tarif$premiumCalculation, + c(list(pvBenefits=self$presentValues, + pvCosts=self$presentValuesCosts, + premiumSum = self$premiumSum), + self$params)); + self$premiumCoefficients = res[["coefficients"]]; + self$premiums = res[["premiums"]] + self$premiums + }, + + updatePresentValues = function() { + pvAllBenefits = do.call(self$tarif$presentValueBenefits, + c(list(presentValues = self$presentValues, + presentValuesCosts = self$presentValuesCosts, + premiums = self$premiums, + premiumSum = self$premiumSum), + self$params)); self$presentValues = cbind(self$presentValues, pvAllBenefits) - self$presentValues + self$presentValue }, - calculatePremiums = function() { - # the premiumCalculation function returns the premiums AND the cofficients, so we have to extract the coefficients and store them in a separate variable - res = self$tarif$premiumCalculation(self$presentValues, self$presentValuesCosts, premiumSum = self$premiumSum, sumInsured = self$sumInsured, loadings = self$loadings); - self$premiumCoefficients = res$coefficients; - self$premiums = res$premiums - self$premiums + calculateAbsCashFlows = function() { + absCashFlows = do.call(self$tarif$getAbsCashFlows, + c(list(premiums = self$premiums, + premiumSum = self$premiumSum, + cashflows = self$cashFlows, + cashflowsCosts = self$cashFlowsCosts), + self$params)); + self$absCashFlows = absCashFlows + self$absCashFlows + }, + + calculateAbsPresentValues = function() { + absPresentValues = do.call(self$tarif$getAbsPresentValues, + c(list(premiums = self$premiums, + premiumSum = self$premiumSum, + presentValues = self$presentValues, + presentValuesCosts = self$presentValuesCosts), + self$params)); + self$absPresentValues = absPresentValues + self$absPresentValues }, calculateReserves = function() { - self$reserves = self$tarif$reserveCalculation(premiums=self$premiums, pvBenefits=self$presentValues, pvCosts=self$presentValuesCosts, sumInsured=self$sumInsured, premiumSum = self$premiumSum, loadings = self$loadings); + self$reserves = do.call(self$tarif$reserveCalculation, + c(list(premiums=self$premiums, + presentValues=self$absPresentValues, + cashflows = self$absCashFlows, + premiumSum = self$premiumSum), + self$params)); + self$reserves }, premiumAnalysis = function() { - self$premiumComposition = self$tarif$premiumDecomposition(premiums=self$premiums, reserves=self$reserves, pvBenefits=self$presentValues, pvCosts=self$presentValuesCosts, sumInsured=self$sumInsured); - # self$premiums = cbind(self$premiums, premiumComposition) - # self$premiums + self$premiumComposition = do.call(self$tarif$premiumDecomposition, + c(list(premiums=self$premiums, + reserves=self$reserves, + cashflows=self$absCashFlows, + presentValues=self$absPresentValues, + q=self$transitionProbabilities), + self$params)); + self$premiumComposition }, - dummy=NA + dummy=NULL ) ); + diff --git a/R/InsuranceTarif.R b/R/InsuranceTarif.R index be0951f23d6e905b5727cf48d8c3c3ae2c58a334..735e76be0aadafa0ce5be5a32d1435fdf85d49fa 100644 --- a/R/InsuranceTarif.R +++ b/R/InsuranceTarif.R @@ -38,7 +38,7 @@ InsuranceTarif = R6Class( desc = "Description of the contract", states = c("alive", "dead"), - mortalityTable = NA, + mortalityTable = NULL, i = 0, # guaranteed interest rate v = 1, # discount factor tariffType = TariffTypeEnum("wholelife"), # possible values: annuity, wholelife, endowment, pureendowment, terme-fix @@ -46,9 +46,10 @@ InsuranceTarif = R6Class( benefitFrequencyOrder = 0, widowFactor = 0, - defaultPremiumPeriod = NA, + defaultPremiumPeriod = NULL, premiumRefund = 0, premiumRefundLoading = 0, # Mindesttodesfallrisiko soll damit erreicht werden, z.B. 105% der einbezahlten Prämien + surrenderValueCalculation = NULL, # By default, not surrender penalties costs = list(), benefitFrequencyLoading = list("1" = 0.0, "2" = 0.0, "4" = 0.0, "12" = 0.0), # TODO: Properly implement this @@ -68,11 +69,12 @@ InsuranceTarif = R6Class( ), features = list( #Special cases for the calculations - "betaGammaInZillmer" = FALSE # Whether beta and gamma-costs should be included in the Zillmer premium calculation + "betaGammaInZillmer" = FALSE, # Whether beta and gamma-costs should be included in the Zillmer premium calculation + "alphaRefundLinear" = TRUE # Whether the refund of alpha-costs on surrender is linear in t or follows the NPV of an annuity ), - initialize = function(name = NA, mortalityTable = NA, i = NA, type = "wholelife", ..., features = list(), premiumPeriod = NA, premiumFrequencyOrder = 0, benefitFrequencyOrder = 0, costs) { + initialize = function(name = NULL, mortalityTable = NULL, i = NULL, type = "wholelife", ..., features = list(), premiumPeriod = NULL, premiumFrequencyOrder = 0, benefitFrequencyOrder = 0, costs, surrenderValueCalculation) { if (!missing(name)) self$name = name; if (!missing(mortalityTable)) self$mortalityTable = mortalityTable; if (!missing(i)) self$i = i; @@ -83,6 +85,7 @@ InsuranceTarif = R6Class( # Set default premiumPeriod, e.g. single premium, to be used when the contract has no explicit premium period if (!missing(premiumPeriod)) self$defaultPremiumPeriod = premiumPeriod; if (!missing(features)) self$features = c(features, self$features); + if (!missing(surrenderValueCalculation)) self$surrenderValueCalculation = surrenderValueCalculation; self$v = 1/(1+self$i); @@ -101,9 +104,6 @@ InsuranceTarif = R6Class( if (age > 0) { q = q[-age:-1]; } - # p = 1 - q; - # len = length(p); - # df = data.frame(p, q=q, rep(0,len), rep(1,len), row.names = ages) df = data.frame(age=ages, q=q, p=1-q, row.names = ages-age) df }, @@ -131,7 +131,7 @@ InsuranceTarif = R6Class( cf }, - getCashFlows = function(age, ..., premiumPayments = "in advance", benefitPayments = "in advance", guaranteed = 0, policyPeriod=Inf, premiumPaymentPeriod = policyPeriod, deferral=0, maxAge = getOmega(self$mortalityTable), basicCashFlows = NA) { + getCashFlows = function(age, ..., premiumPayments = "in advance", benefitPayments = "in advance", guaranteed = 0, policyPeriod=Inf, premiumPeriod = policyPeriod, deferral=0, maxAge = getOmega(self$mortalityTable), basicCashFlows = NULL) { if (missing(basicCashFlows)) { basicCashFlows = self$getBasicCashFlows(age = age, ..., guaranteed = guaranteed, policyPeriod = policyPeriod, deferral = deferral, maxAge = maxAge); @@ -153,7 +153,7 @@ InsuranceTarif = R6Class( ); # Premiums: - premiums = pad0(rep(1, min(premiumPaymentPeriod, policyPeriod)), cflen); + premiums = pad0(rep(1, min(premiumPeriod, policyPeriod)), cflen); if (premiumPayments == "in advance") { cf$premiums_advance = premiums; } else { @@ -183,10 +183,10 @@ InsuranceTarif = R6Class( cf }, - getCashFlowsCosts = function(age, ..., policyPeriod=Inf, premiumPaymentPeriod = policyPeriod, maxAge = getOmega(self$mortalityTable)) { + getCashFlowsCosts = function(age, ..., policyPeriod=Inf, premiumPeriod = policyPeriod, maxAge = getOmega(self$mortalityTable)) { maxlen = min(maxAge - age, policyPeriod)+1; policyPeriod = min(maxAge - age, policyPeriod); - premiumPeriod = min(policyPeriod, premiumPaymentPeriod); + premiumPeriod = min(policyPeriod, premiumPeriod); dm = dim(self$costs); dmnames = dimnames(self$costs); @@ -210,20 +210,17 @@ InsuranceTarif = R6Class( len = length(cashflows$premiums_advance); qq = self$getTransitionProbabilities (age, ...); q = pad0(qq$q, len); - ages = pad0(qq$age, len); benefitFrequencyCorrection = correctionPaymentFrequency(m = benefitFrequency, i = self$i, order = self$benefitFrequencyOrder); premiumFrequencyCorrection = correctionPaymentFrequency(m = premiumFrequency, i = self$i, order = self$premiumFrequencyOrder); - # pv = as.matrix(data.frame( # TODO: Find a better way to combine the vectors into a matrix with given row/column names! - pv = (cbind( # TODO: Find a better way to combine the vectors into a matrix with given row/column names! - age = ages, + pv = cbind( premiums = calculatePVSurvival (q, cashflows$premiums_advance, cashflows$premiums_arrears, m=premiumFrequency, mCorrection=premiumFrequencyCorrection, v=self$v), guaranteed = calculatePVSurvival (q*0, cashflows$guaranteed_advance, cashflows$guaranteed_arrears, m=benefitFrequency, mCorrection=benefitFrequencyCorrection, v=self$v), survival = calculatePVSurvival (q, cashflows$survival_advance, cashflows$survival_arrears, m=benefitFrequency, mCorrection=benefitFrequencyCorrection, v=self$v), death_SumInsured = calculatePVDeath (q, cashflows$death_SumInsured, v=self$v), death_GrossPremium = calculatePVDeath (q, cashflows$death_GrossPremium, v=self$v), death_PremiumFree = calculatePVDeath (q, cashflows$death_PremiumFree, v=self$v) - )); + ); rownames(pv) <- pad0(rownames(qq), len); pv }, @@ -248,23 +245,64 @@ InsuranceTarif = R6Class( dimnames(res) = list(nm[[1]], colnames) res }, - presentValueBenefits = function(presentValues, presentValuesCosts, premiums, sumInsured=1, premiumSum=0) { + + getAbsCashFlows = function(cashflows, cashflowsCosts, premiums, sumInsured=1, premiumSum=0, ...) { + refundAddon = self$premiumRefundLoading; + + # Multiply each CF column by the corresponding basis + cashflows[,c("premiums_advance", "premiums_arrears")] = cashflows[,c("premiums_advance", "premiums_arrears")] * premiums[["gross"]]; + cashflows[,c("guaranteed_advance", "guaranteed_arrears", "survival_advance", "survival_arrears", "death_SumInsured", "death_PremiumFree")] = + cashflows[,c("guaranteed_advance", "guaranteed_arrears", "survival_advance", "survival_arrears", "death_SumInsured", "death_PremiumFree")] * sumInsured; + cashflows[,"death_GrossPremium"] = cashflows[,"death_GrossPremium"] * premiums[["gross"]] * (1+refundAddon); + + # Sum all death-related payments to "death" and remove the death_GrossPremium column + cashflows[,"death_SumInsured"] = cashflows[,"death_SumInsured"] + cashflows[,"death_GrossPremium"] + colnames(cashflows)[colnames(cashflows)=="death_SumInsured"] = "death"; + cashflows[,"death_GrossPremium"] = NULL; + + cashflowsCosts = cashflowsCosts[,,"SumInsured"] * sumInsured + + cashflowsCosts[,,"SumPremiums"] * premiumSum * premiums[["gross"]] + + cashflowsCosts[,,"GrossPremium"] * premiums[["gross"]]; + + cbind(cashflows, cashflowsCosts) + }, + + getAbsPresentValues = function(presentValues, premiums, sumInsured=1, premiumSum=0, ...) { + refundAddon = self$premiumRefundLoading; + pv = presentValues; + + #pv[,"age"] = pv[,"premiums"]; + #colnames(pv)[colnames(pv)=="age"] = "premiums.unit"; + + # Multiply each CF column by the corresponding basis + pv[,"premiums"] = pv[,"premiums"] * premiums[["gross"]]; + pv[,c("guaranteed", "survival", "death_SumInsured", "death_PremiumFree")] = + pv[,c("guaranteed", "survival", "death_SumInsured", "death_PremiumFree")] * sumInsured; + pv[,"death_GrossPremium"] = pv[,"death_GrossPremium"] * premiums[["gross"]] * (1+refundAddon); + pv[,c("benefits", "benefitsAndRefund", "alpha", "Zillmer", "beta", "gamma", "gamma_nopremiums")] = + pv[,c("benefits", "benefitsAndRefund", "alpha", "Zillmer", "beta", "gamma", "gamma_nopremiums")] * sumInsured; + + # Sum all death-related payments to "death" and remove the death_GrossPremium column + pv[,"death_SumInsured"] = pv[,"death_SumInsured"] + pv[,"death_GrossPremium"] + colnames(pv)[colnames(pv)=="death_SumInsured"] = "death"; + + cbind("premiums.unit"=presentValues[,"premiums"], pv) + }, + + + presentValueBenefits = function(presentValues, presentValuesCosts, premiums, sumInsured=1, premiumSum=0, ...) { refundAddon = self$premiumRefundLoading; # TODO: Here we don't use the securityLoading parameter => Shall it be used or are these values to be understood without additional security loading? - benefits.unit = presentValues[,"survival"] + presentValues[,"death_SumInsured"]; - benefits = benefits.unit * sumInsured; - allBenefits.unit = presentValues[,"survival"] + presentValues[,"death_SumInsured"] + presentValues[,"death_GrossPremium"] * premiums[["unit.gross"]] * (1+refundAddon); - allBenefits = allBenefits.unit * sumInsured; + benefits = presentValues[,"survival"] + presentValues[,"death_SumInsured"]; + allBenefits = presentValues[,"survival"] + presentValues[,"death_SumInsured"] + presentValues[,"death_GrossPremium"] * premiums[["unit.gross"]] * (1+refundAddon); - benefitsCosts = presentValuesCosts[,,"SumInsured"]*sumInsured + - presentValuesCosts[,,"SumPremiums"] * premiumSum * premiums[["gross"]] + - presentValuesCosts[,,"GrossPremium"] * premiums[["gross"]]; + benefitsCosts = presentValuesCosts[,,"SumInsured"] + + presentValuesCosts[,,"SumPremiums"] * premiumSum * premiums[["unit.gross"]] + + presentValuesCosts[,,"GrossPremium"] * premiums[["unit.gross"]]; cbind( - benefits.unit=benefits.unit, benefits=benefits, - allBenefits.unit=allBenefits.unit, - allBenefits=allBenefits, + benefitsAndRefund=allBenefits, benefitsCosts) }, @@ -274,54 +312,54 @@ InsuranceTarif = R6Class( securityLoading = loadings$security; refundAddon = self$premiumRefundLoading; - coefficients = list( + coeff = list( "SumInsured" = list("benefits" = coeffBenefits*0, "costs" = coeffCosts*0), "Premium" = list("benefits" = coeffBenefits*0, "costs" = coeffCosts*0) ); - coefficients[["Premium"]][["benefits"]][["premiums"]] = 1; + coeff[["Premium"]][["benefits"]][["premiums"]] = 1; - coefficients[["SumInsured"]][["benefits"]][["guaranteed"]] = 1+securityLoading; - coefficients[["SumInsured"]][["benefits"]][["survival"]] = 1+securityLoading; - coefficients[["SumInsured"]][["benefits"]][["death_SumInsured"]] = 1+securityLoading; + coeff[["SumInsured"]][["benefits"]][["guaranteed"]] = 1+securityLoading; + coeff[["SumInsured"]][["benefits"]][["survival"]] = 1+securityLoading; + coeff[["SumInsured"]][["benefits"]][["death_SumInsured"]] = 1+securityLoading; # Premium refund is handled differently for gross and net premiums, because it is proportional to the gross premium if (type == "gross") { - coefficients[["Premium"]][["benefits"]][["death_GrossPremium"]] = -(1+refundAddon) * (1+securityLoading); + coeff[["Premium"]][["benefits"]][["death_GrossPremium"]] = -(1+refundAddon) * (1+securityLoading); } else if (type=="net" || type=="Zillmer") { - coefficients[["SumInsured"]][["benefits"]][["death_GrossPremium"]] = premiums[["unit.gross"]] * (1+refundAddon) * (1+securityLoading); + coeff[["SumInsured"]][["benefits"]][["death_GrossPremium"]] = premiums[["unit.gross"]] * (1+refundAddon) * (1+securityLoading); } # coefficients for the costs if (type=="gross") { - coefficients[["SumInsured"]][["costs"]]["alpha", "SumInsured"] = 1; - coefficients[["SumInsured"]][["costs"]]["beta", "SumInsured"] = 1; - coefficients[["SumInsured"]][["costs"]]["gamma", "SumInsured"] = 1; + coeff[["SumInsured"]][["costs"]]["alpha", "SumInsured"] = 1; + coeff[["SumInsured"]][["costs"]]["beta", "SumInsured"] = 1; + coeff[["SumInsured"]][["costs"]]["gamma", "SumInsured"] = 1; # TODO: How to handle beta costs proportional to Sum Insured - coefficients[["Premium"]][["costs"]]["alpha", "SumPremiums"] = -premiumSum; - coefficients[["Premium"]][["costs"]]["beta", "SumPremiums"] = -premiumSum; - coefficients[["Premium"]][["costs"]]["gamma", "SumPremiums"] = -premiumSum; + coeff[["Premium"]][["costs"]]["alpha", "SumPremiums"] = -premiumSum; + coeff[["Premium"]][["costs"]]["beta", "SumPremiums"] = -premiumSum; + coeff[["Premium"]][["costs"]]["gamma", "SumPremiums"] = -premiumSum; - coefficients[["Premium"]][["costs"]]["alpha", "GrossPremium"] = -1; - coefficients[["Premium"]][["costs"]]["beta", "GrossPremium"] = -1; - coefficients[["Premium"]][["costs"]]["gamma", "GrossPremium"] = -1; + coeff[["Premium"]][["costs"]]["alpha", "GrossPremium"] = -1; + coeff[["Premium"]][["costs"]]["beta", "GrossPremium"] = -1; + coeff[["Premium"]][["costs"]]["gamma", "GrossPremium"] = -1; } else if (type=="Zillmer") { - coefficients[["SumInsured"]][["costs"]]["Zillmer","SumInsured"] = 1; - coefficients[["SumInsured"]][["costs"]]["Zillmer","SumPremiums"] = premiumSum * premiums[["unit.gross"]]; - coefficients[["SumInsured"]][["costs"]]["Zillmer","GrossPremium"] = premiums[["unit.gross"]]; + coeff[["SumInsured"]][["costs"]]["Zillmer","SumInsured"] = 1; + coeff[["SumInsured"]][["costs"]]["Zillmer","SumPremiums"] = premiumSum * premiums[["unit.gross"]]; + coeff[["SumInsured"]][["costs"]]["Zillmer","GrossPremium"] = premiums[["unit.gross"]]; if (self$features$betaGammaInZillmer) { - coefficients[["SumInsured"]][["costs"]]["beta", "SumInsured"] = 1; - coefficients[["SumInsured"]][["costs"]]["gamma", "SumInsured"] = 1; - coefficients[["SumInsured"]][["costs"]]["beta", "SumPremiums"] = premiumSum * premiums[["unit.gross"]]; - coefficients[["SumInsured"]][["costs"]]["gamma", "SumPremiums"] = premiumSum * premiums[["unit.gross"]]; - coefficients[["SumInsured"]][["costs"]]["beta", "GrossPremium"] = premiums[["unit.gross"]]; - coefficients[["SumInsured"]][["costs"]]["gamma", "GrossPremium"] = premiums[["unit.gross"]]; + coeff[["SumInsured"]][["costs"]]["beta", "SumInsured"] = 1; + coeff[["SumInsured"]][["costs"]]["gamma", "SumInsured"] = 1; + coeff[["SumInsured"]][["costs"]]["beta", "SumPremiums"] = premiumSum * premiums[["unit.gross"]]; + coeff[["SumInsured"]][["costs"]]["gamma", "SumPremiums"] = premiumSum * premiums[["unit.gross"]]; + coeff[["SumInsured"]][["costs"]]["beta", "GrossPremium"] = premiums[["unit.gross"]]; + coeff[["SumInsured"]][["costs"]]["gamma", "GrossPremium"] = premiums[["unit.gross"]]; } } - coefficients + coeff }, premiumCalculation = function(pvBenefits, pvCosts, costs=self$costs, premiumSum=0, sumInsured=1, premiumFrequency = 1, loadings=list(), ...) { @@ -377,31 +415,75 @@ InsuranceTarif = R6Class( list("premiums"=premiums, "coefficients"=coefficients) }, - reserveCalculation = function (premiums, pvBenefits, pvCosts, sumInsured=1, premiumSum=0, ...) { - resNet = pvBenefits[,"allBenefits"]*(1+self$loadings$security) - premiums[["net"]] * pvBenefits[,"premiums"]; - BWLminBWP = pvBenefits[,"allBenefits"]*(1+self$loadings$security) - premiums[["net"]] * pvBenefits[,"premiums"]; - BWZcorr = pvBenefits["0","Zillmer"]/pvBenefits["0", "premiums"]*pvBenefits[,"premiums"]; - resZ=BWLminBWP - BWZcorr; + reserveCalculation = function (premiums, presentValues, cashflows, sumInsured=1, premiumSum=0, policyPeriod = 1, age = 0, ...) { + # Net, Zillmer and Gross reserves + resNet = presentValues[,"benefitsAndRefund"] * (1+self$loadings$security) - premiums[["net"]] * presentValues[,"premiums.unit"]; + BWZcorr = presentValues["0", "Zillmer"] / presentValues["0", "premiums"] * presentValues[,"premiums"]; + resZ = resNet - BWZcorr; - #premiums[["Zillmer"]] * pvBenefits[,"premiums"]; - res.gamma = pvBenefits[,"gamma"] - pvBenefits["0", "gamma"]/pvBenefits["0", "premiums"]*pvBenefits[,"premiums"] + resAdeq = presentValues[,"benefitsAndRefund"] * (1+self$loadings$security) + + presentValues[,"alpha"] + presentValues[,"beta"] + presentValues["gamma"] - + premiums[["gross"]] * presentValues[,"premiums.unit"]; - # res.premiumfree = - # res.gamma.premiumfree = + #premiums[["Zillmer"]] * presentValues[,"premiums"]; + resGamma = presentValues[,"gamma"] - presentValues["0", "gamma"] / presentValues["0", "premiums"] * presentValues[,"premiums"] + + + resConversion = (resZ + resGamma) * (1-self$loadings$advanceProfitParticipation); + + # Alpha refund: Distribute alpha-costs to 5 year (or if shorter, the policy period): + r = min(policyPeriod, 5); + ZillmerSoFar = Reduce("+", cashflows$Zillmer, accumulate = TRUE); + ZillmerTotal = sum(cashflows$Zillmer); + len = length(ZillmerSoFar); + if (self$features$alphaRefundLinear) { + ZillmerVerteilungCoeff = pad0((0:r)/r, len, 1); + } else { + q = self$getTransitionProbabilities (age, ...); + # vector of all ä_{x+t, r-t} + pvAlphaTmp = calculatePVSurvival(q = pad0(q$q, len), advance = pad0(rep(1,r), len), v = self$v); + ZillmerVerteilungCoeff = (1-pvAlphaTmp/pvAlphaTmp[[1]]); + } + alphaRefund = ZillmerSoFar - ZillmerVerteilungCoeff * ZillmerTotal; + + # Reduction Reserve: Reserve used for contract modifications: + resReduction = pmax(0, resZ+resGamma+alphaRefund) # V_{x,n}^{Rkf} - res = cbind("net"=resNet, "Zillmer"=resZ, "gamma"=res.gamma + # Collect all reserved to one large matrix + res = cbind("net"=resNet, "Zillmer"=resZ, "adequate"= resAdeq, "gamma"=resGamma, + "contractual"=resZ+resGamma, "conversion"=resConversion, "alphaRefund"=alphaRefund, "reduction"=resReduction #, "Reserve.premiumfree"=res.premiumfree, "Reserve.gamma.premiumfree"=res.gamma.premiumfree); ); - rownames(res) <- rownames(pvBenefits); - res + rownames(res) <- rownames(presentValues); + + # The surrender value functions can have arbitrary form, so we store a function + # here in the tarif and call that, passing the reduction reserve as + # starting point, but also all reserves, cash flows, premiums and present values + if (!is.null(self$surrenderValueCalculation)) { + surrenderValue = self$surrenderValueCalculation( + resReduction, reserves=res, premiums=premiums, presentValues=presentValues, + cashflows=cashflows, sumInsured=sumInsured, premiumSum=premiumdSum, + policyPeriod = policyPeriod, age = age, ...); + } else { + surrenderValue = resReduction; + } + + + # res.premiumfree = + # res.gamma.premiumfree = + + cbind(res, "Surrender"=surrenderValue) }, - premiumDecomposition = function(premiums, reserves, pvBenefits, pvCosts, sumInsured=1, ...) { + premiumDecomposition = function(premiums, reserves, cashflows, presentValues, q, sumInsured=1, ...) { + l = dim(reserves)[[1]]; premium.savings = getSavingsPremium(reserves[,"Zillmer"], self$v) + getSavingsPremium(reserves[,"gamma"], self$v); + # TODO: Switch to use the Ziller or net or adequate reserve! + premium.risk = self$v * (cashflows[,"death"] - c(reserves[,"Zillmer"][-1], 0)) * pad0(q$q, l); + # premium.risk = self$v * (cashflows[,"death"] - c(reserves[,"Zillmer"][-1], 0)) * q$q; - # TODO: Sparprämie, - res = cbind("savings"=premium.savings); + res = cbind("savings"=premium.savings, "risk"=premium.risk, "savings+risk"= premium.savings+premium.risk, "gamma"=cashflows[,"gamma"]); rownames(res) <- rownames(premiums); res }, diff --git a/R/ValuationTables.R b/R/ValuationTables.R index fcbf247ecd08a0cd302fcb56f24ed02d18cc9ef3..80b5ca1dbebf94d7769de1a78750b88b28858466 100644 --- a/R/ValuationTables.R +++ b/R/ValuationTables.R @@ -343,4 +343,4 @@ plotValuationTables = function(data, ..., title = "", legend.position=c(0.9,0.1) # plotValuationTables(mort.AT.census.1869.male, mort.AT.census.1869.female, mort.AT.census.2011.male, mort.AT.census.2011.female, AVOe2005R.male, AVOe2005R.female, YOB=1972,title="Vergleich österreichische Sterbetafeln, YOB=1972 (bei Generationentafeln)") # # plotValuationTables(mort.AT.census.2001.male, AVOe2005R.male, YOB=1972, title="Vergleich österreichische Sterbetafeln") - plotValuationTables(getCohortTable(AVOe2005R.male, YOB=1972), getCohortTable(AVOe2005R.male, YOB=2016), title="Vergleich österreichische Sterbetafeln") +# plotValuationTables(getCohortTable(AVOe2005R.male, YOB=1972), getCohortTable(AVOe2005R.male, YOB=2016), title="Vergleich österreichische Sterbetafeln") diff --git a/R/exportInsuranceContract_xlsx.R b/R/exportInsuranceContract_xlsx.R new file mode 100644 index 0000000000000000000000000000000000000000..8a3e0aca997edefc80664d9af73c16d594c5c441 --- /dev/null +++ b/R/exportInsuranceContract_xlsx.R @@ -0,0 +1,347 @@ + +################################################ +# Helper Functions +################################################ + + +writeAgeQTable = function (wb, sheet, probs, crow=1, ccol=1, styles=list()) { + writeData(wb, sheet, "Sterblichkeiten", startCol = ccol+2, startRow = crow); + addStyle(wb, sheet, style=styles$header, rows=crow, cols = ccol+2, stack=TRUE); + mergeCells(wb, sheet, rows=crow, cols=(ccol+2):(ccol+3)) + writeDataTable(wb, sheet, probs, + startRow=crow+1, startCol = ccol, colNames = TRUE, rowNames = TRUE, + tableStyle = "TableStyleMedium3", withFilter = FALSE, headerStyle = styles$tableHeader); + freezePane(wb, sheet, firstActiveRow=crow+2, firstActiveCol = ccol+2) + addStyle(wb, sheet, style=styles$center, rows=(crow+2):(crow+1+dim(probs)[[1]]), cols=ccol:(ccol+1), gridExpand = TRUE, stack=TRUE); + dim(probs)[[2]] + 2; +}; + +writeValuesTable = function (wb, sheet, values, caption=NULL, crow=1, ccol=1, rowNames=FALSE, tableStyle="TableStyleMedium3", tableName=NULL, withFilter=FALSE, headerStyle=styles$tableHeader, valueStyle=NULL) { + nrrow = dim(values)[[1]]; + nrcol = dim(values)[[2]]; + addcol = if (rowNames) 1 else 0; + ecol = ccol + addcol + nrcol - 1; + if (!missing(caption)) { + writeData(wb, sheet, caption, startCol = ccol+addcol, startRow = crow); + addStyle(wb, sheet, style=headerStyle, rows=crow, cols = ccol+addcol, stack=TRUE); + mergeCells(wb, sheet, rows=crow, cols=(ccol+addcol):ecol); + } + + writeDataTable(wb, sheet, values, startRow=crow+1, startCol=ccol, colNames=TRUE, + rowNames=rowNames, tableStyle=tableStyle, + tableName=tableName, withFilter = withFilter, headerStyle = headerStyle) + if (!missing(valueStyle)) { + addStyle(wb, sheet, style=valueStyle, rows=(crow+2):(crow+nrrow+1), cols=(ccol+addcol):ecol, gridExpand = TRUE, stack = TRUE); + } + # width of table is the return value + nrcol + addcol +}; + +writePremiumCoefficients = function(wb, sheet, values, tarif=NULL, type="benefits", crow=crow, ccol=ccol) { + writeData(wb, sheet, matrix(c( + "Nettoprämie", "", "Zillmerprämie", "", "Bruttoprämie", "", + "rel. zu VS", "rel. zu Prämie", "rel. zu VS", "rel. zu Prämie", "rel. zu VS", "rel. zu Prämie"), 6, 2 + ), startCol = ccol, startRow = crow, colNames = FALSE, borders = "rows", borderColour = "gray5", borderStyle = "thin"); + mergeCells(wb, sheet, cols = ccol, rows = crow:(crow+1)); + mergeCells(wb, sheet, cols = ccol, rows = (crow+2):(crow+3)); + mergeCells(wb, sheet, cols = ccol, rows = (crow+4):(crow+5)); + addStyle(wb, sheet, style=createStyle(halign = "left", valign = "center", + borderColour = "gray5", border = "LeftBottomTop", + borderStyle = "thin"), + rows = crow:(crow+5), cols = ccol); + addStyle(wb, sheet, style=createStyle(halign = "right", valign = "center", + borderColour = "gray5", border = "RightBottomTop", + borderStyle = "thin"), + rows = crow:(crow+5), cols = ccol+1); + + # The first column of the benefits coefficients is for "age", which we want to remove + mod = function(a) { as.data.frame(t(a)) }; + if (type=="costs") { + mod = function(vals) { + vals = setInsuranceValuesLabels(vals); + newvals=vals; + dimn = dimnames(newvals); + dim(newvals) = c(1, dim(vals)); + dimnames(newvals) = c(list("Coeff"), dimn); + as.data.frame(tarif$costValuesAsMatrix(newvals)) + }; + } + coeff = rbind(mod(values[["net"]][["SumInsured"]][[type]]), + mod(values[["net"]][["Premium"]][[type]]), + mod(values[["Zillmer"]][["SumInsured"]][[type]]), + mod(values[["Zillmer"]][["Premium"]][[type]]), + mod(values[["gross"]][["SumInsured"]][[type]]), + mod(values[["gross"]][["Premium"]][[type]])); + + writeData(wb, sheet, coeff, startCol = ccol+2, startRow = crow, colNames=FALSE, borders="rows", borderColour="gray5", borderStyle="thin"); + dim(coeff)[[2]] +} + +labelsReplace = function(labels) { + labels[labels=="alpha"] = "α"; + labels[labels=="Zillmer"] = "Zill."; + labels[labels=="beta"] = "β"; + labels[labels=="gamma"] = "γ"; + labels[labels=="gamma_nopremiums"] = "γ_prf"; + labels[labels=="SumInsured"] = "VS"; + labels[labels=="SumPremiums"] = "PS"; + labels[labels=="GrossPremium"] = "BP"; + + labels[labels=="premiums"] = "Präm."; + labels[labels=="guaranteed"] = "Gar."; + labels[labels=="survival"] = "Erl."; + labels[labels=="death_SumInsured"] = "Abl. VS"; + labels[labels=="death_GrossPremium"] = "Abl. BP"; + labels[labels=="death"] = "Abl."; + labels[labels=="death_PremiumFree"] = "Abl. prf"; + labels[labels=="benefits"] = "Abl.Lst."; + labels[labels=="benefitsAndRefund"] = "Abl. + RG"; + + labels[labels=="once"] = "einm." + labels[labels=="PremiumPeriod"] = "PD" + labels[labels=="PremiumFree"] = "Pr.Fr." + labels[labels=="PolicyPeriod"] = "LZ" + + + labels +} + +setInsuranceValuesLabels = function(vals) { + dimnames(vals) = lapply(dimnames(vals), labelsReplace); + vals +} + + +################################################################################ +# +# The actual export function +# +# exportInsuranceContract.xlsx(contract, filename) +# +################################################################################ + + +exportInsuranceContract.xlsx = function(contract, filename) { + # TODO: argument checking for contract and filename + + ### + nrrows = dim(contract$cashFlows)[[1]]; # Some vectors are longer (e.g. qx), so determine the max nr or rows + qp = contract$transitionProbabilities[1:nrrows,]; # extract the probabilities once, will be needed in every sheet + + ################################################ + # Style information + ################################################ + styles = list( + header = createStyle(border="TopLeftRight", borderColour="#DA9694", borderStyle="medium", + bgFill="#C0504D", fontColour="#FFFFFF", + halign="center", valign="center", textDecoration="bold"), + tableHeader = createStyle(halign="center", valign="center", textDecoration="bold"), + hide0 = createStyle(numFmt="General; General; \"\""), + cost0 = createStyle(numFmt="0.000%; 0.000%; \"\""), + wrap = createStyle(wrapText=TRUE), + center = createStyle(halign="center", valign="center") + ); + + ################################################ + # General Workbook setup + ################################################ + wb = openxlsx::createWorkbook(); + addWorksheet(wb, "Tarifinformationen"); + addWorksheet(wb, "Reserven"); + addWorksheet(wb, "abs.Barwerte"); + addWorksheet(wb, "abs.Cash-Flows"); + addWorksheet(wb, "Barwerte"); + addWorksheet(wb, "Cash-Flows"); + + # Print out general Contract and Tariff information, including results + crow = 1; + writeData(wb, "Tarifinformationen", matrix(c( + "Tarif:", contract$tarif$tarif, + "Tarifname:", contract$tarif$name, + "Description:", contract$tarif$desc + ), 3, 2, byrow = TRUE), startCol=1, startRow=1, colNames=FALSE, rowNames=FALSE); + mergeCells(wb, "Tarifinformationen", cols=2:10, rows=1); + mergeCells(wb, "Tarifinformationen", cols=2:10, rows=2); + mergeCells(wb, "Tarifinformationen", cols=2:10, rows=3); + addStyle(wb, "Tarifinformationen", style=styles$wrap, rows=3, cols=2:10, stack=TRUE); + addStyle(wb, "Tarifinformationen", style=createStyle(valign="top"), rows=1:3, cols=1:10, gridExpand=TRUE, stack=TRUE); + + crow = crow+4; + + ################################################ + # Basic parameters + ################################################ + sheet = "Tarifinformationen" + values=c( + "Sum insured"=contract$params$sumInsured, + "Mortality table"=contract$tarif$mortalityTable@name, + "YOB"=contract$params$YOB, + "Age"=contract$params$age, + "Policy duration"=contract$params$policyPeriod, + "Premium period"=contract$params$premiumPeriod, + "Deferral"=contract$params$deferral, + "Guaranteed payments"=contract$params$guaranteed, + i=contract$tarif$i); + + writeData(wb, sheet, "Basisdaten des Vertrags und Tarifs", startCol=1, startRow=crow); + mergeCells(wb, sheet, cols=1:length(values), rows=crow:crow); + writeDataTable(wb, sheet, as.data.frame(t(values)), + startCol=1, startRow=crow+1, colNames=TRUE, rowNames=FALSE, + tableStyle="TableStyleMedium3", withFilter = FALSE, headerStyle = styles$tableHeader); + crow = crow + 4; + + # Premiums + writeData(wb, sheet, "Prämien", startCol=1, startRow=crow); + mergeCells(wb, sheet, cols=1:length(contract$premiums), rows=crow:crow); + writeDataTable(wb, sheet, setInsuranceValuesLabels(as.data.frame(t(contract$premiums))), + startCol=1, startRow=crow+1, colNames=TRUE, rowNames=FALSE, + tableStyle="TableStyleMedium3", withFilter = FALSE, headerStyle = styles$tableHeader); + crow = crow + 4; + + # Cost structure: + costtable = as.data.frame.table(setInsuranceValuesLabels(contract$tarif$costs), responseName = "Kostensatz", dnn = c("Kostenart", "Basis", "Periode"), exclude=c(0)) + costtable = costtable[costtable[,"Kostensatz"]!=0.0000,] + writeData(wb, sheet, costtable, startCol=1, startRow=crow+1, colNames=FALSE, rowNames=FALSE,borders = "surrounding", borderColour = "red", borderStyle = "medium") + + ################################################ + # Print out Reserves and premium decomposition + ################################################ + + # Age, death and survival probabilities + ccol = 1; + crow = 4; + sheet = "Reserven"; + + ccol = ccol + writeAgeQTable(wb, sheet, probs=qp, crow=crow, ccol=1, styles=styles); + ccol = ccol + writeValuesTable(wb, sheet, as.data.frame(setInsuranceValuesLabels(contract$reserves)), + crow=crow, ccol=ccol, tableName="Reserves", + caption="Reserven", valueStyle=styles$hide0) + 1; + ccol = ccol + writeValuesTable(wb, sheet, as.data.frame(setInsuranceValuesLabels(contract$premiumComposition)), + crow=crow, ccol=ccol, tableName="Premium_Decomposition", + caption = "Prämienzerlegung", valueStyle=styles$hide0) + 1; + + + ################################################ + # Print out absolute values of present values + ################################################ + + # Age, death and survival probabilities + ccol = 1; + crow = 4; + sheet = "abs.Barwerte"; + ccol = ccol + writeAgeQTable(wb, sheet, probs=qp, crow=crow, ccol=1, styles=styles); + + ccol = ccol + writeValuesTable(wb, sheet, as.data.frame(setInsuranceValuesLabels(contract$absPresentValues)), + crow=crow, ccol=ccol, tableName="PresentValues_absolute", + caption = "abs. Leistungs- und Kostenbarwerte", valueStyle=styles$hide0) + 1; + + + ################################################ + # Print out absolute values for cash flows + ################################################ + + # Age, death and survival probabilities + ccol = 1; + crow = 4; + sheet = "abs.Cash-Flows"; + ccol = ccol + writeAgeQTable(wb, sheet, probs=qp, crow=crow, ccol=1, styles=styles); + ccol = ccol + writeValuesTable(wb, sheet, as.data.frame(setInsuranceValuesLabels(contract$absCashFlows)), + crow=crow, ccol=ccol, tableName="CashFlows_absolute", + caption="abs. Leistungs- und Kostencashflows", withFilter=TRUE, valueStyle=styles$hide0) + 1; + + + ################################################ + # Print out present values + ################################################ + + # Age, death and survival probabilities + costPV = as.data.frame(contract$tarif$costValuesAsMatrix(setInsuranceValuesLabels(contract$presentValuesCosts))); + ccol = 1; + crow = 4; + sheet = "Barwerte"; + # We add six lines before the present values to show the coefficients for the premium calculation + ccol = ccol + writeAgeQTable(wb, sheet, probs=qp, crow=crow+6, ccol=1, styles=styles); + + # Store the start/end columns of the coefficients, since we need them later in the formula for the premiums! + w1 = writePremiumCoefficients(wb, sheet, contract$premiumCoefficients, type="benefits", crow=crow, ccol=ccol-2, tarif=contract$tarif); + area.premiumcoeff = paste0(int2col(ccol), "%d:", int2col(ccol+w1-1), "%d"); + area.premiumvals = paste0("$", int2col(ccol), "$", crow+6+2, ":$", int2col(ccol+w1-1), "$", crow+6+2); + ccol = ccol + writeValuesTable(wb, sheet, as.data.frame(setInsuranceValuesLabels(contract$presentValues)), + crow=crow+6, ccol=ccol, tableName="PresentValues_Benefits", + caption = "Leistungsbarwerte", valueStyle=styles$hide0) + 1; + + w2 = writePremiumCoefficients(wb, sheet, contract$premiumCoefficients, type="costs", crow=crow, ccol=ccol-2, tarif=contract$tarif); + area.costcoeff = paste0(int2col(ccol), "%d:", int2col(ccol+w2-1), "%d"); + area.costvals = paste0("$", int2col(ccol), "$", crow+6+2, ":$", int2col(ccol+w2-1), "$", crow+6+2); + ccol = ccol + writeValuesTable(wb, sheet, as.data.frame(costPV), + crow=crow+6, ccol=ccol, tableName="PresentValues_Costs", + caption = "Kostenbarwerte", valueStyle=styles$cost0) + 1; + + # Now print out the formulas for premium calculation into the columns 2 and 3: + writeData(wb, sheet, as.data.frame(c("Nettoprämie", contract$premiums[["net"]],"Zillmerprämie", contract$premiums[["Zillmer"]], "Bruttoprämie", contract$premiums[["gross"]])), startCol = 1, startRow=crow, colNames = FALSE, borders = "rows"); + for (i in 0:5) { + writeFormula(wb, sheet, paste0("SUMPRODUCT(", sprintf(area.premiumcoeff, crow+i, crow+i), ", ", area.premiumvals, ") + SUMPRODUCT(", sprintf(area.costcoeff, crow+i, crow+i), ", ", area.costvals, ")"), startCol = 3, startRow = crow+i); + } + for (i in c(0,2,4)) { + writeFormula(wb, sheet, paste0(int2col(3), crow+i, "/", int2col(3), crow+i+1), startCol=2, startRow = crow+i); + } + for (i in c(1,3,5)) { + writeFormula(wb, sheet, paste0(int2col(2), crow+i-1, "*", contract$params$sumInsured), startCol=2, startRow = crow+i); + } + + + ################################################ + # Print out cash flows + ################################################ + + # Age, death and survival probabilities + costCF = as.data.frame(contract$tarif$costValuesAsMatrix(setInsuranceValuesLabels(contract$cashFlowsCosts))); + ccol = 1; + crow = 4; + sheet = "Cash-Flows"; + ccol = ccol + writeAgeQTable(wb, sheet, probs=qp, crow=crow, ccol=1, styles=styles); + ccol = ccol + writeValuesTable(wb, sheet, setInsuranceValuesLabels(contract$cashFlows), + crow=crow, ccol=ccol, tableName="CashFlows_Benefits", + caption="Leistungscashflows", withFilter=TRUE, valueStyle=styles$hide0) + 1; + ccol = ccol + writeValuesTable(wb, sheet, costCF, + crow=crow, ccol=ccol, tableName="CashFlows_Costs", + caption="Kostencashflows", withFilter=TRUE, valueStyle=styles$cost0) + 1; + + + openxlsx::saveWorkbook(wb, filename, overwrite = TRUE) + + + # #### Contract + # premiumPayments = PaymentTimeEnum("in advance"), + # benefitPayments = PaymentTimeEnum("in advance"), + # premiumFrequency = 1, + # benefitFrequency = 1, # Only for annuities! + # loadings = list(), # Allow overriding the tariff-defined loadings (see the InsuranceTariff class for all possible names) + # premiumSum = 0, + # + # + # #### TARIF: + # tariffType = TariffTypeEnum("wholelife"), # possible values: annuity, wholelife, endowment, pureendowment, terme-fix + # premiumFrequencyOrder = 0, + # benefitFrequencyOrder = 0, + # widowFactor = 0, + # premiumRefund = 0, + # premiumRefundLoading = 0, # Mindesttodesfallrisiko soll damit erreicht werden, z.B. 105% der einbezahlten Prämien + # costs = list(), + # benefitFrequencyLoading = list("1" = 0.0, "2" = 0.0, "4" = 0.0, "12" = 0.0), # TODO: Properly implement this + # premiumFrequencyLoading = list("1" = 0.0, "2" = 0.0, "4" = 0.0, "12" = 0.0), # TODO: Implement this + # loadings = list( # Loadings can also be function(sumInsured, premiums) # TODO: Add other possible arguments + # "ongoingAlphaGrossPremium" = 0, # Acquisition cost that increase the gross premium + # "tax" = 0.04, # insurance tax, factor on each premium paid + # "unitcosts" = 0, # annual unit cost for each policy (Stückkosten), absolute value + # "security" = 0, # Additional security loading on all benefit payments, factor on all benefits + # "noMedicalExam" = 0, # Loading when no medicial exam is done, % of SumInsured + # "noMedicalExamRelative" = 0, # Loading when no medicial exam is done, % of gross premium + # "sumRebate" = 0, # gross premium reduction for large premiums, % of SumInsured + # "premiumRebate" = 0, # gross premium reduction for large premiums, % of gross premium # TODO + # "advanceProfitParticipation" = 0, # Vorweggewinnbeteiligung (%-Satz der Bruttoprämie) + # "advanceProfitParticipationInclUnitCost" = 0, # Vorweggewinnbeteiligung (%-Satz der Prämie mit Zu-/Abschlägen, insbesondere nach Stückkosten) + # "partnerRebate" = 0 # Partnerrabatt auf Prämie mit Zu-/Abschlägen, wenn mehr als 1 Vertrag gleichzeitig abgeschlossen wird, additiv mit advanceBonusInclUnitCost and premiumRebate + # ), + +}