1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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
47
48
49
50
51
52
53
54
55
56
57
58
59 public BigDecimal getCurrentPrice(BigDecimal faceValue, BigDecimal yield, BigDecimal paymentsPerYear, BigDecimal remainingCouponPayments,
60 BigDecimal couponValue) {
61
62
63 BigDecimal faceValuePV = compoundInterestCalc.getPresentValue(faceValue, yield, paymentsPerYear, remainingCouponPayments);
64
65
66 BigDecimal couponPaymentsSum = getCouponYearAmount(faceValue, couponValue);
67
68
69 BigDecimal couponSumPV = annuityCalc.getPresentValue(couponPaymentsSum, yield, paymentsPerYear, remainingCouponPayments);
70
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
131
132
133
134
135
136
137
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173 public BigDecimal getRemainingCouponPayments(BigDecimal currentPrice, BigDecimal faceValue, BigDecimal yield, BigDecimal paymentsPerYear,
174 BigDecimal couponRate) {
175
176 BigDecimal cr = mathCalc.div(getCouponYearAmount(faceValue, couponRate), yield);
177
178 BigDecimal value = mathCalc.div(currentPrice.subtract(cr), faceValue.subtract(cr));
179
180 BigDecimal base = mathCalc.ONE.add(mathCalc.div(yield, paymentsPerYear));
181
182 return mathCalc.log(base, value).negate();
183 }
184
185
186
187
188
189
190
191
192
193
194
195 public BigDecimal getModifiedDuration(BigDecimal duration, BigDecimal yield, BigDecimal paymentsPerYear) {
196
197 return compoundInterestCalc.getPresentValue(duration, yield, paymentsPerYear, mathCalc.ONE);
198 }
199
200
201
202
203
204
205
206
207
208
209
210
211
212 public BigDecimal estimatePriceDifference(BigDecimal modifiedDuration, BigDecimal price, BigDecimal yieldDifference) {
213
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
228
229
230
231
232
233
234
235
236
237
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
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 }