tunnel trading beta
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © zxcvbnm3260

//@version=5
indicator('vegas tunnel indicator', overlay=true)
// calc_on_every_tick = true

//版本更新记录：
// v1.0  2021/08/25  周三 测试版上线。
// v1.1  2021/10/02  周六 短期趋势线变色；加入止盈和止损标记；允许自定义盈亏比。
// v1.2  2021/10/03  周日 上一版本脚本并不处理最近30根bar，而这一版本解决了这一问题。（The previous version script does not handle the last 30 bars, but this version solves this bug.）
// v1.3  2021/10/03  周日 最后一根bar出现买入信号时，止损线和止盈线往前延伸。
// v1.4  2021/10/19  周二 使用ATR来校正过大的止盈和止损比例。（Use ATR to set excessive take-profit and stop-loss ranges.）
// v2.0  2021/11/01  周一 将策略版改回指标版，以便获得进行中的bar的提醒。注释中包括了下单策略。（The order strategy is included in the comments）


// 0.常量和初始化
N = input.float(title='请设置盈亏比', defval=1)
trail_offset1  = 0.5 // 移动止盈开始位，是几倍止盈位？
ratio_max_loss = 0.5 // 移动止盈开始后，最大浮盈损失多少比例就出场？

DOWN_RATIO_FOR_ENTRY = -0.25 // 信号bar高点下降多少后可以入场？（1表示信号bar高点与低点之间的距离）

var title='long1'

// 1.基础变量
ema169 = ta.ema(close, 169)
// ema144 = ta.ema(close, 144)
ema12 = ta.ema(close, 12)

ema576 = ta.ema(close, 576)
ema676 = ta.ema(close, 676)

plot(ema169, color=color.new(color.orange, 0), linewidth=2)
// plot(ema144, color=color.orange)
// plot(ema676, color=color.orange, linewidth=1)

mtr = math.max(high - low, math.abs(close[1] - high), math.abs(close[1] - low))
atr = ta.ema(mtr, 30)


is_0930 = hour(time, 'GMT-4') == 9  and minute(time, 'GMT-4') == 30
is_1500 = hour(time, 'GMT-4') == 15 and minute(time, 'GMT-4') == 00
is_1530 = hour(time, 'GMT-4') == 15 and minute(time, 'GMT-4') == 30

is_yangxian = close>open
is_yinxian = close<open

// 2.基本趋势标记

big_trend  = ema12 >= ema169 ? 1 : 0
big_trend2 = ema12 <= ema169 ? 1 : 0

// 背景的变色处理：
bgcolor(big_trend == 1 ? color.new(color.green, 90) : color.new(color.red, 90) )

big_trend1 = big_trend

// 放宽EMA限制，让信号提早出现：
if ema12<ema169 and math.abs(ema12-ema169)/ema169<0.001
    big_trend1 := 1
if ema12>ema169 and math.abs(ema12-ema169)/ema169<0.001
    big_trend2 := 1


// 上涨趋势开始：
// up1 = ema12 >= ema169 and ema12[1] < ema169[1]
up1 = big_trend1==1 and big_trend1[1]==0
bar_up1 = ta.valuewhen(up1, bar_index, 0)
// if up1
    // line1 = line.new(bar_index, low, bar_index, high, extend=extend.both, color=color.new(color.green, 50), width=5)
    // label.new(bar_index, high*1.1, '上涨趋势开始', style=label.style_label_down, textcolor=color.white, size=size.large)

// 下降趋势开始：
down1 = big_trend2==1 and big_trend2[1]==0
bar_down1 = ta.valuewhen(down1, bar_index, 0)
// if down1
    // line1 = line.new(bar_index, low, bar_index, high, extend=extend.both, color=color.new(color.red, 50), width=5)
    // label.new(bar_index, low*0.9, '下降趋势开始', style=label.style_label_up, textcolor=color.white, size=size.large)

