Post Reply 
Bonds
06-01-2015, 09:20 AM (This post was last modified: 06-01-2015 04:22 PM by salvomic.)
Post: #1
Bonds
hi,
I would present a program useful to calculate price and yield of a Bond.
The program treat Bonds with coupons paid annually or semi-annually (or other ends) and also "Zero bonds", both for "actual year" and "financial year" (30/360).

For some examples see HP 12C User Guide or here.

Some examples may differ from the calculations made by HP 12C but always below 0.1, due to different rounding for the numbers...

Enjoy and let me know if you have further improvement.

Salvo M.

Code:

smenu();

EXPORT Bonds()
BEGIN
smenu();
END;

EXPORT Price()
// by Salvo Micciché 2015
// For Zero Coupon bonds input 0 in "Coupon" field
BEGIN
  local mesg, days1, days2, toReg, N, finAct;
  local sd, sm, sy, md, mm, my, ann, nextReg;
  local coupon, yi, years, price, inter, dayPer;
  local m1, d1;
  input ({ {sd,[0],{15,15,1}}, 
  {sm,{"1","2","3", "4", "5", "6", "7", "8", "9", "10", "11", "12"}, {40,15,1}}, 
  {sy,[0],{70,20,1}},
  {md,[0],{15,15,2}}, 
  {mm,{"1","2","3", "4", "5", "6", "7", "8", "9", "10", "11", "12"}, {40,15,2}}, 
  {my,[0],{70,20,2}},
  {coupon,[0],{40,15,4}}, {yi,[0], {75,15,4}},
  {ann,[0], {40,15,5}}, {finAct,2,{85,2, 5}}
  }, 
  "Price of Bond (maturity 100)", {"sd", "sm", "sy", "md", "mm", "my", 
  "Coupon", "y%", "ann", "Financial"}, 
  {"Settlement day", "Settlement month", "Settlement year (purchase)", 
  "Maturity day", "Maturity month", "Maturity year (regulation)", 
  "Coupon payment % (0 for zero coupon)", "yield %", "n. of annuality (i.e 2=semi)", "Financial year (360)"}, 
  {1, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, {1, 0, 2015, 1, 0, 2015, 6.75, 4.75, 2, 0} );

  sd:=EVAL(sd); sm:=EVAL(sm);
  md:=EVAL(md); mm:=EVAL(mm);
  sy:=EVAL(sy); my:=EVAL(my);
  days1 := sy+sm/100+sd/10000;
  days2 := my+mm/100+md/10000;
  toReg := sy+mm/100+md/10000; // days to the regulation
  years := my-sy; // years to the maturity
  IF years<1 THEN years:= 1; END;
  IF (finAct==0) THEN
  N:= DDAYS(days1, days2);
  IF (days1>toReg) THEN nextReg:=DATEADD(toReg,IP(365/ann)); ELSE nextReg:=toReg; END;
  toReg := DDAYS(days1, nextReg);
  dayPer := IP(365/ann);
  ELSE
  N:= 30*12*years;
  IF (days1>toReg) THEN nextReg:=DATEADD(toReg,IP(360/ann)); ELSE nextReg:=toReg; END;
  m1:=IP(FP(nextReg)*100); d1:=FP(FP(nextReg)*100)*100;
  toReg:= 30*(m1-sm)+d1-sd;
  dayPer:= IP(360/ann);
  END; // if

  price := (coupon/ann)*( (1-1/(1+(yi/(ann*100)))^(ann*years)) / (yi/(ann*100)) ) + 100/(1+(yi/(ann*100)))^(ann*years);
  inter := (coupon/ann)*((dayPer - toReg)/dayPer);
  IF (coupon == 0) THEN // Zero Coupon
  RECT_P();
    TEXTOUT_P("Zero Coupon", 10, 50);
    TEXTOUT_P("from " + sd + "-" + sm + "-" + sy + " to " + md + "-" + mm + "-" + my , 10, 65 );
    TEXTOUT_P("Price of Bond: " + price, 10, 85);
    TEXTOUT_P("Paid " + ann + " times per year", 10, 105);
    TEXTOUT_P("Press ESC to continue", 10, 130);
  ELSE
  RECT_P();
    TEXTOUT_P("Days: " + N + " to the end (" + years + " years)", 10, 50);
   TEXTOUT_P("from " + sd + "-" + sm + "-" + sy + " to " + md + "-" + mm + "-" + my , 10, 65 );
    TEXTOUT_P("Days to next regulation " + toReg, 10, 85);
    TEXTOUT_P("Price of Bond: " + price, 10, 105);
    TEXTOUT_P("Paid " + ann + " times per year", 10, 125);
    TEXTOUT_P("Interest: " + inter, 10, 145);
    TEXTOUT_P("paid for " + EVAL(dayPer-toReg) + " days", 10, 165);
    TEXTOUT_P("Press ESC to continue", 10, 190);
  END; //if
  WAIT;
smenu();
  RETURN {ROUND(price, 3), ROUND(inter,3), ROUND(price+inter,3)};

END;

EXPORT Yield()
BEGIN
  local mesg, days1, days2, toReg, N, finAct;
  local sd, sm, sy, md, mm, my, years, yield;
  local coupon, price, ann, inter, dayPer, nextReg;
  local d1, m1;
  input ({ {sd,[0],{15,15,1}}, 
  {sm,{"1","2","3", "4", "5", "6", "7", "8", "9", "10", "11", "12"}, {40,15,1}}, 
  {sy,[0],{70,20,1}},
  {md,[0],{15,15,2}}, 
  {mm,{"1","2","3", "4", "5", "6", "7", "8", "9", "10", "11", "12"}, {40,15,2}}, 
  {my,[0],{70,20,2}},
  {coupon,[0],{40,15,4}}, {price,[0], {75,15,4}},
  {ann,[0], {40,15,5}}, {finAct,2,{85,2, 5}}
  }, 
  "Bond YTM: yield to maturity", {"sd", "sm", "sy", "md", "mm", "my", 
  "Coupon", "Price", "annu", "Financial"}, 
  {"Settlement day", "Settlement month", "Settlement year (purchase)", 
  "Maturity day", "Maturity month", "Maturity year (regulation)", 
  "Coupon payment % (0 for zero coupon)", "Price now", "n. of annuality (i.e 2=semi)", "Financial year (360)"}, 
  {1, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, {1, 0, 2015, 1, 0, 2015, 6.75, 120, 2, 0} );

  sd:=EVAL(sd); sm:=EVAL(sm);
  md:=EVAL(md); mm:=EVAL(mm);
  sy:=EVAL(sy); my:=EVAL(my);
  days1 := sy+sm/100+sd/10000;
  days2 := my+mm/100+md/10000;
  toReg := sy+mm/100+md/10000; // days to the regulation
  years := my-sy; // years to the maturity
  IF years<1 THEN years:= 1; END;
  IF (finAct==0) THEN
  N:= DDAYS(days1, days2);
  IF (days1>toReg) THEN nextReg:=DATEADD(toReg,IP(365/ann)); ELSE nextReg:=toReg; END;
  toReg := DDAYS(days1, nextReg);
  dayPer := IP(365/ann);
  ELSE
  N:= 30*12*years;
  IF (days1>toReg) THEN nextReg:=DATEADD(toReg,IP(360/ann)); ELSE nextReg:=toReg; END;
  m1:=IP(FP(nextReg)*100); d1:=FP(FP(nextReg)*100)*100;
  toReg:= 30*(m1-sm)+d1-sd;
  dayPer:= IP(360/ann);
  END; // if

  C:= coupon; A:= ann; Y:= years; P:= price;
  // yield := Solve.SOLVE((C/A)*( (1-1/(1+(X/A))^(A*Y)) / (X/A) ) + 100/(1+(X/A))^(A*Y) = P, X);
  yield := FNROOT((C/A)*( (1-1/(1+(X/A))^(A*Y)) / (X/A) ) + 100/(1+(X/A))^(A*Y) = P, X);
  IF (coupon==0) THEN // Zero Coupon
RECT_P();
    TEXTOUT_P("Zero Coupon", 10, 50);
    TEXTOUT_P("from " + sd + "-" + sm + "-" + sy + " to " + md + "-" + mm + "-" + my , 10, 65 );
    TEXTOUT_P("Price of Bond: " + price, 10, 85);
    TEXTOUT_P("paid " + ann + " times per year", 10, 105);
    TEXTOUT_P("Yield to Maturity: " + ROUND(100*yield,3) + "%", 10, 125);
    TEXTOUT_P("Press ESC to continue", 10, 150);
  ELSE
RECT_P();
    TEXTOUT_P("Days: " + N + " to the end (" + years + " years)", 10, 50);
    TEXTOUT_P("from " + sd + "-" + sm + "-" + sy + " to " + md + "-" + mm + "-" + my , 10, 65 );
   TEXTOUT_P("Days to next regulation " + toReg, 10, 85);
    TEXTOUT_P("Price of Bond: " + price, 10, 105);
   TEXTOUT_P("paid " + ann + " times per year", 10, 125);
    TEXTOUT_P("Yield to Maturity: " + ROUND(100*yield,3) + "%", 10, 145);
    TEXTOUT_P("Press ESC to continue", 10, 170);
  END; //if
  WAIT;
  smenu();
  RETURN ROUND(100*yield,3);

END;

smenu()
BEGIN
  local ch;
  CHOOSE(ch, "Bonds: price, YTM, interest", "Price", "Yield", "Quit");
  CASE
   IF ch==1 THEN Price(); END;
   IF ch==2 THEN Yield(); END;
   IF ch==3 THEN RETURN; END;
  DEFAULT
  END; // case
END;

∫aL√0mic (IT9CLU) :: HP Prime 50g 41CX 71b 42s 39s 35s 12C 15C - DM42, DM41X - WP34s Prime Soft. Lib
Visit this user's website Find all posts by this user
Quote this message in a reply
08-20-2017, 09:40 PM
Post: #2
RE: Bonds
Salvo-

I am a new Prime user and an occasional buyer of US municipal bonds.

I have a few questions:

From section 4 page 82 of the HP12C manual the examples are:


Treasury Bonds (actual/actual) semi annual payments

28 April 2004 settle date
04 June 2018 mature date
6.75% coupon
4.75% desired yield at maturity
? what is the bond price

HP 12C answer: 120.38 123.07 (bond price + accrued interest)
Salvo Prime answer: 120.28 2.69 accrued interest = 122.97 (bond price + accrued interest)

Bond Yield Example:

28 April 2004 settle date
04 June 2018 mature date
6.75% coupon
122.125 = price what is yield?

answer:

hp12c yield = 4.60 Salvo Prime answer = 4.59


Section 16 Page 175 has an hp12c program for 30/360 bonds such as municipal bonds. In using your program I understand that checking the financial box causes 30/360 calculations.

Example:

28 August 2004 settle date
01 June 2008 mature date
5.5% coupon
desired yield = 4.75 what is the bond price?

hp12c answer: 102.55 accrued interest 1.33
Salvo Prime answer: 102.703 accrued interest 1.375

desired yield = 4.50 what is the bond price?

hp12c answer: 103.41 accrued interest 1.33
Salvo Prime answer: 103.62 accrued interest 1.375

Your writeup says your answers are within .1 of the hp12c manual. Except for the example directly above (102.55 versus 102.70 and 103.41 versus 103.62) that is the case.

From the examples have I used your program correctly?

If the financial box is checked are bond calculations made on a 30/360 basis?

Read your bio on qrz.

Thanks Randy WA0RAD
Find all posts by this user
Quote this message in a reply
08-21-2017, 09:48 AM (This post was last modified: 08-21-2017 03:16 PM by salvomic.)
Post: #3
RE: Bonds
dear Randal,
thank you for your report.
You're right, there are some (and not good) approximations in my program. I made it in 2015 and it seemed to work well then, but now I can see your observed differences.
I don't know if the problem stay in the new firmware or in my formulas. For example the use of solve() function like this code:
Code:
C:= coupon; A:= ann; Y:= years; P:= price;
  // yield := Solve.SOLVE((C/A)*( (1-1/(1+(X/A))^(A*Y)) / (X/A) ) + 100/(1+(X/A))^(A*Y) = P, X);
  yield := FNROOT((C/A)*( (1-1/(1+(X/A))^(A*Y)) / (X/A) ) + 100/(1+(X/A))^(A*Y) = P, X);

I hope someone could be so kind to help us to find the bug.

73, regards
salvo it9clu

EDIT (PS): maybe sometime HP could implement such function inside the standard APPs in the Prime. I'm looking forward :-) ;-)

∫aL√0mic (IT9CLU) :: HP Prime 50g 41CX 71b 42s 39s 35s 12C 15C - DM42, DM41X - WP34s Prime Soft. Lib
Visit this user's website Find all posts by this user
Quote this message in a reply
Post Reply 




User(s) browsing this thread: 1 Guest(s)