public static double[] SolveYield(Country country, DateTime settleDate, double cleanPrice, DateTime startDate, DateTime firstCpnDate, DateTime maturityDate, double coupon, long freq, int exDivDays = 0, InflationCurve infCurve = null) { bool bLongFirstCpn = firstCpnDate > settleDate && firstCpnDate > startDate.AddDays(1).AddMonths((int)freq).AddDays(-1); DateTime adjSettleDate = (startDate > settleDate ? startDate : settleDate); List<DateTime> schedule = GenerateSchedule((bLongFirstCpn && adjSettleDate < firstCpnDate ? startDate : adjSettleDate), maturityDate, freq); List<DateTime> adjSchedule = GenerateAdjustedSchedule(schedule, null, "F"); double[] result = new double[4]; double guess = coupon / cleanPrice * freq / 12.0, firstCpnSize = 1.0; long nCpns = schedule.Count, ctr; // Boundary case if (settleDate.Date >= maturityDate.Date) return result; // Specific country conventions: // Italy: accrual rounding, semi-annual coupons but yield quoted with annual compounding frequency. // All (US and Eurozone) bar France use US convention - i.e. last coupon period simple yield. // Spain: final principal payment use adjusted payment date. // UK and some others have ex-div periods. bool bExDiv = (settleDate.AddDays(exDivDays) >= schedule[(bLongFirstCpn ? 2 : 1)]); double dcf = CalcBondAccrualDcf(adjSettleDate, startDate, firstCpnDate, schedule, country); // In case of stub period at start if (firstCpnDate > settleDate) { if (bLongFirstCpn) { firstCpnSize = 1.0 + (schedule[1] - startDate).TotalDays / (schedule[1] - schedule[0]).TotalDays; } else { firstCpnSize = (firstCpnDate - (schedule[0] < startDate ? startDate : schedule[0])).TotalDays / (schedule[1] - schedule[0]).TotalDays; } } double accrual = 100.0 * coupon * freq / 1200 * (dcf - (bExDiv ? firstCpnSize : 0.0)); // Interpolate inflation curve. double[] infFactors = InterpolateInflationFactors(adjSchedule, settleDate, infCurve); accrual *= infFactors[nCpns]; cleanPrice *= infFactors[nCpns]; // Modify DCF for use in discounting loop. If long first coupon then need to reduce i by 1 in the coupon loop. Do this by incrementing adjusted DCF. dcf += (bLongFirstCpn ? 2.0 : 1.0) - firstCpnSize; if (country == Country.IT) { accrual = Math.Round(accrual, 5); } for (ctr = 0; ctr < 100; ctr++) { double pv = -cleanPrice - accrual; double dPvdY = 0.0; double d2PvdY2 = 0.0; double delay = 0.0; double tmp; // In final coupon period and US convention if (nCpns == 2 && country != Country.FR && country != Country.UK) { if (country == Country.ES) { delay = (adjSchedule[1] - schedule[1]).TotalDays / (schedule[1] - schedule[0]).TotalDays; } tmp = 100.0 * infFactors[1] * (1.0 + (bExDiv ? 0.0 : 1.0) * coupon * freq / 1200) / (1.0 + guess * (1.0 + delay - dcf)); pv += tmp; dPvdY -= (1 + delay - dcf) * tmp / (1.0 + guess * (1.0 + delay - dcf)); d2PvdY2 -= 2.0 * (1 + delay - dcf) * dPvdY / (1.0 + guess * (1.0 + delay - dcf)); } else { // First coupon int i = (bLongFirstCpn ? 2 : 1); tmp = firstCpnSize * 100.0 * infFactors[1] * (bExDiv ? 0.0 : 1.0) * coupon * freq / 1200 / Math.Pow(1.0 + guess, i - dcf); pv += tmp; dPvdY -= (i - dcf) * tmp / (1.0 + guess); d2PvdY2 += (i - dcf) * (i + 1 - dcf) * tmp / ((1.0 + guess) * (1.0 + guess)); for (i++; i < nCpns; i++) { tmp = 100.0 * infFactors[i] * coupon * freq / 1200 / Math.Pow(1.0 + guess, i - dcf); pv += tmp; dPvdY -= (i - dcf) * tmp / (1.0 + guess); d2PvdY2 += (i - dcf) * (i + 1 - dcf) * tmp / ((1.0 + guess) * (1.0 + guess)); } // Final Principal if (country == Country.ES) { delay = (adjSchedule[i - 1] - schedule[i - 1]).TotalDays / (schedule[i - 1] - schedule[i - 2]).TotalDays; } tmp = 100.0 * infFactors[i - 1] / Math.Pow(1.0 + guess, i - 1 + delay - dcf); pv += tmp; dPvdY -= (i - 1 + delay - dcf) * tmp / (1.0 + guess); d2PvdY2 += (i - 1 + delay - dcf) * (i + delay - dcf) * tmp / ((1.0 + guess) * (1.0 + guess)); } // Convergence criterion is Pv within 1.0e-8 after allowing for Price ~ 100 if (Math.Abs(pv) < 1.0e-8) { if (country == Country.IT && nCpns > 2) { tmp = 1.0 + 0.5 * guess * 12 / freq; result[0] = tmp * tmp - 1.0; result[1] = dPvdY / tmp * freq / 12; result[2] = -result[1] / (cleanPrice + accrual); result[3] = 0.0001 * (d2PvdY2 * freq * freq / 144 - 0.5 * result[1]) / tmp / tmp; } else { result[0] = guess * 12 / freq; result[1] = dPvdY * freq / 12; result[2] = -result[1] / (cleanPrice + accrual); result[3] = 0.0001 * d2PvdY2 * freq * freq / 144; } return result; } else { // Halley's method guess = guess - pv / (dPvdY - pv * d2PvdY2 / (2.0 * dPvdY)); } } result[0] = -1.0; result[1] = -1.0; result[2] = -1.0; result[3] = -1.0; return result; }
public static double[] SolveZSpread2(Country country, DateTime settleDate, double cleanPrice, DateTime startDate, DateTime firstCpnDate, DateTime maturityDate, double coupon, long freq, DateTime[] curveDates, double[] dfs, List<DateTime> holidays, int exDivDays = 0, InflationCurve infCurve = null) { double[] result = new double[2]; bool bLongFirstCpn = firstCpnDate > settleDate && firstCpnDate > startDate.AddDays(1).AddMonths((int)freq).AddDays(-1); DateTime adjSettleDate = startDate > settleDate ? startDate : settleDate; List<DateTime> schedule = GenerateSchedule((bLongFirstCpn && adjSettleDate < firstCpnDate ? startDate : adjSettleDate), maturityDate, freq); List<DateTime> adjSchedule = GenerateAdjustedSchedule(schedule, holidays, "F"); double[] interpTimes = new double[adjSchedule.Count]; List<double> flowZcTimes = new List<double>(); List<double> flowZcRates = new List<double>(); double guess = 0.0, firstCpnSize = 1.0; long nCpns = schedule.Count, ctr; int i; // Boundary case if (settleDate.Date >= maturityDate.Date) return result; bool bExDiv = settleDate.AddDays(exDivDays) >= schedule[bLongFirstCpn ? 2 : 1]; double dcf = CalcBondAccrualDcf(adjSettleDate, startDate, firstCpnDate, schedule, country); // In case of stub period at start if (firstCpnDate > settleDate) { if (bLongFirstCpn) { firstCpnSize = 1.0 + (schedule[1] - startDate).TotalDays / (schedule[1] - schedule[0]).TotalDays; } else { firstCpnSize = (firstCpnDate - (startDate > schedule[0] ? startDate : schedule[0])).TotalDays / (schedule[1] - schedule[0]).TotalDays; } } double accrual = 100.0 * coupon * freq / 1200 * (dcf - (bExDiv ? firstCpnSize : 0.0)); if (country == Country.IT) { accrual = Math.Round(accrual, 5); } // Use adjusted schedule dates for payments. // Convert dates to times for spline interpolator interpTimes[0] = ((settleDate > startDate ? settleDate : startDate) - curveDates[0]).TotalDays; for (i = 1; i < adjSchedule.Count; i++) { interpTimes[i] = (adjSchedule[i] - curveDates[0]).TotalDays; } List<double> flowDfs = InterpDfSpline(interpTimes, curveDates, dfs); // Calculate base discount factors (before spread) and convert to zero coupon rates. Store rate * freq / 12 for efficiency. double dt = 12.0 / freq * interpTimes[0] / 365.0; if (dt > 0.0) { flowZcRates.Add((Math.Pow(1.0 / flowDfs[0], 1.0 / dt) - 1.0)); } else { flowZcRates.Add(0.0); } flowZcTimes.Add(dt); for (i = 1; i < nCpns; i++) { if (nCpns == 2) { dt = 12.0 / freq * interpTimes[i] / 365.0; flowZcRates.Add((1.0 / flowDfs[i] - 1.0) / dt); flowZcTimes.Add(dt); } else { dt = 12.0 / freq * interpTimes[i] / 365.0; flowZcRates.Add((Math.Pow(1.0 / flowDfs[i], 1.0 / dt) - 1.0)); flowZcTimes.Add(dt); } } // Interpolate inflation curve. double[] infFactors = InterpolateInflationFactors(adjSchedule, settleDate, infCurve); accrual *= infFactors[nCpns]; cleanPrice *= infFactors[nCpns]; // Iterative solver: Halley's method as faster convergence than Newton-Raphson. for (ctr = 0; ctr < 100; ctr++) { // Initial payment double tmp = (cleanPrice + accrual) * Math.Pow(1.0 + (flowZcRates[0] + guess), -flowZcTimes[0]); double pv = -tmp; double dPvdZ = flowZcTimes[0] * tmp / (1.0 + flowZcRates[0] + guess); double d2PvdZ2 = -flowZcTimes[0] * (1.0 + flowZcTimes[0]) * tmp / ((1.0 + flowZcRates[0] + guess) * (1.0 + flowZcRates[0] + guess)); if (nCpns == 2 && country != Country.FR && country != Country.UK) { tmp = 100.0 * infFactors[1] * (1.0 + (bExDiv ? 0.0 : coupon) * freq / 1200) / (1.0 + (flowZcRates[1] + guess) * flowZcTimes[1]); pv += tmp; dPvdZ -= flowZcTimes[1] * tmp / (1.0 + (flowZcRates[1] + guess) * flowZcTimes[1]); d2PvdZ2 += 2.0 * flowZcTimes[1] * flowZcTimes[1] * tmp / ((1.0 + (flowZcRates[1] + guess) * flowZcTimes[1]) * (1.0 + (flowZcRates[1] + guess) * flowZcTimes[1])); } else { // first coupon i = (bLongFirstCpn ? 2 : 1); tmp = (bExDiv ? 0.0 : firstCpnSize) * 100.0 * infFactors[i] * coupon * freq / 1200 * Math.Pow(1.0 + flowZcRates[i] + guess, -flowZcTimes[i]); pv += tmp; dPvdZ -= flowZcTimes[i] * tmp / (1.0 + flowZcRates[i] + guess); d2PvdZ2 += flowZcTimes[i] * (1.0 + flowZcTimes[i]) * tmp / ((1.0 + flowZcRates[i] + guess) * (1.0 + flowZcRates[i] + guess)); for (i++; i < nCpns; i++) { tmp = 100.0 * infFactors[i] * coupon * freq / 1200 * Math.Pow(1.0 + flowZcRates[i] + guess, -flowZcTimes[i]); pv += tmp; dPvdZ -= flowZcTimes[i] * tmp / (1.0 + flowZcRates[i] + guess); d2PvdZ2 += flowZcTimes[i] * (1.0 + flowZcTimes[i]) * tmp / ((1.0 + flowZcRates[i] + guess) * (1.0 + flowZcRates[i] + guess)); } // Final principal tmp = 100.0 * infFactors[i - 1] * Math.Pow(1.0 + flowZcRates[i - 1] + guess, -flowZcTimes[i - 1]); pv += tmp; dPvdZ -= flowZcTimes[i - 1] * tmp / (1.0 + flowZcRates[i - 1] + guess); d2PvdZ2 += flowZcTimes[i - 1] * (1.0 + flowZcTimes[i - 1]) * tmp / ((1.0 + flowZcRates[i - 1] + guess) * (1.0 + flowZcRates[i - 1] + guess)); } // Convergence criterion is Pv within 1.0e-8 after allowing for Price ~ 100 if (Math.Abs(pv) < 1.0e-7) { result[0] = guess * 12.0 / freq; result[1] = dPvdZ * freq / 12.0; return result; } else { // Halley's method guess = guess - pv / (dPvdZ - pv * d2PvdZ2 / (2.0 * dPvdZ)); } } result[0] = -1.0; result[1] = 0.0; return result; }
public static double[] PriceFromYield(Country country, DateTime settleDate, double yield, DateTime startDate, DateTime firstCpnDate, DateTime maturityDate, double coupon, long freq, int exDivDays = 0, InflationCurve infCurve = null) { bool bLongFirstCpn = firstCpnDate > settleDate && firstCpnDate > startDate.AddDays(1).AddMonths((int)freq).AddDays(-1); DateTime adjSettleDate = (startDate > settleDate ? startDate : settleDate); List<DateTime> schedule = GenerateSchedule((bLongFirstCpn && adjSettleDate < firstCpnDate ? startDate : adjSettleDate), maturityDate, freq); List<DateTime> adjSchedule = GenerateAdjustedSchedule(schedule, null, "F"); double[] result = new double[2]; double delay = 0.0, firstCpnSize = 1.0; long nCpns = schedule.Count; // Boundary case if (settleDate.Date >= maturityDate.Date) { if (settleDate.Date == maturityDate.Date) result[0] = 100.0 + coupon * freq / 12; return result; } // Specific country conventions: // Italy: accrual rounding, semi-annual coupons but yield quoted with annual compounding frequency. // All (US and Eurozone) bar France use US convention - i.e. last coupon period simple yield. // Spain: final principal payment use adjusted payment date. double dcf = CalcBondAccrualDcf(settleDate, startDate, firstCpnDate, schedule, country); // In case of stub period at start if (firstCpnDate > settleDate) { if (bLongFirstCpn) { firstCpnSize = 1.0 + (schedule[1] - startDate).TotalDays / (schedule[1] - schedule[0]).TotalDays; } else { firstCpnSize = (firstCpnDate - (schedule[0] < startDate ? startDate : schedule[0])).TotalDays / (schedule[1] - schedule[0]).TotalDays; } } double accrual = 100.0 * coupon * freq / 1200 * dcf; // Interpolate inflation curve. double[] infFactors = InterpolateInflationFactors(adjSchedule, settleDate, infCurve); accrual *= infFactors[nCpns]; // Modify DCF for use in discounting loop. If long first coupon then need to reduce i by 1 in the coupon loop. Do this by incrementing adjusted DCF. dcf += (bLongFirstCpn ? 2.0 : 1.0) - firstCpnSize; if (country == Country.IT) { accrual = Math.Round(accrual, 5); if (nCpns > 2) { yield = 2.0 * (Math.Sqrt(1 + yield) - 1.0); } } double pv = 0.0; double dPvdY = 0.0; if (nCpns == 2 && country != Country.FR && country != Country.UK) { if (country == Country.ES) { delay = (adjSchedule[1] - schedule[1]).TotalDays / (schedule[1] - schedule[0]).TotalDays; } pv += 100.0 * infFactors[1] * (1.0 + coupon * freq / 1200) / (1.0 + yield * freq / 12 * (1.0 + delay - dcf)); dPvdY -= 100.0 * infFactors[1] * (1 - dcf) * freq / 12 * (1.0 + coupon * freq / 1200) / Math.Pow(1.0 + yield * freq / 12 * (1.0 + delay - dcf), 2); } else { int i; for (i = (bLongFirstCpn ? 2 : 1); i < nCpns; i++) { pv += 100.0 * infFactors[i] * coupon * freq / 1200 / Math.Pow(1.0 + yield * freq / 12, i - dcf); dPvdY -= 100.0 * infFactors[i] * (i - dcf) * freq / 12 * coupon * freq / 1200 / Math.Pow(1.0 + yield * freq / 12, i + 1 - dcf); // If stub period then first coupon is non-standard size. if (i == (bLongFirstCpn ? 2 : 1)) { pv *= firstCpnSize; dPvdY *= firstCpnSize; } } if (country == Country.ES) { delay = (adjSchedule[i - 1] - schedule[i - 1]).TotalDays / (schedule[i - 1] - schedule[i - 2]).TotalDays; } pv += 100.0 * infFactors[i - 1] / Math.Pow(1.0 + yield * freq / 12, i - 1 + delay - dcf); dPvdY -= 100.0 * infFactors[i - 1] * freq / 12 * (i - dcf) / Math.Pow(1.0 + yield * freq / 12, i - 1 + delay - dcf); } result[0] = (pv - accrual) / infFactors[nCpns]; result[1] = dPvdY; // Handle exdiv period. First coupon payment comes out of pv calculation and out of accrual. if (settleDate.AddDays(exDivDays) >= schedule[1]) { if (nCpns == 2 && country != Country.FR && country != Country.UK) { if (country == Country.ES) { delay = (adjSchedule[1] - schedule[1]).TotalDays/(schedule[1] - schedule[0]).TotalDays; } result[0] += coupon * infFactors[1] * freq / 12 * (1.0 - 1.0 / (1.0 + yield * freq / 12 * (1.0 + delay - dcf))); result[1] += (1.0 + delay - dcf) * coupon * infFactors[1] * freq / 12 * freq / 12 / Math.Pow(1.0 + yield * freq / 12 * (1.0 + delay - dcf), 2); } else { result[0] += (bLongFirstCpn ? firstCpnSize : 1.0) * coupon * infFactors[1] * freq / 12 * (1.0 - Math.Pow(1.0 + yield * freq / 12, dcf - (bLongFirstCpn ? 2.0 : 1.0))); result[1] += (bLongFirstCpn ? firstCpnSize * (2.0 - dcf) : 1.0 - dcf) * coupon * infFactors[1] * freq / 12 * freq / 12 * Math.Pow(1.0 + yield * freq / 12, dcf - (bLongFirstCpn ? 2.0 : 1.0)); } } return result; }
public static double[] InterpolateInflationFactors(List<DateTime> schedule, DateTime settleDate, InflationCurve infCurve) { double[] infFactors; int k = 0, i = 0; if (schedule != null) { infFactors = new double[schedule.Count + 1]; for (i = 0; i < schedule.Count; i++) { if (infCurve == null || infCurve.InterpMethod != 1 || schedule[i].AddMonths(-infCurve.InfGap) < infCurve.FcstDates[0]) infFactors[i] = 1.0; else { while (k < infCurve.FcstDates.Length - 1 && schedule[i].AddMonths(-infCurve.InfGap).Date > infCurve.FcstDates[k].Date) k++; // linear interpolation infFactors[i] = ((schedule[i].AddMonths(-infCurve.InfGap) - infCurve.FcstDates[k - 1]).TotalDays * infCurve.FcstIndex[k] + (infCurve.FcstDates[k] - schedule[i].AddMonths(-infCurve.InfGap)).TotalDays * infCurve.FcstIndex[k - 1]) / (infCurve.FcstDates[k] - infCurve.FcstDates[k - 1]).TotalDays; infFactors[i] /= infCurve.InitialFixing; } } } else infFactors = new double[1]; // Factor for settleDate goes at end if (infCurve == null || infCurve.InterpMethod != 1 || settleDate.AddMonths(-infCurve.InfGap) < infCurve.FcstDates[0]) infFactors[i] = 1.0; else { k = 0; while (k < infCurve.FcstDates.Length - 1 && settleDate.AddMonths(-infCurve.InfGap).Date > infCurve.FcstDates[k].Date) k++; // linear interpolation infFactors[i] = ((settleDate.AddMonths(-infCurve.InfGap) - infCurve.FcstDates[k - 1]).TotalDays * infCurve.FcstIndex[k] + (infCurve.FcstDates[k] - settleDate.AddMonths(-infCurve.InfGap)).TotalDays * infCurve.FcstIndex[k - 1]) / (infCurve.FcstDates[k] - infCurve.FcstDates[k - 1]).TotalDays; infFactors[i] /= infCurve.InitialFixing; } return infFactors; }
// Par-Par Asset Swap pricer public static double ParParAssetSwapSpread(DateTime settleDate, DateTime bondEffectiveDate, DateTime bondFirstCpnDate, DateTime bondMaturityDate, double bondCoupon, Country country, DayCountType bondDctType, long bondFreq, double bondCleanPrice, DayCountType floatDctType, long floatFreq, DateTime[] discCurveDates, double[] discDfs, DateTime[] fcstCurveDates, double[] fcstDfs, List<DateTime> holidays, double lastFixing = 0.0, int blendIndex = 0, InflationCurve infCurve = null) { bool bLongFirstCpn = bondFirstCpnDate > settleDate && bondFirstCpnDate > bondEffectiveDate.AddDays(1).AddMonths((int)bondFreq).AddDays(-1); DateTime adjSettleDate = (bondEffectiveDate > settleDate ? bondEffectiveDate : settleDate); List<DateTime> schedule = GenerateSchedule(bLongFirstCpn && adjSettleDate > bondFirstCpnDate ? bondEffectiveDate : adjSettleDate, bondMaturityDate, bondFreq); double dcf = CalcBondAccrualDcf(settleDate, bondEffectiveDate, bondFirstCpnDate, schedule, country); double accrual = 100.0 * bondCoupon * bondFreq / 1200 * dcf; // Interpolate inflation curve. double[] infFactors = InterpolateInflationFactors(null, settleDate, infCurve); double fixedPv = SwapAnalytics.PriceFixedLeg(bondEffectiveDate, bondMaturityDate, bondCoupon, bondDctType, bondFreq, discCurveDates, discDfs, holidays, blendIndex, false, bLongFirstCpn, infCurve, infFactors[0])[0]; return SwapAnalytics.CalcSwapMargin(bondEffectiveDate, bondMaturityDate, -1.0 + (bondCleanPrice + accrual) / 100.0 - fixedPv / infFactors[0], floatDctType, floatFreq, discCurveDates, discDfs, fcstCurveDates, fcstDfs, holidays, lastFixing, blendIndex); }
public static double PriceFromZSpread(Country country, DateTime settleDate, double spread, DateTime startDate, DateTime firstCpnDate, DateTime maturityDate, double coupon, long freq, DateTime[] curveDates, double[] dfs, List<DateTime> holidays, int exDivDays = 0, InflationCurve infCurve = null) { bool bLongFirstCpn = firstCpnDate > settleDate && firstCpnDate > startDate.AddDays(1).AddMonths((int)freq).AddDays(-1); DateTime adjSettleDate = (startDate > settleDate ? startDate : settleDate); List<DateTime> schedule = GenerateSchedule((bLongFirstCpn && adjSettleDate < firstCpnDate ? startDate : adjSettleDate), maturityDate, freq); List<DateTime> adjSchedule = GenerateAdjustedSchedule(schedule, holidays, "F"); double[] interpTimes = new double[adjSchedule.Count]; List<double> flowZcTimes = new List<double>(); List<double> flowZcRates = new List<double>(); double firstCpnSize = 1.0; long nCpns = schedule.Count; int i; bool bExDiv = settleDate.AddDays(exDivDays) >= schedule[(bLongFirstCpn ? 2 : 1)]; spread *= freq / 12.0; double dcf = CalcBondAccrualDcf(settleDate, startDate, firstCpnDate, schedule, country); // In case of stub period at start if (firstCpnDate > settleDate) { if (bLongFirstCpn) { firstCpnSize = 1.0 + (schedule[1] - startDate).TotalDays / (schedule[1] - schedule[0]).TotalDays; } else { firstCpnSize = (firstCpnDate - (startDate > schedule[0] ? startDate : schedule[0])).TotalDays / (schedule[1] - schedule[0]).TotalDays; } } double accrual = 100.0 * coupon * freq / 1200 * (dcf - (bExDiv ? firstCpnSize : 0.0)); if (country == Country.IT) { accrual = Math.Round(accrual, 5); } // Use adjusted schedule dates for payments. // Convert dates to times for spline interpolator interpTimes[0] = (settleDate - curveDates[0]).TotalDays; for (i = 1; i < adjSchedule.Count; i++) { interpTimes[i] = (adjSchedule[i] - curveDates[0]).TotalDays; } List<double> flowDfs = InterpDfSpline(interpTimes, curveDates, dfs); // Calculate base discount factors (before spread) and convert to zero coupon rates. Store rate * freq / 12 for efficiency. double dt = 12.0 / freq * interpTimes[0] / 365.0; if (dt > 0.0) { flowZcRates.Add((Math.Pow(1.0 / flowDfs[0], 1.0 / dt) - 1.0)); } else { flowZcRates.Add(0.0); } flowZcTimes.Add(dt); for (i = 1; i < nCpns; i++) { if (nCpns == 2) { dt = 12.0 / freq * interpTimes[i] / 365.0; flowZcRates.Add((1.0 / flowDfs[i] - 1.0) / dt); flowZcTimes.Add(dt); } else { dt = 12.0 / freq * interpTimes[i] / 365.0; flowZcRates.Add((Math.Pow(1.0 / flowDfs[i], 1.0 / dt) - 1.0)); flowZcTimes.Add(dt); } } // Interpolate inflation curve. double[] infFactors = InterpolateInflationFactors(adjSchedule, settleDate, infCurve); accrual *= infFactors[nCpns]; // Initial payment double pv = -accrual * Math.Pow(1.0 + (flowZcRates[0] + spread), -flowZcTimes[0]); if (nCpns == 2 && country != Country.FR && country != Country.UK) { pv += 100.0 * infFactors[1] * (1.0 + (bExDiv ? 0.0 : coupon) * freq / 1200) / (1.0 + (flowZcRates[1] + spread) * flowZcTimes[1]); } else { // first coupon i = (bLongFirstCpn ? 2 : 1); pv += firstCpnSize * 100.0 * infFactors[i] * (bExDiv ? 0.0 : coupon) * freq / 1200 * Math.Pow(1.0 + flowZcRates[i] + spread, -flowZcTimes[i]); for (i++; i < nCpns; i++) { pv += 100.0 * infFactors[i] * coupon * freq / 1200 * Math.Pow(1.0 + flowZcRates[i] + spread, -flowZcTimes[i]); } // Final principal pv += 100.0 * infFactors[i - 1] * Math.Pow(1.0 + flowZcRates[i - 1] + spread, -flowZcTimes[i - 1]); } return pv * Math.Pow(1.0 + (flowZcRates[0] + spread), flowZcTimes[0]) / infFactors[nCpns]; }
public static double SolveZSpread(Country country, DateTime settleDate, double cleanPrice, DateTime startDate, DateTime firstCpnDate, DateTime maturityDate, double coupon, long freq, DateTime[] curveDates, double[] dfs, List<DateTime> holidays, int exDivDays = 0, InflationCurve infCurve = null) { return SolveZSpread2(country, settleDate, cleanPrice, startDate, firstCpnDate, maturityDate, coupon, freq, curveDates, dfs, holidays, exDivDays, infCurve)[0]; }