Guides · bonds

Clean vs dirty bond price and accrued interest, explained with code

Dirty price = clean price + accrued interest. Markets quote the clean price; the buyer pays the dirty price at settlement. Below, the same 5% bond is priced at two settlement dates over a REST API — on a coupon date the two prices are identical, three and a half months later they differ by exactly the accrued coupon.

01The call, mid coupon period

A 5% annual bond issued 2024-07-01, maturing 2029-07-01, at a flat 4% yield, settling 2026-10-15 — 106 days after the July coupon:

request
curl -s https://quantbox.dev/v1/bonds/price \
  -H "X-API-Key: $KEY" -H "Content-Type: application/json" \
  -d '{"coupon_rate": 0.05,
       "issue_date": "2024-07-01",
       "maturity_date": "2029-07-01",
       "settlement_date": "2026-10-15",
       "frequency": "annual",
       "day_count": "act/act",
       "yield_rate": 0.04}'

Runs on the free tier — 500 calls/month, no credit card. Get a key.

response
{
  "clean_price": 102.4892,
  "dirty_price": 103.9413,
  "accrued": 1.4521,
  "ytm": 0.04,
  "modified_duration": 2.4748,
  "convexity": 8.7012,
  "bpv": -0.0257,
  "conventions": {
    "prices": "per 100 of face value (market quote convention)",
    "settlement": "as provided (settlementDays=0, no implicit T+2)",
    "ytm": "compounded annual, act/act",
    "zero_curve": "zero rates continuously compounded, act/365",
    "calendar": "TARGET, unadjusted schedule"
  }
}

102.4892 + 1.4521 = 103.9413. The identity is exact, not approximate — it is how the dirty price is defined.

02Both dates in one Python loop

python
import requests

KEY = "YOUR_API_KEY"  # free key from https://quantbox.dev

bond = {"coupon_rate": 0.05, "issue_date": "2024-07-01",
        "maturity_date": "2029-07-01", "frequency": "annual",
        "day_count": "act/act", "yield_rate": 0.04}

for settlement in ["2026-07-01", "2026-10-15"]:
    r = requests.post("https://quantbox.dev/v1/bonds/price",
        headers={"X-API-Key": KEY},
        json={**bond, "settlement_date": settlement})
    d = r.json()
    print(f"{settlement}  clean {d['clean_price']:.4f}"
          f"  accrued {d['accrued']:.4f}  dirty {d['dirty_price']:.4f}")

# 2026-07-01  clean 102.7641  accrued 0.0000  dirty 102.7641
# 2026-10-15  clean 102.4892  accrued 1.4521  dirty 103.9413

03The two responses, side by side

Field2026-07-01 (coupon date)2026-10-15 (mid period)
clean_price102.7641102.4892
accrued0.01.4521
dirty_price102.7641103.9413
ytm0.040.04
modified_duration2.7542.4748
convexity10.42988.7012
bpv-0.0283-0.0257

On 2026-07-01 the coupon has just been paid, so nothing has accrued and clean = dirty = 102.7641. On 2026-10-15 the holder has earned 106 days of the next coupon: clean and dirty split apart by exactly that amount.

Note the clean price itself also moved (102.7641 → 102.4892) at the same 4% yield. That is not accrued — it is the passage of time pulling a premium bond toward par as it gets shorter. Duration and BPV shrink for the same reason.

04Where the 1.4521 comes from

Accrued interest is the coupon earned pro rata since the last payment date, measured with the bond's day count. Under act/act, from the 2026-07-01 coupon to the 2026-10-15 settlement is 106 actual days, in a coupon period of 365 actual days:

accrued = coupon × days elapsed / days in period
        = 5.00 × 106 / 365
        = 1.4521   (per 100 of face value)

Change the day count and the accrued changes — under 30/360 the elapsed-day count is computed on idealized 30-day months. That is why the request names the day count explicitly instead of assuming one.

05Why markets quote clean

Accrued interest is deterministic: it grows linearly day by day and drops to zero at each coupon, a sawtooth that has nothing to do with market conditions. Quoting the dirty price would make every bond look like it rallies daily and crashes on coupon dates. The clean price strips the sawtooth out, isolating the market-driven component — so quotes are comparable across days. Settlement, however, is cash for value: the seller gets compensated for the coupon they earned but will not receive, which is why you settle dirty.

06Conventions

ConventionValue
pricesper 100 of face value (market quote convention)
settlementas provided (settlementDays=0, no implicit T+2)
ytmcompounded annual, act/act
zero_curvezero rates continuously compounded, act/365
calendarTARGET, unadjusted schedule

Settlement is taken literally — no implicit T+2. If your trade settles T+2, pass the actual settlement date; the accrued is computed to that date, not to "today".

Run both calls yourself. Key in seconds, 500 free calls a month, full request schema in the docs.

Get a free API key

No credit card · schemas and error codes in /docs

Related: Price a fixed-rate bond with a REST API · DV01 / BPV of a bond via API · Bond duration, convexity and BPV over HTTP