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).
.
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.
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;