I am trying to extract the clean price, dirty price, accrued amount, and cash flows from a FixedRateBond object after setting it with a DiscountingBondEngine object. I can get back the accrued amount and cash flows but not the clean price and dirty price. Instead an exception is thrown:
The weird thing is I'm running essentially the same code using NQuantLib64 (NuGet package containing QuantLib-SWIG for C# x64) and everything works fine, including matching outputs (for those in QLNet being outputted). I'm also using QLNet version 1.4.0.27.
Would you know what I'm doing wrong? Or maybe there's some issue with QLNet? See code below.
public class Program
{
public static void Main(string[] args)
{
double faceValue = 100;
double coupon = 0.01;
int settleDays = 3;
DateTime issueDate = DateTime.Parse("2015-01-06");
DateTime settleDate = DateTime.Parse("2015-02-23");
DateTime maturityDate = DateTime.Parse("2019-01-08");
Dictionary<DateTime, double> termStructure = new Dictionary<DateTime, double>
{
{ DateTime.Parse("2015-01-06"), 1.0},
{ DateTime.Parse("2016-01-06"), 0.98},
{ DateTime.Parse("2017-01-06"), 0.95},
{ DateTime.Parse("2018-01-08"), 0.96},
{ DateTime.Parse("2019-01-08"), 0.97}
};
// Initialize and price QLNet fixed coupon bond
QLNetFixedCouponBond qlnFixedCouponBond = new QLNetFixedCouponBond(faceValue, coupon, settleDays, termStructure, settleDate, issueDate, maturityDate);
qlnFixedCouponBond.Price();
// Display QLNet fixed coupon bond price info
Console.WriteLine("QLNet Fixed Coupon Bond Pricing Info");
Console.WriteLine(" Clean price: {0}", qlnFixedCouponBond.CleanPrice);
Console.WriteLine(" Dirty price: {0}", qlnFixedCouponBond.DirtyPrice);
Console.WriteLine(" Accrued amount: {0}", qlnFixedCouponBond.AccruedAmount);
Console.WriteLine(" Cash flows:");
foreach (var cf in qlnFixedCouponBond.CashFlow)
{
Console.WriteLine(" {0}", cf.amount());
}
}
}
public class QLNetFixedCouponBond
{
public double FaceValue { get; set; }
public double Coupon { get; set; }
public Calendar Calendar { set; get; }
public int SettleDays { get; set; }
public List<Date> Dates { get; set; }
public List<double> DiscountFactors { get; set; }
public Date SettleDate { get; set; }
public Date IssueDate { get; set; }
public Date MaturityDate { get; set; }
public double CleanPrice { get; set; }
public double DirtyPrice { get; set; }
public double AccruedAmount { get; set; }
public List<CashFlow> CashFlow { get; set; }
public QLNetFixedCouponBond(double faceValue, double coupon, int settleDays, Dictionary<DateTime, double> termStructure, DateTime settleDate, DateTime issueDate, DateTime maturityDate)
{
FaceValue = faceValue;
Coupon = coupon;
SettleDays = settleDays;
Calendar = new Germany();
IssueDate = new Date(issueDate.Day, (Month)issueDate.Month, issueDate.Year);
SettleDate = new Date(settleDate.Day, (Month)settleDate.Month, settleDate.Year);
MaturityDate = new Date(maturityDate.Day, (Month)maturityDate.Month, maturityDate.Year);
Dates = new List<Date>();
DiscountFactors = new List<double>();
foreach (var t in termStructure)
{
Dates.Add(new Date(t.Key.Day, (Month)t.Key.Month, t.Key.Year));
DiscountFactors.Add(t.Value);
}
}
public void Price()
{
// Prepare QLNet objects for initialization of fixed coupon bond object
BusinessDayConvention dayCountConvention = BusinessDayConvention.Unadjusted;
Schedule schedule = new Schedule(
effectiveDate: SettleDate,
terminationDate: MaturityDate,
tenor: new Period(Frequency.Semiannual),
calendar: new NullCalendar(),
convention: dayCountConvention,
terminationDateConvention: dayCountConvention,
rule: DateGeneration.Rule.Backward,
endOfMonth: false);
// Initialize QLNet bond pricing engine
Handle<YieldTermStructure> discountCurve = CreateZeroCouponTermstructure(SettleDate, Dates, DiscountFactors);
IPricingEngine bondEngine = new DiscountingBondEngine(discountCurve);
// Initialize QLNet fixed coupon bond and set pricing engine
FixedRateBond fixedCouponBond = new FixedRateBond(
settlementDays: SettleDays,
faceAmount: FaceValue,
schedule: schedule,
coupons: new List<double> { Coupon },
accrualDayCounter: new ActualActual(ActualActual.Convention.Bond),
paymentConvention: dayCountConvention,
redemption: FaceValue,
issueDate: IssueDate);
fixedCouponBond.setPricingEngine(bondEngine);
// Get price info from QLNet fixed coupon bond
//CleanPrice = fixedCouponBond.cleanPrice();
DirtyPrice = fixedCouponBond.dirtyPrice();
AccruedAmount = fixedCouponBond.accruedAmount();
CashFlow = fixedCouponBond.cashflows();
}
private Handle<YieldTermStructure> CreateZeroCouponTermstructure(Date settleDate, List<Date> dates, List<double> discountfactors)
{
var bondInstruments = new List<RateHelper>();
for (var i = 0; i < discountfactors.Count; i++)
{
if (dates[i] <= settleDate) continue;
var df = new Handle<Quote>(new SimpleQuote(discountfactors[i]));
var matu = dates[i];
var schedule = new Schedule(
effectiveDate: settleDate,
terminationDate: matu,
tenor: new Period(Frequency.Once),
calendar: new NullCalendar(),
convention: BusinessDayConvention.Unadjusted,
terminationDateConvention: BusinessDayConvention.Unadjusted,
rule: DateGeneration.Rule.Backward,
endOfMonth: true);
var coupons = new List<double> { 0.0 };
var helper = new FixedRateBondHelper(
price: df,
settlementDays: 0,
faceAmount: 1.0,
schedule: schedule,
coupons: coupons,
dayCounter: new ActualActual(),
paymentConvention: BusinessDayConvention.Unadjusted,
redemption: 1.0,
issueDate: settleDate);
bondInstruments.Add(helper);
}
// Cubic interpolation of discount factors
YieldTermStructure bondDiscountingTermStructure = new PiecewiseYieldCurve<Discount, Cubic>(settleDate, bondInstruments, new ActualActual());
return new Handle<YieldTermStructure>(bondDiscountingTermStructure);
}
}