Entanglement Pen
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © slmgtv1

// v1.0 2022/02/20
// v1.1 2022/02/27 新增“破前低”的标签。（Add "Break-previous-low" label.）

// v2.0 2022/03/17 大幅简化计算流程，使得呈现速度更快，覆盖的bar数也更多。（Significantly simplifies the calculation process, making rendering faster and covering more bars.）
// v2.1 2022/03/18 更正“前低”的计算逻辑，并使“破前低”标记覆盖所有bar。（Correct the calculation logic of "pre-low" and make the "break pre-low" marker cover all bars.）

//@version=5
indicator("缠论笔", overlay=true)

// ---------------------------------------------------------------------------------------------------------------
// 1.定义变量
// ---------------------------------------------------------------------------------------------------------------

// N_small = input.int(N_small, '小顶和小底的计算bar数：')
N_small = 2000
is_plot_qiandi = input.bool(true, '是否画出“前低”线？')
is_display_labels = input.bool(true, '是否呈现各类label（顶、底、破前低）？')

n1 = 0
n2 = 0
n3 = 0 // 计算顶部强度的周期
n4 = 0 // 计算底部强度的周期

is_top_strong = false // 顶部是否足够强？即：顶部与底部的价差足够大：顶部的low>底部的high
is_bottom_strong = false // 底部是否足够强？即：顶部与底部的价差足够大：顶部的low>底部的high

is_up_strong = false // 涨势是否足够强？即：low和high都在连续上涨
is_down_strong = false // 跌势是否足够强？即：low和high都在连续下跌

is_last_top = false // 短期内连续出现多个顶部bar时，此bar是否最后一个顶部bar
is_last_bottom = false


// ---------------------------------------------------------------------------------------------------------------
// 2.计算简单周期
// ---------------------------------------------------------------------------------------------------------------

// n1 ： 此BAR的高点是多少bar的最高点？
// N1HIGH:=TOPRANGE(H);

for i = 1 to N_small
    n1 := i-1
    if high<=high[i]
        break

// n2 ： 此BAR的低点是多少bar的最低点？
// N2LOW:=LOWRANGE(L);

for i = 1 to N_small
    n2 := i-1
    if low>low[i]
        break

// n3 ： n1内，high的最低点到当前BAR的bar数
// N3:=LLVBARS(H,N1HIGH);

lowest_high = high
if n1>1
    for i = 1 to n1-1
        lowest_high := math.min(lowest_high, high[i])

n3 := na
if n1>0
    for i = 0 to n1
        n3 := i
        if high[i]==lowest_high
            break

// n4 ： n2内，low的最高点到当前BAR的bar数
// N4:=HHVBARS(L,N2LOW);

highest_low = low
if n2>1
    for i = 1 to n2-1
        highest_low := math.max(highest_low, low[i])

n4 := na
if n2>0
    for i = 0 to n2
        n4 := i
        if low[i]==highest_low
            break

// ---------------------------------------------------------------------------------------------------------------
// 3.计算复杂标签
// ---------------------------------------------------------------------------------------------------------------

// 顶部是否足够强？即：顶部与底部的价差足够大：顶部的low>底部的high
// N3+1内，L的最高点，是否大于H的最低点？
// TOPSTRENGTH:=HHV(L,N3+1)>LLV(H,N3+1);

highest_low_n3 = low
if n3>1
    for i = 1 to n3
        highest_low_n3 := math.max(highest_low_n3, low[i])

lowest_high_n3 = high
if n3>1
    for i = 1 to n3
        lowest_high_n3 := math.min(lowest_high_n3, high[i])

is_top_strong := highest_low_n3 > lowest_high_n3

// 底部是否足够强？即：顶部与底部的价差足够大：顶部的low>底部的high
// N4+1内，L的最高点，是否大于H的最低点？
// BOTTOMSTRENGTH:=HHV(L,N4+1)>LLV(H,N4+1);

highest_low_n4 = low
if n4>1
    for i = 1 to n4
        highest_low_n4 := math.max(highest_low_n4, low[i])

lowest_high_n4 = high
if n4>1
    for i = 1 to n4
        lowest_high_n4 := math.min(lowest_high_n4, high[i])

