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.fin.element;
22  
23  import java.math.BigDecimal;
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.Comparator;
27  import java.util.List;
28  
29  import org.apache.commons.lang.builder.EqualsBuilder;
30  import org.apache.commons.lang.builder.HashCodeBuilder;
31  
32  import com.nickokiss.investor.calc.MathCalc;
33  import com.nickokiss.investor.fin.element.creator.StreamElementCreator;
34  import com.nickokiss.investor.fin.env.Env;
35  
36  /**
37   * 
38   * @author Tomasz Koscinski <tomasz.koscinski@nickokiss.com>
39   */
40  public class CashFlowStream implements FinElement {
41  
42    private MathCalc mathCalc = new MathCalc();
43    
44    private BigDecimal STARTING_TIME = mathCalc.ZERO;
45  
46    private List<StreamElement> elements = new ArrayList<StreamElement>();
47  
48    public CashFlowStream() {
49    };
50  
51    public CashFlowStream(CashFlowStream cashFlowStream) {
52      add(cashFlowStream);
53    }
54  
55    public void sort() {
56      Collections.sort(elements, new TimeComparator());
57    }
58    
59    public void addElement(StreamElement streamElement) {
60      elements.add(streamElement);
61    }
62  
63    public List<StreamElement> getElements() {
64      return elements;
65    }
66  
67    public BigDecimal getEndTime() {
68      return Collections.max(elements, new TimeComparator()).getTime();
69    }
70  
71    public BigDecimal getStartTime() {
72      return Collections.min(elements, new TimeComparator()).getTime();
73    }
74  
75    private class TimeComparator implements Comparator<StreamElement> {
76      public int compare(StreamElement se1, StreamElement se2) {
77        return se1.getTime().compareTo(se2.getTime());
78      }
79    }
80  
81    public List<StreamElement> getElements(StreamElementType streamElementType) {
82      List<StreamElement> selectedElements = new ArrayList<StreamElement>();
83      for (StreamElement streamElement : elements) {
84        if (streamElement.getType().equals(streamElementType)) {
85          selectedElements.add(streamElement);
86        }
87      }
88      return selectedElements;
89    }
90  
91    public void repeat(int times) {
92      if (times <= 0) {
93        return;
94      }
95      CashFlowStream repeatablePart = new CashFlowStream();
96      repeatablePart.add(this);
97      for (int i = 0; i < times; i++) {
98        add(repeatablePart);
99      }
100   }
101 
102   public void add(CashFlowStream cashFlowStream) {
103     CashFlowStream partToAdd = cashFlowStream.getCopy();
104     if (elements.size() > 0) {
105       partToAdd.move(this.length());
106     }
107     elements.addAll(partToAdd.getElements());
108   }
109 
110   public int hashCode() {
111     return HashCodeBuilder.reflectionHashCode(this);
112   }
113 
114   public boolean equals(Object obj) {
115     return EqualsBuilder.reflectionEquals(this, obj);
116   }  
117 
118   public BigDecimal length() {
119     return getEndTime().subtract(getStartTime());
120   }
121 
122   public void move(BigDecimal time) {
123     for (StreamElement streamElement : elements) {
124       streamElement.setTime(streamElement.getTime().add(time));
125     }
126   }
127 
128   public CashFlowStream getCopy() {
129     CashFlowStream copy = new CashFlowStream();
130     for (StreamElement se : elements) {
131       copy.addElement(new StreamElement(se.getValue(), se.getTime()));
132     }
133     return copy;
134   }
135 
136   public void addElements(StreamElementCreator streamElementCreator, int quantity) {
137     for (int i = 0; i < quantity; i++) {
138       StreamElement streamElement = streamElementCreator.create(this);
139       addElement(streamElement);
140     }
141 
142   }
143 
144   public BigDecimal getFirstValue() {
145     return Collections.min(elements, new TimeComparator()).getValue();
146   }
147 
148   public BigDecimal getLastValue() {
149     return Collections.max(elements, new TimeComparator()).getValue();
150   }
151 
152   private BigDecimal getStartingTime() {
153     if (elements.size() == 0) {
154       return STARTING_TIME;
155     } else {
156       return getEndTime();
157     }
158   }
159 
160   public void addElements(BigDecimal value, int quantity, BigDecimal distance) {
161     BigDecimal time = getStartingTime();
162     for (int i = 1; i <= quantity; i++) {
163       time = time.add(distance);
164       addElement(new StreamElement(value, time));
165     }
166   }
167 
168   public BigDecimal getValue(Env env, BigDecimal time) {
169     BigDecimal value = mathCalc.ZERO;
170     for (StreamElement finElement : elements) {
171       value = value.add(finElement.getValue(env, time));
172     }
173     return value;
174   }
175 
176   public BigDecimal getDuration(Env env) {
177     BigDecimal presentValue = getValue(env, mathCalc.ZERO);
178     BigDecimal weightedSum = mathCalc.ZERO;
179     for (StreamElement element : elements) {
180       BigDecimal timeValue = element.getValue().multiply(element.getTime());
181       BigDecimal durationMultiplicand = env.getDurationMultiplicand(element.getTime());
182       BigDecimal weightedElement = timeValue.multiply(durationMultiplicand);
183       weightedSum = weightedSum.add(weightedElement);
184     }
185     BigDecimal duration = mathCalc.div(weightedSum, presentValue);
186     return duration;
187   }
188 
189 }