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  import java.util.List;
25  
26  import com.nickokiss.investor.fin.element.CashFlowStream;
27  import com.nickokiss.investor.fin.element.StreamElement;
28  import com.nickokiss.investor.fin.element.StreamElementType;
29  import com.nickokiss.investor.fin.env.Env;
30  import com.nickokiss.investor.function.CashFlowPresentValueFunction;
31  import com.nickokiss.investor.functioncalc.Div2FunctionCalc;
32  import com.nickokiss.investor.util.TkFinConstants;
33  import com.nickokiss.investor.util.TkFinConstructionException;
34  
35  /**
36   * <pre>
37   * V = x0*(1+r/m)^(t-t0) + x1*(1+r/m)^(t-t1) + ... + xN*(1+r/m)^(t-tN)
38   * V = SUM(R=0:N)((xR*(1+r/m)^(t-tR))
39   *
40   * V - value of the stream at time t
41   * t - selected time point since now (in years)
42   * xN - value of n's element's of the stream
43   * tN - time of occurrence of n's element's of the stream
44   * r - nominal interest rate
45   * m - number of compounding periods per year
46   * </pre>
47   *
48   * @author Tomasz Koscinski <tomasz.koscinski@nickokiss.com>
49   */
50  public class CashFlowStreamCalc {
51  
52    private MathCalc mathCalc = new MathCalc();
53  
54    public BigDecimal getBenefitsValue(CashFlowStream cashFlowStream, BigDecimal nominalInterestRate, BigDecimal periodsPerYear,
55        BigDecimal timePoint) {
56      List<StreamElement> elements = cashFlowStream.getElements(StreamElementType.BENEFIT);
57      return getValue(elements, nominalInterestRate, periodsPerYear, timePoint);
58    }
59  
60    public BigDecimal getCostsValue(CashFlowStream cashFlowStream, BigDecimal nominalInterestRate, BigDecimal periodsPerYear, BigDecimal timePoint) {
61      List<StreamElement> elements = cashFlowStream.getElements(StreamElementType.COST);
62      return getValue(elements, nominalInterestRate, periodsPerYear, timePoint);
63    }
64  
65    private BigDecimal getValue(List<StreamElement> elements, BigDecimal nominalInterestRate, BigDecimal periodsPerYear, BigDecimal timePoint) {
66      BigDecimal streamValue = mathCalc.ZERO;
67      for (StreamElement streamElement : elements) {
68        BigDecimal valueAtTime = getStreamElementValue(nominalInterestRate, periodsPerYear, timePoint, streamElement);
69        streamValue = streamValue.add(valueAtTime);
70      }
71      return streamValue;
72    }
73  
74    private BigDecimal getStreamElementValue(BigDecimal nominalInterestRate, BigDecimal periodsPerYear, BigDecimal timePoint,
75        StreamElement streamElement) {
76      BigDecimal presentValue = streamElement.getValue();
77      BigDecimal elementTime = streamElement.getTime();
78      BigDecimal timeDifference = timePoint.subtract(elementTime);
79      BigDecimal investmentPeriods = timeDifference.multiply(periodsPerYear);
80      CompoundInterestCalc interestCalc = new CompoundInterestCalc();
81      BigDecimal valueAtTime = interestCalc.getFutureValue(presentValue, nominalInterestRate, periodsPerYear, investmentPeriods);
82      return valueAtTime;
83    }
84  
85    /**
86     * D = (PV(t0)*t0 + PV(t1)*t1 + ... + PV(tn)*tn) / PV
87     */
88    public BigDecimal getDuration(CashFlowStream cashFlowStream, BigDecimal nominalInterestRate, BigDecimal periodsPerYear) {
89      BigDecimal timePoint = mathCalc.ZERO;
90      BigDecimal weightedSum = mathCalc.ZERO;
91      List<StreamElement> elements = cashFlowStream.getElements();
92      for (StreamElement streamElement : elements) {
93        BigDecimal valueAtTime = getStreamElementValue(nominalInterestRate, periodsPerYear, timePoint, streamElement);
94        BigDecimal weightedElement = valueAtTime.multiply(streamElement.getTime());
95        weightedSum = weightedSum.add(weightedElement);
96      }
97      BigDecimal presentValue = getPresentValue(cashFlowStream, nominalInterestRate, periodsPerYear);
98      return mathCalc.div(weightedSum, presentValue);
99    }
100 
101   public BigDecimal getValue(CashFlowStream cashFlowStream, BigDecimal nominalInterestRate, BigDecimal periodsPerYear, BigDecimal timePoint) {
102     List<StreamElement> elements = cashFlowStream.getElements();
103     return getValue(elements, nominalInterestRate, periodsPerYear, timePoint);
104   }
105 
106   public BigDecimal getValueContComp(CashFlowStream cashFlowStream, BigDecimal nominalInterestRate, BigDecimal timePoint) {
107     ContCompInterestCalc interestCalc = new ContCompInterestCalc();
108     List<StreamElement> elements = cashFlowStream.getElements();
109     BigDecimal streamValue = mathCalc.ZERO;
110     for (StreamElement streamElement : elements) {
111       BigDecimal presentValue = streamElement.getValue();
112       BigDecimal elementTime = streamElement.getTime();
113       BigDecimal timeDifference = timePoint.subtract(elementTime);
114       BigDecimal valueAtTime = interestCalc.getFutureValue(presentValue, nominalInterestRate, timeDifference);
115       streamValue = streamValue.add(valueAtTime);
116     }
117     return streamValue;
118 
119   }
120 
121   public BigDecimal getEndValue(CashFlowStream cashFlowStream, BigDecimal nominalInterestRate, BigDecimal periodsPerYear) {
122     BigDecimal timePoint = cashFlowStream.getEndTime();
123     return getValue(cashFlowStream, nominalInterestRate, periodsPerYear, timePoint);
124   }
125 
126   public BigDecimal getStartValue(CashFlowStream cashFlowStream, BigDecimal nominalInterestRate, BigDecimal periodsPerYear) {
127     BigDecimal timePoint = cashFlowStream.getStartTime();
128     return getValue(cashFlowStream, nominalInterestRate, periodsPerYear, timePoint);
129   }
130 
131   public BigDecimal getPresentValue(CashFlowStream cashFlowStream, BigDecimal nominalInterestRate, BigDecimal periodsPerYear) {
132     BigDecimal timePoint = mathCalc.ZERO;
133     return getValue(cashFlowStream, nominalInterestRate, periodsPerYear, timePoint);
134   }
135 
136   /**
137    * <pre>
138    * PVt = PVf + (1 / (1+r/m))^len * PVt
139    * PVt - total present value
140    * PVf - present value of the first cycle
141    * r - nominal interest rate
142    * m - periods per year
143    * len - length of the cycle
144    * </pre>
145    *
146    * TRANSFORMATION:
147    *
148    * <pre>
149    * PVt = PVf + (1 + r/m)^(-len) * PVt
150    * PVt = PVf + growth * PVt
151    * PVt - (PVt * growth) = PVf
152    * PVt * (1 - growth) = PVf
153    * PVt = PVf / 1 - growth
154    * </pre>
155    */
156   public BigDecimal getInfinitePresentValue(CashFlowStream cashFlowCycle, BigDecimal nominalInterestRate, BigDecimal periodsPerYear) {
157     if (cashFlowCycle.length().compareTo(mathCalc.ZERO) == 0) {
158       throw new TkFinConstructionException(TkFinConstants.MESSAGE_INFIN_PV_CYCLE_LENGHT_0);
159     }
160     CompoundInterestCalc intCalc = new CompoundInterestCalc();
161     BigDecimal cyclePV = getStartValue(cashFlowCycle, nominalInterestRate, periodsPerYear);
162     BigDecimal investmentPeriods = cashFlowCycle.length().negate().multiply(periodsPerYear);
163     BigDecimal growth = intCalc.getGrowth(nominalInterestRate, periodsPerYear, investmentPeriods);
164     BigDecimal totalPV = mathCalc.div(cyclePV, mathCalc.ONE.subtract(growth));
165     return totalPV;
166   }
167 
168   public BigDecimal getFutureValueContComp(CashFlowStream cashFlowStream, BigDecimal nominalInterestRate) {
169     BigDecimal timePoint = cashFlowStream.getEndTime();
170     return getValueContComp(cashFlowStream, nominalInterestRate, timePoint);
171   }
172 
173   public BigDecimal getPresentValueContComp(CashFlowStream cashFlowStream, BigDecimal nominalInterestRate) {
174     BigDecimal timePoint = cashFlowStream.getStartTime();
175     return getValueContComp(cashFlowStream, nominalInterestRate, timePoint);
176   }
177 
178   // TODO: write the same for continuous compounding
179   public BigDecimal getInternalRateOfReturn(CashFlowStream cashFlowStream, int scale) {
180     BigDecimal expectedFutureValue = mathCalc.ZERO;
181     CashFlowPresentValueFunction function = new CashFlowPresentValueFunction();
182     function.setCashFlowStream(cashFlowStream);
183     Div2FunctionCalc calc = new Div2FunctionCalc();
184     BigDecimal internalRateOfReturn = calc.getParameterForValue(expectedFutureValue, function, scale);
185     return internalRateOfReturn;
186   }
187 
188   public BigDecimal getPresentWorthOfBenefits(CashFlowStream cashFlowStream, BigDecimal nominalInterestRate, BigDecimal periodsPerYear) {
189     BigDecimal timePoint = cashFlowStream.getStartTime();
190     return getBenefitsValue(cashFlowStream, nominalInterestRate, periodsPerYear, timePoint);
191   }
192 
193   public BigDecimal getPresentWorthOfCosts(CashFlowStream cashFlowStream, BigDecimal nominalInterestRate, BigDecimal periodsPerYear) {
194     BigDecimal timePoint = cashFlowStream.getStartTime();
195     return getCostsValue(cashFlowStream, nominalInterestRate, periodsPerYear, timePoint);
196   }
197 
198   public BigDecimal getRunningPresentValue(CashFlowStream cashFlowStream, Env env, BigDecimal time) {
199     int size = cashFlowStream.getElements().size();
200     if (size == 0) {
201       return mathCalc.ZERO;
202     }
203     cashFlowStream.sort();
204     List<StreamElement> streamElements = cashFlowStream.getElements();
205     int lastElementIndex = streamElements.size() - 1;
206     BigDecimal runningPV = streamElements.get(lastElementIndex).getValue();
207     for (int currentIndex = lastElementIndex - 1; currentIndex >= 0; currentIndex--) {
208       // PV[k] = x[k] + d[k,k+1] * PV[k+1]
209       BigDecimal startTime = streamElements.get(currentIndex).getTime();
210       BigDecimal endTime = streamElements.get(currentIndex + 1).getTime();
211       runningPV = runningPV.multiply(env.getDiscountFactor(startTime, endTime));
212       runningPV = runningPV.add(streamElements.get(currentIndex).getValue());
213     }
214     return runningPV;
215   }
216 
217 }