Calculation catalog › Monte Carlo

Monte carlo guardrail action counts

Total number of guardrail cut and raise actions summed over all path-years.

Field: Guardrail actions across path-years Unit: count Basis: no dollar basis Computed by: Engine Status: Complete chain

Appears in: monte-carlo-page (Layered success tile "{cut} cuts · {raise} raises")

Calculation records

  • Guardrail action counts summed across paths Formula record, justified by derivation

    Purpose: How many guardrail cuts, raises and holds the whole run took, counted as actions rather than as paths.

    Statement: guardrailActionCounts.cut, .raise and .hold are each that action's count summed over every path, with idle paths still in the set. For per-path records (2, 1, 3), (0, 2, 1), (4, 0, 2) and (0, 0, 0) the totals are cut 6, raise 3 and hold 6. Units: guardrail actions. Rounding: none; sums of integers, compared exactly.

    Formula: total(action) = sum_p p.guardrailActionCounts[action] for action in {cut, raise, hold}

    • p.guardrailActionCounts: One path's cut, raise and hold counts (unit: actions) (domain: worksheet (2,1,3), (0,2,1), (4,0,2), (0,0,0))
    • total(action): Run-wide count of that action (unit: actions) (domain: worksheet 6, 3, 6)

    Timing: once per aggregation, over the completed path set

    Rounding: none; integer sums

    Limits:

    • A sum, not an average and not a path incidence count: averaging over four paths gives 1.5, 0.75 and 1.5, and counting the paths with at least one action of each kind gives 2, 2 and 3
    • A path with no actions stays in the set; it adds zero, and it is not dropped from any denominator here because there is none
    • The path-share and conditional-magnitude statistics live in the adjustments block (monte-carlo-guardrail-adjustments), which counts paths, not actions
    • The evidence supplies guardrailActionCounts only; every other MonteCarloPath field is at the test’s neutral (no depletion or shortfall, floor and target met, attainment 1, no cut years, zero balances, no bequest target)

    Provenance: derived by codex, implemented by claude.

    Independent review: cursor

    Gates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass

    Worksheet: monte-carlo-guardrail-action-counts.md Mutation receipt: monte-carlo-guardrail-action-counts.mutation.md

    Evidence tests: run.evidence.test.ts

    Implemented in: run.ts

    Is the number of: monte-carlo-guardrail-action-counts

