Files
tradingview/多周期EMA52_Pro.pine
T
2026-05-14 14:17:09 +08:00

412 lines
21 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//@version=6
indicator("多周期EMA52_Pro", 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="显示")
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="显示")
// 固定 12 周期开关(本级别也会跟随对应周期开关)
showRes1 = input.bool(true, "显示 1m", group="级别开关")
showRes2 = input.bool(true, "显示 3m", group="级别开关")
showRes3 = input.bool(true, "显示 5m", group="级别开关")
showRes4 = input.bool(true, "显示 15m", group="级别开关")
showRes5 = input.bool(true, "显示 30m", group="级别开关")
showRes6 = input.bool(true, "显示 45m", group="级别开关")
showRes7 = input.bool(true, "显示 1h", group="级别开关")
showRes8 = input.bool(true, "显示 2h", group="级别开关")
showRes9 = input.bool(true, "显示 4h", group="级别开关")
showRes10 = input.bool(true, "显示 6h", group="级别开关")
showRes11 = input.bool(true, "显示 8h", group="级别开关")
showRes12 = input.bool(true, "显示 12h", 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(2, "本级别线宽", 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 / TEMAPine 无内置 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
f_showRes(string tf) => switch tf
"1" => showRes1
"3" => showRes2
"5" => showRes3
"15" => showRes4
"30" => showRes5
"45" => showRes6
"60" => showRes7
"120" => showRes8
"240" => showRes9
"360" => showRes10
"480" => showRes11
"720" => showRes12
=> true
// 颜色调色板(循环使用)
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)
// 计算可视范围边界(近 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"...
// 仅保留分钟与小时级别。
var string[] ALL_RES = array.new_string()
if barstate.isfirst and array.size(ALL_RES) == 0
// 由低到高,便于筛选
// 分钟
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")
// ======================== 周期间关系与选择 ========================
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)
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)
// ======================== 计算各周期 EMA52 ========================
// 预分配数值数组(保持固定最大长度)
var float[] htfVals = array.new_float()
var float[] ltfVals = array.new_float()
var float[] htfLabelVals = array.new_float()
var float[] ltfLabelVals = array.new_float()
float ctfVal = na
float ctfLabelVal = na
if barstate.isfirst
for _i = 0 to MAX_HTF - 1
array.push(htfVals, na)
array.push(htfLabelVals, na)
for _j = 0 to MAX_LTF - 1
array.push(ltfVals, na)
array.push(ltfLabelVals, na)
// 填充长级别数值
for i = 0 to MAX_HTF - 1
float val = na
float labelVal = na
if enableHTF and i < htfCountSelected
tfStr = array.get(htfRes, i)
if f_showRes(tfStr)
// 计算该长级别的 EMA52
val := request.security(syminfo.tickerid, tfStr, ta.ema(close, 52), barmerge.gaps_off, barmerge.lookahead_off)
// 标签不跟随虚线断续,仅跟随级别开关与可视区间
labelVal := f_gate(f_smooth(val, smoothLenHTF))
array.set(htfVals, i, val)
array.set(htfLabelVals, i, labelVal)
// 填充次级别数值
for i = 0 to MAX_LTF - 1
float val = na
float labelVal = na
if enableLTF and i < ltfCountSelected
tfStr = array.get(ltfSel, i)
if f_showRes(tfStr)
// 计算该次级别的 EMA52(低级别向上合成,注意其更频繁更新)
val := request.security(syminfo.tickerid, tfStr, ta.ema(close, 52), barmerge.gaps_off, barmerge.lookahead_off)
// 标签不跟随虚线断续,仅跟随级别开关与可视区间
labelVal := f_gate(f_smooth(val, smoothLenLTF))
array.set(ltfVals, i, val)
array.set(ltfLabelVals, i, labelVal)
// 本级别 EMA52
if enableCTF and f_showRes(curResStr)
ctfVal := ta.ema(close, 52)
ctfLabelVal := (enableCTF and f_showRes(curResStr)) ? f_gate(f_smooth(ctfVal, smoothLenCTF)) : na
// ======================== 绘制(虚线 + 标签) ========================
// 顶层固定 plot(避免在局部/循环中调用 plot)
plot((enableCTF and f_showRes(curResStr)) ? f_gate(f_smooth(ctfVal, smoothLenCTF)) : na, title='CTF EMA52', color=color.new(color.white, 0), linewidth=lineWidthCTF, style=plot.style_line)
plot((enableHTF and 0 < htfCountSelected and not na(array.get(htfVals, 0))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 0), smoothLenHTF), 0 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 1', color=f_paletteColor(0), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 1 < htfCountSelected and not na(array.get(htfVals, 1))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 1), smoothLenHTF), 1 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 2', color=f_paletteColor(1), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 2 < htfCountSelected and not na(array.get(htfVals, 2))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 2), smoothLenHTF), 2 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 3', color=f_paletteColor(2), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 3 < htfCountSelected and not na(array.get(htfVals, 3))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 3), smoothLenHTF), 3 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 4', color=f_paletteColor(3), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 4 < htfCountSelected and not na(array.get(htfVals, 4))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 4), smoothLenHTF), 4 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 5', color=f_paletteColor(4), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 5 < htfCountSelected and not na(array.get(htfVals, 5))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 5), smoothLenHTF), 5 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 6', color=f_paletteColor(5), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 6 < htfCountSelected and not na(array.get(htfVals, 6))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 6), smoothLenHTF), 6 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 7', color=f_paletteColor(6), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 7 < htfCountSelected and not na(array.get(htfVals, 7))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 7), smoothLenHTF), 7 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 8', color=f_paletteColor(7), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 8 < htfCountSelected and not na(array.get(htfVals, 8))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 8), smoothLenHTF), 8 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 9', color=f_paletteColor(8), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 9 < htfCountSelected and not na(array.get(htfVals, 9))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 9), smoothLenHTF), 9 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 10', color=f_paletteColor(9), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 10 < htfCountSelected and not na(array.get(htfVals, 10))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 10), smoothLenHTF), 10 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 11', color=f_paletteColor(10), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 11 < htfCountSelected and not na(array.get(htfVals, 11))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 11), smoothLenHTF), 11 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 12', color=f_paletteColor(11), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableLTF and 0 < ltfCountSelected and not na(array.get(ltfVals, 0))) ? f_gate(f_dash(f_smooth(array.get(ltfVals, 0), smoothLenLTF), 0 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF EMA52 1', color=color.new(f_paletteColor(0), 30), linewidth=lineWidthLTF, style=plot.style_line)
plot((enableLTF and 1 < ltfCountSelected and not na(array.get(ltfVals, 1))) ? f_gate(f_dash(f_smooth(array.get(ltfVals, 1), smoothLenLTF), 1 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF EMA52 2', color=color.new(f_paletteColor(1), 30), linewidth=lineWidthLTF, style=plot.style_line)
plot((enableLTF and 2 < ltfCountSelected and not na(array.get(ltfVals, 2))) ? f_gate(f_dash(f_smooth(array.get(ltfVals, 2), smoothLenLTF), 2 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF EMA52 3', color=color.new(f_paletteColor(2), 30), linewidth=lineWidthLTF, style=plot.style_line)
plot((enableLTF and 3 < ltfCountSelected and not na(array.get(ltfVals, 3))) ? f_gate(f_dash(f_smooth(array.get(ltfVals, 3), smoothLenLTF), 3 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF EMA52 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(htfLabelVals, i)
col = f_paletteColor(i)
txt = f_prettyTf(tfStr) + ' ' + str.tostring(v, format.mintick)
// 与曲线显示条件同步:平滑后且可视区间内才显示标签
if na(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(ltfLabelVals, i)
base = f_paletteColor(i)
col = color.new(base, 0)
txt = f_prettyTf(tfStr) + ' ' + str.tostring(v, format.mintick)
if na(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 and f_showRes(curResStr)
v = ctfLabelVal
col = color.new(color.white, 0)
txt = f_prettyTf(curResStr) + ' ' + str.tostring(v, format.mintick)
if na(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) 次级别(低于当前)EMA52 也计算并显示,更新频率更高,默认更淡。