{"id":13558392,"url":"https://github.com/DavidCain/stay-the-course","last_synced_at":"2025-04-03T13:31:11.904Z","repository":{"id":147051720,"uuid":"166345211","full_name":"DavidCain/stay-the-course","owner":"DavidCain","description":"Lazy portfolio rebalancer for GnuCash users","archived":false,"fork":false,"pushed_at":"2024-06-30T20:38:41.000Z","size":950,"stargazers_count":9,"open_issues_count":0,"forks_count":4,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-24T12:46:58.076Z","etag":null,"topics":["gnucash","passive-investing","personal-finance","retirement","rust"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DavidCain.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-01-18T04:44:53.000Z","updated_at":"2025-03-04T06:08:07.000Z","dependencies_parsed_at":"2024-11-04T09:30:58.293Z","dependency_job_id":"7edc0621-d761-47d5-9644-c9ab3abdb74e","html_url":"https://github.com/DavidCain/stay-the-course","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidCain%2Fstay-the-course","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidCain%2Fstay-the-course/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidCain%2Fstay-the-course/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DavidCain%2Fstay-the-course/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DavidCain","download_url":"https://codeload.github.com/DavidCain/stay-the-course/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247009553,"owners_count":20868568,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["gnucash","passive-investing","personal-finance","retirement","rust"],"created_at":"2024-08-01T12:04:55.635Z","updated_at":"2025-04-03T13:31:11.534Z","avatar_url":"https://github.com/DavidCain.png","language":"Rust","funding_links":[],"categories":["Rust","rust"],"sub_categories":[],"readme":"[![Build Status](https://github.com/DavidCain/stay-the-course/actions/workflows/ci.yml/badge.svg)](https://github.com/DavidCain/stay-the-course/actions)\n\n**Warning:** The author is neither a tax professional nor a retirement advisor.\nAny statements contained within this document do not constitute legal, tax, or\ninvestment advice.\n\n\n# About\nLong-term investors try to keep a mix of asset types in proportions that match\ntheir risk preferences. Over time, prices fluctuate and the actual worth of\nassets will inevitably change. This tool calculates the optimal way to invest a\nfixed sum of money so that each asset type's value is as close as possible to\nits desired ratio of the whole portfolio.\n\nBroadly, this tool provides a means of [\"staying the course\"][stay_the_course]\nthrough regular contributions into the right mutual funds.\n\n## Demo\nJust clone and `cargo run` to see a demonstration with a real GnuCash database:\n\n```\n$ git clone git@github.com:DavidCain/stay-the-course.git\n$ cd stay-the-course\n$ cargo run\nUsing default example configuration. Write to config.toml for real use.\n-----------------------------------------------------------------------\nInternational stocks: $8,861 (🎯 32.15%)\n  - VTIAX (VTIAX): $8861.10 (273.7445 x $32.37)\nUS bonds: $6,851 (🎯 19.62%)\n  - VBTLX (VBTLX): $6850.79 (1151.3937 x $5.95)\nUS total market: $5,894 (🎯 26.52%)\n  - FZROX (FZROX): $5202.48 (274.3919 x $18.96)\n  - VTSAX (VTSAX): $691.13 (5.2905 x $130.63)\nUS small + mid cap: $4,000 (🎯 13.66%)\n  - VSMAX (VSMAX): $4000.00 (38.2409 x $104.60)\nREIT: $3,080 (🎯 8.03%)\n  - VGSLX (VGSLX): $3080.26 (26.1438 x $117.82)\nPortfolio total: $28,686\n\nWorth at retirement (Assuming 7% growth):\n - 39:     $28,686  SWR:    $1,147\n - 50:     $58,391  SWR:    $2,336\n - 55:     $81,893  SWR:    $3,276\n - 60:    $114,875  SWR:    $4,595\n - 65:    $161,111  SWR:    $6,444\n\nAfter-tax income: $49,700\nCharitable giving: $5,000 (10% of after-tax income)\nMinimum to bring all assets to target: $9,635\nHow much to contribute or withdraw?\n3000\nContribute the following amounts:\n - US total market: $2083.11\n   20.54% -\u003e 25.17% (🎯 26.52%) Δ [22.5% -\u003e 5.0%]\n - International stocks: $807.65\n   30.89% -\u003e 30.51% (🎯 32.15%) Δ [3.9% -\u003e 5.0%]\n - US small + mid cap: $109.22\n   13.94% -\u003e 12.96% (🎯 13.66%) Δ [-2.0% -\u003e 5.0%]\n - US bonds: $0.00\n   23.88% -\u003e 21.62% (🎯 19.62%) Δ [-21.7% -\u003e -10.1%]\n - REIT: $0.00\n   10.73% -\u003e 9.72% (🎯 8.03%) Δ [-33.5% -\u003e -20.9%]\n```\n\n### Sample GnuCash accounting records\n\nIn `example/` are two (identical) sample files in XML and sqlite3 format. Each\nmay be opened with [GnuCash 3][gnucash]. The transactions, security prices, and\naccount balances contained within are the basis of rebalancing logic:\n\n![GnuCash user interface for included sample files][img-gnucash-interface]\n\n## How it works\nThe tool accepts a few key inputs:\n\n1. The path to a [GnuCash][gnucash] data file (in either XML or SQLite format)\n2. The desired target allocation per asset type\n3. The amount of money the investor intends to invest\n\nWith that information supplied, the tool will:\n\n1. Use the contained price database to calculate the current worth of each investment fund\n2. Classify each fund into an asset type\n3. Sum up asset values by asset type, calculate the ratio of each asset class\n   to the total portfolio value\n4. Sequentially identify asset classes which have deviated most from their\n   target, invest into those asset classes until they are as close to their\n   target as the next furthest asset class (repeat until the desired\n   contribution amount has been fully allocated to all funds)\n5. Output the optimal contributions\n\n## Fetching quotes from 3rd party APIs\nI'm using the AlphaVantage free API. To use it, make sure that:\n\n1. `ALPHAVANTAGE_API_KEY` is an available env var. ([Get an API key][av-api-key] first)\n2. `update_prices = true` is set in `[gnucash]` within `config.toml`\n\nWhen configured, this ensures that the latest stock prices per fund\nare incorporated into the allocation recommendations.\n\n\n# Background - target asset allocation\n[Asset allocation][asset_allocation] is the process of reconciling one's risk\npreferences and investment goals with a long-term strategy.\n\nStocks are generally understood to grant higher returns in the long run, at the\nrisk of greater volatility in the short run. Conversely, many consider bonds a\nsource of stable income, with lessened potential for long-term growth. Some\nasset classes (such as [TIPS][TIPS]) may provide some insurance against changing\neconomic conditions, while other funds provide broad exposure to varied\neconomic sectors (adding diversity to a portfolio).\n\nAny long-term investment strategy should be tailored to the risk preferences\nof the investor. A young professional will likely desire a riskier portfolio\nthan somebody nearing retirement: The young professional is able to tolerate\nshort-term volatility in the hopes of greater returns. The retiree needs a\nsteady stream of income, and is willing to forego greater returns in exchange\nfor stability.\n\n## Why use this strategy?\n[Rebalancing][rebalancing] can be expensive. When selling taxable funds, an\ninvestor must realize capital gains (or losses) in order to move money from one\ninvestment to another. If shares have not been held for a sufficiently long\nperiod of time, short-term capital gains may even be realized (at a potentially\nhigher tax rate).\n\nFor residents of California, capital gains are taxed as normal income. If the\ninvestor plans to retire in another state, they may desire to postpone the\nrealization of capital gains.\n\nSelling shares adds some complexity to annual tax returns, since capital gains\nmust be reported to the IRS. A buy-and-hold investor may instead be able to go\nyears without selling any assets, avoiding the need to report realized gains.\n\n\n## When rebalancing should be performed instead\nRegular re-investment may be sufficient to keep [asset allocation][asset_allocation]\nin line with targets. However, a number of scenarios might cause the ratio of\nvarious asset classes to deviate too far from targets:\n\n- Significant market changes (e.g. domestic or international expansion/contraction)\n- Change in risk preferences (changing target weights for stocks, bonds, TIPS, etc.)\n- Regular contributions being too small a fraction of the overall portfolio.\n\nOnly an individual investor can decide when their portfolio's composition has\ndeviated too far from its targets. At that time, it may become necessary to sell\noverweighted assets and direct the proceeds to an underweighted asset class.\n\nTaking dividends in cash (rather than automatically re-investing into the\noriginating fund) can help alleviate the need for rebalancing. The dividends\nfrom overweighted funds may be transferred into underweighted funds.\nAdditionally, receiving dividends this way can help avoid a \"wash sale\" if the\ninvestor plains to perform [tax loss harvesting][tax_loss_harvesting].\n\n\n## Assumptions\n- While this algorithm can handle any number of funds and corresponding asset classes,\n  we do assume that the user employs a [\"lazy portfolio\"][lazy_portfolio]\n  strategy (a portfolio in which desired allocations are spread across a small\n  number of asset classes and underlying mutual funds). The GnuCash integration\n  only considers assets whose underlying commodities are of type `FUND`.\n- Current values are based on the last known price. The user must keep their\n  price database current within GnuCash in order to get current estimates.\n\n## Considerations this tool does not make\n1. No differentiation is made between taxable and tax-advantaged accounts. It is up\n   to the user to manage [efficient fund placement][tax_efficient_placement] and direct\n   funds to the appropriate accounts.\n2. No consideration is made of minimum investments: Investing in a new mutual fund often\n   requires a minimum investment, but this tool does not account for that. The included\n   example files show an investor building a diverse portfolio first before\n   working towards bringing their various funds in line with target ratios.\n\n## Major tasks outstanding\nThis project is very much a work in progress. Some key outstanding tasks:\n\n- [ ] Optimize XML parsing (currently takes a couple seconds on a 20MB file)\n- [ ] Command line interface\n- [ ] Return Result instead of just panicking on error conditions\n\n## External resources\n- [Optimal rebalancing (archive)][optimal_rebalancing]: Though it has since\n  been taken down (it lived at `http://optimalrebalancing.tk`, now spam), this\n  was an excellent web tool by Albert H. Mao. It provided a textual interface\n  to lazily rebalance.\n- [`rebalance-app`][rebalance-app] by Alberto Leal: another Rust implementation\n  based off [Optimal rebalancing][optimal_rebalancing], but without GnuCash\n  integration and relying on a different underlying type libraries. Probably better\n  suited for people who don't make use of GnuCash. Also includes some features not\n  present in this project!\n\n\n\n[gnucash]: https://www.gnucash.org/\n[optimal_rebalancing]: https://archive.ph/IUimB\n[rebalance-app]: https://github.com/dashed/rebalance-app\n\n[TIPS]: https://en.wikipedia.org/wiki/United_States_Treasury_security#TIPS\n\n[asset_allocation]: https://www.bogleheads.org/wiki/Asset_allocation\n[rebalancing]: https://www.bogleheads.org/wiki/Rebalancing\n[stay_the_course]: https://www.bogleheads.org/blog/bogleheads-principles-stay-the-course/\n[lazy_portfolio]: https://www.bogleheads.org/wiki/Lazy_portfolios#Three_fund_lazy_portfolios\n[tax_loss_harvesting]: https://www.bogleheads.org/wiki/Tax_loss_harvesting\n[tax_efficient_placement]: https://www.bogleheads.org/wiki/Tax-efficient_fund_placement\n[av-api-key]: https://www.alphavantage.co/support/#api-key\n\n\n[img-gnucash-interface]: https://github.com/DavidCain/stay-the-course/blob/master/images/gnucash_interface.png\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDavidCain%2Fstay-the-course","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FDavidCain%2Fstay-the-course","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDavidCain%2Fstay-the-course/lists"}