is_bottom_strong := highest_low_n4 > lowest_high_n4

// 涨势是否足够强？即：low和high都在连续上涨
// N3内，此低>=前低，的次数，是否>2；且：周期3内，此高>=前高，的次数，是否>2
// TOPINCLUDE:=COUNT(L>=REF(L,1),N3)>2 AND COUNT(H>=REF(H,1),N3)>2;

n_up_low = 0
if n3>0
    for i = 0 to n3
        if low[i]>low[i+1]
            n_up_low := n_up_low + 1

n_up_high = 0
if n3>0
    for i = 0 to n3
        if high[i]>high[i+1]
            n_up_high := n_up_high + 1

is_up_strong := (n_up_low>2) and (n_up_high>2)

// 跌势是否足够强？即：low和high都在连续下跌
// {N4内，此高<=前高，的次数，是否>2；且：周期5内，此低<=前低，的次数，是否>2}{标记4}
// BOTTOMINCLUDE:=COUNT(H<=REF(H,1),N4)>2 AND COUNT(L<=REF(L,1),N4)>2;

n_down_low = 0
if n4>0
    for i = 0 to n4
        if low[i]<low[i+1]
            n_down_low := n_down_low + 1

n_down_high = 0
if n4>0
    for i = 0 to n4
        if high[i]<high[i+1]
            n_down_high := n_down_high + 1

is_down_strong := (n_down_low>2) and (n_down_high>2)

// ---------------------------------------------------------------------------------------------------------------
// 4.确定小顶和小底
// ---------------------------------------------------------------------------------------------------------------

// {顶BAR成立的前提条件：周期1 且 标记1 且 标记3 且 周期3>3}
// TOP0:=N1HIGH AND TOPSTRENGTH AND TOPINCLUDE AND N3>3;

is_top_0 = (n1>0) and (n3>3) and is_top_strong and is_up_strong

// {底BAR成立的前提条件：周期2 且 标记2 且 标记4 且 周期4>3}
// BOTTOM0:=N2LOW AND BOTTOMSTRENGTH AND BOTTOMINCLUDE AND N4>3;

is_bottom_0 = (n2>0) and (n4>3) and is_bottom_strong and is_down_strong

// {TOPDAYS：前一个TOP0的BAR，距离现在几根BAR？}
// TOP0DAYS:=BARSLAST(TOP0);

n_top0_previous = bar_index - ta.valuewhen(is_top_0, bar_index, 0)
n_bottom0_previous = bar_index - ta.valuewhen(is_bottom_0, bar_index, 0)

// {间隔是否足够长？如果本BAR就是TOP0，则返回BOTTOMDAYS，若有则返回TOPDAYS；BAR数是否>=3}
// IS3BAR:=IF(TOP0DAYS=0,BOTTOM0DAYS,TOP0DAYS)>=3;

is_3_bar = (n_top0_previous==0 ? n_bottom0_previous : n_top0_previous)>=3

// {顶BAR：顶BAR前提 且 足够长 且 此高是前一个BOTTOM0成立以来的最高点}
// TOP:=TOP0 AND IS3BAR AND H=HHV(H, BOTTOM0DAYS);

highest_high = high
if n_bottom0_previous>1
    for i = 1 to n_bottom0_previous
        highest_high := math.max(highest_high, high[i])


is_top = is_top_0 and is_3_bar and high==highest_high
if is_top and is_display_labels
    label.new(bar_index, high, str.tostring('顶'), style=label.style_label_down, color=color.gray, textcolor=color.white, size=size.small)

// {底BAR：底BAR前提 且 足够长 且 此低是前一个TOP0成立以来的最低点}
// BOTTOM:=BOTTOM0 AND IS3BAR AND L=LLV(L, TOP0DAYS);

lowest_low = low
if n_top0_previous>1
    for i = 1 to n_top0_previous
        lowest_low := math.min(lowest_low, low[i])


// is_top_last = false
// is_bottom_last = false
// label_top2 = '顶'

