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