vanilla-wow-addons – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 --[[
2 Auctioneer Addon for World of Warcraft(tm).
3 Version: 3.9.0.1000 (Kangaroo)
4 Revision: $Id: AucStatistic.lua 972 2006-08-22 19:05:01Z mentalpower $
5  
6 Auctioneer statistical functions.
7 Functions to calculate various forms of statistics from the auction data.
8  
9 License:
10 This program is free software; you can redistribute it and/or
11 modify it under the terms of the GNU General Public License
12 as published by the Free Software Foundation; either version 2
13 of the License, or (at your option) any later version.
14  
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19  
20 You should have received a copy of the GNU General Public License
21 along with this program(see GPL.txt); if not, write to the Free Software
22 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
23 ]]
24  
25 --Local function prototypes
26 local subtractPercent, addPercent, percentLessThan, getLowest, getMedian, getPercentile, getMeans, getItemSnapshotMedianBuyout, getItemHistoricalMedianBuyout, getUsableMedian, getCurrentBid, isBadResaleChoice, profitComparisonSort, roundDownTo95, findLowestAuctions, buildLowestCache, doLow, doMedian, doHSP, getBidBasedSellablePrice, getMarketPrice, getHSP, determinePrice, setScanLength, setScanAge, getScanLength, getScanAge
27  
28 -- Subtracts/Adds given percentage from/to a value
29  
30 function subtractPercent(value, percentLess) --function subtractPercent(value, percentLess)
31 return math.floor(value * ((100 - percentLess)/100));
32 end
33  
34 function addPercent(value, percentMore)
35 return math.floor(value * ((100 + percentMore)/100));
36 end
37  
38 -- returns the integer representation of the percent less value2 is from value1
39 -- example: value1=10, value2=7, percentLess=30
40 function percentLessThan(value1, value2)
41 if Auctioneer.Util.NullSafe(value1) > 0 and Auctioneer.Util.NullSafe(value2) < Auctioneer.Util.NullSafe(value1) then
42 return 100 - math.floor((100 * Auctioneer.Util.NullSafe(value2))/Auctioneer.Util.NullSafe(value1));
43 else
44 return 0;
45 end
46 end
47  
48 function getLowest(valuesTable)
49 if (not valuesTable or table.getn(valuesTable) == 0) then
50 return nil, nil;
51 end
52 local tableSize = table.getn(valuesTable);
53 local lowest = tonumber(valuesTable[1]) or 0;
54 local second = nil
55 if (tableSize > 1) then
56 for i=2, tableSize do
57 second = tonumber(valuesTable[i]) or 0;
58 if (second > lowest) then
59 return lowest, second;
60 end
61 end
62 end
63 return lowest, nil;
64 end
65  
66 -- Returns the median value of a given table one-dimentional table
67 function getMedian(valuesTable)
68 return getPercentile(valuesTable, 0.5)
69 end
70  
71 -- Return weighted average percentile such that returned value
72 -- is larger than or equal to (100*pct)% of the table values
73 -- 0 <= pct <= 1
74 function getPercentile(valuesTable, pct)
75 if (type(valuesTable) ~= "table") or (not tonumber(pct)) then
76 return nil -- make valuesTable a required table argument
77 end
78 pct = math.min(math.max(pct, 0), 1) -- Make sure 0 <= pct <= 1
79  
80 local _percentile = function(sortedTable, p, first, last)
81 local f = (last - first) * p + first
82 local i1, i2 = math.floor(f), math.ceil(f)
83 f = f - i1
84  
85 return sortedTable[i1] * (1 - f) + sortedTable[i2] * f
86 end
87  
88 local tableSize = table.getn(valuesTable) or 0
89  
90 if (tableSize == 0) then
91 return 0, 0; -- if there is an empty table, returns median = 0, count = 0
92 elseif (tableSize == 1) then
93 return tonumber(valuesTable[1]), 1
94 end
95  
96 -- The following calculations require a sorted table
97 table.sort(valuesTable)
98  
99 -- Skip IQR calculations if table is too small to have outliers
100 if tableSize <= 4 then
101 return _percentile(valuesTable, pct, 1, tableSize), tableSize
102 end
103  
104 -- REWORK by Karavirs to use IQR*1.5 to ignore outliers
105 -- q1 is median 1st quartile q2 is median of set q3 is median of 3rd quartile iqr is q3 - q1
106 local q1 = _percentile(valuesTable, 0.25, 1, tableSize)
107 local q3 = _percentile(valuesTable, 0.75, 1, tableSize)
108 assert(q3 >= q1)
109  
110 local iqr = (q3 - q1) * 1.5
111 local iqlow, iqhigh = q1 - iqr, q3 + iqr
112  
113 -- Find first and last index to include in median calculation
114 local first, last = 1, tableSize
115  
116 -- Skip low outliers
117 while valuesTable[first] < iqlow do
118 first = first + 1
119 end
120  
121 -- Skip high outliers
122 while valuesTable[last] > iqhigh do
123 last = last - 1
124 end
125 assert(last >= first)
126  
127 return _percentile(valuesTable, pct, first, last), last - first + 1
128 end
129  
130  
131 -- Return all of the averages for an item
132 -- Returns: avgMin,avgBuy,avgBid,bidPct,buyPct,avgQty,aCount
133 function getMeans(itemKey, from)
134 local auctionPriceItem = Auctioneer.Core.GetAuctionPriceItem(itemKey, from);
135 if (not auctionPriceItem.data) then
136 EnhTooltip.DebugPrint("Error, GetAuctionPriceItem", itemKey, from, "returns", auctionPriceItem);
137 end
138 local aCount,minCount,minPrice,bidCount,bidPrice,buyCount,buyPrice = Auctioneer.Core.GetAuctionPrices(auctionPriceItem.data);
139 local avgMin,avgBuy,avgBid,bidPct,buyPct,avgQty;
140  
141 if aCount > 0 then
142 avgQty = math.floor(minCount / aCount);
143 avgMin = math.floor(minPrice / minCount);
144 bidPct = math.floor(bidCount / minCount * 100);
145 buyPct = math.floor(buyCount / minCount * 100);
146  
147 avgBid = 0;
148 if (bidCount > 0) then
149 avgBid = math.floor(bidPrice / bidCount);
150 end
151  
152 avgBuy = 0;
153 if (buyCount > 0) then
154 avgBuy = math.floor(buyPrice / buyCount);
155 end
156 end
157 return avgMin,avgBuy,avgBid,bidPct,buyPct,avgQty,aCount;
158 end
159  
160 -- Returns the current snapshot median for an item
161 function getItemSnapshotMedianBuyout(itemKey, auctKey, buyoutPrices)
162 if (not auctKey) then auctKey = Auctioneer.Util.GetAuctionKey() end
163  
164 local stat, count;
165  
166 if (AuctionConfig.stats and AuctionConfig.stats.snapmed and AuctionConfig.stats.snapmed[auctKey]) then
167 stat = AuctionConfig.stats.snapmed[auctKey][itemKey];
168 count = AuctionConfig.stats.snapcount[auctKey][itemKey];
169 end
170  
171 if (not stat) or (not count) then
172 if (not buyoutPrices) then
173 local sbuy = Auctioneer.Core.GetSnapshotInfo(auctKey, itemKey);
174 if (sbuy) then
175 buyoutPrices = sbuy.buyoutPrices;
176 end
177 end
178  
179 if (buyoutPrices) then
180 stat, count = getMedian(buyoutPrices);
181 else
182 stat, count = 0, 0;
183 end
184  
185 -- save median to the savedvariablesfile
186 Auctioneer.Storage.SetSnapMed(auctKey, itemKey, stat, count)
187 end
188  
189 return stat, count;
190 end
191  
192 -- Returns the historical median for an item
193 function getItemHistoricalMedianBuyout(itemKey, auctKey, buyoutHistoryTable)
194 if (not auctKey) then auctKey = Auctioneer.Util.GetAuctionKey() end
195  
196 local stat, count;
197  
198 if (AuctionConfig.stats and AuctionConfig.stats.histmed and AuctionConfig.stats.histmed[auctKey]) then
199 stat = AuctionConfig.stats.histmed[auctKey][itemKey];
200 count = AuctionConfig.stats.histcount[auctKey][itemKey];
201 end
202  
203 if (not stat) or (not count) then
204 if (not buyoutHistoryTable) then
205 buyoutHistoryTable = Auctioneer.Core.GetAuctionBuyoutHistory(itemKey, auctKey);
206 end
207  
208 if (buyoutHistoryTable) then
209 stat, count = getMedian(buyoutHistoryTable);
210 else
211 stat, count = 0, 0;
212 end
213  
214 -- save median to the savedvariablesfile
215 Auctioneer.Storage.SetHistMed(auctKey, itemKey, stat, count);
216 end
217  
218 return stat, count;
219 end
220  
221 -- this function returns the most accurate median possible,
222 -- if an accurate median cannot be obtained based on min seen counts then nil is returned
223 function getUsableMedian(itemKey, realm, buyoutPrices)
224 if not realm then
225 realm = Auctioneer.Util.GetAuctionKey();
226 end
227  
228 --get snapshot median
229 local snapshotMedian, snapCount = getItemSnapshotMedianBuyout(itemKey, realm, buyoutPrices)
230 --get history median
231 local historyMedian, histCount = getItemHistoricalMedianBuyout(itemKey, realm);
232  
233 local median, count
234 if (histCount >= Auctioneer.Core.Constants.MinBuyoutSeenCount) then
235 median, count = historyMedian, histCount;
236 end
237 if (snapCount >= Auctioneer.Core.Constants.MinBuyoutSeenCount) then
238 if (histCount < snapCount) then
239 -- History median isn't shown in tooltip if histCount < snapCount so use snap median in this case
240 median, count = snapshotMedian, snapCount;
241 elseif (snapshotMedian < 1.2 * historyMedian) then
242 median, count = snapshotMedian, snapCount;
243 end
244 end
245 return median, count;
246 end
247  
248 -- Returns the current bid on an auction
249 function getCurrentBid(auctionSignature)
250 local x,x,x, x, x,min,x,_ = Auctioneer.Core.GetItemSignature(auctionSignature);
251 local auctKey = Auctioneer.Util.GetAuctionKey();
252 local itemCat = Auctioneer.Util.GetCatForSig(auctionSignature);
253 local snap = Auctioneer.Core.GetSnapshot(auctKey, itemCat, auctionSignature);
254 if (not snap) then return 0 end
255 local currentBid = tonumber(snap.bidamount) or 0;
256 if currentBid == 0 then currentBid = min end
257 return currentBid;
258 end
259  
260 -- This filter will return true if an auction is a bad choice for reselling
261 function isBadResaleChoice(auctSig, auctKey)
262 if (not auctKey) then auctKey = Auctioneer.Util.GetAuctionKey() end
263  
264 local isBadChoice = false;
265 local id,rprop,enchant, name, count,min,buyout,uniq = Auctioneer.Core.GetItemSignature(auctSig);
266 local itemKey = id..":"..rprop..":"..enchant;
267 local itemCat = Auctioneer.Util.GetCatForKey(itemKey);
268 local auctionItem = Auctioneer.Core.GetSnapshot(auctKey, itemCat, auctSig);
269 local auctionPriceItem = Auctioneer.Core.GetAuctionPriceItem(itemKey, auctKey);
270 local aCount,minCount,minPrice,bidCount,bidPrice,buyCount,buyPrice = Auctioneer.Core.GetAuctionPrices(auctionPriceItem.data);
271 local bidPercent = math.floor(bidCount / minCount * 100);
272  
273 if (auctionItem) then
274 local itemLevel = tonumber(auctionItem.level);
275 local itemQuality = tonumber(auctionItem.quality);
276  
277 -- bad choice conditions
278 if Auctioneer.Core.Constants.BidBasedCategories[auctionItem.category] and bidPercent < Auctioneer.Core.Constants.MinBidPercent then
279 isBadChoice = true; -- bidbased items should have a minimum bid percent
280 elseif (itemLevel >= 50 and itemQuality == Auctioneer.Core.Constants.Quality.Uncommon and bidPercent < Auctioneer.Core.Constants.MinBidPercent) then
281 isBadChoice = true; -- level 50 and greater greens that do not have bids do not sell well
282 elseif auctionItem.owner == UnitName("player") or auctionItem.highBidder then
283 isBadChoice = true; -- don't display auctions that we own, or are high bidder on
284 elseif itemQuality == Auctioneer.Core.Constants.Quality.Poor then
285 isBadChoice = true; -- gray items are never a good choice
286 end
287 end
288  
289 return isBadChoice;
290 end
291  
292 -- method to pass to table.sort() that sorts auctions by profit descending
293 function profitComparisonSort(a, b)
294 local aid,arprop,aenchant, aName, aCount, x, aBuyout, x = Auctioneer.Core.GetItemSignature(a.signature);
295 local bid,brprop,benchant, bName, bCount, x, bBuyout, x = Auctioneer.Core.GetItemSignature(b.signature);
296 local aItemKey = aid .. ":" .. arprop..":"..aenchant;
297 local bItemKey = bid .. ":" .. brprop..":"..benchant;
298 local realm = Auctioneer.Util.GetAuctionKey()
299 local aProfit = (getHSP(aItemKey, realm) * aCount) - aBuyout;
300 local bProfit = (getHSP(bItemKey, realm) * bCount) - bBuyout;
301 return (aProfit > bProfit)
302 end
303  
304 -- this function takes copper and rounds to 5 silver below the the nearest gold if it is less than 15 silver above of an even gold
305 -- example: this function changes 1g9s to 95s
306 -- example: 1.5g will be unchanged and remain 1.5g
307 function roundDownTo95(copper)
308 local g,s,c = EnhTooltip.GetGSC(copper);
309 if g > 0 and s < 10 then
310 return (copper - ((s + 5) * 100)); -- subtract enough copper to round to 95 silver
311 end
312 return copper;
313 end
314  
315  
316 -- given an item name, find the lowest price for that item in the current AHSnapshot
317 -- if the item does not exist in the snapshot or the snapshot does not exist
318 -- a nil is returned.
319 function findLowestAuctions(itemKey, auctKey)
320 local itemID, itemRand, enchant = Auctioneer.Util.BreakItemKey(itemKey);
321 if (itemID == nil) then return nil; end
322 if (not auctKey) then
323 auctKey = Auctioneer.Util.GetAuctionKey();
324 end
325 if not (Auctioneer_Lowests and Auctioneer_Lowests[auctKey]) then buildLowestCache(auctKey) end
326  
327 local lowKey = itemID..":"..itemRand;
328  
329 local itemCat = nil;
330 local lowSig = nil;
331 local nextSig = nil;
332 local lowestPrice = 0;
333 local nextLowest = 0;
334  
335 local lows = Auctioneer_Lowests[auctKey][lowKey];
336 if (lows) then
337 lowSig = lows.lowSig;
338 nextSig = lows.nextSig;
339 lowestPrice = lows.lowestPrice or 0;
340 nextLowest = lows.nextLowest or 0;
341 itemCat = lows.cat;
342 end
343  
344 return lowSig, lowestPrice, nextSig, nextLowest, itemCat;
345 end
346  
347 function buildLowestCache(auctKey)
348 if (Auctioneer_Lowests == nil) then Auctioneer_Lowests = {}; end
349 Auctioneer_Lowests[auctKey] = {}
350  
351 local id, rprop, enchant, name, count, min, buyout, uniq, lowKey, priceForOne, lowests;
352 if (AuctionConfig and AuctionConfig.snap and AuctionConfig.snap[auctKey]) then
353 for itemCat, cData in pairs(AuctionConfig.snap[auctKey]) do
354 for sig, sData in pairs(cData) do
355 id,rprop,enchant, name, count,min,buyout,uniq = Auctioneer.Core.GetItemSignature(sig);
356  
357 lowKey = id..":"..rprop;
358 if (not Auctioneer_Lowests[auctKey][lowKey]) then Auctioneer_Lowests[auctKey][lowKey] = {cat = itemCat} end
359 lowests = Auctioneer_Lowests[auctKey][lowKey]
360  
361 if (Auctioneer.Util.NullSafe(buyout) > 0) then
362 priceForOne = Auctioneer.Util.PriceForOne(buyout, count)
363  
364 if (lowests.lowestPrice == nil) or (priceForOne < lowests.lowestPrice) then
365 lowests.lowestPrice, lowests.nextLowest = priceForOne, lowests.lowestPrice
366 lowests.lowSig, lowests.nextSig = sig, lowests.lowSig
367 elseif (lowests.nextLowest == nil) or (priceForOne < lowests.nextLowest) then
368 lowests.nextLowest = priceForOne
369 lowests.nextSig = sig
370 end
371 end
372 end
373 end
374 end
375 end
376  
377 -- execute the '/auctioneer low <itemName>' that returns the auction for an item with the lowest buyout
378 function doLow(link)
379  
380 local auctKey = Auctioneer.Util.GetAuctionKey();
381 local items = Auctioneer.Util.GetItems(link);
382 local itemLinks = Auctioneer.Util.GetItemHyperlinks(link);
383  
384 if (items) then
385 for pos,itemKey in pairs(items) do
386  
387 local auctionSignature = findLowestAuctions(itemKey);
388 if (not auctionSignature) then
389 Auctioneer.Util.ChatPrint(string.format(_AUCT('FrmtNoauct'), itemLinks[pos]));
390  
391 else
392 local itemCat = Auctioneer.Util.GetCatForKey(itemKey);
393 local auction = Auctioneer.Core.GetSnapshot(auctKey, itemCat, auctionSignature);
394 local x,x,x, x, count, x, buyout, x = Auctioneer.Core.GetItemSignature(auctionSignature);
395 Auctioneer.Util.ChatPrint(string.format(_AUCT('FrmtLowLine'), Auctioneer.Util.ColorTextWhite(count.."x")..auction.itemLink, EnhTooltip.GetTextGSC(buyout), Auctioneer.Util.ColorTextWhite(auction.owner), EnhTooltip.GetTextGSC(buyout / count), Auctioneer.Util.ColorTextWhite(percentLessThan(getUsableMedian(itemKey), buyout / count).."%")));
396 end
397 end
398 end
399 end
400  
401 function doMedian(link)
402  
403 local items = Auctioneer.Util.GetItems(link);
404 local itemLinks = Auctioneer.Util.GetItemHyperlinks(link);
405  
406 if (items) then
407 for pos,itemKey in pairs(items) do
408  
409 local median, count = getUsableMedian(itemKey);
410 if (not median) then
411 Auctioneer.Util.ChatPrint(string.format(_AUCT('FrmtMedianNoauct'), Auctioneer.Util.ColorTextWhite(itemName)));
412 else
413 if (not count) then count = 0 end
414 Auctioneer.Util.ChatPrint(string.format(_AUCT('FrmtMedianLine'), count, Auctioneer.Util.ColorTextWhite(itemName), EnhTooltip.GetTextGSC(median)));
415 end
416 end
417 end
418 end
419  
420 function doHSP(link)
421  
422 local items = Auctioneer.Util.GetItems(link);
423 local itemLinks = Auctioneer.Util.GetItemHyperlinks(link);
424  
425 if (items) then
426 for pos,itemKey in pairs(items) do
427  
428 local highestSellablePrice = getHSP(itemKey, Auctioneer.Util.GetAuctionKey());
429 Auctioneer.Util.ChatPrint(string.format(_AUCT('FrmtHspLine'), itemLinks[pos], EnhTooltip.GetTextGSC(Auctioneer.Util.NilSafeString(highestSellablePrice))));
430 end
431 end
432 end
433  
434 function getBidBasedSellablePrice(itemKey,realm, avgMin,avgBuy,avgBid,bidPct,buyPct,avgQty,seenCount)
435 -- We can pass these values along if we have them.
436 if (seenCount == nil) then
437 avgMin,avgBuy,avgBid,bidPct,buyPct,avgQty,seenCount = getMeans(itemKey, realm);
438 end
439 local bidBasedSellPrice = 0;
440 local typicalBuyout = 0;
441  
442 local medianBuyout = getUsableMedian(itemKey, realm);
443 if medianBuyout and avgBuy then
444 typicalBuyout = math.min(avgBuy, medianBuyout);
445 elseif medianBuyout then
446 typicalBuyout = medianBuyout;
447 else
448 typicalBuyout = avgBuy or 0;
449 end
450  
451 if (avgBid) then
452 bidBasedSellPrice = math.floor((3*typicalBuyout + avgBid) / 4);
453 else
454 bidBasedSellPrice = typicalBuyout;
455 end
456 return bidBasedSellPrice;
457 end
458  
459 -- returns the best market price - 0, if no market price could be calculated
460 function getMarketPrice(itemKey, realm, buyoutValues)
461 -- make sure to call this function with valid parameters! No check is being performed!
462 local buyoutMedian = Auctioneer.Util.NullSafe(getUsableMedian(itemKey, realm, buyoutValues))
463 local avgMin, avgBuy, avgBid, bidPct, buyPct, avgQty, meanCount = getMeans(itemKey, realm)
464 local commonBuyout = 0
465  
466 -- assign the best common buyout
467 if buyoutMedian > 0 then
468 commonBuyout = buyoutMedian
469 elseif meanCount and meanCount > 0 then
470 -- if a usable median does not exist, use the average buyout instead
471 commonBuyout = avgBuy;
472 end
473  
474 local playerMade, skill, level = Auctioneer.Core.IsPlayerMade(itemKey);
475 if Auctioneer.Core.Constants.BidBasedCategories[Auctioneer.Core.GetItemCategory(itemKey)] and not (playerMade and level < 250 and commonBuyout < 100000) then
476 -- returns bibasedSellablePrice for bidbaseditems, playermade items or if the buyoutprice is not present or less than 10g
477 return getBidBasedSellablePrice(itemKey,realm, avgMin,avgBuy,avgBid,bidPct,buyPct,avgQty,seenCount)
478 end
479  
480 -- returns buyoutMedian, if present - returns avgBuy otherwise, if meanCount > 0 - returns 0 otherwise
481 return commonBuyout
482 end
483  
484 -- Returns market information relating to the HighestSellablePrice for one of the given items.
485 -- If you use cached data it may be affected by buying/selling items.
486 HSPCOUNT = 0; CACHECOUNT = 0;
487 function getHSP(itemKey, realm, buyoutValues, itemCat)
488 if (itemKey == nil) then -- make itemKey a required parameter
489 EnhTooltip.DebugPrint("ERROR: Calling Auctioneer.Statistic.GetHSP(itemKey, realm) - Function requires valid itemKey.");
490 return nil;
491 end
492 if (realm == nil) then
493 EnhTooltip.DebugPrint("WARNING: Auctioneer.Statistic.GetHSP(itemKey, realm) - Defaulting to player realm.");
494 EnhTooltip.DebugPrint("This is only some debugging code. THIS IS NO BUG!");
495 realm = Auctioneer.Util.GetAuctionKey();
496 end
497  
498 if (not Auctioneer_HSPCache) then Auctioneer_HSPCache = {}; end
499 CACHECOUNT = CACHECOUNT + 1;
500  
501 if (not Auctioneer_HSPCache[realm]) then Auctioneer_HSPCache[realm] = {} end
502 local cached = Auctioneer_HSPCache[realm][itemKey];
503 if (cached) then
504 local cache = Auctioneer.Util.Split(cached, ";");
505 return tonumber(cache[1]), tonumber(cache[2]), tonumber(cache[3]), cache[4], tonumber(cache[5]), cache[6];
506 end
507 HSPCOUNT = HSPCOUNT + 1;
508  
509 local highestSellablePrice = 0;
510 local warn = _AUCT('FrmtWarnNodata');
511 EnhTooltip.DebugPrint("Getting HSP, calling GetMarketPrice", itemKey, realm);
512 if (not buyoutValues) then
513 local sbuy = Auctioneer.Core.GetSnapshotInfo(realm, itemKey);
514 if sbuy then
515 buyoutValues = sbuy.buyoutPrices;
516 end
517 end
518  
519 local marketPrice = getMarketPrice(itemKey, realm, buyoutValues);
520  
521 -- Get our user-set pricing parameters
522 local lowestAllowedPercentBelowMarket = tonumber(Auctioneer.Command.GetFilterVal('pct-maxless'));
523 local discountLowPercent = tonumber(Auctioneer.Command.GetFilterVal('pct-underlow'));
524 local discountMarketPercent = tonumber(Auctioneer.Command.GetFilterVal('pct-undermkt'));
525 local discountNoCompetitionPercent = tonumber(Auctioneer.Command.GetFilterVal('pct-nocomp'));
526 local vendorSellMarkupPercent = tonumber(Auctioneer.Command.GetFilterVal('pct-markup'));
527  
528 local x, histCount = getUsableMedian(itemKey, realm, buyoutValues);
529 histCount = Auctioneer.Util.NullSafe(histCount);
530  
531 local id = Auctioneer.Util.BreakItemKey(itemKey);
532  
533 -- Get the snapshot sigs of the two lowest auctions
534 local currentLowestSig = nil;
535 local currentLowestBuyout = nil;
536 local currentLowestCount = nil;
537  
538 local nextLowestSig = nil;
539 local nextLowestBuyout = nil;
540 local nextLowestCount = nil;
541  
542 local lowSig, lowPrice, nextSig, nextPrice, itemCat = findLowestAuctions(itemKey, realm);
543 if lowSig then
544 currentLowestSig = lowSig;
545 currentLowestBuyout = lowPrice;
546 nextLowestSig = nextSig;
547 nextLowestBuyout = nextPrice;
548 end
549  
550 if (not itemCat) then itemCat = Auctioneer.Util.GetCatForKey(itemKey) end
551  
552 local hsp, market, warn = determinePrice(id, realm, marketPrice, currentLowestBuyout, currentLowestSig, lowestAllowedPercentBelowMarket, discountLowPercent, discountMarketPercent, discountNoCompetitionPercent, vendorSellMarkupPercent, itemCat);
553 local nexthsp, x, nextwarn = determinePrice(id, realm, marketPrice, nextLowestBuyout, nextLowestSig, lowestAllowedPercentBelowMarket, discountLowPercent, discountMarketPercent, discountNoCompetitionPercent, vendorSellMarkupPercent, itemCat);
554  
555  
556 if (not hsp) then
557 EnhTooltip.DebugPrint("Unable to calc HSP for",id, realm, marketPrice, currentLowestBuyout, currentLowestSig);
558 hsp = 0;
559 warn = "";
560 end
561 if (not nexthsp) then nexthsp = 0; nextwarn = ""; end
562  
563 EnhTooltip.DebugPrint("Auction data: ", hsp, histCount, market, warn, nexthsp, nextwarn);
564  
565 local cache = string.format("%d;%d;%d;%s;%d;%s", hsp,histCount,market,warn, nexthsp,nextwarn);
566 Auctioneer_HSPCache[realm][itemKey] = cache;
567  
568 return hsp, histCount, market, warn, nexthsp, nextwarn;
569 end
570  
571 function determinePrice(id, realm, marketPrice, currentLowestBuyout, currentLowestSig, lowestAllowedPercentBelowMarket, discountLowPercent, discountMarketPercent, discountNoCompetitionPercent, vendorSellMarkupPercent, itemCat)
572  
573 local warn, highestSellablePrice, lowestBuyoutPriceAllowed;
574  
575 if marketPrice and marketPrice > 0 then
576 if currentLowestBuyout and currentLowestBuyout > 0 then
577 lowestBuyoutPriceAllowed = subtractPercent(marketPrice, lowestAllowedPercentBelowMarket);
578 if (not itemCat) then itemCat = Auctioneer.Util.GetCatForSig(currentLowestSig) end
579  
580 -- since we don't want to decode the full data unless there's a chance it belongs to the player
581 -- do a substring search for the players name first.
582 -- For some reason AuctionConfig.snap[realm][itemCat][currentLowestSig] sometimes doesn't
583 -- exist, even if currentLowestBuyout is set. Added a check for this as a workaround, but
584 -- the real cause should probably be tracked down - Thorarin
585 local snap;
586 if (AuctionConfig.snap[realm][itemCat][currentLowestSig] and string.find(AuctionConfig.snap[realm][itemCat][currentLowestSig], UnitName("player"), 1, true)) then
587 snap = Auctioneer.Core.GetSnapshot(realm, itemCat, currentLowestSig);
588 end
589 if snap and snap.owner == UnitName("player") then
590 highestSellablePrice = currentLowestBuyout; -- If I am the lowest seller use same low price
591 warn = _AUCT('FrmtWarnMyprice');
592 elseif (currentLowestBuyout < lowestBuyoutPriceAllowed) then
593 highestSellablePrice = subtractPercent(marketPrice, discountMarketPercent);
594 warn = _AUCT('FrmtWarnToolow');
595 else
596 if (currentLowestBuyout > marketPrice) then
597 highestSellablePrice = subtractPercent(marketPrice, discountNoCompetitionPercent);
598 warn = _AUCT('FrmtWarnAbovemkt');
599 end
600 -- Account for negative discountNoCompetitionPercent values
601 if (currentLowestBuyout <= marketPrice or highestSellablePrice >= currentLowestBuyout) then
602 -- set highest price to "Discount low"
603 highestSellablePrice = subtractPercent(currentLowestBuyout, discountLowPercent);
604 warn = string.format(_AUCT('FrmtWarnUndercut'), discountLowPercent);
605 end
606 end
607 else -- no low buyout, use discount no competition
608 -- set highest price to "Discount no competition"
609 highestSellablePrice = subtractPercent(marketPrice, discountNoCompetitionPercent);
610 warn = _AUCT('FrmtWarnNocomp');
611 end
612 else -- no market
613 -- Note: urentLowestBuyout is nil, incase the realm is not the current player's realm
614 if currentLowestBuyout and currentLowestBuyout > 0 then
615 -- set highest price to "Discount low"
616 EnhTooltip.DebugPrint("Discount low case 2");
617 highestSellablePrice = subtractPercent(currentLowestBuyout, discountLowPercent);
618 warn = string.format(_AUCT('FrmtWarnUndercut'), discountLowPercent);
619 else
620 local baseData;
621 if (Informant) then baseData = Informant.GetItem(id) end
622  
623 if (baseData and baseData.sell) then
624 -- use vendor prices if no auction data available
625 local vendorSell = Auctioneer.Util.NullSafe(baseData.sell); -- use vendor prices
626 highestSellablePrice = addPercent(vendorSell, vendorSellMarkupPercent);
627 warn = string.format(_AUCT('FrmtWarnMarkup'), vendorSellMarkupPercent);
628 end
629 end
630 end
631  
632 return highestSellablePrice, marketPrice, warn;
633 end
634  
635  
636 -------------------------------------------------------------------------------
637 -- Scan Statistic Functions
638 -------------------------------------------------------------------------------
639 function setScanLength(startTime, endTime)
640 --Make both parameters required ones and make sure they're numbers
641 if (not (tonumber(startTime) and tonumber(endTime))) then
642 return
643 end
644  
645 --Initialize our data structure
646 if (not AuctionConfig.scanStats) then
647 AuctionConfig.scanStats = {}
648 end
649  
650 AuctionConfig.scanStats.lastScanLength = (endTime - startTime)
651 end
652  
653 function setScanAge(endTime)
654 --Make our parameter a required one and make sure its a number
655 endTime = tonumber(endTime)
656 if (not endTime) then
657 return
658 end
659  
660 --Initialize our data structure
661 if (not AuctionConfig.scanStats) then
662 AuctionConfig.scanStats = {}
663 end
664  
665 AuctionConfig.scanStats.lastScanAge = endTime
666 end
667  
668 function getScanLength()
669 if (AuctionConfig and AuctionConfig.scanStats) then
670 return AuctionConfig.scanStats.lastScanLength
671 end
672 end
673  
674 function getScanAge()
675 if (AuctionConfig and AuctionConfig.scanStats and tonumber(AuctionConfig.scanStats.lastScanAge)) then
676 return time() - AuctionConfig.scanStats.lastScanAge
677 end
678 end
679  
680  
681 Auctioneer.Statistic = {
682 SubtractPercent = subtractPercent,
683 AddPercent = addPercent,
684 PercentLessThan = percentLessThan,
685 GetLowest = getLowest,
686 GetMedian = getMedian,
687 GetPercentile = getPercentile,
688 GetMeans = getMeans,
689 GetItemSnapshotMedianBuyout = getItemSnapshotMedianBuyout,
690 GetSnapMedian = getItemSnapshotMedianBuyout,
691 GetItemHistoricalMedianBuyout = getItemHistoricalMedianBuyout,
692 GetHistMedian = getItemHistoricalMedianBuyout,
693 GetUsableMedian = getUsableMedian,
694 GetCurrentBid = getCurrentBid,
695 IsBadResaleChoice = isBadResaleChoice,
696 ProfitComparisonSort = profitComparisonSort,
697 RoundDownTo95 = roundDownTo95,
698 FindLowestAuctions = findLowestAuctions,
699 BuildLowestCache = buildLowestCache,
700 DoLow = doLow,
701 DoMedian = doMedian,
702 DoHSP = doHSP,
703 GetBidBasedSellablePrice = getBidBasedSellablePrice,
704 GetMarketPrice = getMarketPrice,
705 GetHSP = getHSP,
706 DeterminePrice = determinePrice,
707 SetScanLength = setScanLength,
708 SetScanAge = setScanAge,
709 GetScanLength = getScanLength,
710 GetScanAge = getScanAge,
711 }