is_bottom = is_bottom_0 and is_3_bar and low==lowest_low
if is_bottom and is_display_labels
    label.new(bar_index, low, str.tostring('底'), style=label.style_label_up, color=color.gray, textcolor=color.white, size=size.small)


// ---------------------------------------------------------------------------------------------------------------
// 5.画出大顶和大底，并连线
// ---------------------------------------------------------------------------------------------------------------

x1 = bar_index
x2 = bar_index
y1 = 0.0
y2 = 0.0

if is_bottom // 对于1个小底来说，它之前最近的1个小顶，就是大顶
    for i = 1 to N_small
        if is_top[i]
            x2 := bar_index[i]
            y2 := high[i]
            if is_display_labels
                label.new(x2, y2, '顶', style=label.style_label_down, color=color.blue, textcolor=color.white, size=size.normal)
            
            for j = 1 to N_small // 然后从这个大顶出发，找之前最近的一个小底，即最近的一个大底，连线它俩
                if is_bottom[i+j]
                    x1 := bar_index[i+j]
                    y1 := low[i+j]
                    line.new(x1, y1, x2, y2, color = color.black)
                    break
            break
            
        if is_bottom[i] // 若先找到小底，则说明是连续小底，则退出循环
            break
            
if is_top // 对于1个小顶来说，它之前最近的1个小底，就是大底
    for i = 1 to N_small
        if is_bottom[i]
            x2 := bar_index[i]
            y2 := low[i]
            if is_display_labels
                label.new(x2, y2, '底', style=label.style_label_up, color=color.blue, textcolor=color.white, size=size.normal)
            
            for j = 1 to N_small
                if is_top[i+j]
                    x1 := bar_index[i+j]
                    y1 := high[i+j]
                    line.new(x1, y1, x2, y2, color = color.black)
                    break
            break
        if is_top[i] // 若先找到小顶，则说明是连续小顶，则退出循环
            break

// bottom_value = 0.0
// bottom_value := is_bottom ? low : bottom_value[1]
// plot(bottom_value, color = color.purple)

// top_value = 0.0
// top_value := is_top ? high : top_value[1]
// plot(top_value, color = color.olive)

// ---------------------------------------------------------------------------------------------------------------
// 6.当前bar是否破前低
// ---------------------------------------------------------------------------------------------------------------

qiandi = 0.0 // 前低是多少
is_top_near = false // 最近一个有标记的bar是top bar吗？
is_po_qiandi = false
ts_di = 0

if is_top // 若此bar是顶bar，则允许更新前低值
    for i = 0 to N_small // 从当前bar出发，往前找最近一个小底（也是大底）：
        if is_bottom[i] 
            qiandi := low[i]
            ts_di := time[i]
            break
            
else if is_bottom[1] or is_po_qiandi[1] // 若前一个bar是小底或破前低bar，则允许更新前低值
    qiandi := low[1]
    ts_di := time[1]
    
else // 其他情况下，前低值不变
    qiandi := qiandi[1]
    ts_di := ts_di[1]

plot(is_plot_qiandi ? qiandi : na, linewidth=2)
    
msg1 = '{ "group": "8", "ticker": "'+ syminfo.ticker + '", "k_time": "' + timeframe.period + '", "timestamp":  "' + str.tostring(time) + '", "price":  "' + str.tostring(close,  "#.000") + '", "type": "break_previous_low", "ts_di": "' + str.tostring(ts_di) + '" }'

label_po_qiandi = 0.0
if close < qiandi
    is_po_qiandi := true
    label_po_qiandi := close
    alert(msg1, alert.freq_once_per_bar_close)
    if is_display_labels
        label.new(bar_index, high, '破前低', style=label.style_label_down, color=color.red, textcolor=color.white, size=size.normal)

// plot(label_po_qiandi, color = color.red, linewidth=1)
plot(is_plot_qiandi ? label_po_qiandi : na, color = color.maroon, linewidth=1, style = plot.style_histogram )

// label.new(bar_index, low, str.tostring(n2), style=label.style_label_up, color=color.blue, textcolor=color.white, size=size.normal)
// label.new(bar_index, low, str.tostring(bar_index), style=label.style_label_up, color=color.blue, textcolor=color.white, size=size.normal)