// 均线的变色处理：
//上涨趋势中，短期均线上升时是绿色，下降时是酒红色。
// ema12>ema12[1]?color.green:color.purple
//下降趋势中，短期均线上升时是绿色，下降时是鲜红色。
// ema12>ema12[1]?color.green:color.red
plot(ema12, color=big_trend == 1 ? ema12 > ema12[1] ? color.green : color.purple : ema12 > ema12[1] ? color.green : color.red)
plot(ema676, color=ema676 > ema676[1] ? color.green : color.black)

is_entry0 = false
if not is_1500 and not is_1530 and year>=2020 and month>=1
    is_entry0 := true

// 3.自定义函数

// 今日开盘至此bar时的最高点
slm_max_high_day()=>
    high1 = 0.0
    for i = 0 to 20
        if dayofmonth==dayofmonth[i] and time>=time[i]
            high1 := math.max(high1, high[i])
    high1

// 今日开盘至此bar时的最低点
slm_min_low_day()=>
    low1 = 99999.9
    for i = 0 to 20
        if dayofmonth==dayofmonth[i] and time>=time[i]
            low1 := math.min(low1, low[i])
    low1

// 标出各种点位
slm_lines(title, time2, stop_loss, price_in_max, stop_profit0, stop_profit)=>
    label.new(bar_index, low, title, style=label.style_label_up, textcolor=color.white, size=size.normal)
    line.new(time, stop_loss, time2, stop_loss, color=color.red, width=2, xloc = xloc.bar_time)  // 止损位
    line.new(time, price_in_max, time2, price_in_max, color=color.blue, xloc = xloc.bar_time)  // 入场位
    line.new(time, stop_profit0, time2, stop_profit0, color=color.green, xloc = xloc.bar_time)  // 止盈位开始位
    line.new(time, stop_profit, time2, stop_profit, color=color.green, width=2, xloc = xloc.bar_time)  // 止盈位

// 做多类
slm_price_long()=>
    // stop_loss0 = is_0930 ? low : low[1]
    stop_loss0 = low[1]
    price_in_max = high - (high - low) * DOWN_RATIO_FOR_ENTRY
    length_stop_loss = math.min(price_in_max - stop_loss0, atr * 2)
    stop_loss = price_in_max - length_stop_loss
    stop_profit0 = price_in_max + length_stop_loss * trail_offset1
    stop_profit = price_in_max + length_stop_loss * N
    trail_profit_max_loss = length_stop_loss * trail_offset1 * ratio_max_loss // 假设移动止盈开始后，浮盈立即损失最大比例（ratio_max_loss），则损失了多少利润？
    trail_offset2 = trail_profit_max_loss / stop_profit0
    qty1 = int(10000/price_in_max)
    time2 = time+1000*3600*24
    [stop_loss, price_in_max, stop_profit0, stop_profit, trail_offset2, qty1, time2]

// slm_trades_long1(qty1, stop_loss, price_in_max, stop_profit0, stop_profit, trail_offset2)=>
//     strategy.entry("long1_open", strategy.long, qty1, limit = price_in_max, when = strategy.position_size==0)
//     strategy.exit("long1_close", qty = qty1, limit = stop_profit, trail_price = stop_profit0, trail_offset = trail_offset2, stop = stop_loss, oca_name = "exit")

// slm_trades_long2(qty1, stop_loss, price_in_max, stop_profit0, stop_profit, trail_offset2)=>
//     strategy.entry("long2_open", strategy.long, qty1, limit = price_in_max, when = strategy.position_size==0)
//     strategy.exit("long2_close", qty = qty1, limit = stop_profit, trail_price = stop_profit0, trail_offset = trail_offset2, stop = stop_loss, oca_name = "exit")

