// █▀▀▄ ──▀ █▀▀█ █▀▀█ █▀▀▀ █──█ █▀▄▀█ // █▀▀▄ ──█ █──█ █▄▄▀ █─▀█ █──█ █─▀─█ // ▀▀▀─ █▄█ ▀▀▀▀ ▀─▀▀ ▀▀▀▀ ─▀▀▀ ▀───▀ // This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/ // © Bjorgum //@version=6 indicator('BjorgumKeyLevels', 'BjKeyLevels', overlay = true, max_boxes_count = 500, max_labels_count = 500, max_lines_count = 500) import Bjorgum/BjCandlePatterns/3 as bj // ================================== // // ------------> 提示信息 <-------------- // // ================================== // leftTip = '向左查找摆动高低点的K线数量以形成枢轴点。数值越高,脚本向左查找最高/最低点的范围越大' rightTip = '向右查找摆动高低点的K线数量以形成枢轴点。数值越高,脚本向右查找最高/最低点的范围越大' nPivTip = '设置数组大小,即同时跟踪的枢轴点数量(x个高点和x个低点)' atrLenTip = '平均K线数量。ATR用于标准化不同资产和时间框架之间的区域宽度' multTip = 'ATR乘数设置区域宽度。默认为从区域底部到顶部的半个ATR' perTip = '区域大小占价格的最大百分比。某些资产在低价位时可能过于波动,会创建不合理大小的区域' maxTip = '历史跟踪蜡烛图形态的最大框数。注意:数值越高,由于同时允许的框元素数量限制,回顾时跟踪的枢轴区域越少' futTip = '价格水平标签的偏移K线数' srcTip = '枢轴点的源输入。默认跟踪HA蜡烛的最高和最低实体以平均价格行为,这可能导致水平位于支撑和阻力重叠区域' alignZonesTip = '对齐边缘重叠现有区域的重复区域,创建随时间老化并在视觉上增强的区域' extendTip = '向右延伸当前区域' lLabTip = '显示从关键水平延伸的价格水平标签' dhighsTip = '禁用将阻止跟踪高点' dlowsTip = '禁用将阻止跟踪低点' detectBOTip = '显示价格突破所有枢轴点的位置。显示来自下方的箭头' detectBDTip = '显示价格跌破所有枢轴点的位置。显示来自上方的箭头' breakUpTip = '显示价格突破阻力的位置。显示来自下方的箭头' breakDnTip = '显示价格跌破支撑的位置。显示来自上方的箭头' falseBullTip = '显示价格最初跌破支撑后反转的位置。假突破可能导致相反方向的快速移动(空头陷阱)。显示来自下方的大箭头' falseBearTip = '显示价格最初突破阻力后反转的位置。假突破可能导致相反方向的快速移动(多头陷阱)。显示来自上方的大箭头' supPushTip = '显示在支撑区域内检测到的上涨蜡烛。可以显示支撑被尊重的点。显示来自下方的三角形' resPushTip = '显示在阻力区域内检测到的下跌蜡烛。可以显示阻力被尊重的点。显示来自上方的三角形' curlTip = '当在关键区域范围内检测到蜡烛时显示Bjorgum TSI\'卷曲\'。可以显示关键水平的动量转换。(与Bjorgum TSI指标相关)' // 添加标签大小配置的提示信息 labelSizeTip = '设置价格水平标签的字体大小' repaintTip = '等待蜡烛结束后再检测形态。设为False将在确认前显示潜在形态' labelsTip = '为检测到的蜡烛形态显示标签' sBoxTip = '在检测到的蜡烛形态周围显示框' dTip = '检测十字星蜡烛形态' beTip = '检测吞没形态' hsTip = '检测锤子线和流星线形态' dgTip = '检测蜻蜓十字和墓碑十字形态' twTip = '检测镊子顶和镊子底形态' stTip = '检测纺锤顶形态' pcTip = '检测刺透线和乌云盖顶形态' bhTip = '检测孕线形态' lsTip = '检测长上影线和长下影线形态' ecWickTip = '确定吞没蜡烛是否必须吞没前一根蜡烛的影线或仅吞没实体' colorMatchTip = '确定锤子线是否必须是阳线,流星线是否必须是阴线' closeHalfTip = '确定镊子形态是否必须收盘超过前一根蜡烛的中点' atrMaxTip = '设置蜡烛的最大尺寸(作为当前ATR的倍数)' rejectWickTip = '形态解决蜡烛上拒绝影线允许的最大影线尺寸(占实体尺寸的百分比)。0禁用过滤器' hammerFibTip = '锤子线和流星线的实体与蜡烛尺寸的关系(即实体占总蜡烛尺寸的33%)' hsShadowPercTip = '允许的最大对立影线尺寸占实体尺寸的百分比(例如锤子形态的上影线等)' hammerSizeTip = '锤子线、流星线或长影线的最小尺寸(ATR的倍数)。(用于过滤微小设置)' dojiSizeTip = '实体与蜡烛尺寸的关系(即实体占总蜡烛尺寸的5%)' dojiWickSizeTip = '相对于对立影线的最大影线尺寸。(例如2 = 下影线必须小于或等于上影线的2倍)' luRatioTip = '上影线与整体蜡烛尺寸的关系,以百分比表示' lookbackTip = '可包含在假突破信号中的蜡烛数量' swingTip = '摆动检测用于过滤突破类型信号。数值越高意味着更重要的点,但数量更少' reflectTip = '过滤以确保设置是重要的摆动点。回顾这么远' offsetTip = '蜡烛形态高/低点与绝对摆动高/低点的距离。例如:0将过滤仅为最高/最低的形态,1过滤重要长度内的第二高等' bullPivotTip = '看涨关键水平的颜色\n(边框,背景)' bearPivotTip = '看跌关键水平的颜色\n(边框,背景)' breakoutTip = '突破箭头的颜色\n(看涨,看跌)' SnRTip = '支撑或阻力突破三角形的颜色\n(看涨,看跌)' falseBreakTip = '假突破箭头的颜色\n(看涨,看跌,箭头最大高度像素)' moveTip = '在区域内检测到的蜡烛三角形的颜色\n(看涨,看跌)' patTip = '包围蜡烛图形态的框颜色\n背景:(看涨,中性,看跌)\n边框:(看涨,中性,看跌)' labTip = '标记蜡烛图形态的标签颜色\n文本:(看涨,中性,看跌)\n标签:(看涨,中性,看跌)' stratTip = 'TSI速度控制预设。两种速度都与Bjorgum TSI指标相关' // ================================== // // ---------> 用户输入 <----------- // // ================================== // left = input.int(20, '向左查找', group = '区域设置', tooltip = leftTip) right = input.int(15, '向右查找', group = '区域设置', tooltip = rightTip) nPiv = input.int(4, '枢轴点数量', group = '区域设置', tooltip = nPivTip) atrLen = input.int(30, 'ATR长度', group = '区域设置', tooltip = atrLenTip) mult = input.float(0.5, '区域宽度(ATR)', group = '区域设置', tooltip = multTip, step = 0.1) per = input.float(5, '最大区域百分比', group = '区域设置', tooltip = perTip) max = input.float(10, '形态最大框数', group = '区域设置', tooltip = maxTip) fut = input.int(30, '标签偏移量', group = '区域设置', tooltip = futTip) src = input.string('HA', '枢轴点源', group = '区域设置', tooltip = srcTip, options = ['HA', 'High/Low Body', 'High/Low']) alignZones = input.bool(true, '对齐区域', group = '区域设置', tooltip = alignZonesTip) extend = input.bool(false, '向右延伸', group = '区域设置', tooltip = extendTip) lLab = input.bool(false, '显示水平标签', group = '区域设置', tooltip = lLabTip) dhighs = input.bool(true, '检测枢轴高点', group = '检测设置', tooltip = dhighsTip) dlows = input.bool(true, '检测枢轴低点', group = '检测设置', tooltip = dlowsTip) detectBO = input.bool(false, '检测突破', group = '检测设置', tooltip = detectBOTip) detectBD = input.bool(false, '检测跌破', group = '检测设置', tooltip = detectBDTip) breakUp = input.bool(false, '检测阻力突破', group = '检测设置', tooltip = breakUpTip) breakDn = input.bool(false, '检测支撑跌破', group = '检测设置', tooltip = breakDnTip) falseBull = input.bool(false, '检测假跌破', group = '检测设置', tooltip = falseBullTip) falseBear = input.bool(false, '检测假突破', group = '检测设置', tooltip = falseBearTip) supPush = input.bool(false, '检测支撑反弹', group = '检测设置', tooltip = supPushTip) resPush = input.bool(false, '检测阻力回落', group = '检测设置', tooltip = resPushTip) curl = input.bool(false, '检测TSI卷曲', group = '检测设置', tooltip = curlTip) repaint = input.bool(true, '等待确认K线', group = '蜡烛形态', tooltip = repaintTip) labels = input.bool(false, '显示标签', group = '蜡烛形态', tooltip = labelsTip) sBox = input.bool(false, '显示形态框', group = '蜡烛形态', tooltip = sBoxTip) d_ = input.bool(false, '检测十字星', group = '蜡烛形态', tooltip = dTip) be_ = input.bool(false, '检测吞没形态', group = '蜡烛形态', tooltip = beTip) hs_ = input.bool(false, '检测锤子线和流星线', group = '蜡烛形态', tooltip = hsTip) dg_ = input.bool(false, '检测蜻蜓和墓碑十字', group = '蜡烛形态', tooltip = dgTip) tw_ = input.bool(false, '检测镊子形态', group = '蜡烛形态', tooltip = twTip) st_ = input.bool(false, '检测纺锤顶', group = '蜡烛形态', tooltip = stTip) pc_ = input.bool(false, '检测刺透线和乌云盖顶', group = '蜡烛形态', tooltip = pcTip) bh_ = input.bool(false, '检测孕线', group = '蜡烛形态', tooltip = bhTip) ls_ = input.bool(false, '检测长影线', group = '蜡烛形态', tooltip = lsTip) alertMode = input.string(alert.freq_once_per_bar_close, '警报模式', group = '警报频率', options = [alert.freq_once_per_bar, alert.freq_once_per_bar_close]) ecWick = input.bool(false, '吞没必须包含影线', group = '蜡烛过滤器', tooltip = ecWickTip) colorMatch = input.bool(false, '锤子线必须匹配颜色', group = '蜡烛过滤器', tooltip = colorMatchTip) closeHalf = input.bool(false, '镊子收盘超过一半', group = '蜡烛过滤器', tooltip = closeHalfTip) atrMax = input.float(0.0, '最大蜡烛尺寸(× ATR)', group = '蜡烛过滤器', tooltip = atrMaxTip, step = 0.1) rejectWickMax = input.float(0.0, '[吞没]最大拒绝影线尺寸', group = '蜡烛过滤器', tooltip = rejectWickTip, step = 1) hammerFib = input.float(33, '[锤子]锤子线比例(%)', group = '蜡烛过滤器', tooltip = hammerFibTip, step = 1) hsShadowPerc = input.float(5, '[锤子]对立影线百分比(%)', group = '蜡烛过滤器', tooltip = hsShadowPercTip, step = 1) hammerSize = input.float(0.1, '[锤子]最小尺寸(× ATR)', group = '蜡烛过滤器', tooltip = hammerSizeTip, step = 0.1) dojiSize = input.float(5, '[十字]十字星尺寸(%)', group = '蜡烛过滤器', tooltip = dojiSizeTip, step = 1) dojiWickSize = input.float(2, '[十字]最大影线尺寸', group = '蜡烛过滤器', tooltip = dojiWickSizeTip, step = 1) luRatio = input.float(75, '[长影]长影线比例(%)', group = '蜡烛过滤器', tooltip = luRatioTip, step = 1) lookback = input.int(2, '突破回顾期', group = '回顾设置', tooltip = lookbackTip) swing = input.int(5, '摆动高低点', group = '回顾设置', tooltip = swingTip) reflect = input.int(10, '重要高低点', group = '回顾设置', tooltip = reflectTip) offset = input.int(1, '距离高低点K线数', group = '回顾设置', tooltip = offsetTip) // 添加标签大小配置 labelSize = input.string('small', '标签字体大小', group = '显示设置', tooltip = labelSizeTip, options = ['tiny', 'small', 'normal', 'large', 'huge']) bullBorder = input.color(color.new(#64b5f6, 60), '', inline = '0', group = '枢轴颜色') bullBgCol = input.color(color.new(#64b5f6, 95), '', inline = '0', group = '枢轴颜色', tooltip = bullPivotTip) bearBorder = input.color(color.new(#ffeb3b, 60), '', inline = '1', group = '枢轴颜色') bearBgCol = input.color(color.new(#ffeb3b, 95), '', inline = '1', group = '枢轴颜色', tooltip = bearPivotTip) upCol = input.color(color.new(#ff6d00, 25), '', inline = '2', group = '突破颜色') dnCol = input.color(color.new(#ff00ff, 25), '', inline = '2', group = '突破颜色', tooltip = breakoutTip) supCol = input.color(color.new(#17ff00, 25), '', inline = '3', group = '支撑阻力突破颜色') resCol = input.color(color.new(#ff0000, 25), '', inline = '3', group = '支撑阻力突破颜色', tooltip = SnRTip) fBull = input.color(color.new(#17ff00, 25), '', inline = '4', group = '假突破颜色') fBear = input.color(color.new(#ff0000, 25), '', inline = '4', group = '假突破颜色') arrowMax = input.int(75, '', inline = '4', group = '假突破颜色', tooltip = falseBreakTip) moveBullCol = input.color(color.new(#64b5f6, 25), '', inline = '5', group = '支撑阻力反弹颜色') moveBearCol = input.color(color.new(#ffeb3b, 25), '', inline = '5', group = '支撑阻力反弹颜色', tooltip = moveTip) curlBullCol = input.color(color.new(#17ff00, 40), '', inline = '6', group = '动量卷曲颜色') curlBearCol = input.color(color.new(#f3ff00, 40), '', inline = '6', group = '动量卷曲颜色', tooltip = curlTip) patBullBg = input.color(color.new(#17ff00, 90), '', inline = '7', group = '形态框颜色') patNeutBg = input.color(color.new(#b2b5be, 90), '', inline = '7', group = '形态框颜色') patBearBg = input.color(color.new(#ff0000, 90), '', inline = '7', group = '形态框颜色') patBullBo = input.color(color.new(#17ff00, 80), '', inline = '8', group = '形态框颜色') patNeutBo = input.color(color.new(#b2b5be, 80), '', inline = '8', group = '形态框颜色') patBearBo = input.color(color.new(#ff0000, 80), '', inline = '8', group = '形态框颜色', tooltip = patTip) textBullCol = input.color(color.new(#17ff00, 0), '', inline = '9', group = '标签颜色(文本/背景)') textNeutCol = input.color(color.new(#b2b5be, 0), '', inline = '9', group = '标签颜色(文本/背景)') textBearCol = input.color(color.new(#ff0000, 0), '', inline = '9', group = '标签颜色(文本/背景)') labBullCol = input.color(color.new(#17ff00, 80), '', inline = '10', group = '标签颜色(文本/背景)') labNeutCol = input.color(color.new(#b2b5be, 80), '', inline = '10', group = '标签颜色(文本/背景)') labBearCol = input.color(color.new(#ff0000, 80), '', inline = '10', group = '标签颜色(文本/背景)', tooltip = labTip) strat = input.string('Fast', '选择速度', group = 'TSI速度控制', tooltip = stratTip, options = ['Fast', 'Slow']) longf = input.int(25, '长周期长度', group = 'TSI快速设置') shortf = input.int(5, '短周期长度', group = 'TSI快速设置') signalf = input.int(14, '信号长度', group = 'TSI快速设置') longs = input.int(25, '长周期长度', group = 'TSI慢速设置') shorts = input.int(13, '短周期长度', group = 'TSI慢速设置') signals = input.int(13, '信号长度', group = 'TSI慢速设置') // ================================== // // -----> 不可变常量 <------ // // ================================== // sync = bar_index labUp = label.style_label_up labDn = label.style_label_down confirmed = barstate.isconfirmed extrap = extend ? extend.right : extend.none // 标签大小常量定义 labelSizeValue = labelSize == 'tiny' ? size.tiny : labelSize == 'small' ? size.small : labelSize == 'normal' ? size.normal : labelSize == 'large' ? size.large : size.huge var pivotHigh = array.new_box(nPiv) var pivotLows = array.new_box(nPiv) var highBull = array.new_bool(nPiv) var lowsBull = array.new_bool(nPiv) var boxes = array.new_box() haSrc = src == 'HA' hiLoSrc = src == 'High/Low' tsifast = strat == 'Fast' tsislow = strat == 'Slow' // ================================== // // ---> Functional Declarations <---- // // ================================== // atr = ta.atr(atrLen) perMax = close * 0.02 min = math.min(perMax, atr * 0.3) _haBody() => haClose = (open + high + low + close) / 4 haOpen = float(na) haOpen := na(haOpen[1]) ? (open + close) / 2 : (nz(haOpen[1]) + nz(haClose[1])) / 2 [haOpen, haClose] _extend(_x) => for i = 0 to array.size(_x) - 1 by 1 box.set_right(array.get(_x, i), sync) _arrayLoad(_x, _max, _val) => array.unshift(_x, _val) if array.size(_x) > _max array.pop(_x) _arrayBox(_x, _max, _val) => array.unshift(_x, _val) if array.size(_x) > _max _b = array.pop(_x) if extend box.set_extend(_b, extend.none) _arrayWrap(_x, _max, _val) => array.unshift(_x, _val) if array.size(_x) > _max box.delete(array.pop(_x)) _delLab(_x) => if array.size(_x) > 0 label.delete(array.pop(_x)) _delLine(_x) => if array.size(_x) > 0 line.delete(array.pop(_x)) _delLevels(_x, _y) => for i = 0 to array.size(_x) - 1 by 1 _delLab(_x) _delLine(_y) _box(_x1, _t, _r, _b, _boCol, _bgCol, _e) => box.new(_x1, _t, _r, _b, xloc = xloc.bar_index, extend = _e, border_color = _boCol, bgcolor = _bgCol) _wrap(_cond, _x, _bb, _bc, _bgc) => _t = ta.highest(high, _bb) + min _b = ta.lowest(low, _bb) - min _l = bar_index - _bb _r = bar_index + 1 if _cond _arrayWrap(_x, max, _box(_l, _t, _r, _b, _bc, _bgc, extend.none)) _getBox(_x, _i) => _box = array.get(_x, _i) _t = box.get_top(_box) _b = box.get_bottom(_box) [_t, _b] _align(_x, _y) => for i = 0 to array.size(_x) - 1 by 1 [_T, _B] = _getBox(_y, 0) [_t, _b] = _getBox(_x, i) if _T > _b and _T < _t or _B < _t and _B > _b or _T > _t and _B < _b or _B > _b and _T < _t box.set_top(array.get(_y, 0), _t) box.set_bottom(array.get(_y, 0), _b) _color(_x, _y) => var int _track = nPiv for i = 0 to array.size(_x) - 1 by 1 [t_, b_] = _getBox(_x, i) _isBull = array.get(_y, i) if close > t_ and not _isBull box.set_extend(array.get(_x, i), extend.none) array.set(_x, i, _box(sync, t_, sync, b_, bullBorder, bullBgCol, extrap)) array.set(_y, i, true) _track := _track + 1 _track if close < b_ and _isBull box.set_extend(array.get(_x, i), extend.none) array.set(_x, i, _box(sync, t_, sync, b_, bearBorder, bearBgCol, extrap)) array.set(_y, i, false) _track := _track - 1 _track _track _detect(_x, _y) => int _i = 0 bool _found = false bool _isBull = false while not _found and _i < array.size(_x) [t_, b_] = _getBox(_x, _i) if low < t_ and high > b_ _isBull := array.get(_y, _i) _found := true _found _i := _i + 1 _i [_found, _isBull] _falseBreak(_l) => bool _d = false bool _u = false for i = 1 to lookback by 1 if _l[i] < _l and _l[i + 1] >= _l and _l[1] < _l _d := true _d if _l[i] > _l and _l[i + 1] <= _l and _l[1] > _l _u := true _u [_d, _u] _numLevel(_x, _y) => int _above = 0 int _fill = 0 for i = 0 to array.size(_x) - 1 by 1 if i < array.size(_x) _isBull = array.get(_x, i) if _isBull == true _above := _above + 1 _above if _isBull == true or _isBull == false // Only count if it's a valid boolean (not na) _fill := _fill + 1 _fill for i = 0 to array.size(_y) - 1 by 1 if i < array.size(_y) _isBull = array.get(_y, i) if _isBull == true _above := _above + 1 _above if _isBull == true or _isBull == false // Only count if it's a valid boolean (not na) _fill := _fill + 1 _fill [_above, _fill] _check(_src, _l) => bool _check = false for i = 0 to _l by 1 if _src[i] _check := true _check _check _count(_src, _l) => int _result = 0 for i = 0 to _l by 1 if _src > _src[i] _result := _result + 1 _result _result // 修改 _label 函数 // 添加 x_offset_adjust 参数,默认为0 _label(_x, _y, y_price, _s, _col1, _col2, x_offset_adjust = 0) => transp = math.min(color.t(_col1), color.t(_col2)) // 将 x_offset_adjust 添加到标签和线的 x 坐标计算中 label_x_position = sync + fut + x_offset_adjust line_x2_position = sync + fut + x_offset_adjust array.unshift(_x, label.new(label_x_position, y_price, text = str.tostring(math.round_to_mintick(y_price)), color = color.new(_col1, transp), style = _s, textcolor = color.white, size = labelSizeValue)) if not extend and fut > 0 // 只有当 fut > 0 且不向右无限延长时才绘制连接线 // 线的起点是 sync (当前K线),终点是标签的 x 位置 array.unshift(_y, line.new(sync, y_price, line_x2_position, y_price, color = color.new(_col1, transp))) // 修改 _level 函数 // 添加 isHighPivotZone 参数 (true 表示是 pivotHigh 区域, false 表示是 pivotLows 区域) _level(_x, _y, isHighPivotZone) => var array