3

I cannot re-calculate the "asked yield" from today's Wall Street Journal for the following two Treasury notes/bonds:

MATURITY    COUPON  BID     ASK     ASK-YIELD
5/15/2023   0.125   98.2500 98.2540 4.695
5/15/2023   1.750   99.0640 99.0700 4.709

Here is what I did. I calculated 0.26 years left to maturity by counting days divided by 365, and similarly 0.24 years since last coupon date of 11/13/2022. I multiplied the coupon rate by 0.24 to get the accrued interest of 0.0298 for the first bond. Adding it to the quoted ask price, I get 98.2798 for the first bond. Next I calculate the payment on maturity as 100 + half the coupon rate = 100.0625. I then calculate the ratio of payment on maturity and cash price: 1.0181 and then raise it to the power (1 / 0.26) to get 1.0707 implying a yield to maturity of 7.07%

But 7.07% is very far from the yield quoted in the WSJ, which is 4.695%. So what am I doing wrong in my calculation?

98.2500 0.125   5/15/2023   0.26    11/13/2022  0.24    0.0298  98.2798 100.0625    1.0181  1.0707
99.0700 1.750   5/15/2023   0.26    11/13/2022  0.24    0.4171  99.4871 100.8750    1.0140  1.0541
3
  • 2
    What's the coupon frequency? Also, if the bond is callable, they might quote the yield-to-worst rather than YTM.
    – 0xFEE1DEAD
    Feb 8 at 3:02
  • 1
    @0xFEE1DEAD It is a Treasury note/bond, so there is no call option and the coupon frequency is semi annual. It is a very basic situation, so I'm just making some silly mistake that I can't figure out myself. Feb 8 at 4:41
  • 2
    I never look at WSJ but are the quotes usually correct? As would say, the 1/8 should quote as 98 -25+ which is equivalent to 98 + 25/32 + 1/64 ~98.8 which is in line with the computed ytm.
    – AKdemy
    Feb 8 at 18:06

1 Answer 1

6

There are a lot of things wrong in here:

  • WSJ does not seem to display the quotes properly. According to the website, Tullett Prebon is the source. Tullett is a very reputable broker with high quality data. The quotes are most likely received following market standard, which is to quote in 1/32nd. When you asked the question, the quote was 98-25+ when I looked at it, which is equal to 98 + 25/32 + 1/64 ~98.7968.
  • Your daycount is wrong because you need to use ACT/ACT and the period for the last coupon payment is only 181 days.
  • The way you solve for YTM is also not how it usually works. Generally, you have NPV = cashflow / (1+ytm*dcf) where dcf stands for daycount fraction and NPV is net present value (the current bond price).

Below is a screenshot from Bloomberg YAS, on the day you asked the question. I use bid price because my YAS screen is set to default to bid. There is no difference computation wise. enter image description here

Below, I will replicate the computation in Julia. There is no need to know the language. I used names similar to the Bloomberg screen and the code is mainly "mathematical" expressions. The relevant code is in 3 sections:

  • Firstly, I compute all relevant dates and daycount fractions.
  • Secondly, clean price, accrued interest and dirty price is computed
  • Lastly, ytm is used to show the final value is indeed the result from dirty price * ytm (adjusted for proper daycount). I manually compute YTM and show that the logic the OP used to compute YTM is called Equiv 1/Yr in Bloomberg. This however is not the conventionally computed yield for treasuries.

In general, yield computations are more often than not depending on a lot of details and treasury bills are computed differently for example, as can be seen here.

Everything starting at #combine results in a table can be ignored as this simply prepares the data in a readable format.

# import relevant tables 
using Dates, DataFrames, PrettyTables

# compute dates 
today = Date(2023,02,08)
accrued_days = Day(86)
start = today - accrued_days
settle_date = Date(2023,05,15)
days_to_settle = Day(1)
coupon_date = settle_date-days_to_settle
days_to_next_coupon = coupon_date - today
accrued_days_between_coupon = coupon_date - start
dcf_next_coupon = (days_to_next_coupon/accrued_days_between_coupon)/2
dcf_accrued = accrued_days/accrued_days_between_coupon/2

# compute clean price, accrued interest and dirty price 
price_clean = 98+25/32+1/64
accrued = dcf_accrued*(1/8)
price_dirty = price_clean + accrued

# ytm as quoted
ytm = 4.765459
# final cashflow (notional plus interest)
final_cashflow = 100 + (1/8)/2
# compute final cashflow based on ytm
final_cf_computed = round(price_dirty*(1+ytm/100*(dcf_next_coupon)), digits = 6)
# compute YTM 
ytm_computed = (final_cash_flow/price_dirty - 1)/(yf)*200
op_logic = ((final_cash_flow/price_dirty)^(1/(yf/2))-1)*100

# combine results in a table 
yield_comp = ["Price and Yield", "Price Clean", "Accrued Interest", "Price Dirty", "Final Cashflow", "Yield to Maturity (YTM)", "Final Cashflow according to YTM", "YTM computed", "Equiv  1/Yr (OP Logic)"]
yields = ["",price_clean, accrued, price_dirty, final_cashflow, ytm, final_cf_computed, ytm_computed, op_logic]
text = ["Dates", "Today", "Coupon Date", "Days To Next Coupon", "Coupon Start Date", "Accrued Days between coupons", "Daycount Fraction Accrued","Daycount Fraction next coupon" ]
date_vals = ["", today, coupon_date, days_to_next_coupon, start, accrued_days_between_coupon, dcf_accrued , dcf_next_coupon]

df_res = DataFrame(Fields = append!(text, yield_comp), Values = append!(date_vals, yields) )
# pretty print
hl_1 = Highlighter((data,i,j) -> data[i,1] == "Dates", crayon"bg:dark_gray white bold")
hl_2 = Highlighter((data,i,j) -> data[i,1] == "Price and Yield", crayon"bg:dark_gray white bold")
PrettyTables.pretty_table(df_res,  border_crayon = Crayons.crayon"blue", header_crayon = Crayons.crayon"bold green", formatters = ft_printf("%.6f", [2]), highlighters = (hl_1, hl_2))

enter image description here

3
  • 1
    Wow, this is AMAZINGly helpful! Your comment on the question had already helped me figure out the most significant issue, which was that WSJ incorrectly publishes the quotes as if they were decimalized; i. e. it uses the "." instead of the "-" delimiter. However I was still stuck with getting my numbers to match exactly due to the nuances of counting days, so this is incredibly useful for me and anybody in the future who encounters this tricky calculation. Thank you! Feb 10 at 4:30
  • 1
    PS: I use these two lines in the Edge/Chrome devtools bar (F12) to decimalize the numbers on the WSJ page: function f (e) { x = e.textContent; i = x.indexOf('.'); x1 = x.substring(0, i); x2 = x.substring(i + 1); x21 = x2.substring(0, 2); x22 = x2.substring(2, 3); e.textContent = (parseInt(x1) + (parseInt(x21) + parseInt(x22) / 8) / 32).toString(); } and then $("table.WSJTables--table--1QzSOCfq tbody")[0].childNodes.forEach(function (e) { f(e.childNodes[2]); f(e.childNodes[3]); }) Feb 10 at 4:32
  • 1
    You could approach WSJ with this solution. After all, they encourage feedback via their Customer Center according to the page displaying the quotes.
    – AKdemy
    Feb 10 at 13:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .