跳转到内容
打开/关闭菜单
  • 451 条目
  • 808 文件
  • 22 用户
  • 9259 编辑
Blockland Wiki
打开/关闭外观设置菜单
打开/关闭个人菜单
未登录
未登录用户的IP地址会在进行任意编辑后公开展示。

模块:OWMOMarket:修订间差异

来自Blockland Wiki
无编辑摘要
无编辑摘要
 
(未显示同一用户的7个中间版本)
第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
        return "错误:未找到 External Data 的 Lua 接口,请确保插件已正确安装且版本支持。"
    end


     -- 1. 获取最新期数 (注意 getDbData 和 ['order by'] 的正确语法)
     -- 1. 获取最新期数并推算 8 期(1个月)前的起点
     local latest_data, err1 = extData.getDbData{
     local latest_data = extData.getDbData{
         db = 'wp_db',  
         db = 'wp_db', from = 'wp_owmc_currency_rates',
        from = 'wp_owmc_currency_rates',
         ['order by'] = 'year DESC, month DESC', limit = 1,
         ['order by'] = 'year DESC, month DESC',  
        limit = 1,
         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


     -- 2. 计算 8 期前(1 个月前)的期数
     -- 2. 获取数据
     local current_abs = max_y * 104 + max_m
     local where_clause = string.format("(year * 104 + month) >= %d", min_abs)
    local old_abs = current_abs - 8
     local all_rates = extData.getDbData{
    local old_y = math.floor((old_abs - 1) / 104)
         db = 'wp_db', from = 'wp_owmc_currency_rates',
    local old_m = ((old_abs - 1) % 104) + 1
         where = where_clause, limit = 5000,
 
         data = 'currency_code=currency_code, year=year, month=month, rate=rate'
    -- 3. 抓取本期和上月的数据及元数据
     local current_rates = extData.getDbData{  
         db = 'wp_db',  
        from = 'wp_owmc_currency_rates',  
         where = string.format("year=%d AND month=%d", max_y, max_m),  
        limit = 200,
         data = 'currency_code=currency_code, rate=rate'
    }
    local old_rates = extData.getDbData{
        db = 'wp_db',  
        from = 'wp_owmc_currency_rates',
        where = string.format("year=%d AND month=%d", old_y, old_m),
        limit = 200,
        data = 'currency_code=currency_code, rate=rate'
     }
     }
     local meta_data = extData.getDbData{  
     local meta_data = extData.getDbData{
         db = 'wp_db',  
         db = 'wp_db', from = 'wp_owmc_currency_meta', limit = 200,
        from = 'wp_owmc_currency_meta',  
        limit = 200,
         data = 'currency_code=currency_code, currency_name=currency_name'
         data = 'currency_code=currency_code, currency_name=currency_name'
     }
     }


     -- 4. 数据映射与处理
     -- 3. WPV 动态计算 (几何平均)
     local meta_map, old_map = {}, {}
     local meta_map = {}
     if meta_data then  
    if meta_data then for _, r in ipairs(meta_data) do meta_map[r.currency_code] = r.currency_name end end
         for _, r in ipairs(meta_data) do  
    local rates_by_abs = {}
             meta_map[r.currency_code] = r.currency_name
    local codes_set = {}
         end  
     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
     end
     if old_rates then  
     local wpv_by_abs = {}
        for _, r in ipairs(old_rates) do  
    for abs_p = min_abs, max_abs do
             old_map[r.currency_code] = tonumber(r.rate)
        local rates = rates_by_abs[abs_p]
         end  
        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
     end


    -- 4. 统计处理 (WMC归1标价,WPV核算涨跌)
     local stats = {}
     local stats = {}
     if current_rates then
     local wmc_current = rates_by_abs[max_abs] and rates_by_abs[max_abs]['WMC'] or 1.0
        for _, row in ipairs(current_rates) do
    for code, _ in pairs(codes_set) do
            local code = row.currency_code
        local current_rate = rates_by_abs[max_abs] and rates_by_abs[max_abs][code]
            local c_rate = tonumber(row.rate)
        local old_rate = rates_by_abs[min_abs] and rates_by_abs[min_abs][code]
            local o_rate = old_map[code]
        if current_rate and old_rate and current_rate > 0 and old_rate > 0 then
 
            local val_current = wmc_current / current_rate
            if c_rate and o_rate and c_rate > 0 then
            local wpv_current, wpv_old = wpv_by_abs[max_abs], wpv_by_abs[min_abs]
                -- 计算相对价值 (1 本币 = ? 参照),并算出涨跌幅
            local fwd_current, fwd_old = wpv_current / current_rate, wpv_old / old_rate
                local val_current = 1 / c_rate
            local pct = ((fwd_current - fwd_old) / fwd_old) * 100
                local val_old = 1 / o_rate
            local history_vals, min_v, max_v = {}, math.huge, -math.huge
                local pct = ((val_current - val_old) / val_old) * 100
            for abs_p = min_abs, max_abs do
                table.insert(stats, { code = code, name = meta_map[code] or code, val = val_current, pct = pct })
                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_v then min_v = w_val end
                    if w_val > max_v then max_v = 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_v, max_v = max_v })
         end
         end
     end
     end
    -- 按照涨跌幅降序排序
     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. 开始渲染 HTML
     -- 5. 渲染 HTML (优化 UI 与 6 位小数)
     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', Roboto, sans-serif; padding: 2px 4px;")
        :cssText("width: 260px; float: right; clear: right; margin: 0 0 16px 16px; background: #ffffff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 16px; box-shadow: 0 4px 12px rgba(0,0,0,0.03); font-family: -apple-system, 'Segoe UI', sans-serif;")


     local function renderRow(item)
     local function renderRow(item)
