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 byclaude.Independent review:
cursorGates (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 byclaude-subagent.Independent review:
grokGates (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 byclaude-subagent.Independent review:
cursorGates (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.