1   /**
2    * Investor, the open-source investment library
3    *
4    * (C) Copyright 2008, by individual contributors as indicated by the @author tag.
5    *
6    * This library is free software; you can redistribute it and/or modify it
7    * under the terms of the GNU Lesser General Public License as
8    * published by the Free Software Foundation; either version 2.1 of
9    * the License, or (at your option) any later version.
10   *
11   * This software is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14   * Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public
17   * License along with this software; if not, write to the Free
18   * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
19   * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
20   */
21  package com.nickokiss.investor.calc;
22  
23  import java.math.BigDecimal;
24  
25  import com.nickokiss.investor.fin.element.Bond;
26  import com.nickokiss.investor.fin.element.CashFlowStream;
27  import com.nickokiss.investor.fin.element.StreamElement;
28  import com.nickokiss.investor.function.BondPriceFunction;
29  import com.nickokiss.investor.function.Function;
30  import com.nickokiss.investor.functioncalc.FunctionCalc;
31  import com.nickokiss.investor.functioncalc.IterativeFunctionCalc;
32  import com.nickokiss.investor.util.Range;
33  import com.nickokiss.investor.util.TkFinConstants;
34  
35  /**
36   *
37   * @author Tomasz Koscinski <tomasz.koscinski@nickokiss.com>
38   */
39  public class BondCalc {
40  
41    private CompoundInterestCalc compoundInterestCalc = new CompoundInterestCalc();
42    private AnnuityCalc annuityCalc = new AnnuityCalc();
43    private MathCalc mathCalc = new MathCalc();
44  
45    /**
46     * <pre>
47     * P - current price of the bond
48     * F - face value of the bond
49     * r - yield to maturity (YTM)
50     * m - number of coupon payments per year
51     * n - number of remaining coupon payments
52     * C - coupon payments sum = F * couponValue
53     *
54     * Bond price formula:
55     * P = (F * (1 + r/m)^(-n)) + (C/r)*(1 - (1 + r/m)^(-n))
56     * P = compIntCalc.getPresentValue(F,r,m,n) + annCalc.getPresentValue(C,r,m,n)
57     * </pre>
58     */
59    public BigDecimal getCurrentPrice(BigDecimal faceValue, BigDecimal yield, BigDecimal paymentsPerYear, BigDecimal remainingCouponPayments,
60        BigDecimal couponValue) {
61  
62      // (F * (1 + r/m)^(-n))
63      BigDecimal faceValuePV = compoundInterestCalc.getPresentValue(faceValue, yield, paymentsPerYear, remainingCouponPayments);
64  
65      // C = F * couponValue
66      BigDecimal couponPaymentsSum = getCouponYearAmount(faceValue, couponValue);
67  
68      // (C/r)*(1 - (1 + r/m)^(-n))
69      BigDecimal couponSumPV = annuityCalc.getPresentValue(couponPaymentsSum, yield, paymentsPerYear, remainingCouponPayments);
70      // (F * (1 + r/m)^(-n)) + (C/r)*(1 - (1 + r/m)^(-n))
71      return faceValuePV.add(couponSumPV);
72    }
73  
74    private BigDecimal getCouponYearAmount(BigDecimal faceValue, BigDecimal couponValue) {
75      return faceValue.multiply(couponValue);
76    }
77  
78    private BigDecimal getCouponPeriodAmount(BigDecimal faceValue, BigDecimal couponValue, BigDecimal paymentsPerYear) {
79      return mathCalc.div(getCouponYearAmount(faceValue, couponValue), paymentsPerYear);
80    }
81  
82    public BigDecimal getYield(Bond bond, BigDecimal bondPrice, int scale) {
83      BigDecimal remainingCouponPayments = bond.getTimeToMaturity().multiply(bond.getPaymentsPerYear());
84      return getYield(bondPrice, bond.getFaceValue(), bond.getPaymentsPerYear(), remainingCouponPayments, bond.getCouponRate(), scale);
85    }
86  
87    public BigDecimal getYield(BigDecimal currentPrice, BigDecimal faceValue, BigDecimal paymentsPerYear, BigDecimal remainingCouponPayments,
88        BigDecimal couponValue, int scale) {
89      BigDecimal maxSize = mathCalc.getUnit(scale);
90      Range yieldRange = new Range(maxSize, mathCalc.TWO);
91  
92      FunctionCalc functionCalc = new IterativeFunctionCalc();
93      Function function = new BondPriceFunction(faceValue, paymentsPerYear, remainingCouponPayments, couponValue);
94      Range range = functionCalc.getParameterRange(currentPrice, function, maxSize, yieldRange);
95      return range.getMiddle();
96    }
97  
98    public BigDecimal getCurrentYield(BigDecimal currentPrice, BigDecimal faceValue, BigDecimal couponValue) {
99      return mathCalc.div(mathCalc.ONE_HUNDRED.multiply(getCouponYearAmount(faceValue, couponValue)), currentPrice);
100   }
101 
102   private BigDecimal getRemainingCouponPayments(BigDecimal timeToMaturity, BigDecimal paymentsPerYear) {
103     return timeToMaturity.multiply(paymentsPerYear);
104   }
105 
106   public CashFlowStream getCashFlowStream(BigDecimal faceValue, BigDecimal paymentsPerYear, BigDecimal timeToMaturity, BigDecimal couponValue) {
107     CashFlowStream cashFlowStream = new CashFlowStream();
108     BigDecimal couponAmount = getCouponPeriodAmount(faceValue, couponValue, paymentsPerYear);
109     BigDecimal remainingCouponPayments = getRemainingCouponPayments(timeToMaturity, paymentsPerYear);
110     BigDecimal distance = mathCalc.invert(paymentsPerYear);
111     cashFlowStream.addElements(couponAmount, remainingCouponPayments.intValue(), distance);
112     cashFlowStream.addElement(new StreamElement(faceValue, timeToMaturity));
113     return cashFlowStream;
114   }
115 
116   private BigDecimal getYieldPerPeriod(BigDecimal yield, BigDecimal periodsPerYear) {
117     return mathCalc.div(yield, periodsPerYear);
118   }
119 
120   private BigDecimal getCouponRatePerPeriod(BigDecimal couponValue, BigDecimal periodsPerYear) {
121     return mathCalc.div(couponValue, periodsPerYear);
122   }
123 
124   private BigDecimal getRemainingPeriods(BigDecimal timeToMaturity, BigDecimal periodsPerYear) {
125     return timeToMaturity.multiply(periodsPerYear);
126   }
127 
128   /**
129    *
130    * <pre>
131    * D = ((1 + y)/m*y) - (1 + y + n*(c - y))/(m*c*((1+y)^n - 1) + m*y)
132    *
133    * c - coupon rate per period
134    * y - yield per period
135    * m - periods per year
136    * n - remaining periods
137    * </pre>
138    *
139    */
140   public BigDecimal getMacaulayDuration(BigDecimal yield, BigDecimal couponValue, BigDecimal periodsPerYear, BigDecimal timeToMaturity) {
141     BigDecimal c = getCouponRatePerPeriod(couponValue, periodsPerYear);
142     BigDecimal y = getYieldPerPeriod(yield, periodsPerYear);
143     BigDecimal n = getRemainingPeriods(timeToMaturity, periodsPerYear);
144     BigDecimal m = periodsPerYear;
145     BigDecimal y1 = mathCalc.ONE.add(y);
146     BigDecimal my = m.multiply(y);
147     BigDecimal part1 = mathCalc.div(y1, my);
148     BigDecimal part21 = y1.add(n.multiply(c.subtract(y)));
149     BigDecimal part22 = mathCalc.pow(y1, n).subtract(mathCalc.ONE).multiply(c).multiply(m).add(my);
150     return part1.subtract(mathCalc.div(part21, part22));
151   }
152 
153   /**
154    * <pre>
155    * TRANSFORMATION:
156    *
157    * P = (F * (1 + r/m)^(-n)) + (C/r)*(1 - (1 + r/m)^(-n))
158    *
159    * x = (1 + r/m)&circ;(-n)
160    *
161    * P = F*x + (C/r)*(1-x)
162    * P = F*x + C/r - (C/r)*x
163    * P - C/r = F*x - (C/r)*x
164    * P - C/r = x* (F - (C/r))
165    * x = (P - C/r) / (F - C/r)
166    *
167    * (1+r/m)^(-n) = (P - C/r) / (F - C/r)
168    *
169    * (-n) = log[1+r/m]((P - C/r) / (F - C/r))
170    * n = -log[1+r/m]((P - C/r) / (F - C/r))
171    * </pre>
172    */
173   public BigDecimal getRemainingCouponPayments(BigDecimal currentPrice, BigDecimal faceValue, BigDecimal yield, BigDecimal paymentsPerYear,
174       BigDecimal couponRate) {
175     // C/r
176     BigDecimal cr = mathCalc.div(getCouponYearAmount(faceValue, couponRate), yield);
177     // (P - C/r) / (F - C/r)
178     BigDecimal value = mathCalc.div(currentPrice.subtract(cr), faceValue.subtract(cr));
179     // (1 + r/m)
180     BigDecimal base = mathCalc.ONE.add(mathCalc.div(yield, paymentsPerYear));
181     // -log[1+r/m]((P - C/r) / (F - C/r))
182     return mathCalc.log(base, value).negate();
183   }
184 
185   /**
186    * <pre>
187    * Dm = D * ((1 + r/m)^(-1))
188    *
189    * Dm - modified duration
190    * D - duration
191    * r - yield
192    * m - payments per year
193    * </pre>
194    */
195   public BigDecimal getModifiedDuration(BigDecimal duration, BigDecimal yield, BigDecimal paymentsPerYear) {
196     //D * ((1 + r/m)^(-1))
197     return compoundInterestCalc.getPresentValue(duration, yield, paymentsPerYear, mathCalc.ONE);
198   }
199 
200   /**
201    * <pre>
202    * Pdiff = (-1) * Dm * P * rDiff
203    *
204    * Pdiff - price difference
205    * Dm - modified duration
206    * P - price
207    * rDiff - yield difference
208    *
209    * priceDiff = (-1) * modifiedDuration * price * yieldDifference
210    * </pre>
211    */
212   public BigDecimal estimatePriceDifference(BigDecimal modifiedDuration, BigDecimal price, BigDecimal yieldDifference) {
213     //(-1) * Dm * P * rDiff
214     return modifiedDuration.multiply(price).multiply(yieldDifference).negate();
215   }
216 
217   public BigDecimal getCurrentPrice(Bond bond, BigDecimal yield) {
218     BigDecimal remainingCouponPayments = bond.getTimeToMaturity().multiply(bond.getPaymentsPerYear());
219     return getCurrentPrice(bond.getFaceValue(), yield, bond.getPaymentsPerYear(), remainingCouponPayments, bond.getCouponRate());
220   }
221 
222   public BigDecimal getMacaulayDuration(Bond bond) {
223     return getMacaulayDuration(bond.getYield(), bond.getCouponRate(), bond.getPaymentsPerYear(), bond.getTimeToMaturity());
224   }
225 
226   /**
227    * C = (1 / (P * (1+(r/m)^2)) * SUM[k=1,n]((k*(k+1)/m^2) * (ck / (1+(r/m))^k))
228    * C = (1/P) * (1+ r/m)^(-2) * SUM[k=1,n]( (k*(k+1)*ck / m^2) * (1+(r/m))^(-k))
229    * C = PV((1/P), r, m, 2) * SUM[k=1,n](PV((k*(k+1)*ck / m^2), r, m, k))
230    *
231    * C - convexity
232    * P - price
233    * r - yield
234    * m - payments per year
235    * ck - coupon value (coupon period amount)
236    * n - remaining coupon payments
237    * @return
238    */
239   public BigDecimal getConvexity(BigDecimal yield, BigDecimal couponValue, BigDecimal paymentsPerYear, BigDecimal timeToMaturity,
240       BigDecimal faceValue) {
241 
242     BigDecimal remainingCouponPayments = timeToMaturity.multiply(paymentsPerYear);
243     BigDecimal price = getCurrentPrice(faceValue, yield, paymentsPerYear, remainingCouponPayments, couponValue);
244     BigDecimal couponPeriodAmount = getCouponPeriodAmount(faceValue, couponValue, paymentsPerYear);
245     BigDecimal ckm2 = mathCalc.div(couponPeriodAmount, paymentsPerYear.pow(2));
246     // PV((1/P), r, m, 2)
247     BigDecimal first = compoundInterestCalc.getPresentValue(mathCalc.invert(price), yield, paymentsPerYear, mathCalc.TWO);
248 
249     BigDecimal pvSum = mathCalc.ZERO;
250     for (int k = 1; k <= remainingCouponPayments.intValue(); k++) {
251       BigDecimal kk1 = new BigDecimal(k * (k + 1)).setScale(TkFinConstants.DEFAULT_SCALE);
252       BigDecimal kk1ckm2 = kk1.multiply(ckm2);
253       BigDecimal pv = compoundInterestCalc.getPresentValue(kk1ckm2, yield, paymentsPerYear, remainingCouponPayments);
254       pvSum = pvSum.add(pv);
255     }
256     return first.multiply(pvSum);
257   }
258 
259   public BigDecimal estimatePriceDifference(BigDecimal modifiedDuration, BigDecimal price, BigDecimal yieldDifference, BigDecimal convexity) {
260     BigDecimal priceDifference = estimatePriceDifference(modifiedDuration, price, yieldDifference);
261     BigDecimal augend = mathCalc.div(price.multiply(convexity), mathCalc.TWO).multiply(yieldDifference.pow(2));
262     return priceDifference.add(augend);
263   }
264 
265   public BigDecimal getPriceDifference(Bond bond, BigDecimal yield, BigDecimal yieldDiff) {
266     BigDecimal newYield = yield.add(yieldDiff);
267     BigDecimal price = getPrice(bond, yield);
268     BigDecimal newPrice = getPrice(bond, newYield);
269     return newPrice.subtract(price);
270   }
271 
272   public BigDecimal getPrice(Bond bond, BigDecimal yield) {
273     BigDecimal remainingCouponPayments = bond.getTimeToMaturity().multiply(bond.getPaymentsPerYear());
274     return getCurrentPrice(bond.getFaceValue(), yield, bond.getPaymentsPerYear(), remainingCouponPayments, bond.getCouponRate());
275   }
276 
277   public BigDecimal getDurationLimit(BigDecimal yield, BigDecimal paymentsPerYear) {
278     return mathCalc.div(mathCalc.ONE.add(mathCalc.div(yield, paymentsPerYear)), yield);
279   }
280 
281 }