Bonds - salvomic - 06-01-2015 09:20 AM
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;
RE: Bonds - Randall - 08-20-2017 09:40 PM
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
RE: Bonds - salvomic - 08-21-2017 09:48 AM
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 :-) ;-)
|