diff --git a/BjKeyLevels.pine b/BjKeyLevels.pine new file mode 100644 index 0000000..3034c9a --- /dev/null +++ b/BjKeyLevels.pine @@ -0,0 +1,1189 @@ + +// █▀▀▄ ──▀ █▀▀█ █▀▀█ █▀▀▀ █──█ █▀▄▀█ +// █▀▀▄ ──█ █──█ █▄▄▀ █─▀█ █──█ █─▀─█ +// ▀▀▀─ █▄█ ▀▀▀▀ ▀─▀▀ ▀▀▀▀ ─▀▀▀ ▀───▀ + +// 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