// 做空类
slm_price_short()=>
    // stop_loss0 = is_0930 ? high : high[1]
    stop_loss0 = high[1]
    price_in_max = low + (high - low) * DOWN_RATIO_FOR_ENTRY
    length_stop_loss = math.min(stop_loss0 - price_in_max, atr * 2)
    stop_loss = price_in_max + length_stop_loss
    stop_profit0 = price_in_max - length_stop_loss * trail_offset1
    stop_profit = price_in_max - length_stop_loss * N
    trail_profit_max_loss = length_stop_loss * trail_offset1 * ratio_max_loss // 假设移动止盈开始后，浮盈立即损失最大比例（ratio_max_loss），则损失了多少利润？
    trail_offset2 = trail_profit_max_loss / stop_profit0
    qty1 = int(10000/price_in_max)
    time2 = time+1000*3600*24
    [stop_loss, price_in_max, stop_profit0, stop_profit, trail_offset2, qty1, time2]

// slm_trades_short1(qty1, stop_loss, price_in_max, stop_profit0, stop_profit, trail_offset2)=>
//     strategy.entry("short1_open", strategy.short, qty1, limit = price_in_max, when = strategy.position_size==0)
//     strategy.exit("short1_close", qty = qty1, limit = stop_profit, trail_price = stop_profit0, trail_offset = trail_offset2, stop = stop_loss, oca_name = "exit")
    
// slm_trades_short2(qty1, stop_loss, price_in_max, stop_profit0, stop_profit, trail_offset2)=>
//     strategy.entry("short2_open", strategy.short, qty1, limit = price_in_max, when = strategy.position_size==0)
//     strategy.exit("short2_close", qty = qty1, limit = stop_profit, trail_price = stop_profit0, trail_offset = trail_offset2, stop = stop_loss, oca_name = "exit")

slm_msg(title)=>
    msg='{
          "ticker":"' + syminfo.ticker + '",
          "k_time": "'+ timeframe.period + '",
          "type":"' + title + '",
          "timestamp": "' + str.tostring(time) + '",
          "atr": "'   + str.tostring(atr,    "#.000") + '",
          "close": "' + str.tostring(close,  "#.000") + '",
          "high":"'   + str.tostring(high,   "#.000") + '",
          "low":"'    + str.tostring(low,    "#.000") + '",
          "high_1":"' + str.tostring(high[1],"#.000") + '",
          "low_1":"'  + str.tostring(low[1], "#.000") + '"
         }'

msg1 = slm_msg('long1')
msg2 = slm_msg('long2')
msg3 = slm_msg('short1')
msg4 = slm_msg('short2')

// 4.第一做多点（大趋势确立时的做多点）
[stop_loss, price_in_max, stop_profit0, stop_profit, trail_offset2, qty1, time2] = slm_price_long()
stop_loss_confirmed = stop_loss
stop_profit0_confirmed = stop_profit0

interval_up1 = bar_index - bar_up1
chutou_up1 = close > open and close > high[1]  // 是否“出头”

var is_order_long1 = false
if bar_index==bar_up1
    is_order_long1 := false

// long1的各个前提条件：
// 1.大趋势变换次数<=3（排除震荡期）
// 2.信号bar高点是当日最高点（上涨动能之前未消耗太多）
// 3.连涨bar数<=4（上涨动能之前未消耗太多）

is_trend_switch_freq = (bar_index - ta.valuewhen(up1, bar_index, 1))<19
is_highest_today = high==slm_max_high_day()
is_5yang = is_yangxian and is_yangxian[1] and is_yangxian[2] and is_yangxian[3] and is_yangxian[4]

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

