DEV Community

Sergey Kikevich
Sergey Kikevich

Posted on

okama 2.0: New Withdrawal Strategies and a Faster Efficient Frontier

We have released the “anniversary” version, okama 2.0. This release includes many important changes. The main themes are new advanced portfolio withdrawal strategies and a new portfolio optimization workflow for the Efficient Frontier.

First article and howto for beginners is here.

You can install the library or upgrade to the new version right now:

pip install okama -U
Enter fullscreen mode Exit fullscreen mode

With okama 2.0, we deliberately moved from educational and academic examples toward more practical and realistic use cases. On the one hand, the library now includes new tools that are well suited for building retirement strategies with regular withdrawals.

On the other hand, the logic behind the Efficient Frontier has changed: multi-period optimization with rebalancing is now the default behavior. This more advanced type of optimization is also dramatically faster. You no longer need to leave your computer running and go make tea while the calculations finish. This makes okama much better suited for web applications. Changes on okama.io are coming soon.

Cash Flow and Discounting Strategies

The new withdrawal strategies are built into the existing discounted cash flow analysis architecture in the PortfolioDCF class. The portfolio remains a separate object, while the cash flow strategy is attached through pf.dcf.cashflow_parameters. This design makes it possible to test portfolio structure, rebalancing rules, and withdrawal logic independently.

The simplest investment portfolio can be created in just two lines of code:

import okama as ok

pf = ok.Portfolio()  # by default, this is a portfolio with a single SPY fund
Enter fullscreen mode Exit fullscreen mode

Vanguard Dynamic Spending (VDS) Withdrawal Strategy

Version 1.5.0 introduced a percentage-based withdrawal strategy through the PercentageStrategy class. The new VanguardDynamicSpending (VDS) class develops this idea further: withdrawals are still defined as a fixed percentage of the portfolio balance, but additional constraints are applied.

This is an important distinction. The new strategy does not replace the original percentage-based model. It extends it. The underlying ideas come from Vanguard research.

The key new parameter in VDS is floor_ceiling. It limits how much the withdrawal amount can change relative to the previous year. For example, (-0.025, 0.05) means that the new withdrawal cannot be more than 5% higher and cannot be more than 2.5% lower than the previous year’s withdrawal. This prevents spending from changing too abruptly in response to market moves.

Absolute limits can also be defined with min_max_annual_withdrawals. If adjust_min_max=True, these limits are indexed, for example to inflation. The adjust_floor_ceiling parameter works in a similar way: the previous year’s withdrawal can first be indexed, and only then are the floor and ceiling applied. In effect, VDS combines a percentage link to portfolio size with explicit control over consumption volatility.

Limitations: VDS only works with regular withdrawals, without contributions, and the withdrawal frequency is always annual.

The strategy is created as follows:

vds = ok.VanguardDynamicSpending(
    parent=pf,
    initial_investment=1_000_000,  # starting capital
    percentage=-0.08,  # withdrawal rate
    floor_ceiling=(-0.025, 0.05),  # allowed change in the withdrawal amount
)

pf.dcf.cashflow_parameters = vds
Enter fullscreen mode Exit fullscreen mode

In applied portfolio modeling, VDS is useful when an investor wants spending to remain linked to portfolio value, but does not want the amount withdrawn to fluctuate too sharply from year to year. For retirement planning, this is much closer to real-world behavior than a pure percentage rule without guardrails.

Cut Withdrawals If Drawdown (CWD) Strategy

This strategy is built on a different philosophy. The withdrawal amount reflects the retiree’s real spending needs rather than the current portfolio value. This approach first appeared in version 1.5.0 through the IndexationStrategy class.

The new strategy allows withdrawals to be reduced during market drawdowns. This is close to what many advisors and academic studies recommend, because a portfolio is often damaged most severely by withdrawals taken during deep declines.

The CutWithdrawalsIfDrawdown (CWD) class defines this behavior through withdrawal limits that depend on the depth of the portfolio drawdown.

The crash_threshold_reduction parameter is specified as a list of pairs in the form (drawdown threshold, reduction factor). For example, (0.20, 0.40) means that if the portfolio drawdown exceeds 20%, the current withdrawal is reduced by 40%. And (0.35, 1) means that withdrawals are effectively suspended if the drawdown is deeper than 35%.

This strategy is particularly useful in models where the user wants to preserve a clear baseline spending amount while encoding anti-crisis behavior in advance. It is no longer just “withdraw a fixed inflation-indexed amount,” but rather manage the pace of withdrawals depending on drawdown depth.

Creating a CWD strategy:

cwd = ok.CutWithdrawalsIfDrawdown(
    parent=pf,
    initial_investment=1_000_000,  # starting capital
    frequency="year",  # withdrawal frequency
    amount=-60_000,  # target withdrawal amount in today’s money
    indexation="inflation",  # index withdrawals to inflation
    crash_threshold_reduction=[(.10, .25), (.20, .50), (.35, 1)],  # reduction rules
)

