001 package market.statistics;
002
003 import java.util.Calendar;
004 import java.util.Iterator;
005 import java.util.LinkedList;
006 import java.util.List;
007
008 import market.Conversions;
009 import market.MarketCalendar;
010 import market.SMarket;
011
012 /**
013 * Does calculation on statistics. While {@link Statistics#getArticleStats(String, int, int, int, int)}
014 * sums up all prices and concatenates all history lists, this class {@link #cleanUpPriceHistory() cleans up}
015 * the lists and provides methods to evaluate the statistics.
016 */
017 public class EvaluateStatistics {
018
019 private CISalesStats ciss;
020 private List cleanedPriceHistory;
021 private int averagePrice;
022 private double averageOrder;
023 private static final int MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
024
025 /**
026 * @param ciss the history item to be evaluated.
027 */
028 public EvaluateStatistics(CISalesStats ciss) {
029 this.ciss = ciss;
030 cleanedPriceHistory = cleanUpPriceHistory();
031 }
032
033 /**
034 * Removes "duplicate" items from the {@link CISalesStats#priceHistory priceHistory}.<br>
035 * As every price history is initialized with the current price on creation
036 * (see {@link CSalesStats#initPriceHistory}), there might be multiple items in a row that contain
037 * the same price.<br>
038 * Another possibility to create consecutive items with the same price would be to set a new price in
039 * the evening (after market closed), switch to the next day, set the price again to its original
040 * value and change the day again (or open the market).
041 * This method removes all list items that contain the same price as their predecessor.
042 *
043 * @return the cleaned up price history.
044 */
045 private List cleanUpPriceHistory() {
046 List cleaned = new LinkedList();
047 int lastValue = -1;
048 List l = ciss.getPriceHistory();
049 Iterator it = l.iterator();
050 while (it.hasNext()) {
051 HistoryEntry he = (HistoryEntry)it.next();
052 if (he.getValue() != lastValue) {
053 cleaned.add(he);
054 lastValue = he.getValue();
055 }
056 }
057 return cleaned;
058 }
059
060 /**
061 * @return the date of the very first entry in the {@link #cleanUpPriceHistory cleaned up price history}.
062 */
063 private Calendar firstDate() {
064 return ((HistoryEntry)cleanedPriceHistory.get(0)).getDate();
065 }
066
067 /**
068 * @return the date of the very last entry in the {@link #cleanUpPriceHistory cleaned up price history}.
069 */
070 private Calendar lastRecordedDate() {
071 return ((HistoryEntry)ciss.getPriceHistory().get(ciss.getPriceHistory().size() - 1)).getDate();
072 }
073
074
075 /**
076 * Finds the last date of the time range for which the statistics should be computed (e.g. 31.01.2005).<br>
077 * As every month has at least one item in the <b>original</b> price history, the last date can be
078 * computed from that list.
079 * @return the very last date of the statistic's time range.
080 */
081 private Calendar veryLastDate() {
082 Calendar lastRecordedDate = lastRecordedDate();
083 Calendar c = new MarketCalendar(lastRecordedDate.get(Calendar.YEAR),
084 lastRecordedDate.get(Calendar.MONTH),
085 lastRecordedDate.getActualMaximum(Calendar.DAY_OF_MONTH));
086 //do not extrapolate to future, if last month is current month stop computation at day before current date
087 if (!c.before(SMarket.getTime())) {
088 c = new MarketCalendar(SMarket.getYear(), SMarket.getMonth(),
089 SMarket.getTime().get(Calendar.DATE) - 1);
090 }
091 return c;
092 }
093
094 /**
095 * If market has just closed (i.e. day-end closing has taken place), take current day into account,
096 * but if the market is open OR just the day has changed and the market hasn't
097 * opened yet, ignore the current day.
098 *
099 * @return <code>true</code> when ignoring the current day for statistics, otherwise <code>false</code>.
100 */
101 private boolean ignoreCurrentDay() {
102 return SMarket.isOpen() || SMarket.hasTimeAdvanced();
103 }
104
105 /**
106 * @return the number of days covered by the statistics to be evaluated.
107 */
108 private int range() {
109 int difference = Conversions.dayDifference(firstDate(), veryLastDate()) + 1;
110 if (!ignoreCurrentDay()) {
111 difference++;
112 }
113 return difference;
114 }
115
116
117 public List getPriceHistory() {
118 return cleanedPriceHistory;
119 }
120
121 public List getOrderHistory() {
122 return ciss.getOrderHistory();
123 }
124
125 /**
126 * @return the average price of the article.
127 */
128 public int getAveragePrice() {
129 int range = range();
130 Iterator it = cleanedPriceHistory.iterator();
131 HistoryEntry he = (HistoryEntry)it.next();
132 int previousPrice = he.getValue();
133 Calendar previousDate = he.getDate();
134 int sum = 0;
135 while (it.hasNext()) {
136 he = (HistoryEntry)it.next();
137 sum += previousPrice * Conversions.dayDifference(previousDate, he.getDate());
138 previousDate = he.getDate();
139 previousPrice = he.getValue();
140 }
141 sum += previousPrice * (Conversions.dayDifference(previousDate, veryLastDate()) +
142 (ignoreCurrentDay() ? 1 : 2));
143 //catch division by 0 if there are no days to be displayed yet (should only happen on date of opening)
144 return (range == 0) ? SMarket.getArticleCatalog().get(ciss.getArticleID()).getBid() : sum/range;
145 }
146
147 public int getRevenue() {
148 return ciss.getRevenue();
149 }
150
151 public int getAmount() {
152 return ciss.getAmount();
153 }
154
155 /**
156 * @return the average of sold items per day.
157 */
158 public double getAverageItemsSold() {
159 int range = range();
160 return range == 0 ? 0 : new Integer(getAmount()).doubleValue()/range;
161 }
162
163 /**
164 * @return the average of items ordered per order.
165 */
166 public double getAverageOrderAmount() {
167 double avg = 0;
168 Iterator it = ciss.getOrderHistory().iterator();
169 while (it.hasNext()) {
170 avg += ((HistoryEntry)it.next()).getValue();
171 }
172 return ciss.getOrderHistory().size() == 0 ? 0 : avg/ciss.getOrderHistory().size();
173 }
174
175 /**
176 * @return the average days between two orders of the appropriate item.
177 */
178 public double getAverageDaysBetweenOrders() {
179 double avg = 0;
180 Calendar prevOrder = null;
181 Calendar nextOrder = null;
182 Iterator it = ciss.getOrderHistory().iterator();
183 if (it.hasNext()) {
184 prevOrder = ((HistoryEntry)it.next()).getDate();
185 }
186 while (it.hasNext()) {
187 nextOrder = ((HistoryEntry)it.next()).getDate();
188 avg += Conversions.dayDifference(prevOrder, nextOrder);
189 prevOrder = nextOrder;
190 }
191 if (prevOrder != null) {
192 avg += Conversions.dayDifference(prevOrder, SMarket.getTime());
193 }
194 return prevOrder == null ? 0 : avg/ciss.getOrderHistory().size();
195 }
196 }