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.function.EffectiveInterestRateFunction;
26  import com.nickokiss.investor.function.Function;
27  import com.nickokiss.investor.functioncalc.FunctionCalc;
28  import com.nickokiss.investor.functioncalc.IterativeFunctionCalc;
29  import com.nickokiss.investor.util.Range;
30  
31  /**
32   * <pre>
33   * V = A * (1 + r/m)^k
34   *
35   * V - future value
36   * A - present value
37   * r - nominal interest rate
38   * k - number of periods of investment
39   * m - number of periods a year
40   *
41   * er = (1 + r/m)^m - 1
42   *
43   * er - effective interest rate
44   *
45   * Additional variables:
46   * v/a = (1 + r/m)^k - growth
47   * (v/a)^(1/k) = (1 + r/m) = (er + 1)^(1/m) - periodGrowth
48   * er + 1 = (1 + r/m)^m - yearlyGrowth
49   * </pre>
50   * 
51   * @author Tomasz Koscinski <tomasz.koscinski@nickokiss.com>
52   */
53  public class CompoundInterestCalc {
54  
55    private MathCalc mathCalc = new MathCalc();
56  
57    /**
58     * TRANSFORMATION:
59     *
60     * <pre>
61     * V = A * (1 + r/m)^k
62     * v/a = (1 + r/m)^k
63     * 1 + r/m = (v/a)^(1/k)
64     * r/m = (v/a)^(1/k) - 1
65     * r = m * ((v/a)^(1/k) - 1)
66     * </pre>
67     */
68    public BigDecimal getNominalInterestRate(BigDecimal futureValue, BigDecimal presentValue, BigDecimal periodsPerYear,
69        BigDecimal investmentPeriods) {
70  
71      // v/a
72      BigDecimal growth = mathCalc.div(futureValue, presentValue);
73      // (v/a)^(1/k)
74      BigDecimal growthPerPeriod = mathCalc.pow(growth, mathCalc.invert(investmentPeriods));
75      // (v/a)^(1/k) - 1
76      BigDecimal increasePerPeriod = growthPerPeriod.subtract(mathCalc.ONE);
77      // m * ((v/a)^(1/k) - 1)
78      BigDecimal nominalInterestRate = increasePerPeriod.multiply(periodsPerYear);
79      return nominalInterestRate;
80    }
81  
82    /**
83     * TRANSFORMATION:
84     *
85     * <pre>
86     * v = a * (1 + r/m)^k
87     * v/a = (1 + r/m)^k
88     * 1 + r/m = (v/a)^(1/k)
89     * r/m = (v/a)^(1/k) - 1
90     * m = r / ((v/a)^(1/k) - 1)
91     * </pre>
92     */
93    public BigDecimal getPeriodsPerYear(BigDecimal futureValue, BigDecimal presentValue, BigDecimal nominalInterestRate,
94        BigDecimal investmentPeriods) {
95  
96      // V/A
97      BigDecimal growth = mathCalc.div(futureValue, presentValue);
98      // (V/A)^(1/k)
99      BigDecimal growthPerPeriod = mathCalc.pow(growth, mathCalc.invert(investmentPeriods));
100     // (V/A)^(1/k) - 1
101     BigDecimal increasePerPeriod = growthPerPeriod.subtract(mathCalc.ONE);
102     // r / ((V/A)^(1/k) - 1)
103     BigDecimal periodsPerYear = mathCalc.div(nominalInterestRate, increasePerPeriod);
104     return periodsPerYear;
105   }
106 
107   /**
108    * V = A * (1 + r/m)^k
109    */
110   public BigDecimal getFutureValue(BigDecimal presentValue, BigDecimal nominalInterestRate, BigDecimal periodsPerYear,
111       BigDecimal investmentPeriods) {
112 
113     // (1 + r/m)^k
114     BigDecimal growth = getGrowth(nominalInterestRate, periodsPerYear, investmentPeriods);
115     BigDecimal futureValue = presentValue.multiply(growth);
116     return futureValue;
117   }
118 
119   /**
120    * TRANSFORMATION:
121    *
122    * <pre>
123    * V = A * (1 + r/m)^k
124    * A = V / (1 + r/m)^k
125    * </pre>
126    */
127   public BigDecimal getPresentValue(BigDecimal futureValue, BigDecimal nominalInterestRate, BigDecimal periodsPerYear,
128       BigDecimal investmentPeriods) {
129 
130     BigDecimal growth = getGrowth(nominalInterestRate, periodsPerYear, investmentPeriods);
131     BigDecimal presentValue = mathCalc.div(futureValue, growth);
132     return presentValue;
133   }
134 
135   /**
136    * growth = (1 + r/m)^k = v/a
137    */
138   public BigDecimal getGrowth(BigDecimal interest, BigDecimal periodsPerYear, BigDecimal investmentPeriods) {
139 
140     // r/m
141     BigDecimal increasePerPeriod = mathCalc.div(interest, periodsPerYear);
142     // 1 + r/m
143     BigDecimal growthPerPeriod = increasePerPeriod.add(mathCalc.ONE);
144     // growth
145     BigDecimal growth = mathCalc.pow(growthPerPeriod, investmentPeriods);
146     return growth;
147   }
148 
149   /**
150    * TRANSFORMATION:
151    *
152    * <pre>
153    * v = a * (1 + r/m)^k
154    * v/a = (1 + r/m)^k
155    * k = log[1 + r/m](v/a)
156    * k = log(v/a) / log(1 + r/m)
157    * k = log(growth) / log(growthPP)
158    * </pre>
159    */
160   public BigDecimal getInvestmentPeriods(BigDecimal futureValue, BigDecimal presentValue, BigDecimal nominalInterestRate,
161       BigDecimal periodsPerYear) {
162 
163     // v/a
164     BigDecimal growth = mathCalc.div(futureValue, presentValue);
165     // r/m
166     BigDecimal increasePerPeriod = mathCalc.div(nominalInterestRate, periodsPerYear);
167     // 1 + r/m
168     BigDecimal growthPerPeriod = increasePerPeriod.add(mathCalc.ONE);
169     // log[1 + r/m](v/a) = log(v/a) / log(1 + r/m)
170     BigDecimal investmentPeriods = mathCalc.log(growthPerPeriod, growth);
171     return investmentPeriods;
172   }
173 
174   /**
175    * er = (1 + r/m)^m - 1
176    */
177   public BigDecimal getEffectiveInterestRate(BigDecimal nominalInterestRate, BigDecimal periodsPerYear) {
178 
179     // (1 + r/m)^m
180     BigDecimal yearlyGrowth = getGrowth(nominalInterestRate, periodsPerYear, periodsPerYear);
181     BigDecimal effectiveInterestRate = yearlyGrowth.subtract(mathCalc.ONE);
182     return effectiveInterestRate;
183   }
184 
185   /**
186    * TRANSFORMATION:
187    *
188    * <pre>
189    * er = (1 + r/m)^m - 1
190    * er + 1 = (1 + r/m)^m
191    * 1 + r/m = (er + 1)^(1/m)
192    * r/m = (er + 1)^(1/m) - 1
193    * r = m * ((er + 1)^(1/m) - 1)
194    * </pre>
195    */
196   public BigDecimal getNominalInterestRate(BigDecimal effectiveInterestRate, BigDecimal periodsPerYear) {
197 
198     // (er + 1)
199     BigDecimal yearlyGrowth = effectiveInterestRate.add(mathCalc.ONE);
200     // (er + 1)^(1/m)
201     BigDecimal growthPerPeriod = mathCalc.pow(yearlyGrowth, mathCalc.invert(periodsPerYear));
202     // ((er + 1)^(1/m) - 1)
203     BigDecimal increasePerPeriod = growthPerPeriod.subtract(mathCalc.ONE);
204     // m * ((er + 1)^(1/m) - 1)
205     BigDecimal nominalInterestRate = periodsPerYear.multiply(increasePerPeriod);
206     return nominalInterestRate;
207   }
208 
209   public BigDecimal getPeriodsPerYear(BigDecimal effectiveInterestRate, BigDecimal nominalInterestRate) {
210     FunctionCalc functionCalc = new IterativeFunctionCalc();
211     Range initialRange = new Range(mathCalc.ONE, mathCalc.TEN.pow(3));
212     Function function = new EffectiveInterestRateFunction(nominalInterestRate);
213     Range resultRange = functionCalc.getParameterRange(effectiveInterestRate, function, mathCalc.ONE, initialRange);
214     return resultRange.getMiddle();
215   }
216 
217   public BigDecimal getGrowthFactor(BigDecimal nominalInterestRate, BigDecimal periodsPerYear, BigDecimal investmentYears) {
218     return getGrowth(nominalInterestRate, periodsPerYear, periodsPerYear.multiply(investmentYears));
219   }
220 
221   public BigDecimal getDiscountFactor(BigDecimal nominalInterestRate, BigDecimal periodsPerYear, BigDecimal investmentYears) {
222     return getGrowth(nominalInterestRate, periodsPerYear, periodsPerYear.multiply(investmentYears.negate()));
223   }
224 
225   /**
226    * <pre>
227    * f[i,j] = m * ( ((1 + sj/m)^j) / ((1+si/m)^i) )^(1/(j-i)) - m
228    *
229    * m - payments per year
230    * si - spot rate at time i
231    * sj - spot rate at time j
232    * i - time i
233    * j - time j
234    * </pre>
235    */
236   public BigDecimal getForwardRate(BigDecimal spotRate1, BigDecimal time1, BigDecimal spotRate2, BigDecimal time2, BigDecimal periodsPerYear) {
237     // ((1 + sj/m)^j)
238     BigDecimal sjPart = mathCalc.pow(mathCalc.div(spotRate2, periodsPerYear).add(mathCalc.ONE), time2);
239     // ((1 + si/m)^i)
240     BigDecimal siPart = mathCalc.pow(mathCalc.div(spotRate1, periodsPerYear).add(mathCalc.ONE), time1);
241     // ((1 + sj/m)^j) / ((1+si/m)^i)
242     BigDecimal base = mathCalc.div(sjPart, siPart);
243     // (1/(j-i))
244     BigDecimal exponent = mathCalc.div(mathCalc.ONE, time2.subtract(time1));
245     // m * ( ((1 + sj/m)^j) / ((1+si/m)^i) )^(1/(j-i)) - m
246     return periodsPerYear.multiply(mathCalc.pow(base, exponent)).subtract(periodsPerYear);
247   }
248 
249   public BigDecimal getDiscountFactor(BigDecimal spotRate1, BigDecimal time1, BigDecimal spotRate2, BigDecimal time2, BigDecimal periodsPerYear) {
250     BigDecimal forwardRate = getForwardRate(spotRate1, time1, spotRate2, time2, periodsPerYear);
251     return getDiscountFactorFromForwardRate(forwardRate, time1, time2);
252   }
253 
254   /**
255    * d[i,j] = (1 / 1 + f[i,j])^(j-i)
256    */
257   public BigDecimal getDiscountFactorFromForwardRate(BigDecimal forwardRate, BigDecimal time1, BigDecimal time2) {
258     // (1 / 1 + f[i,j])^(j-i)
259     return mathCalc.pow(mathCalc.invert(forwardRate.add(mathCalc.ONE)), time2.subtract(time1));
260   }
261 }