diff --git a/core.lua b/core.lua index de2813b..7dda1cc 100644 --- a/core.lua +++ b/core.lua @@ -1,4 +1,4 @@ -AuxVersion = '2.2.9' +AuxVersion = '2.2.10' AuxAuthors = 'shirsig; Zerf; Zirco (Auctionator); Nimeral (Auctionator backport)' local lastRightClickAction = GetTime() @@ -36,7 +36,7 @@ function Aux_OnLoad() local item_id, suffix_id = EnhTooltip.BreakLink(link) local item_key = (item_id or 0)..':'..(suffix_id or 0) - local auction_count, seen_days, AVG_TDA, MIN_TDA, AVG_EMA5, AVG_EMA30, MIN_EMA5, MIN_EMA30 = Aux.stat_average.get_price_data(item_key) + local auction_count, day_count, TDA, EMA5, EMA30 = Aux.history.get_price_data(item_key) if auction_count == 0 then EnhTooltip.AddLine('Never seen at auction', nil, true) @@ -45,26 +45,21 @@ function Aux_OnLoad() EnhTooltip.AddLine('Seen '..auction_count..' '..Aux_PluralizeIf('time', auction_count)..' at auction', nil, true) EnhTooltip.LineColor(0.5, 0.8, 0.1) - local median = Aux.stat_histogram.get_price_data(item_key) + local market_value = Aux.history.get_market_value(item_key) if count == 1 then - EnhTooltip.AddLine('Median: '..Aux.util.money_string(median), nil, true) + EnhTooltip.AddLine('Market Value: '..Aux.util.money_string(market_value), nil, true) else - EnhTooltip.AddLine('Median: '..Aux.util.money_string(median * count)..' ('..Aux.util.money_string(median)..' ea.)', nil, true) + EnhTooltip.AddLine('Market Value: '..Aux.util.money_string(market_value * count)..' ('..Aux.util.money_string(market_value)..' ea.)', nil, true) end EnhTooltip.LineColor(0.1,0.8,0.5) --- EnhTooltip.AddLine('AVG TDA: '..(AVG_TDA > 0 and Aux.util.money_string(AVG_TDA) or RED_FONT_COLOR_CODE..'n/a'..FONT_COLOR_CODE_CLOSE), nil, true) +-- EnhTooltip.AddLine('TDA: '..(TDA > 0 and Aux.util.money_string(TDA) or RED_FONT_COLOR_CODE..'n/a'..FONT_COLOR_CODE_CLOSE), nil, true) -- EnhTooltip.LineColor(0.1,0.8,0.5) --- EnhTooltip.AddLine('AVG EMA5: '..(AVG_EMA5 > 0 and Aux.util.money_string(AVG_EMA5) or RED_FONT_COLOR_CODE..'n/a'..FONT_COLOR_CODE_CLOSE), nil, true) +-- EnhTooltip.AddLine('EMA5: '..(EMA5 > 0 and Aux.util.money_string(EMA5) or RED_FONT_COLOR_CODE..'n/a'..FONT_COLOR_CODE_CLOSE), nil, true) -- EnhTooltip.LineColor(0.1,0.8,0.5) --- EnhTooltip.AddLine('AVG EMA30: '..(AVG_EMA30 > 0 and Aux.util.money_string(AVG_EMA30) or RED_FONT_COLOR_CODE..'n/a'..FONT_COLOR_CODE_CLOSE), nil, true) --- EnhTooltip.LineColor(0.1,0.8,0.5) --- EnhTooltip.AddLine('MIN TDA: '..(MIN_TDA > 0 and Aux.util.money_string(MIN_TDA) or RED_FONT_COLOR_CODE..'n/a'..FONT_COLOR_CODE_CLOSE), nil, true) --- EnhTooltip.LineColor(0.1,0.8,0.5) --- EnhTooltip.AddLine('MIN EMA5: '..(MIN_EMA5 > 0 and Aux.util.money_string(MIN_EMA5) or RED_FONT_COLOR_CODE..'n/a'..FONT_COLOR_CODE_CLOSE), nil, true) --- EnhTooltip.LineColor(0.1,0.8,0.5) --- EnhTooltip.AddLine('MIN EMA30: '..(MIN_EMA30 > 0 and Aux.util.money_string(MIN_EMA30) or RED_FONT_COLOR_CODE..'n/a'..FONT_COLOR_CODE_CLOSE), nil, true) +-- EnhTooltip.AddLine('EMA30: '..(EMA30 > 0 and Aux.util.money_string(EMA30) or RED_FONT_COLOR_CODE..'n/a'..FONT_COLOR_CODE_CLOSE), nil, true) -- EnhTooltip.LineColor(0.1,0.8,0.5) + end end) end diff --git a/scan.lua b/scan.lua index 711c862..494df50 100644 --- a/scan.lua +++ b/scan.lua @@ -150,8 +150,8 @@ function scan_auctions_helper(i, n, k) local snapshot = Aux.persistence.load_snapshot() if not snapshot.contains(auction_info.signature) then snapshot.add(auction_info.signature, auction_info.duration) - Aux.stat_average.process_auction(auction_info) - Aux.stat_histogram.process_auction(auction_info) + Aux.history.process_auction(auction_info) +-- Aux.stat_histogram.process_auction(auction_info) end else recurse() diff --git a/stat_average.lua b/stat_average.lua index b95915f..3630e74 100644 --- a/stat_average.lua +++ b/stat_average.lua @@ -1,25 +1,38 @@ local private, public = {}, {} -Aux.stat_average = public +Aux.history = public private.PUSH_INTERVAL = 57600 -private.NEW_RECORD = '0:0:0:0:0:0:0:0:0' +private.NEW_RECORD = '0#0#0#0#' function private.load_data() local dataset = Aux.persistence.load_dataset() - dataset.stat_average_data = dataset.stat_average_data or { next_push = time() + private.PUSH_INTERVAL, item_data = {} } - return dataset.stat_average_data + dataset.history = dataset.history or { next_push = time() + private.PUSH_INTERVAL, item_data = {} } + return dataset.history end function public.read_record(item_key) local data = private.load_data() - return Aux.util.map(Aux.persistence.deserialize(data.item_data[item_key] or private.NEW_RECORD, ':'), function(value) - return tonumber(value) - end) + local record = Aux.persistence.deserialize(data.item_data[item_key] or private.NEW_RECORD, '#') + return { + auction_count = tonumber(record[1]), + day_count = tonumber(record[2]), + EMA5 = tonumber(record[3]), + EMA30 = tonumber(record[4]), + histogram = Aux.util.map(Aux.persistence.deserialize(record[5], ';'), function(value) + return tonumber(value) + end), + } end function private.write_record(item_key, record) local data = private.load_data() - data.item_data[item_key] = Aux.persistence.serialize(record, ':') + data.item_data[item_key] = Aux.persistence.serialize({ + record.auction_count, + record.day_count, + record.EMA5, + record.EMA30, + Aux.persistence.serialize(record.histogram, ';'), + },'#') end function public.process_auction(auction_info) @@ -38,97 +51,57 @@ function public.process_auction(auction_info) local item_record = public.read_record(auction_info.item_key) - item_record[1] = item_record[1] + 1 -- auction count - item_record[2] = item_record[2] + 1 -- daily auction count - item_record[3] = item_record[3] + buyout -- daily accumulated buyout - item_record[4] = item_record[4] ~= 0 and min(item_record[4], buyout) or buyout -- daily min buyout + item_record.auction_count = item_record.auction_count + 1 + + for i=1,225 do + item_record.histogram[i] = item_record.histogram[i] or 0 + if buyout < 1.1 ^ i then + item_record.histogram[i] = item_record.histogram[i] + 1 + break + end + end private.write_record(auction_info.item_key, item_record) end function public.get_price_data(item_key) - local auction_count, daily_auction_count, daily_accumulated_buyout, daily_min_buyout, seen_days, avg_EMA5, avg_EMA30, min_EMA5, min_EMA30 = unpack(public.read_record(item_key)) - local daily_average = daily_accumulated_buyout / daily_auction_count - return auction_count, seen_days, daily_average, daily_min_buyout, avg_EMA5, avg_EMA30, min_EMA5, min_EMA30 + local item_record = public.read_record(item_key) + return item_record.auction_count, item_record.day_count, private.daily_market_value(item_record.histogram), item_record.EMA5, item_record.EMA30 end -function public.get_mean(item_key) - local _, daily_auction_count, daily_accumulated_buyout, daily_min_buyout, seen_days, avg_EMA5, avg_EMA30, min_EMA5, min_EMA30 = unpack(public.read_record(item_key)) --- --- local mean = 0 --- local daily_average = daily_accumulated_buyout / daily_auction_count --- --- if seen_days == 0 then --- if daily_auction_count > 0 then --- mean = daily_average --- end --- elseif seen_days <= 3 then -- No EMAs before day 4 --- mean = EMA3 --- if daily_auction_count > 0 then --- mean = (mean * seen_days + daily_average) / (seen_days + 1) --- end --- else --- -- we have 4 or more days of data, potentially enough to perform mean and stddev calculations --- local count = 0 --- local valueset, weightset = {}, {} --- --- -- include daily data if available --- if daily_auction_count > 0 then --- count = 1 --- valueset[count] = daily_average --- weightset[count] = 1 --- end --- --- -- EMA3: standard weight 3, reduced if seenDays < 6, reduced if there was daily data, but never less than 1 --- local weight = 3 - count --- if seen_days < 6 then --- weight = seen_days - 3 --- if weight > 1 then --- weight = weight - count --- end --- end --- count = count + 1 --- valueset[count] = EMA3 --- weightset[count] = weight --- --- -- EMA7: standard weight 4, reduced if seenDays < 10 --- if seen_days > 6 then --- count = count + 1 --- valueset[count] = EMA7 --- if seen_days < 10 then --- weightset[count] = seen_days - 6 --- else --- weightset[count] = 4 --- end --- end --- --- -- EMA14: standard weight 7, reduced if seenDays < 17 --- if seen_days > 10 then --- count = count + 1 --- valueset[count] = EMA14 --- if seen_days < 17 then --- weightset[count] = seen_days - 10 --- else --- weightset[count] = 7 --- end --- end --- --- -- we will use a weighted incremental algorithm, based on sample code by West and Knuth http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance --- local sumWeight, sumSquares = 0, 0 -- actually "sum of squares of differences from the (current) mean", but that's rather long for a variable name. --- for i=1,count do --- local value, weight = valueset[i], weightset[i] --- local nextweight = weight + sumWeight --- local valuediff = value - mean --- local meanadjust = valuediff * weight / nextweight --- mean = mean + meanadjust --- sumSquares = sumSquares + sumWeight * valuediff * meanadjust --- sumWeight = nextweight --- end --- --- -- stddev = sqrt(sumSquares / sumWeight * count / (count - 1)) --- end --- --- return mean +function public.get_market_value(item_key) + local auction_count, day_count, daily_market_value, EMA5, EMA30 = public.get_price_data(item_key) + + if day_count == 0 then + return daily_market_value + else + return EMA5 + end +end + +function private.daily_market_value(histogram) + + local daily_auction_count = 0 + for _, frequency in ipairs(histogram) do + daily_auction_count = daily_auction_count + frequency + end + + if daily_auction_count == 0 then + return 0 + end + + -- average of lowest 25% + local sum, count = 0, 0 + local limit = daily_auction_count * 0.25 + for i, frequency in ipairs(histogram) do + local limited_frequency = min(frequency, limit - count) + sum = sum + 1.1 ^ (i - 1) * 1.05 * limited_frequency + count = count + frequency + if count >= limit then + break + end + end + return sum / limit end function private.push_data() @@ -138,36 +111,57 @@ function private.push_data() for item_key, _ in pairs(item_data) do local item_record = public.read_record(item_key) - local _, daily_auction_count, daily_accumulated_buyout, daily_min_buyout, seen_days, avg_EMA5, avg_EMA30, min_EMA5, min_EMA30 = unpack(item_record) - if daily_auction_count > 0 then + if getn(item_record.histogram) ~= 0 then - local daily_average = daily_accumulated_buyout / daily_auction_count + local daily_market_value = private.daily_market_value(item_record.histogram) - if seen_days == 0 then - avg_EMA5 = daily_average - avg_EMA30 = daily_average - min_EMA5 = daily_min_buyout - min_EMA30 = daily_min_buyout + if item_record.day_count == 0 then + item_record.EMA5 = daily_market_value + item_record.EMA30 = daily_market_value else - avg_EMA5 = 2/3 * avg_EMA5 + 1/3 * daily_average - avg_EMA30 = 13/14 * avg_EMA30 + 1/14 * daily_average - min_EMA5 = 2/3 * min_EMA5 + 1/3 * daily_min_buyout - min_EMA30 = 13/14 * min_EMA30 + 1/14 * daily_min_buyout + item_record.EMA5 = 2/3 * item_record.EMA5 + 1/3 * daily_market_value + item_record.EMA30 = 13/14 * item_record.EMA30 + 1/14 * daily_market_value end - item_record[2] = 0 -- daily auction count - item_record[3] = 0 -- daily accumulated buyout - item_record[4] = 0 -- daily min buyout - item_record[5] = seen_days + 1 -- seen days - item_record[6] = avg_EMA5 -- avg_EMA5 - item_record[7] = avg_EMA30 -- avg_EMA30 - item_record[8] = min_EMA5 -- min_EMA5 - item_record[9] = min_EMA30 -- min_EMA30 + item_record.day_count = item_record.day_count + 1 + item_record.histogram = {} private.write_record(item_key, item_record) end end data.next_push = time() + private.PUSH_INTERVAL -end \ No newline at end of file +end + +--function private.max_heap(array) +-- local self = {} +-- +-- local ROOT = 1 +-- +-- local function parent(i) +-- return floor((i - 2) / 2) + 1 +-- end +-- +-- local function left_child(i) +-- return 2 * (i - 1) + 2 +-- end +-- +-- local function right_child(i) +-- return 2 * (i - 1) + 3 +-- end +-- +-- function self.insert(value) +-- local index = getn(array) + 1 +-- +-- while index > ROOT and array[index - 1] > 0 do +-- index = index - 1 +-- end +-- end +-- +-- function self.extract(signature) +-- return data[signature] ~= nil and data[signature] >= time() +-- end +-- +-- return self +--end \ No newline at end of file