pf.dcf.cashflow_parameters = cwd
Enter fullscreen mode Exit fullscreen mode

A carefully designed guardrail for deep drawdowns makes it possible to use more aggressive retirement portfolios with higher expected returns, but also higher volatility.

In future publications, we will explain in more detail how to apply VDS and CWD in practice.

EfficientFrontier: A Unified Class for Optimization with Rebalancing

The second key part of the release is the update to EfficientFrontier. We merged EfficientFrontier and EfficientFrontierReb into a single EfficientFrontier class. The Efficient Frontier is now built in a multi-period framework with rebalancing by default.

By caching intermediate solutions, using parallel computation, and optimizing the objective functions, we were able to speed up the calculations significantly.

In addition, optimization of rebalanced portfolios now supports bounds, that is, asset weight constraints. This is critical for practical applications: real-world portfolio models almost always require lower and upper bounds, not an abstract unconstrained solution.

ef = ok.EfficientFrontier(
    assets=["SPY.US", "GLD.US"],
    first_date="2004-12",
    last_date="2020-10",
    ccy="USD",
    rebalancing_strategy=ok.Rebalance(period="year"),
    n_points=40,
    ticker_names=True,
    bounds=((0.0, 1.0), (0.0, 0.20)),  # gold is capped at 20% in the portfolio
)
Enter fullscreen mode Exit fullscreen mode

A fast way to find the solution for a target return:

ef.minimize_risk(target_value=0.11)

{'SPY.US': np.float64(0.9101000447520419),
 'GLD.US': np.float64(0.08989995524795805),
 'CAGR': 0.11,
 'Mean return': np.float64(0.11403419643168483),
 'Risk': np.float64(0.15083978371568296),
 'Weights': array([0.91010004, 0.08989996]),
 'iterations': 5}
Enter fullscreen mode Exit fullscreen mode

Arbitrary Withdrawals and Contributions in All Strategies

Any withdrawal or contribution strategy now includes the time_series_dic parameter, which allows arbitrary cash flows to be set by calendar date.

time_series_dic = {
    "2027-02": 2_000,    # portfolio contribution in February 2027
    "2030-03": -4_000    # portfolio withdrawal in March 2030
}
Enter fullscreen mode Exit fullscreen mode

Distribution Backtesting

Distribution backtesting is an important step before forecasting. New tools were added to make this process easier.

Automatic Distribution Selection

Version 2.0 introduces the kstest_for_all_distributions method, which runs the Kolmogorov–Smirnov test across all supported distributions and determines which one fits best.

pf.dcf.mc.kstest_for_all_distributions
Enter fullscreen mode Exit fullscreen mode

Kolmogorov–Smirnov test output

Fitting Distribution Parameters

Any distribution parameter can now be set manually, either fully or partially.

This is especially important for the Student’s t distribution, which is often the best fit for modeling fat tails in forecasting.

For example, the parameters of the Student’s t distribution can now be set as follows:

pf.dcf.mc.distribution_parameters = (4.26, None, None)  # df, loc, scale
Enter fullscreen mode Exit fullscreen mode

The degrees of freedom (df) are specified manually, while the other parameters are estimated automatically.

Moreover, the degrees of freedom can now be optimized automatically for a chosen forecast percentile:

pf.dcf.mc.optimize_df_for_students(var_level=5)  # optimize df for the 5th percentile
Enter fullscreen mode Exit fullscreen mode
4.260007759068121
Enter fullscreen mode Exit fullscreen mode

Q-Q Plot

The quality of distribution backtesting can be visualized with a Quantile–Quantile (Q-Q) plot, where historical data and generated data are compared.

Version 2.0 adds the plot_qq method for this purpose:

pf.dcf.mc.plot_qq(var_level=5)  # 5 is the confidence level for VaR and CVaR
Enter fullscreen mode Exit fullscreen mode

Q-Q Plot

If theoretical and empirical percentiles diverge too much at the selected percentile, it is better to fit the distribution parameters more carefully for a more accurate forecast. We will explain this process in detail in a separate article.

okama Documentation

The okama documentation is now cleaner and includes detailed information about all major additions, together with examples.

The examples section on GitHub has also been substantially improved:

What’s Next for okama

In the near future, we will begin rolling the most important changes into okama.io.

Future versions of okama will include support for Pandas 3 and Python 3.14.

A somewhat longer-term goal is to create an equivalent of a personal financial plan built as a sequence of investment strategies. For example, an aggressive portfolio for building retirement savings followed by a conservative portfolio for spending in retirement. Naturally, the entire sequence of portfolios will need to be tested through Monte Carlo forecasting. We already have a plan for how to do that.

Sources

  1. Project forum
  2. okama on GitHub
  3. okama documentation
  4. Examples in Jupyter Notebook format

If you found the project useful, the easiest way to support it is to star it on GitHub.

Top comments (0)