Bond vs Swap Fixed Leg: The Devil in the Cashflow Details
When pricing asset swaps or building fixed income analytics, it’s easy to assume that a swap’s fixed leg and a bond with the same coupon rate will produce identical cashflows. In practice, subtle convention differences mean the cashflows diverge slightly - and these details matter for accurate pricing.
The Key Differences
1. Business Day Convention
- Bonds: Typically use FOLLOWING - if a payment date falls on a non-business day, move to the next business day (even if it crosses into the next month)
- Swaps: Typically use MODIFIED FOLLOWING - move to the next business day UNLESS that crosses into a new month, then move backwards to the previous business day
Why this matters: If March 30 falls on a weekend and April 1 is the next business day:
- Bond with FOLLOWING: Pays April 1 (crosses month boundary)
- Swap with MODIFIED FOLLOWING: Pays March 29 (stays in original month)
This creates different payment dates and different accrual periods between instruments that theoretically should match.
2. Day Count Conventions
- Bonds: In Sweden often 30E/360 or 30/360 (assumes 30-day months). Intl often Act/Act.
- Swaps: Fixed leg commonly uses 30/360, but the specific variant matters (30/360 vs 30E/360 vs ACT/360)
3. Stub Period Handling
- Bonds: First/last coupon may be irregular; accrual follows bond indenture rules
- Swaps: Stub calculations can differ (short first, long first, etc.) based on swap template
4. Interest Accrual Rules
- Bonds:
IR_MATURITY_NOADJ- accrual to unadjusted maturity - Swaps: Can use
IR_MATURITY_NOADJorIR_MATURITY_ADJ- affects final coupon
Real Example: Swedish Government Bond Asset Swap
Here’s actual output from pricing a Swedish 3.5% 2039 bond (SE1053) traded on 2026-02-10 with a matching fixed-for-floating swap:
| Bond Calc Date | Bond Pay Date | Bond Flow | Swap Date | Swap Flow | Notes |
|---|---|---|---|---|---|
| 2026-03-30 | 2026-03-30 | 35,000.00 | 2026-03-30 | -4,666.67 | Stub period from trade date |
| 2027-03-30 | 2027-03-30 | 35,000.00 | 2027-03-30 | -35,000.00 | Perfect match |
| 2028-03-30 | 2028-03-30 | 35,000.00 | 2028-03-30 | -35,000.00 | Perfect match |
| 2029-03-30 | 2029-04-03 | 35,000.00 | 2029-03-29 | -34,902.78 | Different adjusted dates! |
| 2030-03-30 | 2030-04-01 | 35,000.00 | 2030-03-29 | -35,000.00 | Different pay dates, same accrual |
| 2031-03-30 | 2031-03-31 | 35,000.00 | 2031-03-31 | -35,097.22 | Same pay date, different accrual |
| 2032-03-30 | 2032-03-30 | 35,000.00 | 2032-03-30 | -35,000.00 | Perfect match |
| 2033-03-30 | 2033-03-30 | 35,000.00 | 2033-03-30 | -35,000.00 | Perfect match |
| 2034-03-30 | 2034-03-30 | 35,000.00 | 2034-03-30 | -35,000.00 | Perfect match |
| 2035-03-30 | 2035-03-30 | 35,000.00 | 2035-03-30 | -35,000.00 | Perfect match |
| 2036-03-30 | 2036-03-31 | 35,000.00 | 2036-03-31 | -35,000.00 | Same adjustment |
| 2037-03-30 | 2037-03-30 | 35,000.00 | 2037-03-30 | -35,000.00 | Perfect match |
| 2038-03-30 | 2038-03-30 | 35,000.00 | 2038-03-30 | -35,000.00 | Perfect match |
| 2039-03-30 | 2039-03-30 | 1,035,000.00 | 2039-03-30 | -35,000.00 | Final principal payment |
What’s Happening Here?
First payment (2026-03-30):
- Bond: Full 35,000 coupon (bond accrues from issue date 2000-03-30). Typical in Sweden. Bonds generally can have either full copupon or a proportional first cpn.
- Swap: Short stub from trade date 2026-02-10 → 2026-03-30 = ~48 days
- Stub calculation: 35,000 × (48/360) ≈ 4,667
2029-04-03 vs 2029-03-29 - The Critical Divergence:
- Calculation date: 2029-03-30 (falls on Friday before Easter weekend)
- Bond (FOLLOWING): Adjusts to 2029-04-03 (next business day, crosses into April)
- Swap (MODIFIED FOLLOWING): Adjusts to 2029-03-29 (can’t cross month, goes backward to Thursday)
- Different payment dates = different accrual periods = -34,902.78 vs 35,000.00
- This is the classic FOLLOWING vs MODIFIED FOLLOWING divergence
2030-04-01 vs 2030-03-29:
- Calculation date: 2030-03-30 (Saturday)
- Bond: 2030-04-01 (FOLLOWING)
- Swap: 2030-03-29 (MODIFIED FOLLOWING - can’t cross month boundary)
- Despite different payment dates, cashflows match at 35,000 (accrual periods align)
2031-03-31 - Same Adjusted Date, Different Accruals:
- Both instruments adjust to 2031-03-31
- But different accrual calculation rules create -35,097.22 vs 35,000.00 difference
- Shows that even matching payment dates don’t guarantee matching cashflows!
The Code
// Setup: Swedish market curves
vector(number) years = [1,2,3,4,5,8];
vector(number) ois_rates = [2.0,2.100,2.364,2.38,2.514,2.88]/100;
vector(number) disc_factors = exp(-ois_rates .* years);
vector(number) fwd_rates = [2.04,2.28,2.44,2.6,2.75,3.07]/100;
disc_func df = disc_func_interp(years, disc_factors, ip_linear(), rate_type.RT_CONT);
fwd_func fwd = fwd_func_interp(years, fwd_rates, ip_linear());
// Bond definition - Swedish government bond conventions
INSTR_TMPL.bond_def_tmpl bond_swe_govt = new INSTR_TMPL.bond_def_tmpl(
"SWE_BOND",
bond_yld_method.ISMA,
1, // Annual frequency
DC_30E_360, // 30E/360 day count
false,
"BD4", // FOLLOWING business day convention
first_cpn_type.REG,
last_per_yld_method.SIMPLE,
next_per_yld_method.EFFECTIVE,
odd_last_cpn_type.REG,
"BD2",
CAL_SE, // Swedish calendar
"SEK",
quote_style.YIELD_PCT
);
// Create the bond
bond b = bond(
instr_def_bond(bond_swe_govt),
"SE1053",
#2000-03-30, // Issue date
null, null, null,
#2039-03-30, // Maturity
0.035, // 3.5% coupon
trade_d, // Trade date = "today" (2026-02-10)
null, null, null, null
);
// Swap definition - matching tenor and rate
INSTR_TMPL.swap_fixflt_def_tmpl fixflt_swe = new INSTR_TMPL.swap_fixflt_def_tmpl(
"SWE_FIXFLT",
"BD2", null<string>, null<string>,
1, // Annual fixed leg
DC_30E_360, // Same day count as bond
interest_rule.IR_MATURITY_NOADJ,
"SEK", CAL_SE,
BD_MOD_FOLLOWING, // MODIFIED FOLLOWING - key difference from bond!
false,
4, 4, // Quarterly floating leg
DC_ACT_360, // ACT/360 for floating
interest_rule.IR_MATURITY_NOADJ,
"SEK", CAL_SE,
BD_MOD_FOLLOWING, // MODIFIED FOLLOWING on floating leg too
false,
null<flt_comp_avg_type>,
null<flt_sprd_comp_method>,
null<flt_avg_method>,
null<notional_exchg_style>,
null<flt_stub_fwd_style>,
INSTR_TMPL_IR_INDEX.stibor_3m
);
// Create swap with same maturity and coupon as bond
swap_fixflt swp = swap_fixflt_plain(
fixflt_swe,
"Myswap",
trade_d, // Same trade date
null, null,
b.maturity(), // Match bond maturity
b.coupon_rate(), // Match bond coupon (3.5%)
-1, // Pay fixed (receive floating)
1E6, // 1M SEK notional
true,
df, fwd, null
);
// Extract cashflows
vector(date) b_calc_dates = b.cash_flow_dates(true, false);
vector(date) b_pay_dates = b.cash_flow_dates(true, true);
vector(number) b_flows = b.cash_flows(1E6, true);
vector(date) swp_dates;
vector(number) swp_flows;
swp.payment_data_fix_leg(true, false, swp_dates, cpn_rate, swp_flows,
pv_cpn_cashflow, fee, pv_fee, notional);
Why This Matters
- Asset Swap Pricing: The spread adjustment needs to account for these cashflow mismatches
- Hedging: A naïve 1:1 hedge will have small cashflow timing/amount mismatches
- Risk Management: Your DV01 calculations differ slightly between bond and swap
- P&L Attribution: Unexplained P&L often comes from these convention differences
Practical Implications
When building an asset swap:
- Don’t assume fixed leg = bond cashflows
- Do align day count conventions where possible
- Do track the adjustment spread that compensates for convention differences
- Do test edge cases: leap years, business day adjustments, stub periods
The difference between theory (perfect cashflow match) and practice (convention-driven mismatches) is exactly where quantitative finance gets interesting!
test flows extended.qlw (29.7 KB)