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 }