Records that feed this number

  • Risk-based guardrail: one year's cut, raise or hold against real-balance thresholds Model record, justified by derivation

    Purpose: The decision that rations discretionary spending when the deflated portfolio leaves its solved balance band.

    Statement: adj = (adjustmentPct ?? 10)/100; prev = clamp(prevMultiplier) into [0, max(0, maxMultiplier)]. When startingBalance is not > 0, or currentRealBalance is not finite or is negative: hold at prev. lower = (lowerBalanceThresholdPct/100) x startingBalance when that percent is supplied and > 0, else null; upper likewise from upperBalanceThresholdPct. When both are set and lower >= upper: hold. currentRealBalance < lower: multiplier = clamp(prev - adj), action "cut" when it fell by more than 1e-9, else "hold". currentRealBalance > upper: multiplier = clamp(prev + adj), action "raise" when it rose by more than 1e-9, else "hold". Otherwise hold. Units: balances in real (deflated) dollars; thresholds as percent of the starting real portfolio; the multiplier a fraction. Rounding: none. annualGuardrailFunding.ts deflates the start-of-year portfolio by the year's inflation factor and anchors startingBalance at the first solvent year's real portfolio.

    Formula: L = l/100 x S, U = u/100 x S; m = clamp(prev - adj) if B < L; m = clamp(prev + adj) if B > U; m = prev otherwise, and always when L or U is unset or L >= U

    • B: This year's real start-of-year portfolio (unit: usd, real) (domain: B >= 0 and finite)
    • S: The starting real portfolio the thresholds are scaled from (unit: usd, real) (domain: S > 0)
    • l, u: Lower and upper thresholds as percent of S (unit: percent) (domain: each > 0 when set; l < u)
    • adj: Step size as a fraction of the full discretionary layer (unit: 1) (domain: default 0.1)
    • m: This year's discretionary multiplier (unit: 1) (domain: 0 <= m <= maxMultiplier)

    Timing: once per projection year, before the funding fixed point

    Rounding: none

    Limits:

    • Thresholds come from the shared-path solver (montecarlo/riskBasedGuardrails.ts); a policy whose thresholds are absent, zero or inverted holds, so the mode never acts on unsolved numbers
    • The dollar thresholds the pages print (display-guardrail-balance-thresholds) are this step's L and U on the first-year anchor, published by montecarlo/riskBasedGuardrails.ts#guardrailThresholdDollars (the record guardrail-threshold-dollars); at a zero starting balance the anchor is the first year the portfolio has a balance, which only a simulation knows
    • Same fixed step and clamp semantics as the withdrawal-rate step; clampRange is module-private and reached through nextBalanceGuardrailMultiplier
    • The Monte Carlo action counts and cut-depth percentiles aggregate this decision's action and multiplier exactly as they do for the withdrawal-rate mode

    Provenance: derived by codex, implemented by claude-subagent.

    Independent review: grok

    Gates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass

    Worksheet: balance-risk-guardrail-step.md Mutation receipt: balance-risk-guardrail-step.mutation.md

    Evidence tests: guardrails.evidence.test.ts

    Implemented in: guardrails.ts

    Is the number of: spending-guardrail-factor-annual

    Feeds: monte-carlo-guardrail-action-counts, monte-carlo-max-cut-depth-percentiles, display-guardrail-balance-thresholds

  • Withdrawal-rate guardrail: one year's cut, raise or hold of the discretionary multiplier Model record, justified by derivation

    Purpose: The Guyton-Klinger-style decision that rations discretionary spending when the withdrawal rate leaves its band.

    Statement: upper = (upperGuardrailPct ?? 120)/100; lower = (lowerGuardrailPct ?? 80)/100; adj = (adjustmentPct ?? 10)/100; prev = clamp(prevMultiplier) into [0, max(0, maxMultiplier)] with maxMultiplier defaulting to 1 and a non-finite prev read as min(1, that ceiling). When startingRate is not > 0, or currentRate is not finite or is negative: hold at prev. Otherwise ratio = currentRate/startingRate. ratio > upper: multiplier = clamp(prev - adj), action "cut" when it fell by more than 1e-9, else "hold". ratio < lower: multiplier = clamp(prev + adj), action "raise" when it rose by more than 1e-9, else "hold". Otherwise hold. Units: the multiplier is a fraction of the discretionary layer; the rates are ratios of target spending to the start-of-year portfolio whose units cancel. Rounding: none.

    Formula: ratio = current/starting; m = clamp(prev - adj) if ratio > upper; m = clamp(prev + adj) if ratio < lower; m = prev otherwise; clamp into [0, maxMultiplier]

    • prev: Last year's discretionary multiplier (unit: 1) (domain: clamped into [0, maxMultiplier])
    • current, starting: This year's and the first year's withdrawal rate (unit: 1) (domain: starting > 0, current >= 0 and finite)
    • upper, lower: Band edges as fractions of the starting rate (unit: 1) (domain: defaults 1.2 and 0.8)
    • adj: Step size as a fraction of the full discretionary layer (unit: 1) (domain: default 0.1)
    • m: This year's discretionary multiplier (unit: 1) (domain: 0 <= m <= maxMultiplier)

    Timing: once per projection year, before the funding fixed point

    Rounding: none

    Limits:

    • The signal is gross recurring target spending over the start-of-year portfolio, not the net portfolio draw: a documented simplification that keeps the rule non-circular with the spending it decides
    • The step is a fixed adjustmentPct of the full discretionary layer, not a percentage of the previous multiplier
    • A cut or raise the clamp absorbs is reported as hold, so downstream counts reflect real moves only
    • clampRange is module-private; the evidence reaches it through nextGuardrailMultiplier
    • The Monte Carlo counts each year's action (run.ts) and takes 1 - guardrailFactor as the year's cut depth, so both aggregates are fed by this decision

    Provenance: derived by codex, implemented by claude-subagent.

    Independent review: cursor

    Gates (6 of 6 pass): fixture registers a test: pass; pins resolve: pass; families exist: pass; worksheet exists: pass; mutation receipt exists: pass; provenance independent: pass

    Worksheet: withdrawal-rate-guardrail-step.md Mutation receipt: withdrawal-rate-guardrail-step.mutation.md

    Evidence tests: guardrails.evidence.test.ts

    Implemented in: guardrails.ts

    Is the number of: spending-guardrail-factor-annual

    Feeds: monte-carlo-guardrail-action-counts, monte-carlo-max-cut-depth-percentiles

Engine source: engine/src/montecarlo/run.ts (MonteCarloSummary.guardrailActionCounts.cut / raise)

The evidence links on this page (each record's worksheet, mutation receipt, evidence tests and implementation, and the source links) are pinned to engine commit 2624dbdba9fd, committed 2026-10-11. The live calculation index and output-family census links point at the repository's main branch, so you can check this snapshot against them.