Users Guide

This document replicates the Users guide, except that it’s using owlplanner live.

An example of Owl’s functionality

With about 10 lines of Python code, one can generate a full case study. Here is a typical plan with some comments. A plan starts with the names of the individuals, their birth years and life expectancies, and a name for the plan. Dollar amounts are in k$ (i.e. thousands) and ratios in percentage.

# Load owlplanner

import owlplanner as owl

Participants

  • Jack was born in 1962-07-04 and expects to live to age 89.
  • Jill was born in 1965-12-01 and hopes to live to age 92.
  • Plan starts on Jan 1st of the earliest year
plan = owl.Plan(['Jack', 'Jill'], 
                ['1962-07-14', '1965-12-01'], 
                [89, 92], 
                'jack & jill - tutorial' )

Assets

  • Jack has $90.5k in a taxable investment account, $600.5k in a tax-deferred account and $70k from 2 tax-free accounts.
  • Jill has $60.2k in her taxable account, $150k in a 403b, and $40k in a Roth IRA.
plan.setAccountBalances(
        taxable=[90.5, 60.2],
        taxDeferred=[600.5, 150], 
        taxFree=[50.6 + 20, 40.8]
)

Wages and Contributions

  • An Excel file contains 2 tabs (one for Jill, one for Jack) describing anticipated wages and contributions.
plan.readHFP('../../examples/jack+jill.xlsx')
True

Asset allocations

Over their lifetime, Jack and Jill will want to reset their asset allocations.

  • Jack will glide an s-curve for asset allocations from a 60/40 -> 70/30 stocks/bonds portfolio.
  • Jill will do the same thing but is a bit more conservative from 50/50 -> 70/30 stocks/bonds portfolio.
plan.setInterpolationMethod('s-curve')
plan.setAllocationRatios('individual', 
        generic=[
          [[60, 40, 0, 0], [70, 30, 0, 0]], 
          [[50, 50, 0, 0], [70, 30, 0, 0]]]
)

Pensions and Social Security

  • Jack has no pension, but Jill will receive $10k per year at 65 yo.
plan.setPension([0, 10.5], [65, 65])
  • Jack anticipates receiving social security of $28.4k at age 70.
  • Jill $19.7k at age 62. All values are in today’s $.
plan.setSocialSecurity([28.4, 19.7], [70, 62])

Spending

  • Instead of a ‘flat’ profile, we select a ‘smile’ spending profile, with 60% needs for the survivor.
plan.setSpendingProfile('smile', 60)

Rate Scenario

For this example, we will reproduce the historical sequence of returns starting in year 1969.

plan.setRates('historical', 1969)

Planning Goals

  • Jack and Jill want to leave a bequest of $500k
  • They want to limit Roth conversions to $100k per year.
  • Jill’s 403b plan does not support in-plan Roth conversions.

We solve for the maximum net spending profile under these constraints.

plan.solve(
        'maxSpending', 
        options={
          'maxRothConversion': 100,
          'bequest': 500, 
          'noRothConversions': 'Jill'}
)

Results

Once we set our goals and solve, OWL can tell us more about what’s happening

The output can be seen using the following commands that display various plots of the decision variables in time.

Net Spending

plan.showNetSpending()

Accounts and balances

plan.showAccounts()

Asset distributions

plan.showAssetComposition()

By default, all these plots are in nominal dollars. To get values in today’s $, a call to

plan.setDefaultPlots('today')

would change all graphs to report in today’s dollars. Each plot can also override the default by setting the value parameters to either nominal or today, such as in the following example, which shows the taxable ordinary income over the duration of the plan, along with inflation-adjusted extrapolated tax brackets. Notice how the optimized income is surfing the boundaries of tax brackets.

Gross Income vs Tax Brackets

plan.showGrossIncome(value='nominal')

Optimal Spending Profile

The optimal spending profile is shown in the next plot (in today’s dollars). Notice the drop (recall we selected 60% survivor needs) at the passing of the first spouse.

plan.showProfile('today')

Account balances

The following plot shows the account balances in nominal value for all savings accounts owned by Jack and Jill. It was generated using

plan.showAccounts(value='nominal')

Cash flows and sources

This plot shows the complex cash flow from all sources.

plan.showSources(value='nominal')

Tax liabilities

For taxes, the following call will display Medicare premiums (including Part B IRMAA fees) and federal income tax

plan.showTaxes(value='nominal')

Asset distributions

For the case at hand, recall that asset allocations were selected above through

plan.setAllocationRatios('individual', generic=[[[60, 40, 0, 0], [70, 30, 0, 0]], [[50, 50, 0, 0], [70, 30, 0, 0]]])

