OptiBonds is a tool designed to build, optimize, and visualize bond ladders. It automates the complex process of selecting the best bonds to manage risk while maximizing your investment goals.
- What is a Bond Ladder?
- Strategies: Growth vs. Income
- Diversification
- Ladder Shape
- Advanced Filters
- Setup Guide
- Run
Bond laddering is a fixed-income investing strategy where you spread your capital across bonds that mature at different times, much like rungs on a ladder.
By staggering maturity dates, you ensure a regular stream of liquidity and reduce the impact of interest rate fluctuations.
Step 1 |==== Bond A ====|
Step 2 |====== Bond B ======|
Step 3 |======== Bond C =========|
---> time to maturity
Example:
- Step 1: Bond A matures in 1 year.
- Step 2: Bond B matures in 2 years.
- Step 3: Bond C matures in 3 years.
You can also build a more diversified ladder with multiple bonds for each rung.
Step 1 |==== Bond A ====|==== Bond B ====|
Step 2 |====== Bond C ======|====== Bond D ======|
Step 3 |======== Bond E ========|======== Bond F ========|
---> time to maturity
Example:
- Step 1: Bonds A and B mature in 1 year.
- Step 2: Bonds C and D mature in 2 years.
- Step 3: Bonds E and F mature in 3 years.
- Steady Cash Flow: You receive a consistent return of principal as each "rung" matures.
- Strategic Risk Management:
- Rising Rates: Short-term bonds mature quickly, allowing you to reinvest at current higher rates.
- Falling Rates: Your longer-term bonds continue to pay out at their locked-in higher yields.
- Adaptive Flexibility: You avoid the trap of trying to time the market by diversifying across time.
OptiBonds identifies the mathematically optimal bonds for your specific financial objectives. You can select your preferred strategy in the conditions.yml configuration file.
OptiBonds calculates the best capital allocation to match the values defined in your capital_invested list as closely as possible.
-
$C$ : Capital invested -
$Y$ : Net yield -
$M$ : Maturity (years)
LadderStrategy.MAX_EARNINGS
This strategy focuses on the power of Compound Interest. It assumes that every coupon payment is immediately reinvested at the same rate.
This strategy maximizes total compound earnings:
$C \times (1 + Y)^M$
LadderStrategy.MAX_YTM_CAPITAL
This strategy focuses on Yield to Maturity (YTM) and the amount of capital invested.
This strategy maximizes annualized earnings:
$C \times Y$
LadderStrategy.MAX_RETURN
This strategy focuses on Total Return. It calculates the sum of all coupon payments plus the capital gain (the difference between purchase price and face value).
This strategy maximizes the total absolute return:
$\text{Coupons} + \text{Capital Gain}$
LadderStrategy.MAX_YTM
This strategy focuses on Yield to Maturity (YTM). It identifies bonds offering the highest annualized percentage yield at their current market price.
This strategy maximizes the yield to maturity:
$Y$
LadderStrategy.ALL
This option runs every strategy above over the same eligible bonds. Each strategy's Total Returns table is printed, so you can pick the strategy that best fits your goals. To print the full report, update the strategy in conditions.yml and run the script again.
Set
strategy: "all"inconditions.yml. The--saveflag is ignored in this mode.
Diversification is the cornerstone of a safe bond ladder. OptiBonds helps spread risk across different issuers, currencies, and maturity profiles.
- No Diversification: Set
max_duplicated_issuerstoNoneto simply pick the single best bond for each step. - Enabled Diversification: Set
max_duplicated_issuersto a value (e.g.,1) to prevent over-exposure to a single entity across the ladder.
When using multiple bonds per step, diversification is automatically applied to ensure you don't buy the same issuer multiple times for the same maturity date.
Customize the structure and safety parameters of your ladder in conditions.yml.
ladder_size: The total number of "rungs" or steps.- Example:
15creates a 15-step strategy.
- Example:
step_size: The time gap between rungs.- Example:
1means bonds mature annually (1yr, 2yr, 3yr). - Example:
2means bonds mature every two years (2yr, 4yr, 6yr).
- Example:
capital_invested: The target investment for each rung. The capital allocated will be as close as possible to the values in thecapital_investedlist.- Single value (e.g.,
10000) for equal investment across all rungs. - List (e.g.,
[5000, 10000, 5000]) for custom allocation.
- Single value (e.g.,
years_offset/months_offset: Delays the start of the ladder.- Useful if you want to start your ladder several months or years in the future.
step_width: The number of different bonds to buy per rung.- Set to
1for the single best bond per year. - Set to
2+to split the investment across multiple bonds for better diversification.
- Set to
max_duplicated_issuers: Limits exposure to any single issuer.- Example:
1ensures that if you buy an Italian Government bond for Year 1, the tool skips that issuer for all subsequent years.
- Example:
min_rating: Minimum credit quality (e.g.,'BBB-').
date_tolerance_days_start/_end: Defines how much a bond's maturity can deviate from the target step date.exclude_isins/exclude_issuer_codes: Blacklist specific bonds or issuers.include_issuer_codes: Whitelist specific issuers.currencies: Limit searches to specific currencies (e.g.,['EUR', 'USD']).max_last_price: Ignore bonds trading above a certain price (e.g.,100).min_coupon_rate: Focus on bonds with a minimum interest payment.min_volume_rating: Ensures liquidity by filtering by average daily volume (0-4 scale). The value is calculated as follows: the average daily volume over the last 20 days is calculated, the value is converted to Euro using the last available exchange rate, and a value from 0 to 4 is then assigned considering the following ranges:- 0: no trades in the considered period
- 1: average volume between 0 and 100,000
- 2: average volume between 100,000 and 1,000,000
- 3: average volume between 1,000,000 and 2,500,000
- 4: average volume greater than 2,500,000
Follow these steps to get OptiBonds running on your computer.
You need Python installed. You also need Poetry, a tool that manages the software libraries.
- Install Poetry: Open your terminal and run:
pip install poetry
Navigate to the project folder in your terminal and run:
poetry installYou need fresh data to make decisions. We provide a script to fetch the latest bond prices.
poetry run python download_data.pyThis downloads a file to data/data.csv. You should run this whenever you want updated prices.
Open the file conditions.yml and modify the settings.
ladder_size: 10
step_size: 1
capital_invested: 5000
min_rating: "A"
strategy: "max_earnings" # max_earnings, max_ytm, max_ytm_capital, max_return, all
# ... other settingsChange these values to match your investment plan.
Run the calculator command:
poetry run python calculator.pyBy default, it uses conditions.yml. You can specify a custom configuration file using --config or -c:
poetry run python calculator.py --config my_conditions.ymlYou can also save the generated portfolio to a YAML file using --save or -s. This file can then be used with earnings.py:
poetry run python calculator.py --save my_portfolio.ymlIf you want to see the cash flows of your portfolio, you can run the calculator with the --cashflows flag:
poetry run python calculator.py --cashflowsIf you already have a portfolio (or want to review one generated by calculator.py) and want to calculate its expected earnings, use earnings.py.
- Configure Your Portfolio: Update
portfolio.ymlwith your ISINs and invested amounts. - Run the Earnings Calculator:
poetry run python earnings.pySimilar to the calculator, you can specify a custom portfolio file:
poetry run python earnings.py --config portfolio_1.ymlIt will print the generated Portfolio.
The portfolio is a list of bonds, one for each step of your ladder.
For each step, it provides:
- ISIN: The unique identifier for the bond.
- Name/Issuer: The entity you are lending to.
- Lots: The number of units to purchase.
- Capital: The exact cost of the investment.
The results conclude with a Summary showing your Total Yield and Average Annual Return.
See tests/README.md for test instructions.
The undersigned shall not, under any circumstances, be held liable for any indirect, consequential, incidental, or special damages whatsoever, including, without limitation, any loss of profits or revenues, business interruption, or loss of data, arising from or related to the use of, the inability to use, or reliance upon the materials contained on this website. Furthermore, the information and materials provided are made available solely for general informational purposes and do not constitute, and shall not be construed as, financial, investment, legal, or professional advice of any kind. No financial recommendations, solicitations, or endorsements are made, and users are solely responsible for any decisions taken.
