//@version=6 indicator("多周期WMA52 (HTF/LTF)", overlay=true, max_labels_count=500, max_lines_count=500) // ======================== 配置输入 ======================== // 主控 enableCTF = input.bool(true, "启用本级别", group="显示") enableHTF = input.bool(true, "启用长级别", group="显示") enableLTF = input.bool(true, "启用次级别", group="显示") maxHTFInput = input.int(10, "最多显示长级别数量", minval=1, maxval=12, group="显示") maxLTFInput = input.int(3, "最多显示次级别数量", minval=1, maxval=4, group="显示") includeSeconds = input.bool(false, "包含秒级别(需高级套餐)", group="显示") protectAutoscale = input.bool(true, "防拉伸: 仅绘制可视区间内", group="显示") lookbackBars = input.int(300, "可视区间近多少根K线", minval=50, maxval=5000, group="显示") padPercent = input.float(2.0, "可视边界留白(%)", minval=0.0, maxval=20.0, step=0.1, group="显示") // 视觉(虚线效果) dashLenHTF = input.int(6, "长级别虚线段长度", minval=1, maxval=50, group="线型") dashGapHTF = input.int(4, "长级别虚线间隔", minval=1, maxval=50, group="线型") dashLenLTF = input.int(2, "次级别虚线段长度", minval=1, maxval=50, group="线型") dashGapLTF = input.int(3, "次级别虚线间隔", minval=1, maxval=50, group="线型") lineWidthHTF = input.int(1, "长级别线宽", minval=1, maxval=4, group="线型") lineWidthLTF = input.int(1, "次级别线宽", minval=1, maxval=4, group="线型") lineWidthCTF = input.int(1, "本级别线宽", minval=1, maxval=4, group="线型") enableSmooth = input.bool(true, "平滑显示(EMA)", group="线型") smoothLenHTF = input.int(3, "长级别平滑长度", minval=1, maxval=50, group="线型") smoothLenLTF = input.int(3, "次级别平滑长度", minval=1, maxval=50, group="线型") smoothLenCTF = input.int(3, "本级别平滑长度", minval=1, maxval=50, group="线型") smoothMethod = input.string("ALMA", "平滑方法", options=["EMA","DEMA","TEMA","WMA","HMA","ALMA","LinReg"], group="线型") almaOffset = input.float(0.85, "ALMA偏移(0-1)", minval=0.0, maxval=1.0, step=0.05, group="线型") almaSigma = input.float(6.0, "ALMA Sigma", minval=0.5, maxval=10.0, step=0.5, group="线型") labelBgAlpha = input.int(85, "标签背景透明(0-100)", minval=0, maxval=100, group="标注") labelSize = input.string("normal", "标签字号", options=["tiny","small","normal","large","huge"], group="标注") labelOffsetBars = input.int(2, "标签右移K线的距离(根数)", minval=0, maxval=50, group="标注") // 常量上限(编译期固定,用于生成固定数量的 plot 位) const int MAX_HTF = 12 const int MAX_LTF = 4 // ======================== 工具函数 ======================== f_dash(series float v, int phase, int dashLen, int dashGap) => // 通过丢弃部分柱来营造虚线效果 isShow = (bar_index + phase) % (dashLen + dashGap) < dashLen isShow ? v : na // 自定义 DEMA / TEMA(Pine 无内置 dema/tema) f_dema(series float v, int len) => ema1 = ta.ema(v, len) ema2 = ta.ema(ema1, len) 2.0 * ema1 - ema2 f_tema(series float v, int len) => ema1 = ta.ema(v, len) ema2 = ta.ema(ema1, len) ema3 = ta.ema(ema2, len) 3.0 * (ema1 - ema2) + ema3 // 通用平滑(仅用于显示) f_smooth(series float v, int len) => not enableSmooth or len <= 1 ? v : smoothMethod == "EMA" ? ta.ema(v, len) : smoothMethod == "DEMA" ? f_dema(v, len) : smoothMethod == "TEMA" ? f_tema(v, len) : smoothMethod == "WMA" ? ta.wma(v, len) : smoothMethod == "HMA" ? ta.hma(v, len) : smoothMethod == "ALMA" ? ta.alma(v, len, almaOffset, almaSigma) : ta.linreg(v, len, 0) f_strToSize(string s) => switch s "tiny" => size.tiny "small" => size.small "normal" => size.normal "large" => size.large => size.huge // 颜色调色板(循环使用) var color[] PALETTE = array.new_color() if barstate.isfirst and array.size(PALETTE) == 0 array.push(PALETTE, color.new(color.teal, 0)) array.push(PALETTE, color.new(color.orange, 0)) array.push(PALETTE, color.new(color.fuchsia,0)) array.push(PALETTE, color.new(color.aqua, 0)) array.push(PALETTE, color.new(color.yellow, 0)) array.push(PALETTE, color.new(color.purple, 0)) array.push(PALETTE, color.new(color.lime, 0)) array.push(PALETTE, color.new(color.red, 0)) array.push(PALETTE, color.new(color.blue, 0)) array.push(PALETTE, color.new(color.navy, 0)) array.push(PALETTE, color.new(color.maroon, 0)) array.push(PALETTE, color.new(color.silver, 0)) f_paletteColor(int idx) => sz = math.max(1, array.size(PALETTE)) array.get(PALETTE, idx % sz) // 是否为秒级分辨率,如 "1S","5S" f_isSecondsTf(string tf) => ln = str.length(tf) ln > 0 and str.substring(tf, ln - 1, ln) == 'S' // 计算可视范围边界(近 N 根 K 线 + 百分比留白) rangeHi = ta.highest(high, lookbackBars) rangeLo = ta.lowest(low, lookbackBars) rangeSpan = rangeHi - rangeLo pad = rangeSpan * padPercent * 0.01 minY = rangeLo - pad maxY = rangeHi + pad // 仅在可视区间内绘制(超出返回 na,以避免影响自动缩放)。 f_gate(series float v) => protectAutoscale ? (v >= minY and v <= maxY ? v : na) : v // 检查是否在可视区间内 f_inRange(series float v) => protectAutoscale ? (v >= minY and v <= maxY) : true // 周期字符串美化(如 1 -> 1m, 60 -> 1h, D -> 1D, W -> 1W, 3M -> 3M) f_prettyTf(string tf) => s = timeframe.in_seconds(tf) string out = tf if not na(s) if s < 3600 mins = math.max(1, math.round(s / 60)) out := str.tostring(mins) + 'm' else if s % 3600 == 0 and s < 86400 hrs = math.round(s / 3600) out := str.tostring(hrs) + 'h' else if s % 86400 == 0 and s < 7 * 86400 days = math.round(s / 86400) out := str.tostring(days) + 'D' else if s % (7 * 86400) == 0 and s < 30 * 86400 weeks = math.round(s / (7 * 86400)) out := str.tostring(weeks) + 'W' else out := tf else // 月等返回 na 的分辨率保持原样(M/3M) out := tf out // ======================== 标准时间周期表 ======================== // 备注:TradingView 分辨率字符串 —— 分钟: "1","3","5"...;小时: "60","120"...; // 天: "D","2D";周: "W","2W";月: "M","3M";秒级可能为 "1S","5S" 等。 var string[] ALL_RES = array.new_string() if barstate.isfirst and array.size(ALL_RES) == 0 // 由低到高,便于筛选 // 秒级(若品种/权限不支持,请忽略,筛选时会自动排除) array.push(ALL_RES, "1S") array.push(ALL_RES, "5S") array.push(ALL_RES, "15S") array.push(ALL_RES, "30S") // 分钟 array.push(ALL_RES, "1") array.push(ALL_RES, "3") array.push(ALL_RES, "5") array.push(ALL_RES, "15") array.push(ALL_RES, "30") array.push(ALL_RES, "45") // 小时(以分钟表示) array.push(ALL_RES, "60") array.push(ALL_RES, "120") array.push(ALL_RES, "240") array.push(ALL_RES, "360") array.push(ALL_RES, "480") array.push(ALL_RES, "720") // 日/周/月 array.push(ALL_RES, "D") array.push(ALL_RES, "2D") array.push(ALL_RES, "3D") array.push(ALL_RES, "W") array.push(ALL_RES, "2W") array.push(ALL_RES, "M") array.push(ALL_RES, "3M") // ======================== 周期间关系与选择 ======================== curResStr = timeframe.period curResSec = timeframe.in_seconds(curResStr) // 构建长/次级别列表 var string[] htfRes = array.new_string() var string[] ltfRes = array.new_string() array.clear(htfRes) array.clear(ltfRes) for i = 0 to array.size(ALL_RES) - 1 res = array.get(ALL_RES, i) // 非高级用户默认不包含秒级周期 if not includeSeconds and f_isSecondsTf(res) continue rsec = timeframe.in_seconds(res) // timeframe.in_seconds 不支持时返回 na,需跳过 if na(rsec) continue if rsec > curResSec array.push(htfRes, res) else if rsec < curResSec array.push(ltfRes, res) // 选取靠近当前周期的若干次级别(更有参考意义):取 ltfRes 的末尾(更接近当前) var string[] ltfSel = array.new_string() array.clear(ltfSel) ltfsz = array.size(ltfRes) if ltfsz > 0 take = math.min(maxLTFInput, MAX_LTF, ltfsz) for k = 0 to take - 1 array.push(ltfSel, array.get(ltfRes, ltfsz - 1 - k)) // 限制长级别数量 htfCountSelected = math.min(array.size(htfRes), maxHTFInput, MAX_HTF) ltfCountSelected = math.min(array.size(ltfSel), MAX_LTF) // ======================== 计算各周期 WMA52 ======================== // 预分配数值数组(保持固定最大长度) var float[] htfVals = array.new_float() var float[] ltfVals = array.new_float() float ctfVal = na if barstate.isfirst for _i = 0 to MAX_HTF - 1 array.push(htfVals, na) for _j = 0 to MAX_LTF - 1 array.push(ltfVals, na) // 填充长级别数值 for i = 0 to MAX_HTF - 1 float val = na if enableHTF and i < htfCountSelected tfStr = array.get(htfRes, i) // 计算该长级别的 WMA52 val := request.security(syminfo.tickerid, tfStr, ta.wma(close, 52), barmerge.gaps_off, barmerge.lookahead_off) array.set(htfVals, i, val) // 填充次级别数值 for i = 0 to MAX_LTF - 1 float val = na if enableLTF and i < ltfCountSelected tfStr = array.get(ltfSel, i) // 计算该次级别的 WMA52(低级别向上合成,注意其更频繁更新) val := request.security(syminfo.tickerid, tfStr, ta.wma(close, 52), barmerge.gaps_off, barmerge.lookahead_off) array.set(ltfVals, i, val) // 本级别 WMA52 if enableCTF ctfVal := ta.wma(close, 52) // ======================== 绘制(虚线 + 标签) ======================== // 顶层固定 plot(避免在局部/循环中调用 plot) plot(enableCTF ? f_gate(f_smooth(ctfVal, smoothLenCTF)) : na, title='CTF WMA52', color=color.new(color.white, 0), linewidth=lineWidthCTF, style=plot.style_line) plot((enableHTF and 0 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 0), smoothLenHTF), 0 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 1', color=f_paletteColor(0), linewidth=lineWidthHTF, style=plot.style_line) plot((enableHTF and 1 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 1), smoothLenHTF), 1 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 2', color=f_paletteColor(1), linewidth=lineWidthHTF, style=plot.style_line) plot((enableHTF and 2 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 2), smoothLenHTF), 2 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 3', color=f_paletteColor(2), linewidth=lineWidthHTF, style=plot.style_line) plot((enableHTF and 3 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 3), smoothLenHTF), 3 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 4', color=f_paletteColor(3), linewidth=lineWidthHTF, style=plot.style_line) plot((enableHTF and 4 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 4), smoothLenHTF), 4 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 5', color=f_paletteColor(4), linewidth=lineWidthHTF, style=plot.style_line) plot((enableHTF and 5 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 5), smoothLenHTF), 5 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 6', color=f_paletteColor(5), linewidth=lineWidthHTF, style=plot.style_line) plot((enableHTF and 6 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 6), smoothLenHTF), 6 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 7', color=f_paletteColor(6), linewidth=lineWidthHTF, style=plot.style_line) plot((enableHTF and 7 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 7), smoothLenHTF), 7 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 8', color=f_paletteColor(7), linewidth=lineWidthHTF, style=plot.style_line) plot((enableHTF and 8 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 8), smoothLenHTF), 8 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 9', color=f_paletteColor(8), linewidth=lineWidthHTF, style=plot.style_line) plot((enableHTF and 9 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 9), smoothLenHTF), 9 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 10', color=f_paletteColor(9), linewidth=lineWidthHTF, style=plot.style_line) plot((enableHTF and 10 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 10), smoothLenHTF), 10 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 11', color=f_paletteColor(10), linewidth=lineWidthHTF, style=plot.style_line) plot((enableHTF and 11 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 11), smoothLenHTF), 11 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 12', color=f_paletteColor(11), linewidth=lineWidthHTF, style=plot.style_line) plot((enableLTF and 0 < ltfCountSelected) ? f_gate(f_dash(f_smooth(array.get(ltfVals, 0), smoothLenLTF), 0 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF WMA52 1', color=color.new(f_paletteColor(0), 30), linewidth=lineWidthLTF, style=plot.style_line) plot((enableLTF and 1 < ltfCountSelected) ? f_gate(f_dash(f_smooth(array.get(ltfVals, 1), smoothLenLTF), 1 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF WMA52 2', color=color.new(f_paletteColor(1), 30), linewidth=lineWidthLTF, style=plot.style_line) plot((enableLTF and 2 < ltfCountSelected) ? f_gate(f_dash(f_smooth(array.get(ltfVals, 2), smoothLenLTF), 2 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF WMA52 3', color=color.new(f_paletteColor(2), 30), linewidth=lineWidthLTF, style=plot.style_line) plot((enableLTF and 3 < ltfCountSelected) ? f_gate(f_dash(f_smooth(array.get(ltfVals, 3), smoothLenLTF), 3 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF WMA52 4', color=color.new(f_paletteColor(3), 30), linewidth=lineWidthLTF, style=plot.style_line) // 右侧标签(仅在最后一根柱更新,避免重复创建) var label[] htfLabels = array.new_label() var label[] ltfLabels = array.new_label() var label ctfLabel = na var string lastTicker = na var string lastTf = na if barstate.isfirst for _i = 0 to MAX_HTF - 1 array.push(htfLabels, na) for _j = 0 to MAX_LTF - 1 array.push(ltfLabels, na) lastTicker := syminfo.tickerid lastTf := timeframe.period // 更新/清理标签 // 如果品种或周期变化,清理旧标签 symbolChanged = lastTicker != syminfo.tickerid tfChanged = lastTf != timeframe.period if symbolChanged or tfChanged for i = 0 to MAX_HTF - 1 lab = array.get(htfLabels, i) if not na(lab) label.delete(lab) array.set(htfLabels, i, na) for i = 0 to MAX_LTF - 1 lab = array.get(ltfLabels, i) if not na(lab) label.delete(lab) array.set(ltfLabels, i, na) if not na(ctfLabel) label.delete(ctfLabel) ctfLabel := na lastTicker := syminfo.tickerid lastTf := timeframe.period if barstate.islast // 长级别 for i = 0 to MAX_HTF - 1 lab = array.get(htfLabels, i) if enableHTF and i < htfCountSelected tfStr = array.get(htfRes, i) v = array.get(htfVals, i) col = f_paletteColor(i) txt = f_prettyTf(tfStr) + ' ' + str.tostring(v, format.mintick) // 可视区间外则隐藏标签;区间内按真实价格绘制,确保与标尺一致 if protectAutoscale and not f_inRange(v) if not na(lab) label.delete(lab) array.set(htfLabels, i, na) else if na(lab) lab := label.new(bar_index + labelOffsetBars, v, txt, xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_left, textcolor=col, color=color.new(col, labelBgAlpha), size=f_strToSize(labelSize)) array.set(htfLabels, i, lab) else label.set_xy(lab, bar_index + labelOffsetBars, v) label.set_text(lab, txt) label.set_textcolor(lab, col) label.set_color(lab, color.new(col, labelBgAlpha)) label.set_style(lab, label.style_label_left) label.set_size(lab, f_strToSize(labelSize)) else if not na(lab) label.delete(lab) array.set(htfLabels, i, na) // 次级别 for i = 0 to MAX_LTF - 1 lab = array.get(ltfLabels, i) if enableLTF and i < ltfCountSelected tfStr = array.get(ltfSel, i) v = array.get(ltfVals, i) base = f_paletteColor(i) col = color.new(base, 0) txt = f_prettyTf(tfStr) + ' ' + str.tostring(v, format.mintick) if protectAutoscale and not f_inRange(v) if not na(lab) label.delete(lab) array.set(ltfLabels, i, na) else if na(lab) lab := label.new(bar_index + labelOffsetBars, v, txt, xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_left, textcolor=col, color=color.new(col, labelBgAlpha + 10 > 100 ? 100 : labelBgAlpha + 10), size=f_strToSize(labelSize)) array.set(ltfLabels, i, lab) else label.set_xy(lab, bar_index + labelOffsetBars, v) label.set_text(lab, txt) label.set_textcolor(lab, col) label.set_color(lab, color.new(col, labelBgAlpha + 10 > 100 ? 100 : labelBgAlpha + 10)) label.set_style(lab, label.style_label_left) label.set_size(lab, f_strToSize(labelSize)) else if not na(lab) label.delete(lab) array.set(ltfLabels, i, na) // 本级别 if enableCTF v = ctfVal col = color.new(color.white, 0) txt = f_prettyTf(curResStr) + ' ' + str.tostring(v, format.mintick) if protectAutoscale and not f_inRange(v) if not na(ctfLabel) label.delete(ctfLabel) ctfLabel := na else if na(ctfLabel) ctfLabel := label.new(bar_index + labelOffsetBars, v, txt, xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_left, textcolor=col, color=color.new(col, labelBgAlpha), size=f_strToSize(labelSize)) else label.set_xy(ctfLabel, bar_index + labelOffsetBars, v) label.set_text(ctfLabel, txt) label.set_textcolor(ctfLabel, col) label.set_color(ctfLabel, color.new(col, labelBgAlpha)) label.set_style(ctfLabel, label.style_label_left) label.set_size(ctfLabel, f_strToSize(labelSize)) else if not na(ctfLabel) label.delete(ctfLabel) ctfLabel := na // ======================== 备注 ======================== // 1) 当前周期通过 timeframe.period 自动识别,并用 timeframe.in_seconds( // ) 转换成秒以进行相对大小比较。 // 2) 长级别/次级别集合从标准分辨率表中过滤得出,绘图数量受 MAX_* 与输入上限控制。 // 3) 虚线效果通过在 plot 中周期性输出 na 实现,性能优于逐柱绘制 line 对象。 // 4) 标签在最后一根柱更新,避免过量对象;颜色与曲线一致。 // 5) 次级别(低于当前)WMA52 也计算并显示,更新频率更高,默认更淡。