[
  {
    "objectID": "users_guide.html",
    "href": "users_guide.html",
    "title": "Users Guide",
    "section": "",
    "text": "This document describes the underlying Owl package and how to use it within Python scripts or in Jupyter notebooks."
  },
  {
    "objectID": "users_guide.html#an-example-of-owls-functionality",
    "href": "users_guide.html#an-example-of-owls-functionality",
    "title": "Users Guide",
    "section": "An example of Owl’s functionality",
    "text": "An example of Owl’s functionality\nWith 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.\nimport owlplanner as owl\n# Jack was born in 1962 and expects to live to age 89. Jill was born in 1965 and hopes to live to age 92.\n# Plan starts on Jan 1st of this year.\nplan = owl.Plan(['Jack', 'Jill'], [1962, 1965], [89, 92], 'jack & jill - tutorial', startDate='01-01')\n# Jack has $90.5k in a taxable investment account, $600.5k in a tax-deferred account and $70k from 2 tax-free accounts.\n# Jill has $60.2k in her taxable account, $150k in a 403b, and $40k in a Roth IRA.\nplan.setAccountBalances(taxable=[90.5, 60.2], taxDeferred=[600.5, 150], taxFree=[50.6 + 20, 40.8])\n# An Excel file contains 2 tabs (one for Jill, one for Jack) describing anticipated wages and contributions.\nplan.readContributions('jack+jill.xlsx')\n# Jack will glide an s-curve for asset allocations from a 60/40 -&gt; 70/30  stocks/bonds portfolio.\n# Jill will do the same thing but is a bit more conservative from 50/50 -&gt; 70/30 stocks/bonds portfolio.\nplan.setInterpolationMethod('s-curve')\nplan.setAllocationRatios('individual', generic=[[[60, 40, 0, 0], [70, 30, 0, 0]], [[50, 50, 0, 0], [70, 30, 0, 0]]])\n# Jack has no pension, but Jill will receive $10k per year at 65 yo.\nplan.setPension([0, 10.5], [65, 65])\n# Jack anticipates receiving social security of $28.4k at age 70, and Jill $19.7k at age 62. All values are in today's $.\nplan.setSocialSecurity([28.4, 19.7], [70, 62])\n# Instead of a 'flat' profile, we select a 'smile' spending profile, with 60% needs for the survivor.\nplan.setSpendingProfile('smile', 60)\n# We will reproduce the historical sequence of returns starting in year 1969.\nplan.setRates('historical', 1969)\n# Jack and Jill want to leave a bequest of $500k, and limit Roth conversions to $100k per year.\n# Jill's 403b plan does not support in-plan Roth conversions.\n# We solve for the maximum net spending profile under these constraints.\nplan.solve('maxSpending', options={'maxRothConversion': 100, 'bequest': 500, 'noRothConversions': 'Jill'})\nThe output can be seen using the following commands that display various plots of the decision variables in time.\nplan.showNetSpending()\nplan.showGrossIncome()\nplan.showTaxes()\nplan.showSources()\nplan.showAccounts()\nplan.showAssetDistribution()\n...\nBy default, all these plots are in nominal dollars. To get values in today’s $, a call to\nplan.setDefaultPlots('today')\nwould 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.\nplan.showGrossIncome(value='nominal')\n\nThe 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.\nplan.showProfile('today')\n\nThe following plot shows the account balances in nominal value for all savings accounts owned by Jack and Jill. It was generated using\nplan.showAccounts(value='nominal')\n\nwhile this plot shows the complex cash flow from all sources, which was generated with\nplan.showSources(value='nominal')\n\nFor taxes, the following call will display Medicare premiums (including Part B IRMAA fees) and federal income tax\nplan.showTaxes(value='nominal')\n\nFor the case at hand, recall that asset allocations were selected above through\nplan.setAllocationRatios('individual', generic=[[[60, 40, 0, 0], [70, 30, 0, 0]], [[50, 50, 0, 0], [70, 30, 0, 0]]])\ngliding from a 60%/40% stocks/bonds portfolio to 70%/30% for Jack, and 50%/50% -&gt; 70%/30% for Jill. Assets distribution in all accounts in today’s $ over time can be displayed from\nplan.showAssetDistribution(value='today')\n  \nThese 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:\nplan.showRates()\n\nValues between brackets &lt;&gt; are the average values and volatility over the selected period.\nFor the statisticians, rates distributions and correlations between them can be shown using:\nplan.showRatesCorrelations()\n\nA short text summary of the outcome of the optimization can be displayed through using:\nplan.summary()\nThe 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:\nSUMMARY ================================================================\nNet yearly spending basis in 2025$: $91,812\nNet yearly spending for year 2025: $100,448\nNet spending remaining in year 2025: $100,448\nTotal net spending in 2025$: $2,809,453 ($7,757,092 nominal)\nTotal Roth conversions in 2025$: $320,639 ($456,454 nominal)\nTotal income tax paid on ordinary income in 2025$: $247,788 ($469,522 nominal)\nTotal tax paid on gains and dividends in 2025$: $3,313 ($3,768 nominal)\nTotal Medicare premiums paid in 2025$: $117,660 ($343,388 nominal)\nSpousal wealth transfer from Jack to Jill in year 2051 (nominal): taxable: $0  tax-def: $57,224  tax-free: $2,102,173\nSum of spousal bequests to Jill in year 2051 in 2025$: $499,341 ($2,159,397 nominal)\nPost-tax non-spousal bequests from Jack in year 2051 (nominal): taxable: $0  tax-def: $0  tax-free: $0\nSum of post-tax non-spousal bequests from Jack in year 2051 in 2025$: $0 ($0 nominal)\nPost-tax account values at the end of final plan year 2057 (nominal): taxable: $0  tax-def: $0  tax-free: $2,488,808\nTotal estate value at the end of final plan year 2057 in 2025$: $500,000 ($2,488,808 nominal)\nPlan starting date: 01-01\nCumulative inflation factor from start date to end of plan: 4.98\n        Jack's 27-year life horizon: 2025 -&gt; 2051\n        Jill's 33-year life horizon: 2025 -&gt; 2057\nPlan name: jack & jill - tutorial\nNumber of decision variables: 996\nNumber of constraints: 867\nCase executed on: 2025-02-04 at 22:55:03\n\n------------------------------------------------------------------------\nAnd an Excel workbook can be saved with all the detailed amounts over the years by using the following command:\nplan.saveWorkbook(overwrite=True)\nFor Monte Carlo simulations, the mean return rates, their volatility and covariance are specified and used to generate random scenarios. A histogram of outcomes is generated such as this one for Jack and Jill, which was generated by selecting stochastic rates and using\nplan.runMC('maxSpending', ...)\n\nSimilarly, the next one was generated using\nplan.runMC('maxBequest', ...)\n\nSee tutorial notebooks 1, 2, and 3 for more info."
  },
  {
    "objectID": "users_guide.html#requirements",
    "href": "users_guide.html#requirements",
    "title": "Users Guide",
    "section": "Requirements",
    "text": "Requirements\nOwl relies on common Python modules such as NumPy, Pandas, SciPy, matplotlib, and Seaborn. The user front-end was built on Streamlit. Package odfpy might be required if one read files created by LibreOffice. These dependencies will be installed by pip using the installation guide here..\nThe simplest way to get started with Owl is to use the streamlit browser-based user interface that is started by the owlplanner.cmd script, which will start a user interface on your own browser. Here is a screenshot of one of the multiple tabs of the interface:\n\nAlternatively, one can prefer using Owl from Jupyter notebooks. For that purpose, the examples directory contains many files as a tutorial. The Jupyter Notebook interface is a browser-based application for authoring documents that combines live-code with narrative text, equations and visualizations. Jupyter will run in your default web browser, from your computer to your browser, and therefore no data is ever transferred on the Internet (your computer, i.e., localhost, is the server).\nFor simulating your own realizations, use the files beginning with the word template. Make a copy and rename them with your own names while keeping the same extension. Then you’ll be able to personalize a case with your own numbers and start experimenting with Owl. Notebooks with detailed explanations can be found in tutorial_1, tutorial_2, and tutorial_3.\nFinally, you will also need the capability to read and edit Excel files. One can have an Excel license, or use the LibreOffice free alternative. You can also use Google docs.\nCopyright © 2024 - Martin-D. Lacasse\nDisclaimers: I am not a financial planner. You make your own decisions. This program comes with no guarantee. Use at your own risk."
  },
  {
    "objectID": "index.html",
    "href": "index.html",
    "title": "OWL - Optimal Wealth Lab",
    "section": "",
    "text": "This package is a modeling framework for exploring the sensitivity of retirement financial decisions. Strictly speaking, it is not a planning tool, but more an environment for exploring what if scenarios. It provides different realizations of a financial strategy through the rigorous mathematical optimization of relevant decision variables. Two major objective goals can be set: either maximize net spending, or after-tax bequest under various constraints. Look at the Capabilities section below for more detail.\nOne can certainly have a savings plan, but due to the volatility of financial investments, it is impossible to have a certain asset earnings plan. This does not mean one cannot make decisions. These decisions need to be guided with an understanding of the sensitivity of the parameters. This is exactly where this tool fits in. Given your savings capabilities and spending desires, it can generate different future realizations of your strategy under different market assumptions, helping to better understand your financial situation."
  },
  {
    "objectID": "index.html#overview",
    "href": "index.html#overview",
    "title": "OWL - Optimal Wealth Lab",
    "section": "",
    "text": "This package is a modeling framework for exploring the sensitivity of retirement financial decisions. Strictly speaking, it is not a planning tool, but more an environment for exploring what if scenarios. It provides different realizations of a financial strategy through the rigorous mathematical optimization of relevant decision variables. Two major objective goals can be set: either maximize net spending, or after-tax bequest under various constraints. Look at the Capabilities section below for more detail.\nOne can certainly have a savings plan, but due to the volatility of financial investments, it is impossible to have a certain asset earnings plan. This does not mean one cannot make decisions. These decisions need to be guided with an understanding of the sensitivity of the parameters. This is exactly where this tool fits in. Given your savings capabilities and spending desires, it can generate different future realizations of your strategy under different market assumptions, helping to better understand your financial situation."
  },
  {
    "objectID": "index.html#purpose-and-vision",
    "href": "index.html#purpose-and-vision",
    "title": "OWL - Optimal Wealth Lab",
    "section": "Purpose and vision",
    "text": "Purpose and vision\nOne goal of Owl is to provide a free and open-source ecosystem that has cutting-edge optimization capabilities, allowing for the next generation of Python-literate retirees to experiment with their own financial future while providing a codebase where they can learn and contribute. At the same time, an intuitive and easy-to-use user interface based on Streamlit allows a broad set of users to benefit from the application as it only requires basic financial knowledge.\nThere are and were good retirement optimizers in the recent past, but the vast majority of them are either proprietary platforms collecting your data, or academic papers that share the results without really sharing the details of the underlying mathematical models. The algorithms in Owl rely on the open-source HiGHS linear programming solver but they have also been ported and tested on other platforms such as Mosek and COIN-OR. The complete formulation and detailed description of the underlying mathematical model can be found here.\nIt is anticipated that most end users will use Owl through the graphical interface either at owlplanner.streamlit.app or installed on their own computer. The underlying Python package can also be used directly through Python scripts or Jupyter Notebooks as described here.\nA separate installation guide is available for OWL on WSL using UV.\nNot every retirement decision strategy can be framed as an easy-to-solve optimization problem. In particular, if one is interested in comparing different withdrawal strategies, FI Calc is an elegant application that addresses this need. If, however, you also want to optimize spending, bequest, and Roth conversions, with an approach also considering Medicare and federal income tax over the next few years, then Owl is definitely a tool that can help guide your decisions."
  },
  {
    "objectID": "index.html#capabilities",
    "href": "index.html#capabilities",
    "title": "OWL - Optimal Wealth Lab",
    "section": "Capabilities",
    "text": "Capabilities\nOwl can optimize for either maximum net spending under the constraint of a given bequest (which can be zero), or maximize the after-tax value of a bequest under the constraint of a desired net spending profile, and under the assumption of a heirs marginal tax rate.\nRoth conversions are also considered, subject to an optional maximum conversion amount, and optimized to suit the goals of the selected objective function.\nAll calculations are indexed for inflation, which is either provided as a fixed rate, or through historical values, as are all other rates used for the calculations. These rates can be used for backtesting different scenarios by choosing historical rates, or by choosing historical average rates over a historical year range, or what I coined “histochastic” rates which are generated using the statistical distribution of observed historical rates.\nPortfolios available for experimenting include assets from the S&P 500, Corporate Bonds Baa, Treasury 10-y Notes, and cash assets assumed to just follow inflation which is represented by the Consumer Price Index. Other asset classes can easily be added, but would add complexity while only providing diminishing insights.\nHistorical data used are from Aswath Damodaran at the Stern School of Business.\nAsset allocations are selected for the duration of the plan, and these can glide linearly or along a configurable s-curve over the lifespan of the individual.\nSpending profiles are adjusted for inflation, and so are all other indexable quantities. Proflies can be flat or follow a smile curve which is also adjustable through three simple parameters.\n\nAvailable rates are from 1928 to last year and can be used to test historical performance.\nFixed rates can also be provided, as well as histochastic rates, which are generated using the statistical characteristics (means and covariance matrix) of a selected historical year range.\nPure stochastic rates can also be generated if the user provides means, volatility (expressed as standard deviation), and optionally the correlations between the different assets return rates provided as a matrix, or a list of the off-diagonal elements (see documentation for details).\nAverage rates calculated over a historical data period can also be chosen.\n\nMonte Carlo simulations capabilities are included and provide a probability of success and a histogram of outcomes. These simulations can be used for either determining the probability distribution of the maximum net spending amount under the constraint of a desired bequest, or the probability distribution of the maximum bequest under the constraint of a desired net spending amount.\nUnlike discrete-event simulators, Owl uses an optimization algorithm for every new scenario, which results in more calculations being performed. As a result, the number of cases to be considered should be kept to a reasonable number. For a few hundred cases, a few minutes of calculations can provide very good estimates and reliable probability distributions.\nOptimizing each solution is more representative than event-base simulators in the sense that optimal solutions will naturally adjust to the return scenarios being considered. This is more realistic as retirees would certainly re-evaluate their expectations under severe market drops or gains. This optimal approach provides a net benefit over event-based simulators, which maintain a distribution strategy either fixed, or within guardrails for capturing the retirees’ reactions to the market.\nBasic input parameters can be entered through the user interface while optional additional time series can be read from an Excel spreadsheet that contains future wages, contributions to savings accounts, and planned big-ticket items such as the purchase of a lake house, the sale of a boat, large gifts, or inheritance.\nThree types of savings accounts are considered: taxable, tax-deferred, and tax-free, which are all tracked separately for married individuals. Asset transition to the surviving spouse is done according to beneficiary fractions for each type of savings account. Tax status covers married filing jointly and single, depending on the number of individuals reported.\nMaturation rules for Roth contributions and conversions are implemented as constraints limiting withdrawal amounts to cover Roth account balances for 5 years after the events. Medicare and IRMAA calculations are performed through a self-consistent loop on cash flow constraints. They can also be optimized explicitly as an option, but this choice can lead to longer calculations due to the use of the many additional binary variables required by the formulation. Future Medicare and IRMAA values are simple projections of current values with the assumed inflation rates.\nOwl has a basic social security calculator that determines the actual benefits based on the individual’s primary insurance amount (PIA), full retirement age (FRA), and claiming age. Both spousal’s benefits and survivor’s benefits are calculated for non-complex cases."
  },
  {
    "objectID": "index.html#limitations",
    "href": "index.html#limitations",
    "title": "OWL - Optimal Wealth Lab",
    "section": "Limitations",
    "text": "Limitations\nOwl is work in progress. At the current time:\n\nOnly the US federal income tax is considered (and minimized through the optimization algorithm). Head of household filing status has not been added but can easily be.\nRequired minimum distributions are calculated, but tables for spouses more than 10 years apart are not included. These cases are detected and will generate an error message.\nCurrent version has no optimization of asset allocations between individuals and/or types of savings accounts. If there is interest, that could be added in the future.\nIn the current implementation, social securiy is always taxed at 85%, assuming that your taxable income will be larger than 34 k$ (single) or 44 k$ (married filing jointly).\nWhen Medicare calculations are done through a self-consistent loop, the Medicare premiums are calculated after an initial solution is generated, and then a new solution is re-generated with these premiums as a constraint. In some situations, when the income (MAGI) is near an IRMAA bracket, oscillatory solutions can arise. While the solutions generated are very close to one another, Owl will pick the smallest solution for being conservative. While sometimes computationally costly, a comparison with a full Medicare optimization should always be performed.\nPart D is not included in the IRMAA calculations. Only Part B is taken into account, which is considerably more significant.\nFuture tax brackets are pure speculations derived from the little we know now and projected to the next 30 years. Your guesses are as good as mine.\n\nThe solution from an optimization algorithm has only two states: feasible and infeasible. Therefore, unlike event-driven simulators that can tell you that your distribution strategy runs out of money in year 20, an optimization-based solver can only tell you that a solution does or does not exist for the plan being considered. Examples of infeasible solutions include requesting a bequeathed estate value too large for the savings assets to support, even with zero net spending basis, or maximizing the bequest subject to a net spending basis that is already too large for the savings assets to support, even with no estate being left."
  },
  {
    "objectID": "index.html#disclaimers",
    "href": "index.html#disclaimers",
    "title": "OWL - Optimal Wealth Lab",
    "section": "Disclaimers",
    "text": "Disclaimers\n\nI am not a financial planner.\nYou make your own decisions.\nThis program comes with no guarantees.\nUse at your own risk."
  },
  {
    "objectID": "installation.html",
    "href": "installation.html",
    "title": "Installation",
    "section": "",
    "text": "This document is aimed at software developers desiring to install the Owl source code and run it locally on their computer.\nFor end-users, we suggest accessing Owl from the Streamlit Community Server or, if one prefers to have everything on their own computer, to install and run a Docker image as described in these instructions."
  },
  {
    "objectID": "installation.html#about",
    "href": "installation.html#about",
    "title": "Installation",
    "section": "",
    "text": "This document is aimed at software developers desiring to install the Owl source code and run it locally on their computer.\nFor end-users, we suggest accessing Owl from the Streamlit Community Server or, if one prefers to have everything on their own computer, to install and run a Docker image as described in these instructions."
  },
  {
    "objectID": "installation.html#requirements",
    "href": "installation.html#requirements",
    "title": "Installation",
    "section": "Requirements",
    "text": "Requirements\nYou will need a Python environment, and the pip module installed on your computer for completing the installation. The build module will be required for developers. You will also need git to manage the source code from GitHub which is found here for Windows, and by installing developer tools on MacOS and Linux.\nA good option for a comprehensive Python environment is to use the Anaconda distribution that can be found here for various operating systems. Installation of Anaconda can be done by downloading and running the installation file corresponding to your operating system and hardware.\nInstructions given here are command-line instructions to be entered from a terminal window.\nIf using Anaconda, pip can be installed as follows\nconda install pip\notherwise, your distribution will most likely include pip already.\nThe build module is included in Anaconda. Command pip install build will install it in other distributions."
  },
  {
    "objectID": "installation.html#creating-a-virtual-environment",
    "href": "installation.html#creating-a-virtual-environment",
    "title": "Installation",
    "section": "Creating a virtual environment",
    "text": "Creating a virtual environment\nIt is common practice to create a virtual environment for a specific project. This is to avoid making changes in the base ditribution that could break dependencies. Creating and activating a new environment called owlenv in Anaconda is achieved by the following commands:\nconda create --name owlenv\nconda activate owlenv\nA cheat sheet for Anaconda can be found here.\nWhen not using conda, creating and activating an environment can be done by using the module venv as follows:\npython -m venv owlenv\nThis will create subdirectory owlenv in the current directory. For activating this environment:\n# in MS command\n.\\owlenv\\Scripts\\activate.bat\n\n# in MS PowerShell\n./owlenv/Scripts/activate.ps1\n\n# MacOS or Linux\nsource ./owlenv/Scripts/activate\nMore details on how to create a virtual environment using venv can be found here."
  },
  {
    "objectID": "installation.html#obtaining-owls-source-code",
    "href": "installation.html#obtaining-owls-source-code",
    "title": "Installation",
    "section": "Obtaining Owl’s source code",
    "text": "Obtaining Owl’s source code\nWe assume that you have created and activated a virtual environment at this point. From there, we install the latest version of Owl from GitHub.\ngit clone https://github.com/mdlacasse/Owl.git\n\nThen go (cd) to the directory where you installed Owl.\ncd Owl\nFrom the top directory of the source code run: The following command will install the current version of Owl and all its dependencies:\npip install --upgrade -r requirements.txt\nYou can also install the Owl package directly from the Python Package Index."
  },
  {
    "objectID": "installation.html#running-the-streamlit-frontend-locally",
    "href": "installation.html#running-the-streamlit-frontend-locally",
    "title": "Installation",
    "section": "Running the Streamlit frontend locally",
    "text": "Running the Streamlit frontend locally\nOnce Owl’s source code and all its dependencies as been installed, one can run the Owl user interface locally:\n# From Windows\n./owlplanner.cmd\n\n# From MacOS or Linux\n./owlplanner.sh\nThis will open a tab on your default browser."
  },
  {
    "objectID": "installation.html#installation-steps-for-developers",
    "href": "installation.html#installation-steps-for-developers",
    "title": "Installation",
    "section": "Installation steps for developers",
    "text": "Installation steps for developers\nFirst use the same steps as above to create and activate a virtual environment and install the source code from GitHub. Then make sure that the build module is installed (pip install build).\nThe next commands will build and install the Owl module in “edit mode”\npython -m build\npip install -e .\nThe -e instructs pip to install in editable mode and use the live version in the current directory tree."
  },
  {
    "objectID": "installation.html#publishing-a-version-for-reference-only",
    "href": "installation.html#publishing-a-version-for-reference-only",
    "title": "Installation",
    "section": "Publishing a version (for reference only)",
    "text": "Publishing a version (for reference only)\nRun checks before all commits:\nflake8 ui src tests\npytest\nTo update version, edit number in src/owlplanner/version.py.\nTo update package on pypi or testpypi,\nrm dist/*\npython -m build\ntwine upload --repository [repo] dist/*\nwhere [repo] is testpypi or pypi depending on the type of release."
  },
  {
    "objectID": "examples/example-1.html",
    "href": "examples/example-1.html",
    "title": "Users Guide",
    "section": "",
    "text": "This document replicates the Users guide, except that it’s using owlplanner live."
  },
  {
    "objectID": "examples/example-1.html#an-example-of-owls-functionality",
    "href": "examples/example-1.html#an-example-of-owls-functionality",
    "title": "Users Guide",
    "section": "An example of Owl’s functionality",
    "text": "An example of Owl’s functionality\nWith 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.\n\n# Load owlplanner\n\nimport owlplanner as owl\n\n\nParticipants\n\nJack was born in 1962-07-04 and expects to live to age 89.\nJill was born in 1965-12-01 and hopes to live to age 92.\nPlan starts on Jan 1st of the earliest year\n\n\nplan = owl.Plan(['Jack', 'Jill'], \n                ['1962-07-14', '1965-12-01'], \n                [89, 92], \n                'jack & jill - tutorial' )\n\n\n\nAssets\n\nJack has $90.5k in a taxable investment account, $600.5k in a tax-deferred account and $70k from 2 tax-free accounts.\nJill has $60.2k in her taxable account, $150k in a 403b, and $40k in a Roth IRA.\n\n\nplan.setAccountBalances(\n        taxable=[90.5, 60.2],\n        taxDeferred=[600.5, 150], \n        taxFree=[50.6 + 20, 40.8]\n)\n\n\n\nWages and Contributions\n\nAn Excel file contains 2 tabs (one for Jill, one for Jack) describing anticipated wages and contributions.\n\n\nplan.readContributions('../../examples/jack+jill.xlsx')\n\nTrue\n\n\n\n\nAsset allocations\nOver their lifetime, Jack and Jill will want to reset their asset allocations.\n\nJack will glide an s-curve for asset allocations from a 60/40 -&gt; 70/30 stocks/bonds portfolio.\nJill will do the same thing but is a bit more conservative from 50/50 -&gt; 70/30 stocks/bonds portfolio.\n\n\nplan.setInterpolationMethod('s-curve')\nplan.setAllocationRatios('individual', \n        generic=[\n          [[60, 40, 0, 0], [70, 30, 0, 0]], \n          [[50, 50, 0, 0], [70, 30, 0, 0]]]\n)\n\n\n\nPensions and Social Security\n\nJack has no pension, but Jill will receive $10k per year at 65 yo.\n\n\nplan.setPension([0, 10.5], [65, 65])\n\n\nJack anticipates receiving social security of $28.4k at age 70.\nJill $19.7k at age 62. All values are in today’s $.\n\n\nplan.setSocialSecurity([28.4, 19.7], [70, 62])\n\n\n\nSpending\n\nInstead of a ‘flat’ profile, we select a ‘smile’ spending profile, with 60% needs for the survivor.\n\n\nplan.setSpendingProfile('smile', 60)\n\n\n\nRate Scenario\nFor this example, we will reproduce the historical sequence of returns starting in year 1969.\n\nplan.setRates('historical', 1969)"
  },
  {
    "objectID": "examples/example-1.html#planning-goals",
    "href": "examples/example-1.html#planning-goals",
    "title": "Users Guide",
    "section": "Planning Goals",
    "text": "Planning Goals\n\nJack and Jill want to leave a bequest of $500k\nThey want to limit Roth conversions to $100k per year.\nJill’s 403b plan does not support in-plan Roth conversions.\n\nWe solve for the maximum net spending profile under these constraints.\n\nplan.solve(\n        'maxSpending', \n        options={\n          'maxRothConversion': 100,\n          'bequest': 500, \n          'noRothConversions': 'Jill'}\n)"
  },
  {
    "objectID": "examples/example-1.html#results",
    "href": "examples/example-1.html#results",
    "title": "Users Guide",
    "section": "Results",
    "text": "Results\nOnce we set our goals and solve, OWL can tell us more about what’s happening\nThe output can be seen using the following commands that display various plots of the decision variables in time.\n\nNet Spending\n\nplan.showNetSpending()\n\n                            \n                                            \n\n\n\n\nAccounts and balances\n\nplan.showAccounts()\n\n                            \n                                            \n\n\n\n\nAsset distributions\n\nplan.showAssetComposition()\n\n                            \n                                            \n\n\n                            \n                                            \n\n\n                            \n                                            \n\n\nBy default, all these plots are in nominal dollars. To get values in today’s $, a call to\nplan.setDefaultPlots('today')\nwould 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.\n\n\nGross Income vs Tax Brackets\n\nplan.showGrossIncome(value='nominal')\n\n                            \n                                            \n\n\n\n\nOptimal Spending Profile\nThe 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.\n\nplan.showProfile('today')\n\n                            \n                                            \n\n\n\n\nAccount balances\nThe following plot shows the account balances in nominal value for all savings accounts owned by Jack and Jill. It was generated using\n\nplan.showAccounts(value='nominal')\n\n                            \n                                            \n\n\n\n\nCash flows and sources\nThis plot shows the complex cash flow from all sources.\n\nplan.showSources(value='nominal')\n\n                            \n                                            \n\n\n\n\nTax liabilities\nFor taxes, the following call will display Medicare premiums (including Part B IRMAA fees) and federal income tax\n\nplan.showTaxes(value='nominal')\n\n                            \n                                            \n\n\n\n\nAsset distributions\nFor the case at hand, recall that asset allocations were selected above through\nplan.setAllocationRatios('individual', generic=[[[60, 40, 0, 0], [70, 30, 0, 0]], [[50, 50, 0, 0], [70, 30, 0, 0]]])\nGliding from a 60%/40% stocks/bonds portfolio to 70%/30% for Jack, and 50%/50% -&gt; 70%/30% for Jill. Assets distribution in all accounts in today’s $ over time can be displayed from\n\nplan.showAssetComposition(value='today')\n\n                            \n                                            \n\n\n                            \n                                            \n\n\n                            \n                                            \n\n\n\n\nRates\nThese 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:\n\nplan.showRates()\n\n                            \n                                            \n\n\n\nCorrelations\nValues between brackets &lt;&gt; are the average values and volatility over the selected period.\nFor the statisticians, rates distributions and correlations between them can be shown using:\nplan.showRatesCorrelations()\n\n\n\nSummary\nA short text summary of the outcome of the optimization can be displayed through using:\n\nplan.summary()\n\nSUMMARY ================================================================\nPlan name: jack & jill - tutorial\nNet yearly spending basis . . . . . . . . . . . . . . . . . . . . . . . . . .: $59,449\nNet spending for year 2025: $65,041\nNet spending remaining in year 2025: $2,138\n Total net spending: $1,819,141\n[Total net spending]: $5,022,773\n Total Roth conversions: $276,196\n[Total Roth conversions]: $375,653\n Total tax paid on ordinary income: $116,657\n[Total tax paid on ordinary income]: $202,561\n»  Subtotal in tax bracket 10%: $55,913\n» [Subtotal in tax bracket 10%]: $137,987\n»  Subtotal in tax bracket 12/15%: $26,316\n» [Subtotal in tax bracket 12/15%]: $27,923\n»  Subtotal in tax bracket 22/25%: $34,428\n» [Subtotal in tax bracket 22/25%]: $36,651\n»  Subtotal in tax bracket 24/28%: $0\n» [Subtotal in tax bracket 24/28%]: $0\n»  Subtotal in tax bracket 32/33%: $0\n» [Subtotal in tax bracket 32/33%]: $0\n»  Subtotal in tax bracket 35%: $0\n» [Subtotal in tax bracket 35%]: $0\n»  Subtotal in tax bracket 37/40%: $0\n» [Subtotal in tax bracket 37/40%]: $0\n»  Subtotal in early withdrawal penalty: $0\n» [Subtotal in early withdrawal penalty]: $0\n Total tax paid on gains and dividends: $1,400\n[Total tax paid on gains and dividends]: $1,497\n Total net investment income tax paid: $0\n[Total net investment income tax paid]: $0\n Total Medicare premiums paid: $117,660\n[Total Medicare premiums paid]: $343,388\nYear of partial bequest: 2051\n Sum of spousal transfer to Jill: $488,441\n[Sum of spousal transfer to Jill]: $2,112,259\n»  Spousal transfer to Jill - taxable: $0\n» [Spousal transfer to Jill - taxable]: $0\n»  Spousal transfer to Jill - tax-def: $61,709\n» [Spousal transfer to Jill - tax-def]: $266,859\n»  Spousal transfer to Jill - tax-free: $426,732\n» [Spousal transfer to Jill - tax-free]: $1,845,401\n Sum of post-tax non-spousal bequest from Jack: $0\n[Sum of post-tax non-spousal bequest from Jack]: $0\n»  Post-tax non-spousal bequest from Jack - taxable: $0\n» [Post-tax non-spousal bequest from Jack - taxable]: $0\n»  Post-tax non-spousal bequest from Jack - tax-def: $0\n» [Post-tax non-spousal bequest from Jack - tax-def]: $0\n»  Post-tax non-spousal bequest from Jack - tax-free: $0\n» [Post-tax non-spousal bequest from Jack - tax-free]: $0\nYear of final bequest: 2057\n Total value of final bequest: $500,000\n[Total value of final bequest]: $2,488,808\n»  Post-tax final bequest account value - taxable: $0\n» [Post-tax final bequest account value - taxable]: $0\n»  Post-tax final bequest account value - tax-def: $4,859\n» [Post-tax final bequest account value - tax-def]: $24,185\n»  Post-tax final bequest account value - tax-free: $495,141\n» [Post-tax final bequest account value - tax-free]: $2,464,624\nPlan starting date: 2025-12-20\nCumulative inflation factor at end of final year: 4.98\n          Jack's life horizon: 2025 -&gt; 2051\n          Jack's years planned: 27\n          Jill's life horizon: 2025 -&gt; 2057\n          Jill's years planned: 33\nNumber of decision variables: 1029\nNumber of constraints: 953\nCase executed on: 2025-12-20 at 17:56:42\n------------------------------------------------------------------------\n\n\nThe 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:\n\n\nSaving results\nAnd an Excel workbook can be saved with all the detailed amounts over the years by using the following command:\n\nplan.saveWorkbook(overwrite=True)"
  },
  {
    "objectID": "install-uv.html",
    "href": "install-uv.html",
    "title": "Installation - OWL using UV on WSL",
    "section": "",
    "text": "This installation guide uses the UV family of tools to install OWL.\nThis installation guide assumes that you’re working with the code using WSL/Ubuntu (Windows Subsystem for Linux, Ubuntu distro). Separate guides are available for Conda on Powershell and UV on powershell.\nThis installation guide is OPINIONATED.\nTo use this guide, copy/paste the blocks of code below directly into a WSL terminal. Hover your mouse over any code block and a clipboard icon should appear. Clicking the clipboard will copy the block of code for pasting into a WSL terminal."
  },
  {
    "objectID": "install-uv.html#requirements",
    "href": "install-uv.html#requirements",
    "title": "Installation - OWL using UV on WSL",
    "section": "Requirements",
    "text": "Requirements\nThe following code ensures that you’ve got all the necessary underlying tools. Copy/paste into a WSL terminal. The code is intended to install missing tools. These commands should not negatively impact any tools already installed.\n\nInstall / verify WSL tools\nsudo apt update\nsudo apt install make\nsudo apt install git\nsudo apt install gh\nsudo apt install curl\n\n\nInstall UV\nUV is a very fast tool that combines many tools into a single framework. UV manages python versions (like pyenv), virtual environments (like venv), modules (like pip), packaging (like setuptools), and does it very fast. UV is a replacement for other tools like conda, pyenv and poetry.\nI HIGHLY recommend switching to UV when using OWL.\ncurl -LsSf https://astral.sh/uv/install.sh | sh\nUV offers a very extensive list of installation options and optional tools. See the UV install guide\n\n\nTesting the tools\nIf everything is all set, the following block of commands should run without errors.\nmake --version\ngit --version\ngh --version\ncurl --version\nuv --version"
  },
  {
    "objectID": "install-uv.html#installing-owl",
    "href": "install-uv.html#installing-owl",
    "title": "Installation - OWL using UV on WSL",
    "section": "Installing OWL",
    "text": "Installing OWL\nWith the backup tools installed, we can now install OWL and get it running locally.\nI keep my GITHUB projects in a local projects folder under my personal home directory. The following block of code will clone the owlplanner project under your ~/projects project folder.\ncd ~/projects\ngit clone https://github.com/mdlacasse/Owl.git owlplanner\ncd owlplanner"
  },
  {
    "objectID": "install-uv.html#setting-up-to-run",
    "href": "install-uv.html#setting-up-to-run",
    "title": "Installation - OWL using UV on WSL",
    "section": "Setting up to run",
    "text": "Setting up to run\nThe following command will download and install python (if you don’t already have it), create a virtual environment, and download all the necessary python modules to run OWL. One step … boom!\nuv sync"
  },
  {
    "objectID": "install-uv.html#running-owl",
    "href": "install-uv.html#running-owl",
    "title": "Installation - OWL using UV on WSL",
    "section": "Running OWL",
    "text": "Running OWL\nThe following command starts the streamlit server on your local computer and automatically opens a chromium browser. You’re ready to go!\nRemember - you don’t need to type it in … use the copy-to-clipboard icon and paste into your WSL terminal.\nNote: this command assumes that your in the project root directory: ~/projects/owlplanner if you’ve been following along.\nuv run python -m streamlit run ./ui/main.py \\\n  --browser.gatherUsageStats=false \\\n  --browser.serverAddress=localhost \\\n  --browser.serverPort=8501\nNOTE: There are TWO things going on at the same time:\n\nA streamlit webserver running from the terminal,\nA web browser started up, too, and is probably blocking you from seeing your terminal.\n\nClosing the browser DOES NOT close the streamlit web server. If you close the browser, streamlit is still running.\nYou need to enter CTRL-C in the terminal to kill/close streamlit. THIS IS NORMAL!"
  },
  {
    "objectID": "install-uv.html#updating-owl",
    "href": "install-uv.html#updating-owl",
    "title": "Installation - OWL using UV on WSL",
    "section": "Updating OWL",
    "text": "Updating OWL\nOWL is under active development. You’ll want to work with the latest changes. The command below will fetch the latest changes to your local machine and update the libraries. You can then use the command above to run these changes.\ngit fetch\nuv sync"
  },
  {
    "objectID": "plugable-example.html",
    "href": "plugable-example.html",
    "title": "Plugable example",
    "section": "",
    "text": "for this example, we’ll replace the legacy hard-coded user method that took fixed rates from the config:\nmethod = \"user\"\nvalues = [7.0, 4.0, 3.0, 2.5]\nwith a plugin-based implementation using BaseRateModel.\nThis demonstrates:\n\nhow a simple deterministic model works\nhow reproducibility is irrelevant\nhow it cleanly fits the new interface\nhow it removes logic from Rates.py\n\n\n\n📁 File Structure\nowlplanner/\n    rate_models/\n        base.py\n        user_fixed.py\n\n\n\n🧱 base.py (unchanged)\n# owlplanner/rate_models/base.py\n\nfrom abc import ABC, abstractmethod\nimport numpy as np\n\nclass BaseRateModel(ABC):\n\n    def __init__(self, N, seed=None, reproducible=False, **kwargs):\n        self.N = N\n        self.seed = seed\n        self.reproducible = reproducible\n\n    @abstractmethod\n    def generate(self) -&gt; np.ndarray:\n        \"\"\"\n        Must return array shape (N, 4)\n        Decimal values.\n        \"\"\"\n        pass\n\n    @property\n    def needs_regen(self):\n        return False\n\n\n\n📁 user_fixed.py\n# owlplanner/rate_models/user_fixed.py\n\nimport numpy as np\nfrom .base import BaseRateModel\n\n\nclass RateModel(BaseRateModel):\n    \"\"\"\n    Fixed user-specified rates.\n    Replaces method=\"user\".\n    \"\"\"\n\n    def __init__(self, N, values=None, **kwargs):\n        super().__init__(N, **kwargs)\n\n        if values is None:\n            raise ValueError(\"UserFixedRateModel requires 'values' parameter.\")\n\n        if len(values) != 4:\n            raise ValueError(\"values must contain 4 entries.\")\n\n        # Convert percent → decimal\n        self._rates = np.array(values, dtype=float) / 100.0\n\n    def generate(self) -&gt; np.ndarray:\n        \"\"\"\n        Returns deterministic Nx4 rate matrix.\n        \"\"\"\n        return np.tile(self._rates, (self.N, 1))\n\n    @property\n    def needs_regen(self):\n        return False\n\n\n\n🧠 What This Replaces\nOld Rates.setMethod(\"user\"):\nelif method == \"user\":\n    self.means = np.array(values, dtype=float)\n    self.means /= 100.0\n    self._setFixedRates(self.means)\nNow the logic is entirely isolated inside a clean model.\n\n\n\n📄 Example TOML\n[rates_selection]\nmethod = \"user\"\nmethod_file = \"owlplanner/rate_models/user_fixed.py\"\n\nvalues = [7.0, 4.0, 3.0, 2.5]\nreverse_sequence = false\nroll_sequence = 0\n\n\n\n🔁 How Plan.setRates() Uses It\nInside your new plugin-aware setRates():\nif method_file is not None:\n    from owlplanner.rate_models.loader import load_plugin\n\n    RateModel = load_plugin(method_file)\n\n    model = RateModel(\n        N=self.N_n,\n        seed=seed,\n        reproducible=self.reproducibleRates,\n        values=values\n    )\nThen:\nseries = model.generate()\nself.tau_kn = series.transpose()\n\n\n\n🧪 What Happens at Runtime\nIf:\nvalues = [7.0, 4.0, 3.0, 2.5]\nN = 5\ngenerate() returns:\n[[0.07  0.04  0.03  0.025]\n [0.07  0.04  0.03  0.025]\n [0.07  0.04  0.03  0.025]\n [0.07  0.04  0.03  0.025]\n [0.07  0.04  0.03  0.025]]\nExactly what legacy user produced.\n\n\n\n🧩 Why This Is Architecturally Powerful\nThis demonstrates:\n\n\n\nLegacy Design\nPlugin Design\n\n\n\n\nBig if/elif tree\nOne class per model\n\n\nHard-coded methods\nDrop-in pluggable\n\n\nShared mutable state\nIsolated model state\n\n\nRegen logic scattered\nModel decides via needs_regen\n\n\nHard to extend\nInfinite extensibility\n\n\n\n\n\n\n🚀 Next Natural Migration\nOnce this works:\n\nMove \"default\"\nMove \"optimistic\"\nMove \"conservative\"\nMove \"historical average\"\n\nEventually:\nDelete most of Rates.setMethod() entirely.\n\n\n\n🏁 Final Result\nYou now have:\n\nA research-ready architecture\nClean separation of concerns\nRuntime pluggability\nStreamlit-safe regeneration\nTestable individual rate engines"
  }
]