// if is_entry0 and chutou_up1 and not is_trend_switch_freq and is_highest_today and not is_5yang
if is_entry0 and not is_trend_switch_freq and is_highest_today and not is_5yang
    title:='long1'
    if interval_up1 == 0 and big_trend1==1
        is_order_long1 := true
        stop_loss_confirmed := stop_loss
        stop_profit0_confirmed := stop_profit0
        slm_lines(title, time2, stop_loss, price_in_max, stop_profit0, stop_profit)
        // slm_trades_long1(qty1, stop_loss, price_in_max, stop_profit0, stop_profit, trail_offset2)
        alert(msg1, alert.freq_all)
    else if interval_up1 == 1 and not is_order_long1 and big_trend1*big_trend1[1]==1
        is_order_long1 := true
        stop_loss_confirmed := stop_loss
        stop_profit0_confirmed := stop_profit0
        slm_lines(title, time2, stop_loss, price_in_max, stop_profit0, stop_profit)
        // slm_trades_long1(qty1, stop_loss, price_in_max, stop_profit0, stop_profit, trail_offset2)
        alert(msg1, alert.freq_all)
    else if interval_up1 == 2 and not is_order_long1 and big_trend1*big_trend1[1]*big_trend1[2]==1
        is_order_long1 := true
        stop_loss_confirmed := stop_loss
        stop_profit0_confirmed := stop_profit0
        slm_lines(title, time2, stop_loss, price_in_max, stop_profit0, stop_profit)
        // slm_trades_long1(qty1, stop_loss, price_in_max, stop_profit0, stop_profit, trail_offset2)
        alert(msg1, alert.freq_all)
    else if interval_up1 == 3 and not is_order_long1 and big_trend1*big_trend1[1]*big_trend1[2]*big_trend1[3]==1
        is_order_long1 := true
        stop_loss_confirmed := stop_loss
        stop_profit0_confirmed := stop_profit0
        slm_lines(title, time2, stop_loss, price_in_max, stop_profit0, stop_profit)
        // slm_trades_long1(qty1, stop_loss, price_in_max, stop_profit0, stop_profit, trail_offset2)
        alert(msg1, alert.freq_all)

// 5.回调结束时的做多点
up2 = big_trend1 == 1 and low <= ema169 and ema12 > ema169 and bar_index != bar_up1
// if up2
//     label.new(bar_index, high, '拉回点', style=label.style_label_down, textcolor=color.white, size=size.normal)

bar_up2 = ta.valuewhen(up2, bar_index, 0)
interval_up2 = bar_index - bar_up2
chutou_up2 = chutou_up1 and ema12 > ema12[1] and big_trend1 == 1

var is_order_long2 = false
if bar_index==bar_up2
    is_order_long2 := false

// long2的各个前提条件：
// 1.大趋势变换次数<=3（排除震荡期）
// 2.信号bar高点是当日最高点（上涨动能之前未消耗太多）
// 3.连涨bar数<=4（上涨动能之前未消耗太多）
// 4.最近10根bar内跌破EMA169的bar数<=2（证明EMA169有支撑作用）
// 5.信号bar的高点距离EMA169必须 <= 4倍的ATR（上涨动能之前未消耗太多）

is_under_ema169 = low<ema169
is_under_ema169_freq = (bar_index - ta.valuewhen(is_under_ema169, bar_index, 2))<=10
is_too_high = high-ema169 > atr*4

// plot(ema169+atr*4)
// label.new(bar_index, low, str.tostring(ema169+atr*4), style=label.style_label_up, textcolor=color.white, size=size.normal)

// if is_entry0 and chutou_up2 and not is_trend_switch_freq and is_highest_today and not is_5yang and not is_under_ema169_freq and not is_too_high
if is_entry0 and not is_trend_switch_freq and is_highest_today and not is_5yang and not is_under_ema169_freq and not is_too_high
    title:='long2'
    if interval_up2 == 0 and big_trend1==1
        is_order_long2 := true
        stop_loss_confirmed := stop_loss
        stop_profit0_confirmed := stop_profit0
        slm_lines(title, time2, stop_loss, price_in_max, stop_profit0, stop_profit)
        // slm_trades_long2(qty1, stop_loss, price_in_max, stop_profit0, stop_profit, trail_offset2)
        alert(msg2, alert.freq_all)
    else if interval_up2 == 1 and not is_order_long2 and big_trend1*big_trend1[1]==1
        is_order_long2 := true
        stop_loss_confirmed := stop_loss
        stop_profit0_confirmed := stop_profit0
        slm_lines(title, time2, stop_loss, price_in_max, stop_profit0, stop_profit)
        // slm_trades_long2(qty1, stop_loss, price_in_max, stop_profit0, stop_profit, trail_offset2)
        alert(msg2, alert.freq_all)
    else if interval_up2 == 2 and not is_order_long2 and big_trend1*big_trend1[1]*big_trend1[2]==1
        is_order_long2 := true
        stop_loss_confirmed := stop_loss
        stop_profit0_confirmed := stop_profit0
        slm_lines(title, time2, stop_loss, price_in_max, stop_profit0, stop_profit)
        // slm_trades_long2(qty1, stop_loss, price_in_max, stop_profit0, stop_profit, trail_offset2)
        alert(msg2, alert.freq_all)
    else if interval_up2 == 3 and not is_order_long2 and big_trend1*big_trend1[1]*big_trend1[2]*big_trend1[3]==1
        is_order_long2 := true
        stop_loss_confirmed := stop_loss
        stop_profit0_confirmed := stop_profit0
        slm_lines(title, time2, stop_loss, price_in_max, stop_profit0, stop_profit)
        // slm_trades_long2(qty1, stop_loss, price_in_max, stop_profit0, stop_profit, trail_offset2)
        alert(msg2, alert.freq_all)