第90行: 第88行:
         local sign = is_up and "▲ +" or "▼ "
         local sign = is_up and "▲ +" or "▼ "
         local pct_str = string.format("%.2f%%", item.pct)
         local pct_str = string.format("%.2f%%", item.pct)
         local val_str = string.format("%.4f", item.val)
         local val_str = string.format("%.6f", item.val) -- 修改为 6 位小数


         -- SVG 原生绘制极简走势线
         -- 宽图表计算 (svg_w = 90)
         local svg_path = is_up and "M2,16 L18,10 L38,4" or "M2,4 L18,10 L38,16"
         local svg_w, svg_h = 90, 30
         if item.pct == 0 then svg_path = "M2,10 L38,10" end
        local pts, range_v = {}, item.max_v - item.min_v
         local sparkline = string.format('<svg width="40" height="20" viewBox="0 0 40 20"><path d="%s" fill="none" stroke="%s" stroke-width="2" stroke-linecap="round"/></svg>', svg_path, color)
        if range_v == 0 then range_v = 1 end
        for i, h_val in ipairs(item.history) do
            local x = (i - 1) / (#item.history - 1) * svg_w
            local y = 3 + (1 - (h_val - item.min_v) / range_v) * (svg_h - 6)
            table.insert(pts, string.format("%.1f,%.1f", x, y))
        end
        local line_path = "M" .. table.concat(pts, " L")
         local area_path = line_path .. string.format(" L%.1f,%.1f L0,%.1f Z", svg_w, svg_h, svg_h)
         local raw_svg = string.format('<svg width="%d" height="%d" viewBox="0 0 %d %d" style="display:block; overflow:visible;"><defs><linearGradient id="g_%s" x1="0%%" y1="0%%" x2="0%%" y2="100%%"><stop offset="0%%" stop-color="%s" stop-opacity="0.3" /><stop offset="100%%" stop-color="%s" stop-opacity="0" /></linearGradient></defs><path d="%s" fill="url(#g_%s)" /><path d="%s" fill="none" stroke="%s" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>', svg_w, svg_h, svg_w, svg_h, item.code, color, color, area_path, item.code, line_path, color)
        local sparkline = frame:extensionTag('html', raw_svg)


         return string.format([[
         return string.format([[
         <div style="display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px solid #f8fafc;">
         <div style="display: flex; justify-content: space-between; align-items: center; padding: 16px 8px; border-bottom: 1px solid #f1f5f9; transition: background 0.2s;">
             <div style="flex: 1; text-align: left;">
             <div style="flex: 1.2; text-align: left; min-width: 80px;">
                 <div style="font-weight: 900; color: #0f172a; font-size: 14px; font-family: 'Courier New', monospace;">%s</div>
                 <div style="font-weight: 900; color: #1e293b; font-size: 16px; font-family: 'JetBrains Mono', 'Courier New', monospace; letter-spacing: -0.5px;">%s</div>
                 <div style="font-size: 11px; color: #64748b; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 80px;">%s</div>
                 <div style="font-size: 12px; color: #94a3b8; font-weight: 600; margin-top: 2px;">%s</div>
             </div>
             </div>
             <div style="flex: 0 0 45px; text-align: center;">%s</div>
             <div style="flex: 0 0 95px; text-align: center; display: flex; justify-content: center; margin: 0 10px;">%s</div>
             <div style="flex: 1; text-align: right;">
             <div style="flex: 1.5; text-align: right;">
                 <div style="font-weight: bold; font-family: 'Courier New', monospace; font-size: 14px; color: #0f172a;">%s</div>
                 <div style="font-weight: 800; font-family: 'JetBrains Mono', 'Courier New', monospace; font-size: 16px; color: #0f172a; letter-spacing: -0.2px;">%s</div>
                 <div style="font-size: 11px; font-weight: 800; color: %s; letter-spacing: -0.5px;">%s%s</div>
                 <div style="font-size: 13px; font-weight: 800; color: %s; margin-top: 2px;">%s%s</div>
             </div>
             </div>
         </div>
         </div>
第112行: 第119行:
     end
     end


     -- 渲染主币
     local function renderHeader(title, color)
    html:node(mw.html.create('div'):cssText("font-size: 12px; font-weight: 800; color: #94a3b8; text-transform: uppercase; margin-bottom: 4px;"):wikitext("Core Assets / 主币"))
        return mw.html.create('div'):cssText(string.format("font-size: 13px; font-weight: 900; color: %s; text-transform: uppercase; margin: 20px 0 8px 10px; letter-spacing: 0.8px; display: flex; align-items: center; gap: 6px;", color)):wikitext(title)
    end
 
    html:node(renderHeader("● Core Assets / 主币", "#64748b"))
     local cores = {WMC=true, KCK=true, SKV=true, COS=true}
     local cores = {WMC=true, KCK=true, SKV=true, COS=true}
     for _, item in ipairs(stats) do
     for _, item in ipairs(stats) do if cores[item.code] then html:node(mw.html.create('div'):wikitext(renderRow(item))) end end
        if cores[item.code] then html:node(mw.html.create('div'):wikitext(renderRow(item))) end
    end


    -- 渲染领涨 Top 3
     html:node(renderHeader("● Top Gainers / 领涨", "#f43f5e"))
     html:node(mw.html.create('div'):cssText("font-size: 12px; font-weight: 800; color: #f43f5e; text-transform: uppercase; margin-top: 16px; margin-bottom: 4px;"):wikitext("Top Gainers / 领涨"))
     local count = 0
     local count = 0
     for _, item in ipairs(stats) do
     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
        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


    -- 渲染领跌 Top 3
     html:node(renderHeader("● Top Losers / 领跌", "#10b981"))
     html:node(mw.html.create('div'):cssText("font-size: 12px; font-weight: 800; color: #10b981; text-transform: uppercase; margin-top: 16px; margin-bottom: 4px;"):wikitext("Top Losers / 领跌"))
     count = 0
     count = 0
     for i = #stats, 1, -1 do
     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
        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: #cbd5e1; font-weight: 700; margin-top: 20px; padding: 15px 0; border-top: 1px dashed #e2e8f0;"):wikitext("数据实时同步自: [https://blkl.fun/omoo OMOO 金融大盘]"))
     return tostring(html)
     return tostring(html)
end
end


return p
return p

2026年5月8日 (五) 19:20的最新版本

此模块的文档可以在模块: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

    -- 2. 获取数据
    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
            local val_current = wmc_current / current_rate
            local wpv_current, wpv_old = wpv_by_abs[max_abs], wpv_by_abs[min_abs]
            local fwd_current, fwd_old = wpv_current / current_rate, wpv_old / old_rate
            local pct = ((fwd_current - fwd_old) / fwd_old) * 100
            local history_vals, min_v, max_v = {}, math.huge, -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_v then min_v = w_val end
                    if w_val > max_v then max_v = 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_v, max_v = max_v })
        end
    end
    table.sort(stats, function(a, b) return a.pct > b.pct end)

    -- 5. 渲染 HTML (优化 UI 与 6 位小数)
    local html = mw.html.create('div'):cssText("width: 100%; margin: 0; background: transparent; font-family: -apple-system, 'Segoe UI', Roboto, sans-serif; padding: 2px 4px;")

    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("%.6f", item.val) -- 修改为 6 位小数

        -- 宽图表计算 (svg_w = 90)
        local svg_w, svg_h = 90, 30
        local pts, range_v = {}, item.max_v - item.min_v
        if range_v == 0 then range_v = 1 end
        for i, h_val in ipairs(item.history) do
            local x = (i - 1) / (#item.history - 1) * svg_w
            local y = 3 + (1 - (h_val - item.min_v) / range_v) * (svg_h - 6)
            table.insert(pts, string.format("%.1f,%.1f", x, y))
        end
        local line_path = "M" .. table.concat(pts, " L")
        local area_path = line_path .. string.format(" L%.1f,%.1f L0,%.1f Z", svg_w, svg_h, svg_h)
        local raw_svg = string.format('<svg width="%d" height="%d" viewBox="0 0 %d %d" style="display:block; overflow:visible;"><defs><linearGradient id="g_%s" x1="0%%" y1="0%%" x2="0%%" y2="100%%"><stop offset="0%%" stop-color="%s" stop-opacity="0.3" /><stop offset="100%%" stop-color="%s" stop-opacity="0" /></linearGradient></defs><path d="%s" fill="url(#g_%s)" /><path d="%s" fill="none" stroke="%s" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" /></svg>', svg_w, svg_h, svg_w, svg_h, item.code, color, color, area_path, item.code, line_path, color)
        local sparkline = frame:extensionTag('html', raw_svg)

        return string.format([[
        <div style="display: flex; justify-content: space-between; align-items: center; padding: 16px 8px; border-bottom: 1px solid #f1f5f9; transition: background 0.2s;">
            <div style="flex: 1.2; text-align: left; min-width: 80px;">
                <div style="font-weight: 900; color: #1e293b; font-size: 16px; font-family: 'JetBrains Mono', 'Courier New', monospace; letter-spacing: -0.5px;">%s</div>
                <div style="font-size: 12px; color: #94a3b8; font-weight: 600; margin-top: 2px;">%s</div>
            </div>
            <div style="flex: 0 0 95px; text-align: center; display: flex; justify-content: center; margin: 0 10px;">%s</div>
            <div style="flex: 1.5; text-align: right;">
                <div style="font-weight: 800; font-family: 'JetBrains Mono', 'Courier New', monospace; font-size: 16px; color: #0f172a; letter-spacing: -0.2px;">%s</div>
                <div style="font-size: 13px; font-weight: 800; color: %s; margin-top: 2px;">%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: 900; color: %s; text-transform: uppercase; margin: 20px 0 8px 10px; letter-spacing: 0.8px; display: flex; align-items: center; gap: 6px;", color)):wikitext(title)
    end

    html:node(renderHeader("● Core Assets / 主币", "#64748b"))
    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: #cbd5e1; font-weight: 700; margin-top: 20px; padding: 15px 0; border-top: 1px dashed #e2e8f0;"):wikitext("数据实时同步自: [https://blkl.fun/omoo OMOO 金融大盘]"))
    return tostring(html)
end

return p