Models / Macro / rates_ladder
Rates ladder
What would this bond ladder earn over the horizon on these yields, what does a parallel shift do to it, and what shift takes the return to zero?
Prices a ladder of fixed coupon bonds from the yields sent: dirty and clean price, Macaulay and modified duration, convexity, DV01, a shock table of full repricing against the duration and convexity estimate, carry and roll-down over a horizon, and the parallel shift that takes the horizon return to zero. Yields come from the caller. It is arithmetic on those numbers, not a view on rates.
On these numbers, the ladder is worth 4983423.61 with a DV01 of 1909.3. Over 0.5 years its hypothetical return is 2.42% with roll-down and no shift, and a parallel rise of 72.04 bps at the horizon takes that to zero.
Hypothetical, and on made-up numbers until you replace them. Nobody traded this, and nothing here is on offer.
Readings
The numbers, with their units.
Dirty value
4,983,424
Clean 4974673.61, accrued 8750.
Modified duration
3.8313years
Value-weighted. Macaulay 3.93.
DV01
1,909.3
Value change for 1 bp, on dirty value.
Convexity
23.97
Value-weighted.
Coupon carry over the horizon
2.107%of dirty value
Roll-down over the horizon
0.2404%of dirty value
From the curve sent, with each bond's gap to the curve held.
Hypothetical horizon return
2.4215%
Over 0.5 years, not annualized. Includes pull to par of 0.07%.
Shift that takes it to zero
72.04bps
What flips it
The value of one input where the outcome changes, with the others held.
parallel_shift_bps
72.04
A parallel yield move of this size at the horizon takes the ladder's 0.5-year return to zero, found by repricing every flow. Past it, toward higher yields, the return is negative.
What this leaves out
What the formula leaves out, by name.
- Reinvestment of coupons and of any principal paid back inside the horizon.
- Moves that aren't parallel, other than the one roll-down path.
- Default, calls, amortization, inflation links and floating coupons.
- Day counts, settlement lags and holiday calendars.
- Repo or other financing of the ladder, bid-ask, fees and taxes.
What it took as given (9)
What it took as given to get here.
- Time is in plain years, and flows are counted back from maturity. A fraction of a period at the front is a stub.
- The stub is discounted with a fractional exponent, and accrued interest is straight-line across the coupon period.
- DV01, duration and convexity are on the dirty price. Convexity includes the 1 / f term that goes with periodic compounding.
- Each bond's yield shifts by the same amount in a shock. Book totals add the bonds, so book duration and convexity are value-weighted.
- Coupon carry is the coupon earned over the horizon on an accrual basis: coupons paid plus the change in accrued.
- Pull to par is the rest of the horizon return at unchanged yield.
- Roll-down moves each bond's yield by the curve's change from maturity to shortened maturity, by straight-line interpolation, flat past the ends. The bond's own gap to the curve stays the same.
- Cash paid inside the horizon sits idle, with no reinvestment. Horizon returns aren't annualized.
- The shift that flips the horizon return is applied at the horizon date, on top of roll-down, and found by repricing every flow.
The data
- Full reprice
- Duration and convexity
The data
Bonds, prices per 100 of face
| name | dirty_per_100 | accrued_per_100 | clean_per_100 | macaulay_years | modified_years | convexity | dv01 |
|---|---|---|---|---|---|---|---|
| Bond 1.25y | 100.39 | 0.875 | 99.51 | 1.2243 | 1.2009 | 2.051 | 120.55 |
| Bond 2.5y | 99.76 | 0 | 99.76 | 2.4037 | 2.3554 | 6.8329 | 234.99 |
| Bond 3.75y | 98.97 | 0 | 98.97 | 3.4993 | 3.4621 | 13.38 | 342.63 |
| Bond 5y | 100 | 0 | 100 | 4.5875 | 4.39 | 24.47 | 439 |
| Bond 10y | 99.22 | 0 | 99.22 | 7.9801 | 7.7817 | 73.44 | 772.13 |
| Book | 99.67 | 0.175 | 99.49 | 3.9322 | 3.8313 | 23.97 | 1,909.3 |
Parallel shocks to the book: full reprice against the estimate
| shock_bps | full_change | estimate_change | gap | duration_only_change | full_change_pct |
|---|---|---|---|---|---|
| -100 | 197,073 | 196,903 | 169.87 | 190,930 | 3.9546 |
| -50 | 96,979.2 | 96,958.23 | 20.97 | 95,464.98 | 1.946 |
| 50 | -93,992.19 | -93,971.73 | -20.46 | -95,464.98 | -1.8861 |
| 100 | -185,119 | -184,957 | -161.72 | -190,930 | -3.7147 |
| 200 | -359,231 | -357,968 | -1,263.24 | -381,860 | -7.2085 |
Over the horizon of 0.5 years, money amounts
| name | coupon_carry | pull_to_par | roll_down | total | return_pct | yield_move_bps |
|---|---|---|---|---|---|---|
| Bond 1.25y | 17,500 | 1,905.73 | 1,098.12 | 20,503.85 | 2.0425 | -15 |
| Bond 2.5y | 20,000 | 451.76 | 1,901.56 | 22,353.31 | 2.2406 | -10 |
| Bond 3.75y | 20,000 | 1,284.59 | 2,254.89 | 23,539.48 | 2.3785 | -7.5 |
| Bond 5y | 22,500 | -247.58 | 3,004.68 | 25,257.1 | 2.5257 | -7.5 |
| Bond 10y | 25,000 | 302.17 | 3,719.44 | 29,021.62 | 2.9249 | -5 |
| Book | 105,000 | 3,696.66 | 11,978.69 | 120,675 | 2.4215 | n/a |
Paste it into your agent
No connection needed. This prompt carries the method, the conventions and what this site's engine gives on the made-up numbers, so your agent can check its own work before it touches real ones. Every prompt, with its hash, is listed at /prompts.json. A copy that stays fixed for this version, for a stack to point at: /v/1.0/models/rates_ladder.md.
# Rates ladder: a prompt from hedgefund.mdPaste this into your AI agent. It needs no connection to hedgefund.md. An agent that can run code should write and run it, and shouldn't work a long series by hand.**Question.** What would this bond ladder earn over the horizon on these yields, what does a parallel shift do to it, and what shift takes the return to zero?## MethodFlows at t_k = maturity - k / f. Dirty = sum CF_k / (1 + y / f)^(f t_k). Accrued = coupon per period * (1 - f * first t). Macaulay = sum t PV / dirty. Modified = Macaulay / (1 + y / f). Convexity = sum t (t + 1 / f) PV / dirty / (1 + y / f)^2. DV01 = modified * dirty * 0.0001. Estimate = dirty (-modified s + 0.5 convexity s^2). Horizon value = sum over flows after H of CF / (1 + y / f)^(f (t - H)).## Conventions- Time is in plain years, and flows are counted back from maturity. A fraction of a period at the front is a stub.- The stub is discounted with a fractional exponent, and accrued interest is straight-line across the coupon period.- DV01, duration and convexity are on the dirty price. Convexity includes the 1 / f term that goes with periodic compounding.- Each bond's yield shifts by the same amount in a shock. Book totals add the bonds, so book duration and convexity are value-weighted.- Coupon carry is the coupon earned over the horizon on an accrual basis: coupons paid plus the change in accrued.- Pull to par is the rest of the horizon return at unchanged yield.- Roll-down moves each bond's yield by the curve's change from maturity to shortened maturity, by straight-line interpolation, flat past the ends. The bond's own gap to the curve stays the same.- Cash paid inside the horizon sits idle, with no reinvestment. Horizon returns aren't annualized.- The shift that flips the horizon return is applied at the horizon date, on top of roll-down, and found by repricing every flow.## Step 1: check your method on these made-up numbersThe numbers and names below are made up. They aren't market levels, typical values or anyone's real figures.```json{ "bonds": [ { "name": "Bond 1.25y", "face": 1000000, "coupon_pct": 3.5, "maturity_years": 1.25, "yield_pct": 3.9, "freq": 2 }, { "name": "Bond 2.5y", "face": 1000000, "coupon_pct": 4, "maturity_years": 2.5, "yield_pct": 4.1, "freq": 2 }, { "name": "Bond 3.75y", "face": 1000000, "coupon_pct": 4, "maturity_years": 3.75, "yield_pct": 4.3, "freq": 4 }, { "name": "Bond 5y", "face": 1000000, "coupon_pct": 4.5, "maturity_years": 5, "yield_pct": 4.5, "freq": 1 }, { "name": "Bond 10y", "face": 1000000, "coupon_pct": 5, "maturity_years": 10, "yield_pct": 5.1, "freq": 2 } ], "shocks_bps": [-100, -50, 50, 100, 200], "horizon_years": 0.5, "curve": [ { "years": 0.5, "yield_pct": 3.6 }, { "years": 1, "yield_pct": 3.8 }, { "years": 2, "yield_pct": 4 }, { "years": 3, "yield_pct": 4.2 }, { "years": 5, "yield_pct": 4.5 }, { "years": 10, "yield_pct": 5 } ]}```Work them out with the method above. Yours should match these within 0.5%.- Dirty value: 4983423.6- Modified duration: 3.8313 years- DV01: 1909.2996- Convexity: 23.9715- Coupon carry over the horizon: 2.107 % of dirty value- Roll-down over the horizon: 0.2404 % of dirty value- Hypothetical horizon return: 2.4215 %- Shift that takes it to zero: 72.0433 bpsWhere the outcome flips, with the other inputs held:- parallel_shift_bps at 72.0433: A parallel yield move of this size at the horizon takes the ladder's 0.5-year return to zero, found by repricing every flow. Past it, toward higher yields, the return is negative.If yours differ, stop and tell the person which ones and by how much. Don't go on to real numbers until they match, or until the person has seen the difference. These values come from one engine and can be wrong too.## Step 2: run it on the person's numbersReplace every input with the person's own. Ask for any number you don't have, and don't invent one.## Step 3: reportGive one sentence beginning "On these numbers,", then the values, then where the outcome flips. Always add what this leaves out:- Reinvestment of coupons and of any principal paid back inside the horizon.- Moves that aren't parallel, other than the one roll-down path.- Default, calls, amortization, inflation links and floating coupons.- Day counts, settlement lags and holiday calendars.- Repo or other financing of the ladder, bid-ask, fees and taxes.## LimitsResults are hypothetical: arithmetic on the numbers given, prepared with hindsight. Nobody achieved them, and costs and liquidity are only as assumed. They aren't a forecast, a recommendation, an offer or a record of real trading. Don't say what to buy, sell or hold, and don't use them to market an investment. Whether a person may hold or trade an instrument is a legal question for counsel.Provided as is, at your own risk, by hedgefund.md, which is not a fund and isn't anyone's adviser. Version 1.0. Source: https://hedgefund.md/models/rates_ladder/prompt.md. Terms: https://hedgefund.md/termsCall it
The same code answers on this page, over HTTP, over MCP and on your own machine, and the same inputs give the same bytes. No key, and nothing is stored. Send positions by POST or run the library, because a URL can land in a server log. model.md holds all of this for your agent.
POST the inputs to /api/v1/rates_ladder.
curl -s https://hedgefund.md/api/v1/rates_ladder \ -H 'content-type: application/json' \ -d '{"bonds":[{"name":"Bond 1.25y","face":1000000,"coupon_pct":3.5,"maturity_years":1.25,"yield_pct":3.9,"freq":2},{"name":"Bond 2.5y","face":1000000,"coupon_pct":4,"maturity_years":2.5,"yield_pct":4.1,"freq":2},{"name":"Bond 3.75y","face":1000000,"coupon_pct":4,"maturity_years":3.75,"yield_pct":4.3,"freq":4},{"name":"Bond 5y","face":1000000,"coupon_pct":4.5,"maturity_years":5,"yield_pct":4.5,"freq":1},{"name":"Bond 10y","face":1000000,"coupon_pct":5,"maturity_years":10,"yield_pct":5.1,"freq":2}],"shocks_bps":[-100,-50,50,100,200],"horizon_years":0.5,"curve":[{"years":0.5,"yield_pct":3.6},{"years":1,"yield_pct":3.8},{"years":2,"yield_pct":4},{"years":3,"yield_pct":4.2},{"years":5,"yield_pct":4.5},{"years":10,"yield_pct":5}]}' \ | jq '{summary, flags, flips, not_modeled, inputs_hash}'The tool is rates_ladder on https://hedgefund.md/mcp.
curl -s https://hedgefund.md/mcp \ -H 'content-type: application/json' -H 'accept: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"rates_ladder","arguments":{"bonds":[{"name":"Bond 1.25y","face":1000000,"coupon_pct":3.5,"maturity_years":1.25,"yield_pct":3.9,"freq":2},{"name":"Bond 2.5y","face":1000000,"coupon_pct":4,"maturity_years":2.5,"yield_pct":4.1,"freq":2},{"name":"Bond 3.75y","face":1000000,"coupon_pct":4,"maturity_years":3.75,"yield_pct":4.3,"freq":4},{"name":"Bond 5y","face":1000000,"coupon_pct":4.5,"maturity_years":5,"yield_pct":4.5,"freq":1},{"name":"Bond 10y","face":1000000,"coupon_pct":5,"maturity_years":10,"yield_pct":5.1,"freq":2}],"shocks_bps":[-100,-50,50,100,200],"horizon_years":0.5,"curve":[{"years":0.5,"yield_pct":3.6},{"years":1,"yield_pct":3.8},{"years":2,"yield_pct":4},{"years":3,"yield_pct":4.2},{"years":5,"yield_pct":4.5},{"years":10,"yield_pct":5}]}}}' \ | jq '.result.structuredContent | {summary, flags, flips, not_modeled, inputs_hash}'One file with no dependencies, and nothing is sent anywhere.
curl -fsSL https://hedgefund.md/hedgefund.mjs -o hedgefund.mjs # read it before you run itnode hedgefund.mjs template rates_ladder > mine.json # replace every number in itnode hedgefund.mjs run rates_ladder mine.json | jq '{summary, flags, flips, not_modeled, inputs_hash}'Made-up numbers that run as they stand. Replace every one.
{ "bonds": [ { "name": "Bond 1.25y", "face": 1000000, "coupon_pct": 3.5, "maturity_years": 1.25, "yield_pct": 3.9, "freq": 2 }, { "name": "Bond 2.5y", "face": 1000000, "coupon_pct": 4, "maturity_years": 2.5, "yield_pct": 4.1, "freq": 2 }, { "name": "Bond 3.75y", "face": 1000000, "coupon_pct": 4, "maturity_years": 3.75, "yield_pct": 4.3, "freq": 4 }, { "name": "Bond 5y", "face": 1000000, "coupon_pct": 4.5, "maturity_years": 5, "yield_pct": 4.5, "freq": 1 }, { "name": "Bond 10y", "face": 1000000, "coupon_pct": 5, "maturity_years": 10, "yield_pct": 5.1, "freq": 2 } ], "shocks_bps": [-100, -50, 50, 100, 200], "horizon_years": 0.5, "curve": [ { "years": 0.5, "yield_pct": 3.6 }, { "years": 1, "yield_pct": 3.8 }, { "years": 2, "yield_pct": 4 }, { "years": 3, "yield_pct": 4.2 }, { "years": 5, "yield_pct": 4.5 }, { "years": 10, "yield_pct": 5 } ]}Inputs
| Input | Type | Range | Required | What it is |
|---|---|---|---|---|
| bonds | array | 1 to 40 items | yes | The ladder, up to 40 fixed coupon bonds. |
| bonds[].name | string | yes | A label for the bond, such as Bond 5y. | |
| bonds[].face | number | 0.01 to 1000000000000 | yes | Face amount held. |
| bonds[].coupon_pct | number | 0 to 100 | yes | Annual coupon on face, in percent. |
| bonds[].maturity_years | number | 0.01 to 50 | yes | Years to maturity. Flows are counted back from it, so a fraction makes a stub. |
| bonds[].yield_pct | number | -5 to 60 | yes | Yield to maturity, compounded freq times a year, in percent. |
| bonds[].freq | whole number | 1 to 4 | yes | Coupons a year: 1, 2 or 4. |
| shocks_bps | array | 0 to 15 items | no | Parallel yield shocks to reprice, in basis points. Default -100, -50, 50, 100. |
| horizon_years | number | 0.01 to 30 | yes | Years to carry the ladder forward. |
| curve | array | 1 to 30 items | no | Optional curve for roll-down, up to 30 points. |
| curve[].years | number | 0.01 to 100 | yes | Point on the curve, in years. |
| curve[].yield_pct | number | -5 to 60 | yes | Yield at that point, in percent. |
Hypothetical, and on made-up numbers until you replace them. Nobody traded this, and nothing here is on offer.