@@ -120,8 +120,8 @@ export function defaultScenario(): Scenario {
120120}
121121
122122function simulateYear ( state : YearState , params : Parameters ) : YearState {
123- const newYear = state . year + 1 ;
124- const newAge = state . age + 1 ;
123+ const year = state . year + 1 ;
124+ const age = state . age + 1 ;
125125
126126 const compoundedInflation = state . compoundedInflation * ( 1 + params . inflationRate ) ;
127127 const compoundedInflationYearlyAverage =
@@ -131,83 +131,48 @@ function simulateYear(state: YearState, params: Parameters): YearState {
131131 const thisYearsTaxFreeThreshold = params . taxFreeThreshold * compoundedInflationYearlyAverage ;
132132
133133 const yearlyCashFlows = params . cashFlows . filter ( ( { startYear, endYear } ) => {
134- if ( startYear > newYear ) return false ;
134+ if ( startYear > year ) return false ;
135135 if ( endYear === undefined ) return true ;
136- return endYear > newYear ;
136+ return endYear > year ;
137137 } ) ;
138138
139- const yearlyGrossPublicPensionContributingIncomePostInflation =
140- Math . min (
141- (
142- yearlyCashFlows . filter (
143- ( { kind } ) => kind === 'GrossTaxableEarnedIncome'
144- ) as GrossTaxableEarnedIncome [ ]
145- )
146- . filter ( ( { publicPensionContributing } ) => publicPensionContributing )
147- . reduce ( ( acc , cf ) => acc + cf . yearlyCashFlow , 0 ) ,
148- params . publicPensionContributionThreshold
149- ) * compoundedInflationYearlyAverage ;
150-
151- const incomePensionContributionPostInflation =
152- yearlyGrossPublicPensionContributingIncomePostInflation * params . incomePensionContributionRate ;
153- const premiumPensionContributionPostInflation =
154- yearlyGrossPublicPensionContributingIncomePostInflation * params . premiumPensionContributionRate ;
155-
156- // Simplification: No new pension rights after pension age
157- const incomePensionRights =
158- newAge <= params . statePensionAge
159- ? ( state . incomePensionRights + incomePensionContributionPostInflation / 2 ) *
160- ( 1 + params . incomePensionRate ) +
161- incomePensionContributionPostInflation / 2
162- : state . incomePensionRights ;
163- const premiumPensionRights =
164- newAge <= params . statePensionAge
165- ? ( state . premiumPensionRights + premiumPensionContributionPostInflation / 2 ) *
166- ( 1 + params . premiumPensionRate ) +
167- premiumPensionContributionPostInflation / 2
168- : state . premiumPensionRights ;
169-
170- // Simplification: arvsvinst: https://www.pensionsmyndigheten.se/statistik/publikationer/orange-rapport-2021/a-berakningsfaktorer.html
171- // Current we instead simulate arvsvinst by pretending that the pension is paid out based on life expectancy
172-
173- // Jobbskatteavdrag: https://www.bjornlunden.se/skatt/jobbskatteavdrag__196
174-
175- // grundavdrag https://www4.skatteverket.se/rattsligvagledning/27071.html?date=2024-01-01#section63-3
176- // api grundavdrag: https://skatteverket.entryscape.net/rowstore/dataset/ebbd8d70-9b9c-4327-b2ce-a371ee66744c/html
177-
178- // Simplification: state pension payout is taxed as normal income
179- const accumulatedPublicPensionRights = state . incomePensionRights + state . premiumPensionRights ;
180- const grossPublicPensionPayout =
181- newAge > params . statePensionAge
182- ? accumulatedPublicPensionRights / ( params . generalLifeExpectancy - params . statePensionAge )
183- : 0 ;
184-
185- const yearlyGrossOccupationalPensionContributionPostInflation =
186- (
187- yearlyCashFlows . filter (
188- ( { kind } ) => kind === 'GrossTaxableEarnedIncome'
189- ) as GrossTaxableEarnedIncome [ ]
190- ) . reduce ( ( acc , cf ) => acc + cf . yearlyCashFlow * cf . occupationalPensionContributionRate , 0 ) *
191- compoundedInflationYearlyAverage ;
192-
193- const grossOccupationalPensionPayout =
194- state . age >= params . occupationalPensionAge &&
195- state . age < params . occupationalPensionAge + params . occupationalPensionDuration
196- ? state . occupationalPensionSavings *
197- ( 1 / ( params . occupationalPensionDuration + params . occupationalPensionAge - state . age ) )
198- : 0 ;
139+ const inflationAdjustedCashFlows = yearlyCashFlows . map ( ( cf ) => {
140+ return {
141+ ...cf ,
142+ yearlyCashFlow : cf . yearlyCashFlow * compoundedInflationYearlyAverage
143+ } ;
144+ } ) ;
199145
200- const accumulatedOccupationalPensionInterest =
201- ( state . occupationalPensionSavings +
202- ( yearlyGrossOccupationalPensionContributionPostInflation - grossOccupationalPensionPayout ) /
203- 2 ) *
204- params . occupationalPensionInterestRate ;
146+ const {
147+ grossPayout : grossPublicPensionPayout ,
148+ outgoingRights : { incomePension : incomePensionRights , premiumPension : premiumPensionRights }
149+ } = simulatePublicPension ( {
150+ cashFlows : inflationAdjustedCashFlows ,
151+ ingoingRights : {
152+ incomePension : state . incomePensionRights ,
153+ premiumPension : state . premiumPensionRights
154+ } ,
155+ contributionThreshold : params . publicPensionContributionThreshold ,
156+ incomePensionContributionRate : params . incomePensionContributionRate ,
157+ premiumPensionContributionRate : params . premiumPensionContributionRate ,
158+ age : age ,
159+ pensionAge : params . statePensionAge ,
160+ incomePensionInterestRate : params . incomePensionRate ,
161+ premiumPensionInterestRate : params . premiumPensionRate ,
162+ generalLifeExpectancy : params . generalLifeExpectancy
163+ } ) ;
205164
206- const accumulatedOccupationalPensionSavings =
207- state . occupationalPensionSavings +
208- yearlyGrossOccupationalPensionContributionPostInflation +
209- accumulatedOccupationalPensionInterest -
210- grossOccupationalPensionPayout ;
165+ const {
166+ outgoingBalance : occupationalPensionSavings ,
167+ grossPayout : grossOccupationalPensionPayout
168+ } = simulateOccupationalPensionYear ( {
169+ ingoingBalance : state . occupationalPensionSavings ,
170+ cashFlows : inflationAdjustedCashFlows ,
171+ age : age ,
172+ pensionAge : params . occupationalPensionAge ,
173+ pensionDuration : params . occupationalPensionDuration ,
174+ interestRate : params . occupationalPensionInterestRate
175+ } ) ;
211176
212177 const yearlyGrossTaxableEarnedIncomePostInflation =
213178 yearlyCashFlows
@@ -245,20 +210,155 @@ function simulateYear(state: YearState, params: Parameters): YearState {
245210 const yearlyNetCashFlowPostInflation =
246211 yearlyNetIncomePostInflation - yearlyNetExpensePostInflation ;
247212
248- const iskSavingsYearlyAverage = state . iskSavings + yearlyNetCashFlowPostInflation / 2 ;
249- const iskInterest = iskSavingsYearlyAverage * params . iskInterestRate ;
250- const iskTax = iskSavingsYearlyAverage * params . iskTax ;
251- const newSavings = state . iskSavings + yearlyNetCashFlowPostInflation + iskInterest - iskTax ;
213+ const { outgoingBalance : iskSavings } = simulateISKYear ( {
214+ ingoingBalance : state . iskSavings ,
215+ interestRate : params . iskInterestRate ,
216+ taxRate : params . iskTax ,
217+ deposit : yearlyNetCashFlowPostInflation
218+ } ) ;
252219
253220 return {
254- year : newYear ,
255- age : newAge ,
256- iskSavings : Math . floor ( newSavings ) ,
257- compoundedInflation : compoundedInflation ,
221+ year,
222+ age,
223+ iskSavings,
224+ compoundedInflation,
258225 cashFlow : yearlyNetCashFlowPostInflation ,
259- incomePensionRights : incomePensionRights ,
260- premiumPensionRights : premiumPensionRights ,
261- occupationalPensionSavings : accumulatedOccupationalPensionSavings
226+ incomePensionRights,
227+ premiumPensionRights,
228+ occupationalPensionSavings
229+ } ;
230+ }
231+
232+ function simulatePublicPension ( {
233+ cashFlows,
234+ ingoingRights,
235+ contributionThreshold,
236+ premiumPensionContributionRate,
237+ incomePensionContributionRate,
238+ age,
239+ pensionAge,
240+ incomePensionInterestRate,
241+ premiumPensionInterestRate,
242+ generalLifeExpectancy
243+ } : {
244+ cashFlows : TimeBoundCashFlow [ ] ; // Inflation adjusted before passed to this function
245+ ingoingRights : { incomePension : number ; premiumPension : number } ;
246+ contributionThreshold : number ;
247+ incomePensionContributionRate : number ;
248+ premiumPensionContributionRate : number ;
249+ age : number ;
250+ pensionAge : number ;
251+ incomePensionInterestRate : number ;
252+ premiumPensionInterestRate : number ;
253+ generalLifeExpectancy : number ;
254+ } ) : { outgoingRights : { incomePension : number ; premiumPension : number } ; grossPayout : number } {
255+ const grossContributingIncome = Math . min (
256+ (
257+ cashFlows . filter (
258+ ( { kind } ) => kind === 'GrossTaxableEarnedIncome'
259+ ) as GrossTaxableEarnedIncome [ ]
260+ )
261+ . filter ( ( { publicPensionContributing } ) => publicPensionContributing )
262+ . reduce ( ( acc , cf ) => acc + cf . yearlyCashFlow , 0 ) ,
263+ contributionThreshold
264+ ) ;
265+
266+ const incomePensionContribution = grossContributingIncome * incomePensionContributionRate ;
267+ const premiumPensionContribution = grossContributingIncome * premiumPensionContributionRate ;
268+
269+ const incomePensionInterest =
270+ ( ingoingRights . incomePension + incomePensionContribution / 2 ) * incomePensionInterestRate ;
271+ const outgoingIncomePensionRights =
272+ age <= pensionAge
273+ ? ingoingRights . incomePension + incomePensionInterest + incomePensionContribution
274+ : ingoingRights . incomePension ;
275+
276+ // Simplification: No new pension rights after pension age
277+ const premiumPensionInterest =
278+ ( ingoingRights . premiumPension + premiumPensionContribution / 2 ) * premiumPensionInterestRate ;
279+ const outgoingPremiumPensionRights =
280+ age <= pensionAge
281+ ? ingoingRights . premiumPension + premiumPensionInterest + premiumPensionContribution
282+ : ingoingRights . premiumPension ;
283+
284+ // Simplification: arvsvinst: https://www.pensionsmyndigheten.se/statistik/publikationer/orange-rapport-2021/a-berakningsfaktorer.html
285+ // Current we instead simulate arvsvinst by pretending that the pension is paid out based on life expectancy
286+
287+ // Jobbskatteavdrag: https://www.bjornlunden.se/skatt/jobbskatteavdrag__196
288+
289+ // grundavdrag https://www4.skatteverket.se/rattsligvagledning/27071.html?date=2024-01-01#section63-3
290+ // api grundavdrag: https://skatteverket.entryscape.net/rowstore/dataset/ebbd8d70-9b9c-4327-b2ce-a371ee66744c/html
291+
292+ // Simplification: state pension payout is taxed as normal income
293+ const grossPayout =
294+ age > pensionAge
295+ ? ( outgoingIncomePensionRights + outgoingPremiumPensionRights ) /
296+ ( generalLifeExpectancy - pensionAge )
297+ : 0 ;
298+
299+ return {
300+ outgoingRights : {
301+ incomePension : outgoingIncomePensionRights ,
302+ premiumPension : outgoingPremiumPensionRights
303+ } ,
304+ grossPayout : grossPayout
305+ } ;
306+ }
307+
308+ function simulateOccupationalPensionYear ( {
309+ ingoingBalance,
310+ cashFlows,
311+ age,
312+ pensionAge,
313+ pensionDuration,
314+ interestRate
315+ } : {
316+ ingoingBalance : number ;
317+ cashFlows : TimeBoundCashFlow [ ] ; // Inflation adjusted before passed to this function
318+ age : number ;
319+ pensionAge : number ;
320+ pensionDuration : number ;
321+ interestRate : number ;
322+ } ) : {
323+ outgoingBalance : number ;
324+ grossPayout : number ;
325+ } {
326+ const contribution = (
327+ cashFlows . filter (
328+ ( { kind } ) => kind === 'GrossTaxableEarnedIncome'
329+ ) as GrossTaxableEarnedIncome [ ]
330+ ) . reduce ( ( acc , cf ) => acc + cf . yearlyCashFlow * cf . occupationalPensionContributionRate , 0 ) ;
331+
332+ const grossPayout =
333+ age >= pensionAge && age < pensionAge + pensionDuration
334+ ? ingoingBalance * ( 1 / ( pensionDuration + pensionAge - age ) )
335+ : 0 ;
336+
337+ const interest = ( ingoingBalance + ( contribution - grossPayout ) / 2 ) * interestRate ;
338+
339+ const outgoingBalance = ingoingBalance + contribution + interest - grossPayout ;
340+
341+ return { outgoingBalance, grossPayout } ;
342+ }
343+
344+ function simulateISKYear ( {
345+ ingoingBalance,
346+ interestRate,
347+ taxRate,
348+ deposit
349+ } : {
350+ ingoingBalance : number ;
351+ interestRate : number ;
352+ taxRate : number ;
353+ deposit : number ;
354+ } ) : { outgoingBalance : number } {
355+ const iskSavingsYearlyAverage = ingoingBalance + deposit / 2 ;
356+ const iskInterest = iskSavingsYearlyAverage * interestRate ;
357+ const iskTax = iskSavingsYearlyAverage * taxRate ;
358+ const newSavings = ingoingBalance + deposit + iskInterest - iskTax ;
359+
360+ return {
361+ outgoingBalance : Math . floor ( newSavings )
262362 } ;
263363}
264364
0 commit comments