// 6.第一做空点（大趋势确立时的做空点）
[stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2, trail_offset2_2, qty1_2, time2_2] = slm_price_short()

stop_loss_confirmed_2 = stop_loss_2
stop_profit0_confirmed_2 = stop_profit0_2

interval_down1 = bar_index - bar_down1
chutou_down1 = close < open and close < low[1]  // 是否“落尾”

var is_order_short1 = false
if bar_index==bar_down1
    is_order_short1 := false

// label.new(bar_index, low, str.tostring(bar_index - ta.valuewhen(down1, bar_index, 1)), style=label.style_label_up, textcolor=color.white, size=size.normal)

is_trend_switch_freq2 = (bar_index - ta.valuewhen(down1, bar_index, 1)) <19
is_lowest_today = low==slm_min_low_day()
is_5yin = is_yinxian and is_yinxian[1] and is_yinxian[2] and is_yinxian[3] and is_yinxian[4]

// if is_entry0 and chutou_down1 and not is_trend_switch_freq2 and is_lowest_today and not is_5yin
if is_entry0 and not is_trend_switch_freq2 and is_lowest_today and not is_5yin
    title:='short1'
    if interval_down1 == 0 and big_trend2==1
        is_order_short1 := true
        stop_loss_confirmed_2 := stop_loss_2
        stop_profit0_confirmed_2 := stop_profit0_2
        slm_lines(title, time2_2, stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2)
        // slm_trades_short1(qty1_2, stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2, trail_offset2_2)
        alert(msg3, alert.freq_all)
    else if interval_down1 == 1 and not is_order_short1 and big_trend2*big_trend2[1]==1
        is_order_short1 := true
        stop_loss_confirmed_2 := stop_loss_2
        stop_profit0_confirmed_2 := stop_profit0_2
        slm_lines(title, time2_2, stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2)
        // slm_trades_short1(qty1_2, stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2, trail_offset2_2)
        alert(msg3, alert.freq_all)
    else if interval_down1 == 2 and not is_order_short1 and big_trend2*big_trend2[1]*big_trend2[2]==1
        is_order_short1 := true
        stop_loss_confirmed_2 := stop_loss_2
        stop_profit0_confirmed_2 := stop_profit0_2
        slm_lines(title, time2_2, stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2)
        // slm_trades_short1(qty1_2, stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2, trail_offset2_2)
        alert(msg3, alert.freq_all)
    else if interval_down1 == 3 and not is_order_short1 and big_trend2*big_trend2[1]*big_trend2[2]*big_trend2[3]==1
        is_order_short1 := true
        stop_loss_confirmed_2 := stop_loss_2
        stop_profit0_confirmed_2 := stop_profit0_2
        slm_lines(title, time2_2, stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2)
        // slm_trades_short1(qty1_2, stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2, trail_offset2_2)
        alert(msg3, alert.freq_all)


// 7.反弹结束时的做空点
down2 = big_trend2 == 1 and high >= ema169 and ema12 < ema169 and bar_index != bar_down1
// if down2
    // label.new(bar_index, high, '反弹点', style=label.style_label_down, textcolor=color.white, size=size.normal)

