freMTPL2 pricing engine: frequency × severity with an EVT large-loss layer

Fitted models from an open motor third-party-liability pricing study on freMTPL2 (CASdatasets, Dutang & Charpentier; OpenML 41214/41215, CC0): 678,013 policies, 26,406 claims. Code, article and full results: 000MAK/cafepolice-actuarial-loss-engine.

models.joblib holds three scikit-learn pipelines that take the freMTPL2 policy columns (VehAge, DrivAge, VehBrand, VehPower, VehGas, Region, Area, BonusMalus, Density):

  • freq_gbm: Poisson gradient boosting, annual claim frequency (exposure-weighted)
  • freq_glm: Poisson GLM benchmark
  • sev_gamma: Gamma GLM for attritional cost per claim, capped at €16,024 (99th percentile)

Results on a 20% hold-out (135,603 policies)

Poisson GLM Poisson GBM
Frequency Gini 0.273 0.321
Frequency D² 4.1% 6.2%

Large losses: Generalised Pareto fit to 199 excesses over €16,024 gives ξ = 0.87 (bootstrap 95% CI 0.59–1.11; 19.8% of resamples have ξ ≥ 1, i.e. no finite mean). Pure premium = freq_gbm × (sev_gamma + large-loss load per claim): with the empirical load (€680) predicted/observed is 0.999 and Gini 0.331; with the GPD load (€1,210) it is 1.233. Choosing the load is a risk-appetite decision.

Lorenz curves Tail shape by threshold

Usage

import joblib
from huggingface_hub import hf_hub_download
m = joblib.load(hf_hub_download("makactuary/fremtpl2-pricing-engine", "models.joblib"))
frequency = m["freq_gbm"].predict(policies_df)                  # claims per policy-year
pure_premium = frequency * (m["sev_gamma"].predict(policies_df) + 680.0)   # € per policy-year

Trained with scikit-learn 1.9.1; load with the same version.

Limitations

French portfolio from a single period; no claims inflation, IBNR or reinsurance; the large-loss layer rests on 199 claims. A method illustration, not a tariff.

Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Collection including makactuary/fremtpl2-pricing-engine