Skip to content

find_the_largest_withdrawals_size: maintain_balance goals fail on target_survival_period default #90

@chilango74

Description

@chilango74

Summary

Portfolio.dcf.find_the_largest_withdrawals_size() raises

ValueError: target_survival_period must be less than Monte Carlo simulation period (N).

for the maintain_balance_pv and maintain_balance_fv goals whenever the Monte
Carlo period is 27 years or fewer, even when the caller never passes
target_survival_period. The parameter is not used by these two goals, yet
it is validated unconditionally, so its default value (25) breaks the call.

okama version: 2.2.1 (same code in current master).

Steps to reproduce

import okama as ok

pf = ok.Portfolio(
    ["SPY.US", "AGG.US"],
    weights=[0.6, 0.4],
    inflation=True,
    ccy="USD",
    rebalancing_strategy=ok.Rebalance(period="year"),
)
pc = ok.PercentageStrategy(pf)
pc.initial_investment = 100_000
pc.frequency = "year"
pf.dcf.cashflow_parameters = pc

pf.dcf.set_mc_parameters(distribution="norm", period=25, mc_number=50)

# target_survival_period is NOT passed — it is irrelevant for this goal
pf.dcf.find_the_largest_withdrawals_size(goal="maintain_balance_pv", percentile=5)

Actual behavior

ValueError: target_survival_period must be less than Monte Carlo simulation period (25).

It also fails for goal="maintain_balance_fv", and for any MC period <= 27
(including small/default periods such as 10), because the check is
target_survival_period > period * (1 - tolerance_rel)25 > period * 0.9.

Workaround that confirms the diagnosis

Passing any value below 0.9 * period makes the call succeed, and the value has
no effect on the result for these goals:

pf.dcf.find_the_largest_withdrawals_size(
    goal="maintain_balance_pv", percentile=5, target_survival_period=12
)  # success=True

Root cause

In okama/portfolios/dcf.py:

  1. find_the_largest_withdrawals_size defaults target_survival_period: int = 25
    (signature, ~L874).
  2. It calls self._validate_parameters(...) unconditionally for every goal
    (~L999), and _validate_parameters does not receive goal.
  3. _validate_parameters rejects target_survival_period > self.mc.period * (1 - tolerance_rel)
    (~L1112-1119).
  4. But target_survival_period is only consumed by the survival_period branch
    of _calculate_goal_metrics (~L1150-1152). The maintain_balance_fv /
    maintain_balance_pv branch (~L1140-1149) never reads it — its condition is
    wealth_at_quantile >= start_investment and sp_at_quantile == self.mc.period.

So a parameter that is irrelevant to two of the three goals is validated against
all of them, and its default makes those goals unusable for typical (short) MC
horizons.

Expected behavior

For maintain_balance_pv / maintain_balance_fv, find_the_largest_withdrawals_size
should run regardless of target_survival_period (it is documented as working
with the survival_period goal only — see the target_survival_period docstring).

Suggested fix

Validate / apply target_survival_period only when goal == "survival_period".
For example, pass goal into _validate_parameters and guard the period check:

if goal == WithdrawalGoal.SURVIVAL_PERIOD and target_survival_period > self.mc.period * (1 - tolerance_rel):
    raise ValueError(
        f"target_survival_period must be less than Monte Carlo simulation period ({self.mc.period})."
    )

(or move that single check into the survival_period branch). The other goals
then ignore the parameter entirely, as the docstring already implies.

Downstream impact

Surfaces in okama.io (okama-dash) Portfolio → "Find max withdrawal": selecting
goal "Keep purchasing power (PV)" or "Keep nominal balance (FV)" returns this
error for the default forecast period, because the UI does not pass
target_survival_period for these goals (correctly, since it is unused).

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions