vanilla-wow-addons – Blame information for rev 1
?pathlinks?
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 | } |