IUL Projection Engine: Full Calculation Logic

Overview

This page documents the complete step-by-step logic used in the IUL projection engine, from input data to final ledger output. All formulas, table lookups, and mechanics are described in detail, matching the codebase and README.

Step A: Input Data Model

All fields are validated and used to drive the projection. Optional override tables (COI, corridor, surrender) can be injected for calibration.

Step B: Engine Constants & Tables

7702 Corridor Table (CORRIDOR_FACTORS):
{0:2.5, 40:2.5, 45:2.15, 50:1.85, 55:1.5, 60:1.3, 65:1.2, 70:1.15, 75:1.05, 90:1.05, 95:1.0, ...}
Surrender Charge Table (SURRENDER_CHARGE_PCT_BY_YEAR):
{1:0.0100, 2:0.007069, ..., 10:0.000138}
COI Fallback Curve:
_coi_rate_by_age_base(age) = max(0.001, 0.0008 + (age - 20) * 0.00009) * COI_FALLBACK_SCALE

Step C: Monthly Projection Loop

  1. Initialize state: av_unloaned = 0.0, av_collateral = 0.0, loan_balance = 0.0, lapsed = False
  2. For each policy year (1 to projection_years):
    1. Calculate age: age = issue_age + policy_year - 1
    2. Determine premium for year:
      gross_annual_prem = modal_premium_amount * 12
      gross_monthly_prem = gross_annual_prem / 12
      Only paid if policy_year <= funding_end_policy_year
    3. Set interest/crediting rates for year:
      base_rate_annual = weighted_average_interest_rate (or guaranteed_min_interest_rate)
      bonus = interest_bonus_yrs_2_9 (years 2-9) or interest_bonus_yrs_10_plus (years 10+)
      credited_rate_annual = base_rate_annual + bonus
    4. Set loan collateral credit rate:
      loan_credit_rate_annual = loan_rate_annual - 0.005 (years 1-10), else loan_rate_annual
    5. Convert rates to monthly:
      loan_rate_monthly = loan_rate_annual / 12
      av_charge_monthly_rate = av_charge_annual / 12
    6. Get COI rate for age:
      coi_rate_annual = COI table[age] if provided, else fallback curve
      coi_rate_monthly = coi_rate_annual / 12
    7. Get monthly rider charge:
      monthly_rider_charge = (RIDER_CHARGE_PER_1000_ANNUAL * face / 1000) / 12
    8. Set fixed charges:
      fixed_charges_monthly = MONTHLY_POLICY_FEE + MONTHLY_EXPENSE_CHARGE
    9. 12 monthly steps:
      1. If av_total <= 0 and loan_balance <= 0 and gross_monthly_prem <= 0, set lapsed = True
      2. Premium inflow:
        premium_load = gross_monthly_prem * PREMIUM_LOAD_PCT
        net_premium = gross_monthly_prem - premium_load
        av_unloaned += net_premium
      3. Death Benefit & NAR:
        gross_db_month = max(DB option value, av_total * corridor_factor(age))
        nar = max(0, gross_db_month - av_total)
      4. Monthly charges:
        coi_charge = nar * coi_rate_monthly
        av_charge = av_total * av_charge_monthly_rate
        monthly_total_charges = fixed_charges_monthly + monthly_rider_charge + coi_charge + av_charge
        Deduct from av_unloaned first, then av_collateral. If insufficient, set lapsed = True
      5. Loan interest accrual:
        loan_balance *= (1 + loan_rate_monthly) (if loan_balance > 0)
    10. Year-end crediting:
      av_unloaned *= (1 + credited_rate_annual)
      av_collateral *= (1 + loan_credit_rate_annual)
    11. LIBR income/loan: If in income phase (policy_year >= libr_start_policy_year):
      planned_annual_loan = planned_annual_income * (1 + LOAN_PLANNED_BUFFER_PCT)
      loan_balance += planned_annual_loan
      transfer = min(planned_annual_loan, av_unloaned)
      av_unloaned -= transfer
      av_collateral += transfer
    12. Surrender charge & CSV:
      surrender_charge_pct = surrender_charge_pct_by_year[policy_year] if provided, else default
      surrender_charge = surrender_charge_pct * av_end_year
      csv = max(0, av_end_year - surrender_charge - loan_balance)
      If in income phase and guarantee set, csv = max(csv, minimum_cash_surrender_value)
    13. Net Death Benefit (NDB):
      ndb = max(gross_db_year_end - loan_balance, minimum_net_death_benefit if in income phase)
    14. Ledger row: Save all calculated values for the year.

Step D: Output

References & Calibration