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 }