Gliding from a 60%/40% stocks/bonds portfolio to 70%/30% for Jack, and 50%/50% -> 70%/30% for Jill. Assets distribution in all accounts in today’s $ over time can be displayed from

plan.showAssetComposition(value='today')

Rates

These plots are irregular because we used historical rates from 1969. The volatility of the rates offers Roth conversion benefits which are exploited by the optimizer. The rates used can be displayed by:

plan.showRates()

Correlations

Values between brackets <> are the average values and volatility over the selected period.

For the statisticians, rates distributions and correlations between them can be shown using:

plan.showRatesCorrelations()

Summary

A short text summary of the outcome of the optimization can be displayed through using:

plan.summary()
SUMMARY ================================================================
Plan name: jack & jill - tutorial
Net yearly spending basis . . . . . . . . . . . . . . . . . . . . . . . . . .: $59,449
Net spending for year 2025: $65,041
Net spending remaining in year 2025: $2,138
 Total net spending: $1,819,141
[Total net spending]: $5,022,773
 Total Roth conversions: $276,196
[Total Roth conversions]: $375,653
 Total tax paid on ordinary income: $116,657
[Total tax paid on ordinary income]: $202,561
»  Subtotal in tax bracket 10%: $55,913
» [Subtotal in tax bracket 10%]: $137,987
»  Subtotal in tax bracket 12/15%: $26,316
» [Subtotal in tax bracket 12/15%]: $27,923
»  Subtotal in tax bracket 22/25%: $34,428
» [Subtotal in tax bracket 22/25%]: $36,651
»  Subtotal in tax bracket 24/28%: $0
» [Subtotal in tax bracket 24/28%]: $0
»  Subtotal in tax bracket 32/33%: $0
» [Subtotal in tax bracket 32/33%]: $0
»  Subtotal in tax bracket 35%: $0
» [Subtotal in tax bracket 35%]: $0
»  Subtotal in tax bracket 37/40%: $0
» [Subtotal in tax bracket 37/40%]: $0
»  Subtotal in early withdrawal penalty: $0
» [Subtotal in early withdrawal penalty]: $0
 Total tax paid on gains and dividends: $1,400
[Total tax paid on gains and dividends]: $1,497
 Total net investment income tax paid: $0
[Total net investment income tax paid]: $0
 Total Medicare premiums paid: $117,660
[Total Medicare premiums paid]: $343,388
Year of partial bequest: 2051
 Sum of spousal transfer to Jill: $488,441
[Sum of spousal transfer to Jill]: $2,112,259
»  Spousal transfer to Jill - taxable: $0
» [Spousal transfer to Jill - taxable]: $0
»  Spousal transfer to Jill - tax-def: $61,709
» [Spousal transfer to Jill - tax-def]: $266,859
»  Spousal transfer to Jill - tax-free: $426,732
» [Spousal transfer to Jill - tax-free]: $1,845,401
 Sum of post-tax non-spousal bequest from Jack: $0
[Sum of post-tax non-spousal bequest from Jack]: $0
»  Post-tax non-spousal bequest from Jack - taxable: $0
» [Post-tax non-spousal bequest from Jack - taxable]: $0
»  Post-tax non-spousal bequest from Jack - tax-def: $0
» [Post-tax non-spousal bequest from Jack - tax-def]: $0
»  Post-tax non-spousal bequest from Jack - tax-free: $0
» [Post-tax non-spousal bequest from Jack - tax-free]: $0
Year of final bequest: 2057
 Total value of final bequest: $500,000
[Total value of final bequest]: $2,488,808
»  Post-tax final bequest account value - taxable: $0
» [Post-tax final bequest account value - taxable]: $0
»  Post-tax final bequest account value - tax-def: $4,859
» [Post-tax final bequest account value - tax-def]: $24,185
»  Post-tax final bequest account value - tax-free: $495,141
» [Post-tax final bequest account value - tax-free]: $2,464,624
Plan starting date: 2025-12-20
Cumulative inflation factor at end of final year: 4.98
          Jack's life horizon: 2025 -> 2051
          Jack's years planned: 27
          Jill's life horizon: 2025 -> 2057
          Jill's years planned: 33
Number of decision variables: 1029
Number of constraints: 953
Case executed on: 2025-12-20 at 17:56:42
------------------------------------------------------------------------

The output of the last command reports that if future rates are exactly like those observed starting from 1969 and the following years, Jack and Jill could afford an annual spending of \$97k starting this year (with a basis of \$88.8k - the basis multiplies the profile which can vary over the course of the plan). The summary also contains some details:

Saving results

And an Excel workbook can be saved with all the detailed amounts over the years by using the following command:

plan.saveWorkbook(overwrite=True)