Files
tradingview/多周期MA52.pine

389 lines
19 KiB
Plaintext
Raw Permalink 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("多周期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 / 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
// 颜色调色板(循环使用)
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 也计算并显示,更新频率更高,默认更淡。