bar_down2 = ta.valuewhen(down2, bar_index, 0)
interval_down2 = bar_index - bar_down2
chutou_down2 = chutou_down1 and ema12 < ema12[1] and big_trend2 == 1

var is_order_short2 = false
if bar_index==bar_down2
    is_order_short2 := false

is_over_ema169 = high>ema169
is_over_ema169_freq = (bar_index - ta.valuewhen(is_over_ema169, bar_index, 2)) <=10
is_too_low = ema169-low > atr*4

// plot(ema169-atr*4)

// if is_entry0 and chutou_down2 and not is_trend_switch_freq2 and is_lowest_today and not is_5yin and not is_over_ema169_freq and not is_too_low
if is_entry0 and not is_trend_switch_freq2 and is_lowest_today and not is_5yin and not is_over_ema169_freq and not is_too_low
    title:='short2'
    if interval_down2 == 0 and big_trend2==1
        is_order_short2 := true
        stop_loss_confirmed_2 := stop_loss_2
        stop_profit0_confirmed_2 := stop_profit0_2
        slm_lines(title, time2_2, stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2)
        // slm_trades_short2(qty1_2, stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2, trail_offset2_2)
        alert(msg4, alert.freq_all)
    else if interval_down2 == 1 and not is_order_short2 and big_trend2*big_trend2[1]==1
        is_order_short2 := true
        stop_loss_confirmed_2 := stop_loss_2
        stop_profit0_confirmed_2 := stop_profit0_2
        slm_lines(title, time2_2, stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2)
        // slm_trades_short2(qty1_2, stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2, trail_offset2_2)
        alert(msg4, alert.freq_all)
    else if interval_down2 == 2 and not is_order_short2 and big_trend2*big_trend2[1]*big_trend2[2]==1
        is_order_short2 := true
        stop_loss_confirmed_2 := stop_loss_2
        stop_profit0_confirmed_2 := stop_profit0_2
        slm_lines(title, time2_2, stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2)
        // slm_trades_short2(qty1_2, stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2, trail_offset2_2)
        alert(msg4, alert.freq_all)
    else if interval_down2 == 3 and not is_order_short2 and big_trend2*big_trend2[1]*big_trend2[2]*big_trend2[3]==1
        is_order_short2 := true
        stop_loss_confirmed_2 := stop_loss_2
        stop_profit0_confirmed_2 := stop_profit0_2
        slm_lines(title, time2_2, stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2)
        // slm_trades_short2(qty1_2, stop_loss_2, price_in_max_2, stop_profit0_2, stop_profit_2, trail_offset2_2)
        alert(msg4, alert.freq_all)


// // 尾盘平仓，且撤退场单
// strategy.close_all(when = strategy.position_size!=0 and is_1500)


// // 各类撤单：
// strategy.cancel(id = 'long1_open', when = strategy.position_size==0 and slm_max_high_day()>=stop_profit0_confirmed)
// strategy.cancel(id = 'long2_open', when = strategy.position_size==0 and slm_max_high_day()>=stop_profit0_confirmed)
// strategy.cancel(id = 'short1_open', when = strategy.position_size==0 and slm_min_low_day()<=stop_profit0_confirmed_2)
// strategy.cancel(id = 'short2_open', when = strategy.position_size==0 and slm_min_low_day()<=stop_profit0_confirmed_2)

// strategy.cancel(id = 'long1_close', when = strategy.position_size==0 and slm_max_high_day()>=stop_profit0_confirmed)
// strategy.cancel(id = 'long2_close', when = strategy.position_size==0 and slm_max_high_day()>=stop_profit0_confirmed)
// strategy.cancel(id = 'short1_close', when = strategy.position_size==0 and slm_min_low_day()<=stop_profit0_confirmed_2)
// strategy.cancel(id = 'short2_close', when = strategy.position_size==0 and slm_min_low_day()<=stop_profit0_confirmed_2)


