Highlights of NDPERS assets, liabilities and other important funding metrics over the last two decades.
Unfunded Liability
The funded ratio is calculated by dividing the value of plan assets by liabilities. When “Actuarial Value” is selected the actuarially smoothed assets are used. When “Market Value” is selected the market value of those assets are used. The difference between plan assets and liabilities is the unfunded accrued liability (UAL). Again, when “Actuarial Value” is selected the actuarially smoothed assets are used. When “Market Value” is selected the market value of those assets are used.
This chart also shows the difference between plan assets and liabilities–looking at the difference between those assets and liabilities (UAL).When “Actuarial Value” is selected the actuarially smoothed assets are used. When “Market Value” is selected the market value of those assets are used.
Negative Amortization has resulted in interest on NDPERS debt exceeding the actual debt payments since 2016, adding $259 billion in unfunded liabilities.
Updates to Actuarial Methods & Assumptions to better reflect current market and demographic trends exposed over $232 billion in previously unrecognized unfunded liabilities.
Data Changes & Other changes have added a net $97 billion in unfunded liabilities.
Deviations from Demographic Assumptions contributed $89 billion to NDPERS unfunded liability since 2016.
Net Change, from 2016 to 2020, increased by $388 billion.
Unpaid Interest
Amortization Payments
Assets & Returns
Investment Returns
The chart below compares assumed returns with market, actuarial, and geometric rolling returns. The table provides the geomtetric average return for the last 5, 10, and 15 years.
Asset Allocation
Fixed Income: U.S. government securities, corporate bonds, mortgage-backed securities, asset backed securities, derivative instruments, etc.
Public Equities: Publicly reported and traded stocks.
Private Equity: Private equity holdings.
Other Alternatives: Hedge funds, private equity funds, real estate, and other limited partnerships.
Probability of Hitting Assumed Rate of Return
The boxes below represent the probability that NDPERS will hit a given assumed rate of return based on 10,000 simulations of various capital market assumptions, in addition to plan assumptions and historical data.
// Random number generatorfunctionsfc32(a, b, c, d) {let rng =function() { a >>>=0; b >>>=0; c >>>=0; d >>>=0;var t = (a + b) |0; a = b ^ (b >>>9); b = (c + (c <<3)) |0; c = (c <<21) | (c >>>11); d = (d +1) |0; t = (t + d) |0; c = (c + t) |0;return (t >>>0) /4294967296; };for (let i =0; i <10; i++) {rng(); }return rng;}
radius =3;fillOpacity =0.5;strokeOpacity =75;circleStrokeWidth =0.7;returnInput = object.return/100;height =750;addSharedThings =function(svg) {// capital market boxes svg.append("g").attr("fill","#fff")// .attr("stroke", "#ccc").selectAll("rect").data(y2.domain()).join("rect").attr("x",0).attr("y", d =>y2(d)).attr("width",x(1) -x(0)).attr("height", y2.bandwidth).style("margin",15);// capital market labels svg.append("g").attr("fill","#444").selectAll("text").data(y2.domain()).join("text").attr("x",0).attr("y", d =>y2(d) +40).text(d => d).style("font-family","'Open Sans', sans-serif").style("font-size","30px");// x axis svg.append("g").call(xAxis);}addPlanMeans =function(svg) { svg.append("g").attr("stroke","#222").selectAll("line").data(planAverages).join("line").attr("x1", d =>x(returnInput)).attr("x2", d =>x(returnInput)).attr("y1", margin.top+10).attr("y2", height - margin.bottom).attr("stroke-width",2)}addPlanText =function(svg) { svg.append('text').attr('text-anchor','middle').attr('background-color','white').attr("x", d =>x(returnInput)).attr("y", margin.top).text(d => d3.format(".2%")(returnInput)).style("font-size","30px").style("font-weight",700)}data2Temp =FileAttachment("./data/pers/simulation_data.csv").csv()aq.addFunction('random2',sfc32(1,2,3,4))
data2 = aq.from(data2Temp).derive({"Data": d =>+d["Data"],random: d =>+op.random2() }).sample(10000).objects();data2PA = aq.from(data2Temp).derive({"Data": d =>+d["Data"],random: d =>+op.random2() }).objects();planAverages = aq.from(data2PA).derive({"Data": d =>+d["Data"] *100 }).groupby("Plan Name").rollup({"mean": d => op.mean(+d["Mean Return"])}).objects();x = d3.scaleLinear().domain([-0.1,0.15]).range([margin.left, width - margin.right-100])y2 = d3.scaleBand().domain( [...planAverages].sort((a, b) => a.mean- b.mean).map(d => d["Plan Name"]) )// .domain(data.map(d => d.region)).rangeRound([margin.top, height - margin.bottom]).padding(0.02)xAxis = g => g.attr("transform",`translate(0,${height - margin.bottom})`).call(d3.axisBottom(x).ticks(10).tickSize(10).tickFormat((d) => d3.format(`.0%`)(d))).style("fill","rgb(129, 129, 129, 0. 85)").call(g => g.selectAll(".tick text").style("font-size","22px").style("font-weight",400).attr("fill","rgb(129, 129, 129)")).call(g => g.select(".domain").remove()).attr('font-family',"'Open Sans', sans-serif")margin = ({ top:30,right:10,bottom:30,left:20 })