模块:OWMOMarket:修订间差异
来自Blockland Wiki
更多操作
无编辑摘要 |
无编辑摘要 |
||
| 第3行: | 第3行: | ||
function p.renderSidebar(frame) | function p.renderSidebar(frame) | ||
local extData = mw.ext.externalData | local extData = mw.ext.externalData | ||
if not extData or not extData.getDbData then | if not extData or not extData.getDbData then return "错误:未找到 External Data 接口" end | ||
-- 1. | -- 1. 获取最新期数并推算 8 期(1个月)前的起点 | ||
local latest_data = extData.getDbData{ | local latest_data = extData.getDbData{ | ||
db = 'wp_db', | db = 'wp_db', from = 'wp_owmc_currency_rates', | ||
['order by'] = 'year DESC, month DESC', limit = 1, | |||
['order by'] = 'year DESC, month DESC', | |||
data = 'year=year, month=month' | data = 'year=year, month=month' | ||
} | } | ||
if not latest_data or not latest_data[1] then return "暂无行情数据" end | if not latest_data or not latest_data[1] then return "暂无行情数据" end | ||
local max_y = tonumber(latest_data[1].year) | local max_y = tonumber(latest_data[1].year) | ||
local max_m = tonumber(latest_data[1].month) | local max_m = tonumber(latest_data[1].month) | ||
local max_abs = max_y * 104 + max_m | |||
local min_abs = max_abs - 7 -- 获取包含当前的近 8 期 | |||
-- 2. | -- 2. 一次性抓取这 8 期的所有行情数据 | ||
local | local where_clause = string.format("(year * 104 + month) >= %d", min_abs) | ||
local | local all_rates = extData.getDbData{ | ||
local | db = 'wp_db', from = 'wp_owmc_currency_rates', | ||
where = where_clause, limit = 5000, | |||
data = 'currency_code=currency_code, year=year, month=month, rate=rate' | |||
} | |||
local meta_data = extData.getDbData{ | |||
db = 'wp_db', from = 'wp_owmc_currency_meta', limit = 200, | |||
data = 'currency_code=currency_code, currency_name=currency_name' | |||
} | |||
-- 3. | -- 3. 梳理数据结构 & 动态计算 WPV (对数几何平均) | ||
local | local meta_map = {} | ||
if meta_data then for _, r in ipairs(meta_data) do meta_map[r.currency_code] = r.currency_name end end | |||
local rates_by_abs = {} | |||
local | local codes_set = {} | ||
if | if all_rates then | ||
if | for _, r in ipairs(all_rates) do | ||
local abs_p = tonumber(r.year) * 104 + tonumber(r.month) | |||
if not rates_by_abs[abs_p] then rates_by_abs[abs_p] = {} end | |||
rates_by_abs[abs_p][r.currency_code] = tonumber(r.rate) | |||
codes_set[r.currency_code] = true | |||
end | |||
end | |||
local wpv_by_abs = {} | |||
for abs_p = min_abs, max_abs do | |||
local rates = rates_by_abs[abs_p] | |||
if rates then | |||
local sum_log = 0; local count = 0 | |||
for code, rate in pairs(rates) do | |||
if rate > 0 then sum_log = sum_log + math.log(rate); count = count + 1 end | |||
end | |||
wpv_by_abs[abs_p] = count > 0 and math.exp(sum_log / count) or 1.0 | |||
else | |||
wpv_by_abs[abs_p] = 1.0 | |||
end | |||
end | |||
-- 4. 统计处理 (WMC归1标价,WPV核算涨跌) | |||
local stats = {} | local stats = {} | ||
local wmc_current = rates_by_abs[max_abs] and rates_by_abs[max_abs]['WMC'] or 1.0 | |||
for code, _ in pairs(codes_set) do | |||
local current_rate = rates_by_abs[max_abs] and rates_by_abs[max_abs][code] | |||
local old_rate = rates_by_abs[min_abs] and rates_by_abs[min_abs][code] | |||
if current_rate and old_rate and current_rate > 0 and old_rate > 0 then | |||
-- 绝对值: 世界币归 1 (1 Target = ? WMC) | |||
local val_current = wmc_current / current_rate | |||
-- 涨跌幅: 基于 WPV 价值变动 | |||
local wpv_current = wpv_by_abs[max_abs] | |||
local wpv_old = wpv_by_abs[min_abs] | |||
local fwd_current = wpv_current / current_rate | |||
local fwd_old = wpv_old / old_rate | |||
local pct = ((fwd_current - fwd_old) / fwd_old) * 100 | |||
-- 提取近 1 个月的 8 期走势数据,用于绘制图表 | |||
local history_vals = {} | |||
local min_val = math.huge | |||
local max_val = -math.huge | |||
for abs_p = min_abs, max_abs do | |||
local r = rates_by_abs[abs_p] and rates_by_abs[abs_p][code] | |||
if r and r > 0 then | |||
local w_val = wpv_by_abs[abs_p] / r | |||
table.insert(history_vals, w_val) | |||
if w_val < min_val then min_val = w_val end | |||
if w_val > max_val then max_val = w_val end | |||
else | |||
table.insert(history_vals, 0) | |||
end | |||
end | end | ||
table.insert(stats, { | |||
code = code, name = meta_map[code] or code, | |||
val = val_current, pct = pct, | |||
history = history_vals, min_v = min_val, max_v = max_val | |||
}) | |||
end | end | ||
end | end | ||
| 第54行: | 第103行: | ||
table.sort(stats, function(a, b) return a.pct > b.pct end) | table.sort(stats, function(a, b) return a.pct > b.pct end) | ||
-- 5. | -- 5. 开始渲染 HTML | ||
local html = mw.html.create('div') | local html = mw.html.create('div'):cssText("width: 100%; margin: 0; background: transparent; font-family: -apple-system, 'Segoe UI', sans-serif;") | ||
local function renderRow(item) | local function renderRow(item) | ||
| 第65行: | 第113行: | ||
local val_str = string.format("%.4f", item.val) | local val_str = string.format("%.4f", item.val) | ||
-- | -- 纯 CSS 迷你柱状图:将近 1 个月的数据转为 8 根动态高度的小柱子 | ||
local | local bars_html = {} | ||
if item. | for _, h_val in ipairs(item.history) do | ||
local sparkline = string.format('< | local bar_height_pct = 50 -- 默认平盘高度 | ||
if item.max_v > item.min_v then | |||
bar_height_pct = 15 + ((h_val - item.min_v) / (item.max_v - item.min_v)) * 85 | |||
end | |||
if bar_height_pct > 100 then bar_height_pct = 100 end | |||
table.insert(bars_html, string.format('<div style="width: 4px; height: %f%%; background-color: %s; border-radius: 1px; opacity: 0.85;"></div>', bar_height_pct, color)) | |||
end | |||
local sparkline = string.format('<div style="display: flex; align-items: flex-end; justify-content: space-between; width: 44px; height: 22px; gap: 1px;">%s</div>', table.concat(bars_html, "")) | |||
-- | -- padding 调为 12px 0,保留合适边距又不过于松散 | ||
return string.format([[ | return string.format([[ | ||
<div style="display: flex; justify-content: space-between; align-items: center; padding: | <div style="display: flex; justify-content: space-between; align-items: center; padding: 12px 0; border-bottom: 1px solid #f1f5f9;"> | ||
<div style="flex: 1; text-align: left;"> | <div style="flex: 1; text-align: left;"> | ||
<div style="font-weight: 900; color: #0f172a; font-size: | <div style="font-weight: 900; color: #0f172a; font-size: 15px; font-family: 'Courier New', monospace;">%s</div> | ||
<div style="font-size: | <div style="font-size: 12px; color: #64748b; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100px;">%s</div> | ||
</div> | </div> | ||
<div style="flex: 0 0 | <div style="flex: 0 0 55px; text-align: center; display: flex; justify-content: center;">%s</div> | ||
<div style="flex: 1; text-align: right;"> | <div style="flex: 1; text-align: right;"> | ||
<div style="font-weight: bold; font-family: 'Courier New', monospace; font-size: | <div style="font-weight: bold; font-family: 'Courier New', monospace; font-size: 15px; color: #0f172a;">%s</div> | ||
<div style="font-size: | <div style="font-size: 12px; font-weight: 800; color: %s; letter-spacing: -0.5px;">%s%s</div> | ||
</div> | </div> | ||
</div> | </div> | ||
| 第86行: | 第141行: | ||
end | end | ||
local function renderHeader(title, color) | local function renderHeader(title, color) | ||
return mw.html.create('div'):cssText(string.format("font-size: | return mw.html.create('div'):cssText(string.format("font-size: 13px; font-weight: 800; color: %s; text-transform: uppercase; margin-top: 14px; margin-bottom: 4px;", color)):wikitext(title) | ||
end | end | ||
| 第117行: | 第171行: | ||
end | end | ||
end | end | ||
-- 底部加入数据来源声明 | |||
html:node(mw.html.create('div') | |||
:cssText("text-align: center; font-size: 11px; color: #94a3b8; font-weight: 600; margin-top: 16px; padding-top: 12px; border-top: 1px dashed #e2e8f0;") | |||
:wikitext("数据来源: [https://blkl.fun/omoo OMOO (blkl.fun/omoo)]")) | |||
return tostring(html) | return tostring(html) | ||
2026年5月8日 (五) 18:31的版本
此模块的文档可以在模块:OWMOMarket/doc创建
local p = {}
function p.renderSidebar(frame)
local extData = mw.ext.externalData
if not extData or not extData.getDbData then return "错误:未找到 External Data 接口" end
-- 1. 获取最新期数并推算 8 期(1个月)前的起点
local latest_data = extData.getDbData{
db = 'wp_db', from = 'wp_owmc_currency_rates',
['order by'] = 'year DESC, month DESC', limit = 1,
data = 'year=year, month=month'
}
if not latest_data or not latest_data[1] then return "暂无行情数据" end
local max_y = tonumber(latest_data[1].year)
local max_m = tonumber(latest_data[1].month)
local max_abs = max_y * 104 + max_m
local min_abs = max_abs - 7 -- 获取包含当前的近 8 期
-- 2. 一次性抓取这 8 期的所有行情数据
local where_clause = string.format("(year * 104 + month) >= %d", min_abs)
local all_rates = extData.getDbData{
db = 'wp_db', from = 'wp_owmc_currency_rates',
where = where_clause, limit = 5000,
data = 'currency_code=currency_code, year=year, month=month, rate=rate'
}
local meta_data = extData.getDbData{
db = 'wp_db', from = 'wp_owmc_currency_meta', limit = 200,
data = 'currency_code=currency_code, currency_name=currency_name'
}
-- 3. 梳理数据结构 & 动态计算 WPV (对数几何平均)
local meta_map = {}
if meta_data then for _, r in ipairs(meta_data) do meta_map[r.currency_code] = r.currency_name end end
local rates_by_abs = {}
local codes_set = {}
if all_rates then
for _, r in ipairs(all_rates) do
local abs_p = tonumber(r.year) * 104 + tonumber(r.month)
if not rates_by_abs[abs_p] then rates_by_abs[abs_p] = {} end
rates_by_abs[abs_p][r.currency_code] = tonumber(r.rate)
codes_set[r.currency_code] = true
end
end
local wpv_by_abs = {}
for abs_p = min_abs, max_abs do
local rates = rates_by_abs[abs_p]
if rates then
local sum_log = 0; local count = 0
for code, rate in pairs(rates) do
if rate > 0 then sum_log = sum_log + math.log(rate); count = count + 1 end
end
wpv_by_abs[abs_p] = count > 0 and math.exp(sum_log / count) or 1.0
else
wpv_by_abs[abs_p] = 1.0
end
end
-- 4. 统计处理 (WMC归1标价,WPV核算涨跌)
local stats = {}
local wmc_current = rates_by_abs[max_abs] and rates_by_abs[max_abs]['WMC'] or 1.0
for code, _ in pairs(codes_set) do
local current_rate = rates_by_abs[max_abs] and rates_by_abs[max_abs][code]
local old_rate = rates_by_abs[min_abs] and rates_by_abs[min_abs][code]
if current_rate and old_rate and current_rate > 0 and old_rate > 0 then
-- 绝对值: 世界币归 1 (1 Target = ? WMC)
local val_current = wmc_current / current_rate
-- 涨跌幅: 基于 WPV 价值变动
local wpv_current = wpv_by_abs[max_abs]
local wpv_old = wpv_by_abs[min_abs]
local fwd_current = wpv_current / current_rate
local fwd_old = wpv_old / old_rate
local pct = ((fwd_current - fwd_old) / fwd_old) * 100
-- 提取近 1 个月的 8 期走势数据,用于绘制图表
local history_vals = {}
local min_val = math.huge
local max_val = -math.huge
for abs_p = min_abs, max_abs do
local r = rates_by_abs[abs_p] and rates_by_abs[abs_p][code]
if r and r > 0 then
local w_val = wpv_by_abs[abs_p] / r
table.insert(history_vals, w_val)
if w_val < min_val then min_val = w_val end
if w_val > max_val then max_val = w_val end
else
table.insert(history_vals, 0)
end
end
table.insert(stats, {
code = code, name = meta_map[code] or code,
val = val_current, pct = pct,
history = history_vals, min_v = min_val, max_v = max_val
})
end
end
table.sort(stats, function(a, b) return a.pct > b.pct end)
-- 5. 开始渲染 HTML
local html = mw.html.create('div'):cssText("width: 100%; margin: 0; background: transparent; font-family: -apple-system, 'Segoe UI', sans-serif;")
local function renderRow(item)
local is_up = item.pct >= 0
local color = is_up and "#f43f5e" or "#10b981"
local sign = is_up and "▲ +" or "▼ "
local pct_str = string.format("%.2f%%", item.pct)
local val_str = string.format("%.4f", item.val)
-- 纯 CSS 迷你柱状图:将近 1 个月的数据转为 8 根动态高度的小柱子
local bars_html = {}
for _, h_val in ipairs(item.history) do
local bar_height_pct = 50 -- 默认平盘高度
if item.max_v > item.min_v then
bar_height_pct = 15 + ((h_val - item.min_v) / (item.max_v - item.min_v)) * 85
end
if bar_height_pct > 100 then bar_height_pct = 100 end
table.insert(bars_html, string.format('<div style="width: 4px; height: %f%%; background-color: %s; border-radius: 1px; opacity: 0.85;"></div>', bar_height_pct, color))
end
local sparkline = string.format('<div style="display: flex; align-items: flex-end; justify-content: space-between; width: 44px; height: 22px; gap: 1px;">%s</div>', table.concat(bars_html, ""))
-- padding 调为 12px 0,保留合适边距又不过于松散
return string.format([[
<div style="display: flex; justify-content: space-between; align-items: center; padding: 12px 0; border-bottom: 1px solid #f1f5f9;">
<div style="flex: 1; text-align: left;">
<div style="font-weight: 900; color: #0f172a; font-size: 15px; font-family: 'Courier New', monospace;">%s</div>
<div style="font-size: 12px; color: #64748b; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100px;">%s</div>
</div>
<div style="flex: 0 0 55px; text-align: center; display: flex; justify-content: center;">%s</div>
<div style="flex: 1; text-align: right;">
<div style="font-weight: bold; font-family: 'Courier New', monospace; font-size: 15px; color: #0f172a;">%s</div>
<div style="font-size: 12px; font-weight: 800; color: %s; letter-spacing: -0.5px;">%s%s</div>
</div>
</div>
]], item.code, item.name, sparkline, val_str, color, sign, pct_str)
end
local function renderHeader(title, color)
return mw.html.create('div'):cssText(string.format("font-size: 13px; font-weight: 800; color: %s; text-transform: uppercase; margin-top: 14px; margin-bottom: 4px;", color)):wikitext(title)
end
html:node(renderHeader("Core Assets / 主币", "#94a3b8"))
local cores = {WMC=true, KCK=true, SKV=true, COS=true}
for _, item in ipairs(stats) do
if cores[item.code] then html:node(mw.html.create('div'):wikitext(renderRow(item))) end
end
html:node(renderHeader("Top Gainers / 领涨", "#f43f5e"))
local count = 0
for _, item in ipairs(stats) do
if not cores[item.code] and item.pct > 0 then
html:node(mw.html.create('div'):wikitext(renderRow(item)))
count = count + 1
if count >= 3 then break end
end
end
html:node(renderHeader("Top Losers / 领跌", "#10b981"))
count = 0
for i = #stats, 1, -1 do
local item = stats[i]
if not cores[item.code] and item.pct < 0 then
html:node(mw.html.create('div'):wikitext(renderRow(item)))
count = count + 1
if count >= 3 then break end
end
end
-- 底部加入数据来源声明
html:node(mw.html.create('div')
:cssText("text-align: center; font-size: 11px; color: #94a3b8; font-weight: 600; margin-top: 16px; padding-top: 12px; border-top: 1px dashed #e2e8f0;")
:wikitext("数据来源: [https://blkl.fun/omoo OMOO (blkl.fun/omoo)]"))
return tostring(html)
end
return p