//@version=6 indicator("EMA52横线版 (HTF/LTF 最新值)", overlay=true, max_labels_count=500, max_lines_count=500) // ============ 输入 ============ 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="显示") labelBgAlpha = input.int(85, "标签背景透明(0-100)", minval=0, maxval=100, group="标注") labelSize = input.string("tiny", "标签字号", options=["tiny","small","normal","large","huge"], group="标注") labelOffsetBars = input.int(2, "标签右移K线的距离(根数)", minval=0, maxval=50, group="标注") showLegend = input.bool(true, "显示右侧图例(周期映射)", group="标注") // ============ 按线控制(可见性/颜色/宽度/样式) ============ // 固定时框命名,满足“用 1m/3m/… 显示”的需求 groupHTF = "按线控制 - 长级别" hShow1 = input.bool(true, "显示 30m", group=groupHTF) hColor1 = input.color(color.new(color.teal, 0), "颜色 30m", group=groupHTF) hWidth1 = input.int(1, "线宽 30m", minval=1, maxval=4, group=groupHTF) hStyle1 = input.string("dotted", "样式 30m", options=["solid","dashed","dotted"], group=groupHTF) hShow2 = input.bool(true, "显示 45m", group=groupHTF) hColor2 = input.color(color.new(color.orange, 0), "颜色 45m", group=groupHTF) hWidth2 = input.int(1, "线宽 45m", minval=1, maxval=4, group=groupHTF) hStyle2 = input.string("dotted", "样式 45m", options=["solid","dashed","dotted"], group=groupHTF) hShow3 = input.bool(true, "显示 1h", group=groupHTF) hColor3 = input.color(color.new(color.fuchsia,0), "颜色 1h", group=groupHTF) hWidth3 = input.int(1, "线宽 1h", minval=1, maxval=4, group=groupHTF) hStyle3 = input.string("dotted", "样式 1h", options=["solid","dashed","dotted"], group=groupHTF) hShow4 = input.bool(true, "显示 2h", group=groupHTF) hColor4 = input.color(color.new(color.aqua, 0), "颜色 2h", group=groupHTF) hWidth4 = input.int(1, "线宽 2h", minval=1, maxval=4, group=groupHTF) hStyle4 = input.string("dotted", "样式 2h", options=["solid","dashed","dotted"], group=groupHTF) hShow5 = input.bool(true, "显示 4h", group=groupHTF) hColor5 = input.color(color.new(color.yellow, 0), "颜色 4h", group=groupHTF) hWidth5 = input.int(1, "线宽 4h", minval=1, maxval=4, group=groupHTF) hStyle5 = input.string("dotted", "样式 4h", options=["solid","dashed","dotted"], group=groupHTF) hShow6 = input.bool(true, "显示 6h", group=groupHTF) hColor6 = input.color(color.new(color.purple, 0), "颜色 6h", group=groupHTF) hWidth6 = input.int(1, "线宽 6h", minval=1, maxval=4, group=groupHTF) hStyle6 = input.string("dotted", "样式 6h", options=["solid","dashed","dotted"], group=groupHTF) hShow7 = input.bool(true, "显示 8h", group=groupHTF) hColor7 = input.color(color.new(color.lime, 0), "颜色 8h", group=groupHTF) hWidth7 = input.int(1, "线宽 8h", minval=1, maxval=4, group=groupHTF) hStyle7 = input.string("dotted", "样式 8h", options=["solid","dashed","dotted"], group=groupHTF) hShow8 = input.bool(true, "显示 12h", group=groupHTF) hColor8 = input.color(color.new(color.red, 0), "颜色 12h", group=groupHTF) hWidth8 = input.int(1, "线宽 12h", minval=1, maxval=4, group=groupHTF) hStyle8 = input.string("dotted", "样式 12h", options=["solid","dashed","dotted"], group=groupHTF) hShow9 = input.bool(true, "显示 1D", group=groupHTF) hColor9 = input.color(color.new(color.blue, 0), "颜色 1D", group=groupHTF) hWidth9 = input.int(1, "线宽 1D", minval=1, maxval=4, group=groupHTF) hStyle9 = input.string("dotted", "样式 1D", options=["solid","dashed","dotted"], group=groupHTF) hShow10 = input.bool(true, "显示 2D", group=groupHTF) hColor10 = input.color(color.new(color.navy, 0), "颜色 2D", group=groupHTF) hWidth10 = input.int(1, "线宽 2D", minval=1, maxval=4, group=groupHTF) hStyle10 = input.string("dotted", "样式 2D", options=["solid","dashed","dotted"], group=groupHTF) hShow11 = input.bool(true, "显示 1W", group=groupHTF) hColor11 = input.color(color.new(color.maroon, 0), "颜色 1W", group=groupHTF) hWidth11 = input.int(1, "线宽 1W", minval=1, maxval=4, group=groupHTF) hStyle11 = input.string("dotted", "样式 1W", options=["solid","dashed","dotted"], group=groupHTF) hShow12 = input.bool(true, "显示 1M", group=groupHTF) hColor12 = input.color(color.new(color.silver, 0), "颜色 1M", group=groupHTF) hWidth12 = input.int(1, "线宽 1M", minval=1, maxval=4, group=groupHTF) hStyle12 = input.string("dotted", "样式 1M", options=["solid","dashed","dotted"], group=groupHTF) // 次级别 groupLTF = "按线控制 - 次级别" lShow1 = input.bool(true, "显示 1m", group=groupLTF) lColor1 = input.color(color.new(color.teal, 30), "颜色 1m", group=groupLTF) lWidth1 = input.int(1, "线宽 1m", minval=1, maxval=4, group=groupLTF) lStyle1 = input.string("dotted", "样式 1m", options=["solid","dashed","dotted"], group=groupLTF) lShow2 = input.bool(true, "显示 3m", group=groupLTF) lColor2 = input.color(color.new(color.orange, 30),"颜色 3m", group=groupLTF) lWidth2 = input.int(1, "线宽 3m", minval=1, maxval=4, group=groupLTF) lStyle2 = input.string("dotted", "样式 3m", options=["solid","dashed","dotted"], group=groupLTF) lShow3 = input.bool(true, "显示 5m", group=groupLTF) lColor3 = input.color(color.new(color.fuchsia,30),"颜色 5m", group=groupLTF) lWidth3 = input.int(1, "线宽 5m", minval=1, maxval=4, group=groupLTF) lStyle3 = input.string("dotted", "样式 5m", options=["solid","dashed","dotted"], group=groupLTF) lShow4 = input.bool(true, "显示 15m", group=groupLTF) lColor4 = input.color(color.new(color.aqua, 30),"颜色 15m", group=groupLTF) lWidth4 = input.int(1, "线宽 15m", minval=1, maxval=4, group=groupLTF) lStyle4 = input.string("dotted", "样式 15m", options=["solid","dashed","dotted"], group=groupLTF) // 样式字符串到 line.style 的映射 f_lineStyle(string s) => s == "solid" ? line.style_solid : s == "dashed" ? line.style_dashed : line.style_dotted // 取对应序号的配置 f_htfShow(int i) => i == 0 ? hShow1 : i == 1 ? hShow2 : i == 2 ? hShow3 : i == 3 ? hShow4 : i == 4 ? hShow5 : i == 5 ? hShow6 : i == 6 ? hShow7 : i == 7 ? hShow8 : i == 8 ? hShow9 : i == 9 ? hShow10 : i == 10 ? hShow11 : hShow12 f_htfColor(int i) => i == 0 ? hColor1 : i == 1 ? hColor2 : i == 2 ? hColor3 : i == 3 ? hColor4 : i == 4 ? hColor5 : i == 5 ? hColor6 : i == 6 ? hColor7 : i == 7 ? hColor8 : i == 8 ? hColor9 : i == 9 ? hColor10 : i == 10 ? hColor11 : hColor12 f_htfWidth(int i) => i == 0 ? hWidth1 : i == 1 ? hWidth2 : i == 2 ? hWidth3 : i == 3 ? hWidth4 : i == 4 ? hWidth5 : i == 5 ? hWidth6 : i == 6 ? hWidth7 : i == 7 ? hWidth8 : i == 8 ? hWidth9 : i == 9 ? hWidth10 : i == 10 ? hWidth11 : hWidth12 f_htfStyle(int i) => f_lineStyle(i == 0 ? hStyle1 : i == 1 ? hStyle2 : i == 2 ? hStyle3 : i == 3 ? hStyle4 : i == 4 ? hStyle5 : i == 5 ? hStyle6 : i == 6 ? hStyle7 : i == 7 ? hStyle8 : i == 8 ? hStyle9 : i == 9 ? hStyle10 : i == 10 ? hStyle11 : hStyle12) f_ltfShow(int i) => i == 0 ? lShow1 : i == 1 ? lShow2 : i == 2 ? lShow3 : lShow4 f_ltfColor(int i) => i == 0 ? lColor1 : i == 1 ? lColor2 : i == 2 ? lColor3 : lColor4 f_ltfWidth(int i) => i == 0 ? lWidth1 : i == 1 ? lWidth2 : i == 2 ? lWidth3 : lWidth4 f_ltfStyle(int i) => f_lineStyle(i == 0 ? lStyle1 : i == 1 ? lStyle2 : i == 2 ? lStyle3 : lStyle4) // 固定时框键列表(与上面输入顺序一致) var string[] HTF_KEYS = array.new_string() var string[] LTF_KEYS = array.new_string() if barstate.isfirst and array.size(HTF_KEYS) == 0 array.push(HTF_KEYS, "30") array.push(HTF_KEYS, "45") array.push(HTF_KEYS, "60") array.push(HTF_KEYS, "120") array.push(HTF_KEYS, "240") array.push(HTF_KEYS, "360") array.push(HTF_KEYS, "480") array.push(HTF_KEYS, "720") array.push(HTF_KEYS, "D") array.push(HTF_KEYS, "2D") array.push(HTF_KEYS, "W") array.push(HTF_KEYS, "M") array.push(LTF_KEYS, "1") array.push(LTF_KEYS, "3") array.push(LTF_KEYS, "5") array.push(LTF_KEYS, "15") // ============ 工具 ============ 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) f_isSecondsTf(string tf) => ln = str.length(tf) ln > 0 and str.substring(tf, ln - 1, ln) == 'S' 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 out := tf out // ============ 周期间构建 ============ 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) if na(rsec) continue if rsec > curResSec array.push(htfRes, res) else if rsec < curResSec array.push(ltfRes, res) // 重新按预设优先级对选中的 HTF/LTF 排序以匹配输入面板顺序(Pine 不支持自定义比较器,用手动重排) f_contains(string[] arr, string key) => found = false for ii = 0 to array.size(arr) - 1 if array.get(arr, ii) == key found := true break found // 长级别重排 var string[] _htfOrdered = array.new_string() array.clear(_htfOrdered) for ii = 0 to array.size(HTF_KEYS) - 1 key = array.get(HTF_KEYS, ii) if f_contains(htfRes, key) array.push(_htfOrdered, key) // 用重排后的覆盖原数组 array.clear(htfRes) for ii = 0 to array.size(_htfOrdered) - 1 array.push(htfRes, array.get(_htfOrdered, ii)) // 次级别重排 var string[] _ltfOrdered = array.new_string() array.clear(_ltfOrdered) for ii = 0 to array.size(LTF_KEYS) - 1 key = array.get(LTF_KEYS, ii) if f_contains(ltfRes, key) array.push(_ltfOrdered, key) array.clear(ltfRes) for ii = 0 to array.size(_ltfOrdered) - 1 array.push(ltfRes, array.get(_ltfOrdered, ii)) var string[] ltfSel = array.new_string() array.clear(ltfSel) ltfsz = array.size(ltfRes) if ltfsz > 0 take = math.min(maxLTFInput, 4, ltfsz) for k = 0 to take - 1 array.push(ltfSel, array.get(ltfRes, ltfsz - 1 - k)) htfCountSelected = math.min(array.size(htfRes), maxHTFInput, 12) ltfCountSelected = math.min(array.size(ltfSel), 4) // ============ 计算最新 EMA52 ============ var float[] htfVals = array.new_float() var float[] ltfVals = array.new_float() if barstate.isfirst for _i = 0 to 11 array.push(htfVals, na) for _j = 0 to 3 array.push(ltfVals, na) for i = 0 to 11 float val = na if enableHTF and i < htfCountSelected tfStr = array.get(htfRes, i) val := request.security(syminfo.tickerid, tfStr, ta.ema(close, 52), barmerge.gaps_off, barmerge.lookahead_off) array.set(htfVals, i, val) for i = 0 to 3 float val = na if enableLTF and i < ltfCountSelected tfStr = array.get(ltfSel, i) val := request.security(syminfo.tickerid, tfStr, ta.ema(close, 52), barmerge.gaps_off, barmerge.lookahead_off) array.set(ltfVals, i, val) // ============ 可视区间 ============ rangeHi = ta.highest(high, lookbackBars) rangeLo = ta.lowest(low, lookbackBars) rangeSpan = rangeHi - rangeLo pad = rangeSpan * padPercent * 0.01 minY = rangeLo - pad maxY = rangeHi + pad f_inRange(series float v) => protectAutoscale ? (v >= minY and v <= maxY) : true // ============ 绘制水平线与标签(仅最新值) ============ var line[] htfLines = array.new_line() var line[] ltfLines = array.new_line() var label[] htfLabels = array.new_label() var label[] ltfLabels = array.new_label() var string lastTicker = na var string lastTf = na var table legendTbl = na if barstate.isfirst for _i = 0 to 11 array.push(htfLines, na) array.push(htfLabels, na) for _j = 0 to 3 array.push(ltfLines, na) array.push(ltfLabels, na) lastTicker := syminfo.tickerid lastTf := timeframe.period // 预建图例表(固定行列),右上角显示 legendTbl := table.new(position.top_right, 2, 20, frame_color=color.new(color.black, 100), frame_width=1) // 清理:品种或周期变化 symbolChanged = lastTicker != syminfo.tickerid tfChanged = lastTf != timeframe.period if symbolChanged or tfChanged for i = 0 to 11 l = array.get(htfLines, i) if not na(l) line.delete(l) array.set(htfLines, i, na) lb = array.get(htfLabels, i) if not na(lb) label.delete(lb) array.set(htfLabels, i, na) for i = 0 to 3 l = array.get(ltfLines, i) if not na(l) line.delete(l) array.set(ltfLines, i, na) lb = array.get(ltfLabels, i) if not na(lb) label.delete(lb) array.set(ltfLabels, i, na) lastTicker := syminfo.tickerid lastTf := timeframe.period // 更新:在最后一根柱上画从最左端到最右端的水平线(仅一段),并在右侧放标签 if barstate.islast // 更新图例 if showLegend and not na(legendTbl) // 先清空 for r = 0 to 19 table.cell(legendTbl, 0, r, "", text_color=color.new(color.white, 0), text_halign=text.align_left, bgcolor=color.new(color.black, 100)) table.cell(legendTbl, 1, r, "", text_color=color.new(color.white, 0), text_halign=text.align_right, bgcolor=color.new(color.black, 100)) row = 0 // 长级别 for i = 0 to 11 v = array.get(htfVals, i) if enableHTF and i < htfCountSelected and f_htfShow(i) and not na(v) and f_inRange(v) tfStr = array.get(htfRes, i) col = f_htfColor(i) table.cell(legendTbl, 0, row, f_prettyTf(tfStr), text_color=col, text_halign=text.align_left) table.cell(legendTbl, 1, row, str.tostring(v, format.mintick), text_color=col, text_halign=text.align_right) row += 1 // 次级别 for i = 0 to 3 v = array.get(ltfVals, i) if enableLTF and i < ltfCountSelected and f_ltfShow(i) and not na(v) and f_inRange(v) tfStr = array.get(ltfSel, i) col = f_ltfColor(i) table.cell(legendTbl, 0, row, f_prettyTf(tfStr), text_color=col, text_halign=text.align_left) table.cell(legendTbl, 1, row, str.tostring(v, format.mintick), text_color=col, text_halign=text.align_right) row += 1 // 长级别 for i = 0 to 11 // 颜色/线宽/样式来自按线控制面板 col = f_htfColor(i) v = array.get(htfVals, i) show = enableHTF and i < htfCountSelected and f_htfShow(i) and not na(v) and f_inRange(v) ln = array.get(htfLines, i) lb = array.get(htfLabels, i) if show x1 = bar_index - lookbackBars x2 = bar_index + labelOffsetBars if na(ln) ln := line.new(x1, v, x2, v, xloc=xloc.bar_index, extend=extend.none, color=col, width=f_htfWidth(i), style=f_htfStyle(i)) array.set(htfLines, i, ln) else line.set_xy1(ln, x1, v) line.set_xy2(ln, x2, v) line.set_color(ln, col) line.set_width(ln, f_htfWidth(i)) line.set_style(ln, f_htfStyle(i)) txt = f_prettyTf(array.get(htfRes, i)) + ' ' + str.tostring(v, format.mintick) if na(lb) lb := 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, lb) else label.set_xy(lb, bar_index + labelOffsetBars, v) label.set_text(lb, txt) label.set_textcolor(lb, col) label.set_color(lb, color.new(col, labelBgAlpha)) label.set_style(lb, label.style_label_left) label.set_size(lb, f_strToSize(labelSize)) else if not na(ln) line.delete(ln) array.set(htfLines, i, na) if not na(lb) label.delete(lb) array.set(htfLabels, i, na) // 次级别 for i = 0 to 3 base = f_ltfColor(i) col = base v = array.get(ltfVals, i) show = enableLTF and i < ltfCountSelected and f_ltfShow(i) and not na(v) and f_inRange(v) ln = array.get(ltfLines, i) lb = array.get(ltfLabels, i) if show x1 = bar_index - lookbackBars x2 = bar_index + labelOffsetBars if na(ln) ln := line.new(x1, v, x2, v, xloc=xloc.bar_index, extend=extend.none, color=col, width=f_ltfWidth(i), style=f_ltfStyle(i)) array.set(ltfLines, i, ln) else line.set_xy1(ln, x1, v) line.set_xy2(ln, x2, v) line.set_color(ln, col) line.set_width(ln, f_ltfWidth(i)) line.set_style(ln, f_ltfStyle(i)) txt = f_prettyTf(array.get(ltfSel, i)) + ' ' + str.tostring(v, format.mintick) if na(lb) lb := 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, lb) else label.set_xy(lb, bar_index + labelOffsetBars, v) label.set_text(lb, txt) label.set_textcolor(lb, col) label.set_color(lb, color.new(col, labelBgAlpha + 10 > 100 ? 100 : labelBgAlpha + 10)) label.set_style(lb, label.style_label_left) label.set_size(lb, f_strToSize(labelSize)) else if not na(ln) line.delete(ln) array.set(ltfLines, i, na) if not na(lb) label.delete(lb) array.set(ltfLabels, i, na)