From a414c3d98210d93569934241ae4d7e07d40cb7cb Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Tue, 24 Mar 2026 16:36:47 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=B0=E7=9A=84=E7=AD=96?= =?UTF-8?q?=E7=95=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chan_theory_complete.pine | 715 +++++++++++++++++------------------- ema26_ema52.pine | 36 ++ ema26_ema52_diff.pine | 39 ++ kdt_divergence.pine | 542 +++++++++++++++++++++++++++ last_divergence_simple.pine | 145 ++++++++ 5 files changed, 1092 insertions(+), 385 deletions(-) create mode 100644 ema26_ema52.pine create mode 100644 ema26_ema52_diff.pine create mode 100644 kdt_divergence.pine create mode 100644 last_divergence_simple.pine diff --git a/chan_theory_complete.pine b/chan_theory_complete.pine index 12805ea..3491bf9 100644 --- a/chan_theory_complete.pine +++ b/chan_theory_complete.pine @@ -28,7 +28,7 @@ use_macd_filter = input.bool(false, "MACD过滤分型", tooltip="顶分型要 ema1_len = input.int(24, "EMA1周期", minval=5, maxval=100, group="均线参数", tooltip="最快均线,默认24") ema2_len = input.int(52, "EMA2周期", minval=10, maxval=200, group="均线参数", tooltip="次快均线,默认52") ema3_len = input.int(104, "EMA3周期", minval=20, maxval=300, group="均线参数", tooltip="次慢均线,默认104") -ema4_len = input.int(156, "EMA4周期", minval=30, maxval=500, group="均线参数", tooltip="最慢均线,默认156") +ema4_len = input.int(156, "EMA4周期", minval=30, maxval=500, group="均线参数", tooltip="EMA156仅用于显示,不参与三均线排列/交叉") trend_confirm_bars = input.int(3, "趋势确认K线数", minval=1, maxval=20, group="均线参数", tooltip="排列持续N根K线才确认趋势,增大可减少震荡假信号") color_bi_up = input.color(color.red, "上升笔颜色", group="颜色设置") @@ -59,28 +59,29 @@ plot(show_ema24 ? ema24 : na, "EMA24", color=color_ema24, linewidth=2) plot(show_ema52 ? ema52 : na, "EMA52", color=color_ema52, linewidth=2) plot(show_ema104 ? ema104 : na, "EMA104", color=color_ema104, linewidth=2) plot(show_ema156 ? ema156 : na, "EMA156", color=color_ema156, linewidth=2) +// 趋势/排列与交叉仅使用三条均线:EMA24, EMA52, EMA104(EMA156仅显示) // ============================================================================ -// 四均线趋势排列检测 +// 三均线趋势排列检测 // ============================================================================ -// 多头排列:EMA1 > EMA2 > EMA3 > EMA4(从上到下依次排列) -// 空头排列:EMA1 < EMA2 < EMA3 < EMA4(从下到上依次排列) +// 多头排列:EMA1 > EMA2 > EMA3(从上到下依次排列) +// 空头排列:EMA1 < EMA2 < EMA3(从下到上依次排列) -is_bullish_aligned = ema24 > ema52 and ema52 > ema104 and ema104 > ema156 -is_bearish_aligned = ema24 < ema52 and ema52 < ema104 and ema104 < ema156 +is_bullish_aligned = ema24 > ema52 and ema52 > ema104 +is_bearish_aligned = ema24 < ema52 and ema52 < ema104 // 中枢震荡区间:均线未形成多头或空头排列(均线纠缠状态) is_in_oscillation = not is_bullish_aligned and not is_bearish_aligned -// EMA交叉检测(52和156) -ema_golden_cross = ta.crossover(ema52, ema156) // 金叉:EMA52上穿EMA156 -ema_death_cross = ta.crossunder(ema52, ema156) // 死叉:EMA52下穿EMA156 +// EMA交叉检测(52和104) +ema_golden_cross = ta.crossover(ema52, ema104) // 金叉:EMA52上穿EMA104 +ema_death_cross = ta.crossunder(ema52, ema104) // 死叉:EMA52下穿EMA104 // 绘制交叉点(中枢震荡区间不显示) -plotshape(show_ema52 and show_ema156 and ema_golden_cross and not is_in_oscillation, title="EMA金叉", +plotshape(show_ema52 and show_ema104 and ema_golden_cross and not is_in_oscillation, title="EMA金叉", style=shape.triangleup, location=location.belowbar, color=color.lime, size=size.small, text="金叉") -plotshape(show_ema52 and show_ema156 and ema_death_cross and not is_in_oscillation, title="EMA死叉", +plotshape(show_ema52 and show_ema104 and ema_death_cross and not is_in_oscillation, title="EMA死叉", style=shape.triangledown, location=location.abovebar, color=color.red, size=size.small, text="死叉") @@ -543,11 +544,15 @@ if barstate.islast bi_color = bi_dir == 1 ? color_bi_up : color_bi_down line.new(s_bar, s_price, e_bar, e_price, color=bi_color, width=2) - // ==================== 构建线段(特征序列法) ==================== - // 缠论线段规则: + // ==================== 构建线段(标准特征序列法) ==================== + // 缠论线段标准规则: // 1. 线段至少包含3笔 - // 2. 特征序列由反向笔组成 - // 3. 特征序列出现分型且确认,线段结束 + // 2. 特征序列由与线段方向相反的笔组成 + // 3. 特征序列要先做包含处理(与K线包含处理同理) + // 4. 包含处理后的特征序列出现分型,线段才结束 + // - 上升线段:特征序列(向下笔)出现顶分型 → 线段结束(第一种情况) + // - 下降线段:特征序列(向上笔)出现底分型 → 线段结束(第一种情况) + // 5. 第二种情况:特征序列无分型,但出现反向缺口(gap)确认反转 var int[] xd_start_bars = array.new() var float[] xd_start_prices = array.new() @@ -562,20 +567,31 @@ if barstate.islast array.clear(xd_dirs) if bi_count >= 3 - // 从分型列表构建线段更直接 - // 线段连接相邻的"线段端点分型" + // 特征序列存储(包含处理前的原始序列) + var float[] feat_raw_highs = array.new() + var float[] feat_raw_lows = array.new() + var int[] feat_raw_end_bars = array.new() + var float[] feat_raw_end_prices = array.new() + array.clear(feat_raw_highs) + array.clear(feat_raw_lows) + array.clear(feat_raw_end_bars) + array.clear(feat_raw_end_prices) - // 特征序列存储 - var float[] feat_highs = array.new() - var float[] feat_lows = array.new() - array.clear(feat_highs) - array.clear(feat_lows) + // 特征序列包含处理后的序列 + var float[] feat_merged_highs = array.new() + var float[] feat_merged_lows = array.new() + var int[] feat_merged_end_bars = array.new() + var float[] feat_merged_end_prices = array.new() + array.clear(feat_merged_highs) + array.clear(feat_merged_lows) + array.clear(feat_merged_end_bars) + array.clear(feat_merged_end_prices) // 线段状态 xd_start_bar = array.get(bi_start_bars, 0) xd_start_price = array.get(bi_start_prices, 0) - xd_dir = array.get(bi_dirs, 0) // 第一笔方向就是第一个线段方向 - xd_bi_count = 1 // 当前线段包含的笔数 + xd_dir = array.get(bi_dirs, 0) + xd_bi_count = 1 // 极值追踪 xd_high = math.max(array.get(bi_start_prices, 0), array.get(bi_end_prices, 0)) @@ -604,42 +620,108 @@ if barstate.islast xd_low := curr_bi_low xd_low_bar := curr_bi_dir == -1 ? curr_bi_end_bar : curr_bi_start_bar - // 反向笔加入特征序列 + // 反向笔加入特征序列(上升线段收集向下笔,下降线段收集向上笔) if curr_bi_dir != xd_dir - array.push(feat_highs, curr_bi_high) - array.push(feat_lows, curr_bi_low) + array.push(feat_raw_highs, curr_bi_high) + array.push(feat_raw_lows, curr_bi_low) + array.push(feat_raw_end_bars, curr_bi_end_bar) + array.push(feat_raw_end_prices, curr_bi_end_price) + + // === 特征序列包含处理 === + // 与K线包含处理原理相同: + // 上升线段的特征序列(向下笔):包含处理方向看前一个特征元素 + // 高点升→向上处理(取高高低低);低点降→向下处理(取低低高高) + fm_size = array.size(feat_merged_highs) + if fm_size == 0 + array.push(feat_merged_highs, curr_bi_high) + array.push(feat_merged_lows, curr_bi_low) + array.push(feat_merged_end_bars, curr_bi_end_bar) + array.push(feat_merged_end_prices, curr_bi_end_price) + else + fm_prev_h = array.get(feat_merged_highs, fm_size - 1) + fm_prev_l = array.get(feat_merged_lows, fm_size - 1) + + // 检查是否存在包含关系 + has_contain = (fm_prev_h >= curr_bi_high and fm_prev_l <= curr_bi_low) or (curr_bi_high >= fm_prev_h and curr_bi_low <= fm_prev_l) + + if has_contain + // 确定包含处理方向 + fm_trend = 0 + if fm_size >= 2 + fm_prev2_h = array.get(feat_merged_highs, fm_size - 2) + fm_prev2_l = array.get(feat_merged_lows, fm_size - 2) + fm_trend := fm_prev_h > fm_prev2_h ? 1 : (fm_prev_l < fm_prev2_l ? -1 : 0) + + // 向上处理:取高高低低;向下处理:取低低高高 + if fm_trend >= 0 + array.set(feat_merged_highs, fm_size - 1, math.max(fm_prev_h, curr_bi_high)) + array.set(feat_merged_lows, fm_size - 1, math.max(fm_prev_l, curr_bi_low)) + else + array.set(feat_merged_highs, fm_size - 1, math.min(fm_prev_h, curr_bi_high)) + array.set(feat_merged_lows, fm_size - 1, math.min(fm_prev_l, curr_bi_low)) + // 更新end信息为最新的笔 + array.set(feat_merged_end_bars, fm_size - 1, curr_bi_end_bar) + array.set(feat_merged_end_prices, fm_size - 1, curr_bi_end_price) + else + // 无包含关系,直接加入 + array.push(feat_merged_highs, curr_bi_high) + array.push(feat_merged_lows, curr_bi_low) + array.push(feat_merged_end_bars, curr_bi_end_bar) + array.push(feat_merged_end_prices, curr_bi_end_price) // 至少3笔后检查线段是否结束 + xd_broken = false + xd_break_bar = 0 + xd_break_price = 0.0 + if xd_bi_count >= 3 - feat_size = array.size(feat_highs) + fm_size = array.size(feat_merged_highs) - xd_broken = false - - if feat_size >= 2 - // 简化判断:反向笔突破前高/前低 + // === 第一种情况:特征序列包含处理后出现分型 === + if fm_size >= 3 + fm_h1 = array.get(feat_merged_highs, fm_size - 3) + fm_h2 = array.get(feat_merged_highs, fm_size - 2) + fm_h3 = array.get(feat_merged_highs, fm_size - 1) + fm_l1 = array.get(feat_merged_lows, fm_size - 3) + fm_l2 = array.get(feat_merged_lows, fm_size - 2) + fm_l3 = array.get(feat_merged_lows, fm_size - 1) + if xd_dir == 1 - // 上升线段:向下笔低点创新低 - if curr_bi_dir == -1 and curr_bi_end_price < xd_start_price + // 上升线段:特征序列(向下笔)出现顶分型 → 线段结束 + // 顶分型:中间元素高点最高 + if fm_h2 > fm_h1 and fm_h2 > fm_h3 xd_broken := true - // 或者连续两个特征序列元素低点下移 - if feat_size >= 2 - prev_feat_l = array.get(feat_lows, feat_size - 2) - curr_feat_l = array.get(feat_lows, feat_size - 1) - if curr_feat_l < prev_feat_l and curr_bi_end_price < array.get(bi_start_prices, i - 1) - xd_broken := true else - // 下降线段:向上笔高点创新高 - if curr_bi_dir == 1 and curr_bi_end_price > xd_start_price + // 下降线段:特征序列(向上笔)出现底分型 → 线段结束 + // 底分型:中间元素低点最低 + if fm_l2 < fm_l1 and fm_l2 < fm_l3 + xd_broken := true + + // === 第二种情况:特征序列不满足分型条件,但出现缺口确认反转 === + // 上升线段:如果某向下笔的低点直接跌破前一向上笔的高点(缺口),确认反转 + // 下降线段:如果某向上笔的高点直接突破前一向下笔的低点(缺口),确认反转 + if not xd_broken and fm_size >= 2 + if xd_dir == 1 + // 上升线段第二种情况:最近两个特征元素(向下笔), + // 后者低点 < 前者低点,且后者高点 < 前者低点(缺口) + fm_prev_h2 = array.get(feat_merged_highs, fm_size - 2) + fm_prev_l2 = array.get(feat_merged_lows, fm_size - 2) + fm_curr_h2 = array.get(feat_merged_highs, fm_size - 1) + fm_curr_l2 = array.get(feat_merged_lows, fm_size - 1) + if fm_curr_h2 < fm_prev_l2 + xd_broken := true + else + // 下降线段第二种情况: + // 后者高点 > 前者高点,且后者低点 > 前者高点(缺口) + fm_prev_h2 = array.get(feat_merged_highs, fm_size - 2) + fm_prev_l2 = array.get(feat_merged_lows, fm_size - 2) + fm_curr_h2 = array.get(feat_merged_highs, fm_size - 1) + fm_curr_l2 = array.get(feat_merged_lows, fm_size - 1) + if fm_curr_l2 > fm_prev_h2 xd_broken := true - // 或者连续两个特征序列元素高点上移 - if feat_size >= 2 - prev_feat_h = array.get(feat_highs, feat_size - 2) - curr_feat_h = array.get(feat_highs, feat_size - 1) - if curr_feat_h > prev_feat_h and curr_bi_end_price > array.get(bi_start_prices, i - 1) - xd_broken := true if xd_broken - // 保存线段 + // 保存线段:上升线段终点取最高点,下降线段终点取最低点 xd_end_bar = xd_dir == 1 ? xd_high_bar : xd_low_bar xd_end_price = xd_dir == 1 ? xd_high : xd_low @@ -649,7 +731,7 @@ if barstate.islast array.push(xd_end_prices, xd_end_price) array.push(xd_dirs, xd_dir) - // 新线段 + // 新线段开始 xd_start_bar := xd_end_bar xd_start_price := xd_end_price xd_dir := xd_dir == 1 ? -1 : 1 @@ -662,14 +744,19 @@ if barstate.islast xd_low_bar := curr_bi_dir == -1 ? curr_bi_end_bar : curr_bi_start_bar // 清空特征序列 - array.clear(feat_highs) - array.clear(feat_lows) + array.clear(feat_raw_highs) + array.clear(feat_raw_lows) + array.clear(feat_raw_end_bars) + array.clear(feat_raw_end_prices) + array.clear(feat_merged_highs) + array.clear(feat_merged_lows) + array.clear(feat_merged_end_bars) + array.clear(feat_merged_end_prices) i += 1 - // 最后一个线段:只有在特征序列有至少2个元素时才保存(说明有足够的反向笔) - // 未确认的线段不画出,避免误导 - feat_final_size = array.size(feat_highs) + // 最后一个未完成线段:特征序列包含处理后至少有2个元素才保存 + feat_final_size = array.size(feat_merged_highs) if xd_bi_count >= 3 and feat_final_size >= 2 xd_end_bar = xd_dir == 1 ? xd_high_bar : xd_low_bar xd_end_price = xd_dir == 1 ? xd_high : xd_low @@ -696,10 +783,12 @@ if barstate.islast xd_color = xd_dir_draw == 1 ? color.green : color.red line.new(s_bar, s_price, e_bar, e_price, color=xd_color, width=4) - // ==================== 构建笔中枢 ==================== - // 笔中枢必须在同一线段内,不能跨线段 - // 下跌线段中只有下跌笔中枢(上-下-上) - // 上涨线段中只有上涨笔中枢(下-上-下) + // ==================== 构建笔中枢(线段内部) ==================== + // 缠论标准:笔中枢必须在同一条线段内部构建 + // 中枢方向 = 所在线段的方向(上涨线段内 → 上涨中枢,下跌线段内 → 下跌中枢) + // 中枢由线段内任意连续3笔的价格重叠区间构成 + // ZG = min(3笔高点),ZD = max(3笔低点),ZG > ZD 则构成中枢 + // 中枢延伸:同一线段内后续笔与中枢[ZD, ZG]有重叠,则时间扩展,区间不变 var int[] bi_zs_start_bars = array.new() var int[] bi_zs_end_bars = array.new() var float[] bi_zs_highs = array.new() @@ -712,99 +801,75 @@ if barstate.islast array.clear(bi_zs_lows) array.clear(bi_zs_types) - // 遍历每个线段,在线段内寻找笔中枢 if xd_count > 0 and bi_count >= 3 + // 遍历每条线段,在线段内部寻找笔中枢 for xd_idx = 0 to xd_count - 1 xd_s_bar = array.get(xd_start_bars, xd_idx) xd_e_bar = array.get(xd_end_bars, xd_idx) xd_direction = array.get(xd_dirs, xd_idx) - // 找出该线段范围内的笔索引 - int[] xd_bi_indices = array.new() + // 收集该线段范围内的笔索引 + var int[] xd_bi_indices = array.new() + array.clear(xd_bi_indices) for bi_idx = 0 to bi_count - 1 - bi_s_bar = array.get(bi_start_bars, bi_idx) - bi_e_bar = array.get(bi_end_bars, bi_idx) - // 笔在线段范围内 - if bi_s_bar >= xd_s_bar and bi_e_bar <= xd_e_bar + bi_s = array.get(bi_start_bars, bi_idx) + bi_e = array.get(bi_end_bars, bi_idx) + // 笔的起点和终点都在线段范围内 + if bi_s >= xd_s_bar and bi_e <= xd_e_bar array.push(xd_bi_indices, bi_idx) - xd_bi_count_local = array.size(xd_bi_indices) + local_bi_count = array.size(xd_bi_indices) - // 在线段内寻找符合条件的三笔组合 - if xd_bi_count_local >= 3 + if local_bi_count >= 3 j = 0 - while j <= xd_bi_count_local - 3 + while j <= local_bi_count - 3 idx1 = array.get(xd_bi_indices, j) idx2 = array.get(xd_bi_indices, j + 1) idx3 = array.get(xd_bi_indices, j + 2) - bi1_dir = array.get(bi_dirs, idx1) - bi2_dir = array.get(bi_dirs, idx2) - bi3_dir = array.get(bi_dirs, idx3) + bi1_h = math.max(array.get(bi_start_prices, idx1), array.get(bi_end_prices, idx1)) + bi1_l = math.min(array.get(bi_start_prices, idx1), array.get(bi_end_prices, idx1)) + bi2_h = math.max(array.get(bi_start_prices, idx2), array.get(bi_end_prices, idx2)) + bi2_l = math.min(array.get(bi_start_prices, idx2), array.get(bi_end_prices, idx2)) + bi3_h = math.max(array.get(bi_start_prices, idx3), array.get(bi_end_prices, idx3)) + bi3_l = math.min(array.get(bi_start_prices, idx3), array.get(bi_end_prices, idx3)) - // 根据线段方向确定要找的中枢类型 - // 下跌线段(-1):找下跌笔中枢 (1, -1, 1) - // 上涨线段(1):找上涨笔中枢 (-1, 1, -1) - bool is_valid_zs = false - if xd_direction == -1 - // 下跌线段,找下跌笔中枢(以上升笔开始) - is_valid_zs := bi1_dir == 1 and bi2_dir == -1 and bi3_dir == 1 - else - // 上涨线段,找上涨笔中枢(以下降笔开始) - is_valid_zs := bi1_dir == -1 and bi2_dir == 1 and bi3_dir == -1 + zg = math.min(bi1_h, math.min(bi2_h, bi3_h)) + zd = math.max(bi1_l, math.max(bi2_l, bi3_l)) - if is_valid_zs - bi1_s_p = array.get(bi_start_prices, idx1) - bi1_e_p = array.get(bi_end_prices, idx1) - bi2_s_p = array.get(bi_start_prices, idx2) - bi2_e_p = array.get(bi_end_prices, idx2) - bi3_s_p = array.get(bi_start_prices, idx3) - bi3_e_p = array.get(bi_end_prices, idx3) + if zg > zd + zs_start = array.get(bi_start_bars, idx1) + zs_end = array.get(bi_end_bars, idx3) + zs_type = xd_direction // 中枢方向 = 线段方向 - bi1_h = math.max(bi1_s_p, bi1_e_p) - bi1_l = math.min(bi1_s_p, bi1_e_p) - bi2_h = math.max(bi2_s_p, bi2_e_p) - bi2_l = math.min(bi2_s_p, bi2_e_p) - bi3_h = math.max(bi3_s_p, bi3_e_p) - bi3_l = math.min(bi3_s_p, bi3_e_p) + // 中枢延伸:同一线段内后续笔与中枢有重叠 + last_ext = j + 2 + if j + 3 < local_bi_count + for ext_j = j + 3 to local_bi_count - 1 + ext_idx = array.get(xd_bi_indices, ext_j) + ext_h = math.max(array.get(bi_start_prices, ext_idx), array.get(bi_end_prices, ext_idx)) + ext_l = math.min(array.get(bi_start_prices, ext_idx), array.get(bi_end_prices, ext_idx)) + if ext_l < zg and ext_h > zd + zs_end := array.get(bi_end_bars, ext_idx) + last_ext := ext_j + else + break - zg = math.min(bi1_h, math.min(bi2_h, bi3_h)) - zd = math.max(bi1_l, math.max(bi2_l, bi3_l)) + // 避免与上一个中枢重叠 + should_add_zs = true + zs_arr_size = array.size(bi_zs_start_bars) + if zs_arr_size > 0 + last_zs_end = array.get(bi_zs_end_bars, zs_arr_size - 1) + if zs_start <= last_zs_end + should_add_zs := false - if zg > zd - zs_start = array.get(bi_start_bars, idx1) - zs_end = array.get(bi_end_bars, idx3) - zs_type = xd_direction // 中枢类型与线段方向一致 - - should_add_zs = true - zs_arr_size = array.size(bi_zs_start_bars) - - if zs_arr_size > 0 - last_zs_start = array.get(bi_zs_start_bars, zs_arr_size - 1) - last_zs_end = array.get(bi_zs_end_bars, zs_arr_size - 1) - last_zs_h = array.get(bi_zs_highs, zs_arr_size - 1) - last_zs_l = array.get(bi_zs_lows, zs_arr_size - 1) - - // 检查上一个中枢是否在同一线段内 - last_zs_in_same_xd = last_zs_start >= xd_s_bar and last_zs_end <= xd_e_bar - - if zs_start <= last_zs_end and last_zs_in_same_xd - should_add_zs := false - - // 中枢扩展:只有在同一线段内才能扩展 - if last_zs_in_same_xd and zg >= last_zs_l and zd <= last_zs_h and zs_start <= last_zs_end + 50 - array.set(bi_zs_end_bars, zs_arr_size - 1, zs_end) - should_add_zs := false - - if should_add_zs - array.push(bi_zs_start_bars, zs_start) - array.push(bi_zs_end_bars, zs_end) - array.push(bi_zs_highs, zg) - array.push(bi_zs_lows, zd) - array.push(bi_zs_types, zs_type) - j += 3 - else - j += 1 + if should_add_zs + array.push(bi_zs_start_bars, zs_start) + array.push(bi_zs_end_bars, zs_end) + array.push(bi_zs_highs, zg) + array.push(bi_zs_lows, zd) + array.push(bi_zs_types, zs_type) + j := last_ext + 1 else j += 1 else @@ -813,8 +878,10 @@ if barstate.islast bi_zs_count = array.size(bi_zs_start_bars) // ==================== 构建线段中枢 ==================== - // 跌出上一个中枢 → 新的下跌中枢以上涨线段开始 (1, -1, 1) - // 涨出上一个中枢 → 新的上涨中枢以下降线段开始 (-1, 1, -1) + // 上涨中枢:以下跌线段开始、下跌线段结束 (-1, 1, -1) + // 下跌中枢:以上涨线段开始、上涨线段结束 (1, -1, 1) + // ZG = min(各线段高点),ZD = max(各线段低点),ZG > ZD 则构成中枢 + // 中枢延伸:后续线段与中枢有重叠,则时间扩展,区间不变 var int[] xd_zs_start_bars = array.new() var int[] xd_zs_end_bars = array.new() var float[] xd_zs_highs = array.new() @@ -828,52 +895,19 @@ if barstate.islast array.clear(xd_zs_types) if xd_count >= 3 - // 跟踪上一个中枢信息 - last_zs_high = 0.0 - last_zs_low = 0.0 - last_zs_end_idx = -1 // 上一个中枢结束的线段索引 - i = 0 while i <= xd_count - 3 - // 获取三个线段的方向 + // 检查线段方向模式 xd1_dir = array.get(xd_dirs, i) xd2_dir = array.get(xd_dirs, i + 1) xd3_dir = array.get(xd_dirs, i + 2) - // 检查是否符合中枢形态 - // 下跌中枢:上升-下降-上升 (1, -1, 1) - 以上升线段开始和结束 - // 上涨中枢:下降-上升-下降 (-1, 1, -1) - 以下降线段开始和结束 - is_down_xd_zs = xd1_dir == 1 and xd2_dir == -1 and xd3_dir == 1 + // 上涨中枢: (-1, 1, -1) 下跌线段开始下跌线段结束 is_up_xd_zs = xd1_dir == -1 and xd2_dir == 1 and xd3_dir == -1 + // 下跌中枢: (1, -1, 1) 上涨线段开始上涨线段结束 + is_down_xd_zs = xd1_dir == 1 and xd2_dir == -1 and xd3_dir == 1 - // 检查是否符合离开方向的要求 - bool type_matches = false - if last_zs_end_idx < 0 - // 没有上一个中枢,任意类型都可以 - type_matches := is_down_xd_zs or is_up_xd_zs - else - // 检查从上一个中枢到当前位置之间,是否有线段离开了上一个中枢 - // 找到离开上一个中枢的方向 - leave_direction = 0 // 0=未离开, 1=向上离开, -1=向下离开 - for check_idx = last_zs_end_idx to i - if check_idx < xd_count - check_price = array.get(xd_end_prices, check_idx) - if check_price < last_zs_low and leave_direction != -1 - leave_direction := -1 // 向下离开 - else if check_price > last_zs_high and leave_direction != 1 - leave_direction := 1 // 向上离开 - - if leave_direction == -1 - // 向下离开,期望下跌中枢 - type_matches := is_down_xd_zs - else if leave_direction == 1 - // 向上离开,期望上涨中枢 - type_matches := is_up_xd_zs - else - // 未离开中枢,不形成新中枢 - type_matches := false - - if type_matches and (is_down_xd_zs or is_up_xd_zs) + if is_up_xd_zs or is_down_xd_zs xd1_s_p = array.get(xd_start_prices, i) xd1_e_p = array.get(xd_end_prices, i) xd2_s_p = array.get(xd_start_prices, i + 1) @@ -896,20 +930,26 @@ if barstate.islast zs_end_xd = array.get(xd_end_bars, i + 2) xd_zs_type = is_up_xd_zs ? 1 : -1 + // 中枢延伸:后续线段与中枢有重叠,则延伸时间范围 + last_ext_xd = i + 2 + if i + 3 < xd_count + for ext_idx = i + 3 to xd_count - 1 + ext_s_p = array.get(xd_start_prices, ext_idx) + ext_e_p = array.get(xd_end_prices, ext_idx) + ext_h = math.max(ext_s_p, ext_e_p) + ext_l = math.min(ext_s_p, ext_e_p) + if ext_l < zg_xd and ext_h > zd_xd + zs_end_xd := array.get(xd_end_bars, ext_idx) + last_ext_xd := ext_idx + else + break + should_add_xd_zs = true xd_zs_arr_size = array.size(xd_zs_start_bars) - if xd_zs_arr_size > 0 last_xd_zs_end = array.get(xd_zs_end_bars, xd_zs_arr_size - 1) - if zs_start_xd <= last_xd_zs_end should_add_xd_zs := false - - // 中枢扩展(同类型中枢且有重叠) - last_xd_zs_type = array.get(xd_zs_types, xd_zs_arr_size - 1) - if xd_zs_type == last_xd_zs_type and zg_xd >= last_zs_low and zd_xd <= last_zs_high and zs_start_xd <= last_xd_zs_end + 100 - array.set(xd_zs_end_bars, xd_zs_arr_size - 1, zs_end_xd) - should_add_xd_zs := false if should_add_xd_zs array.push(xd_zs_start_bars, zs_start_xd) @@ -917,13 +957,7 @@ if barstate.islast array.push(xd_zs_highs, zg_xd) array.push(xd_zs_lows, zd_xd) array.push(xd_zs_types, xd_zs_type) - - // 更新上一个中枢信息 - last_zs_high := zg_xd - last_zs_low := zd_xd - last_zs_end_idx := i + 2 - - i += 3 + i := last_ext_xd + 1 else i += 1 else @@ -934,7 +968,7 @@ if barstate.islast xd_zs_count = array.size(xd_zs_start_bars) // ==================== 绘制笔中枢 ==================== - // 上涨中枢用绿色,下跌中枢用红色 + // 上涨中枢(1)=绿色,下跌中枢(-1)=红色 if show_bi_zhongshu and bi_zs_count > 0 for i = 0 to bi_zs_count - 1 zs_start = array.get(bi_zs_start_bars, i) @@ -944,16 +978,13 @@ if barstate.islast zs_type = array.get(bi_zs_types, i) if bar_index - zs_start <= 5000 - // 上涨中枢(1)=绿色边框,下跌中枢(-1)=红色边框 zs_border = zs_type == 1 ? color.green : color.red zs_bg = zs_type == 1 ? color.new(color.green, 85) : color.new(color.red, 85) box.new(zs_start, zs_h, zs_end, zs_l, - border_color=zs_border, - bgcolor=zs_bg, - border_width=1) + border_color=zs_border, bgcolor=zs_bg, border_width=1) // ==================== 绘制线段中枢 ==================== - // 上涨中枢用绿色,下跌中枢用红色 + // 上涨中枢(1)=绿色,下跌中枢(-1)=红色 if show_xd_zhongshu and xd_zs_count > 0 for i = 0 to xd_zs_count - 1 zs_start = array.get(xd_zs_start_bars, i) @@ -963,189 +994,137 @@ if barstate.islast xd_zs_type = array.get(xd_zs_types, i) if bar_index - zs_start <= 5000 - // 上涨中枢(1)=绿色边框,下跌中枢(-1)=红色边框 xd_zs_border = xd_zs_type == 1 ? color.lime : color.maroon xd_zs_bg = xd_zs_type == 1 ? color.new(color.lime, 80) : color.new(color.maroon, 80) box.new(zs_start, zs_h, zs_end, zs_l, - border_color=xd_zs_border, - bgcolor=xd_zs_bg, - border_width=2) + border_color=xd_zs_border, bgcolor=xd_zs_bg, border_width=2) - // ==================== 买卖点(缠论标准三类买卖点) ==================== - // 第一类:趋势背驰点(离开中枢后与进入中枢前的走势发生MACD背驰) - // 第二类:第一类之后回调/反弹不破第一类 - // 第三类:离开中枢后回调/反弹不回到中枢内部 + // ==================== 买卖点(缠论标准三类买卖点 - 与笔中枢方向对应) ==================== + // 笔中枢方向 = 线段方向 + // 下跌中枢(-1):在中枢下方找一买(底背驰) + // 上涨中枢(1):在中枢上方找一卖(顶背驰) + // 三买三卖:离开中枢后回调/反弹不回到中枢内部 - // 存储已标记的买卖点,避免重复 var int[] bs_bars = array.new() - var int[] bs_types = array.new() // 正数=买点,负数=卖点,绝对值=类型 + var int[] bs_types = array.new() var float[] bs_prices = array.new() array.clear(bs_bars) array.clear(bs_types) array.clear(bs_prices) + max_zs_to_process = math.min(bi_zs_count, 15) + zs_start_idx = bi_zs_count - max_zs_to_process + if show_buy_sell and bi_zs_count > 0 and fx_count >= 2 - // 遍历笔中枢,寻找买卖点 - for j = 0 to bi_zs_count - 1 + for j = zs_start_idx to bi_zs_count - 1 zs_start = array.get(bi_zs_start_bars, j) zs_end = array.get(bi_zs_end_bars, j) zs_h = array.get(bi_zs_highs, j) zs_l = array.get(bi_zs_lows, j) zs_type = array.get(bi_zs_types, j) // 1=上涨中枢, -1=下跌中枢 - // ========== 寻找第一类买卖点(背驰)========== - // 找中枢前最后一个同向极值分型(作为背驰比较基准) - float prev_extreme_price = na - float prev_extreme_macd = na + // 找中枢前最后一个底分型(用于一买背驰比较) + float prev_bottom_price = na + float prev_bottom_macd = na - for i = 0 to fx_count - 1 + // 找中枢前最后一个顶分型(用于一卖背驰比较) + float prev_top_price = na + float prev_top_macd = na + + for i = fx_count - 1 to 0 fx_b = array.get(fx_bars, i) - fx_p = array.get(fx_prices, i) + if fx_b >= zs_start + continue fx_t = array.get(fx_types, i) + fx_p = array.get(fx_prices, i) fx_m = array.get(fx_macds, i) - if fx_b < zs_start - // 下跌中枢(需要一买):找之前的底分型 - if zs_type == -1 and fx_t == -1 - prev_extreme_price := fx_p - prev_extreme_macd := fx_m - // 上涨中枢(需要一卖):找之前的顶分型 - else if zs_type == 1 and fx_t == 1 - prev_extreme_price := fx_p - prev_extreme_macd := fx_m + if fx_t == -1 and na(prev_bottom_price) + prev_bottom_price := fx_p + prev_bottom_macd := fx_m + if fx_t == 1 and na(prev_top_price) + prev_top_price := fx_p + prev_top_macd := fx_m + if not na(prev_bottom_price) and not na(prev_top_price) + break - // 找中枢后的极值分型(离开中枢的点) + // 找中枢后的分型 float leave_price = na - float leave_macd = na int leave_bar = na - bool has_left_zs = false // 是否已经离开中枢 + bool has_left_zs = false + bool found_1st = false for i = 0 to fx_count - 1 + if found_1st and has_left_zs + break fx_bar = array.get(fx_bars, i) + if fx_bar <= zs_end + continue + fx_price = array.get(fx_prices, i) fx_type = array.get(fx_types, i) fx_macd = array.get(fx_macds, i) - - if fx_bar <= zs_end - continue - if bar_index - fx_bar > 5000 - continue - - // MACD过滤 macd_valid = not use_macd_filter or (fx_type == 1 and fx_macd > 0) or (fx_type == -1 and fx_macd < 0) - // ========== 第一类买点(一买)========== - // 下跌中枢 + 底分型跌破中枢下沿 + MACD背驰 - if zs_type == -1 and fx_type == -1 and fx_price < zs_l and macd_valid - // 背驰:价格更低,但MACD柱状图绝对值更小(负值更大) - is_divergence = not na(prev_extreme_macd) and not na(prev_extreme_price) and fx_price < prev_extreme_price and fx_macd > prev_extreme_macd - if is_divergence - already_marked = false - if array.size(bs_bars) > 0 - for k = 0 to array.size(bs_bars) - 1 - if array.get(bs_bars, k) == fx_bar - already_marked := true - break - if not already_marked - label.new(fx_bar, fx_price * 0.998, "1买", - color=color.lime, - style=label.style_label_up, - textcolor=color.black, - size=size.tiny) - array.push(bs_bars, fx_bar) - array.push(bs_types, 1) - array.push(bs_prices, fx_price) + // 一买:下跌中枢(-1)下方的底分型背驰 + if not found_1st and zs_type == -1 and fx_type == -1 and fx_price < zs_l and macd_valid + if not na(prev_bottom_macd) and not na(prev_bottom_price) and fx_price < prev_bottom_price and fx_macd > prev_bottom_macd + label.new(fx_bar, fx_price * 0.998, "1买", color=color.lime, style=label.style_label_up, textcolor=color.black, size=size.tiny) + array.push(bs_bars, fx_bar) + array.push(bs_types, 1) + array.push(bs_prices, fx_price) + found_1st := true - // ========== 第一类卖点(一卖)========== - // 上涨中枢 + 顶分型突破中枢上沿 + MACD背驰 - if zs_type == 1 and fx_type == 1 and fx_price > zs_h and macd_valid - // 背驰:价格更高,但MACD柱状图更小 - is_divergence = not na(prev_extreme_macd) and not na(prev_extreme_price) and fx_price > prev_extreme_price and fx_macd < prev_extreme_macd - if is_divergence - already_marked = false - if array.size(bs_bars) > 0 - for k = 0 to array.size(bs_bars) - 1 - if array.get(bs_bars, k) == fx_bar - already_marked := true - break - if not already_marked - label.new(fx_bar, fx_price * 1.002, "1卖", - color=color.red, - style=label.style_label_down, - textcolor=color.white, - size=size.tiny) - array.push(bs_bars, fx_bar) - array.push(bs_types, -1) - array.push(bs_prices, fx_price) + // 一卖:上涨中枢(1)上方的顶分型背驰 + if not found_1st and zs_type == 1 and fx_type == 1 and fx_price > zs_h and macd_valid + if not na(prev_top_macd) and not na(prev_top_price) and fx_price > prev_top_price and fx_macd < prev_top_macd + label.new(fx_bar, fx_price * 1.002, "1卖", color=color.red, style=label.style_label_down, textcolor=color.white, size=size.tiny) + array.push(bs_bars, fx_bar) + array.push(bs_types, -1) + array.push(bs_prices, fx_price) + found_1st := true - // 记录离开中枢的极值点(用于判断三买三卖) - if fx_type == 1 and fx_price > zs_h and not has_left_zs - // 顶分型突破中枢上沿 = 向上离开 - leave_price := fx_price - leave_bar := fx_bar - has_left_zs := true - if fx_type == -1 and fx_price < zs_l and not has_left_zs - // 底分型跌破中枢下沿 = 向下离开 - leave_price := fx_price - leave_bar := fx_bar - has_left_zs := true + // 记录离开中枢点 + if not has_left_zs + if zs_type == -1 and fx_type == 1 and fx_price > zs_h + // 下跌中枢向上离开 + leave_price := fx_price + leave_bar := fx_bar + has_left_zs := true + if zs_type == 1 and fx_type == -1 and fx_price < zs_l + // 上涨中枢向下离开 + leave_price := fx_price + leave_bar := fx_bar + has_left_zs := true - // ========== 第三类买卖点 ========== - // 需要先离开中枢,再回调/反弹不回到中枢 + // 三买三卖 if has_left_zs and not na(leave_bar) for i = 0 to fx_count - 1 fx_bar = array.get(fx_bars, i) + if fx_bar <= leave_bar + continue fx_price = array.get(fx_prices, i) fx_type = array.get(fx_types, i) fx_macd = array.get(fx_macds, i) - - if fx_bar <= leave_bar - continue - if bar_index - fx_bar > 5000 - continue - macd_valid = not use_macd_filter or (fx_type == 1 and fx_macd > 0) or (fx_type == -1 and fx_macd < 0) - // 三买:向上离开后,回调底分型不跌破中枢上沿 - if leave_price > zs_h and fx_type == -1 and fx_price > zs_h and macd_valid - already_marked = false - if array.size(bs_bars) > 0 - for k = 0 to array.size(bs_bars) - 1 - if array.get(bs_bars, k) == fx_bar - already_marked := true - break - if not already_marked - label.new(fx_bar, fx_price * 0.998, "3买", - color=color.new(color.lime, 30), - style=label.style_label_up, - textcolor=color.black, - size=size.tiny) - array.push(bs_bars, fx_bar) - array.push(bs_types, 3) - array.push(bs_prices, fx_price) - break // 只找第一个三买 + // 三买:下跌中枢向上离开后,回调底分型不跌破中枢上沿 + if zs_type == -1 and leave_price > zs_h and fx_type == -1 and fx_price > zs_h and macd_valid + label.new(fx_bar, fx_price * 0.998, "3买", color=color.new(color.lime, 30), style=label.style_label_up, textcolor=color.black, size=size.tiny) + array.push(bs_bars, fx_bar) + array.push(bs_types, 3) + array.push(bs_prices, fx_price) + break - // 三卖:向下离开后,反弹顶分型不涨过中枢下沿 - if leave_price < zs_l and fx_type == 1 and fx_price < zs_l and macd_valid - already_marked = false - if array.size(bs_bars) > 0 - for k = 0 to array.size(bs_bars) - 1 - if array.get(bs_bars, k) == fx_bar - already_marked := true - break - if not already_marked - label.new(fx_bar, fx_price * 1.002, "3卖", - color=color.new(color.red, 30), - style=label.style_label_down, - textcolor=color.white, - size=size.tiny) - array.push(bs_bars, fx_bar) - array.push(bs_types, -3) - array.push(bs_prices, fx_price) - break // 只找第一个三卖 + // 三卖:上涨中枢向下离开后,反弹顶分型不涨过中枢下沿 + if zs_type == 1 and leave_price < zs_l and fx_type == 1 and fx_price < zs_l and macd_valid + label.new(fx_bar, fx_price * 1.002, "3卖", color=color.new(color.red, 30), style=label.style_label_down, textcolor=color.white, size=size.tiny) + array.push(bs_bars, fx_bar) + array.push(bs_types, -3) + array.push(bs_prices, fx_price) + break - // ========== 第二类买卖点 ========== - // 遍历已标记的第一类买卖点,寻找第二类 + // 二买二卖 bs_count = array.size(bs_bars) if bs_count > 0 for k = 0 to bs_count - 1 @@ -1153,83 +1132,49 @@ if barstate.islast bs_type = array.get(bs_types, k) bs_price = array.get(bs_prices, k) - // 第二类买点:一买之后,反弹形成顶分型,再回落形成底分型,且不破一买低点 - if bs_type == 1 - // 找一买之后的顶分型(反弹高点) + if bs_type == 1 // 一买后找二买 int bounce_bar = na for i = 0 to fx_count - 1 fx_b = array.get(fx_bars, i) + if fx_b <= bs_bar + continue fx_t = array.get(fx_types, i) - if fx_b > bs_bar and fx_t == 1 + if na(bounce_bar) and fx_t == 1 bounce_bar := fx_b - break - - // 找反弹后的底分型(二买候选),且必须不破一买 - if not na(bounce_bar) - for i = 0 to fx_count - 1 - fx_b = array.get(fx_bars, i) + continue + if not na(bounce_bar) and fx_t == -1 fx_p = array.get(fx_prices, i) - fx_t = array.get(fx_types, i) - fx_m = array.get(fx_macds, i) - - if fx_b > bounce_bar and fx_t == -1 and fx_p > bs_price - already_marked = false - if array.size(bs_bars) > 0 - for m = 0 to array.size(bs_bars) - 1 - if array.get(bs_bars, m) == fx_b - already_marked := true - break - + if fx_p > bs_price + fx_m = array.get(fx_macds, i) macd_valid = not use_macd_filter or fx_m < 0 - if not already_marked and macd_valid and bar_index - fx_b <= 5000 - label.new(fx_b, fx_p * 0.998, "2买", - color=color.new(color.lime, 50), - style=label.style_label_up, - textcolor=color.black, - size=size.tiny) + if macd_valid + label.new(fx_b, fx_p * 0.998, "2买", color=color.new(color.lime, 50), style=label.style_label_up, textcolor=color.black, size=size.tiny) array.push(bs_bars, fx_b) array.push(bs_types, 2) array.push(bs_prices, fx_p) - break + break - // 第二类卖点:一卖之后,回调形成底分型,再上涨形成顶分型,且不过一卖高点 - if bs_type == -1 - // 找一卖之后的底分型(回调低点) + if bs_type == -1 // 一卖后找二卖 int pullback_bar = na for i = 0 to fx_count - 1 fx_b = array.get(fx_bars, i) + if fx_b <= bs_bar + continue fx_t = array.get(fx_types, i) - if fx_b > bs_bar and fx_t == -1 + if na(pullback_bar) and fx_t == -1 pullback_bar := fx_b - break - - // 找回调后的顶分型(二卖候选),且必须不过一卖 - if not na(pullback_bar) - for i = 0 to fx_count - 1 - fx_b = array.get(fx_bars, i) + continue + if not na(pullback_bar) and fx_t == 1 fx_p = array.get(fx_prices, i) - fx_t = array.get(fx_types, i) - fx_m = array.get(fx_macds, i) - - if fx_b > pullback_bar and fx_t == 1 and fx_p < bs_price - already_marked = false - if array.size(bs_bars) > 0 - for m = 0 to array.size(bs_bars) - 1 - if array.get(bs_bars, m) == fx_b - already_marked := true - break - + if fx_p < bs_price + fx_m = array.get(fx_macds, i) macd_valid = not use_macd_filter or fx_m > 0 - if not already_marked and macd_valid and bar_index - fx_b <= 5000 - label.new(fx_b, fx_p * 1.002, "2卖", - color=color.new(color.red, 50), - style=label.style_label_down, - textcolor=color.white, - size=size.tiny) + if macd_valid + label.new(fx_b, fx_p * 1.002, "2卖", color=color.new(color.red, 50), style=label.style_label_down, textcolor=color.white, size=size.tiny) array.push(bs_bars, fx_b) array.push(bs_types, -2) array.push(bs_prices, fx_p) - break + break // ==================== 信息面板 ==================== var table info_panel = table.new(position.top_right, 2, 8, diff --git a/ema26_ema52.pine b/ema26_ema52.pine new file mode 100644 index 0000000..31cbfd7 --- /dev/null +++ b/ema26_ema52.pine @@ -0,0 +1,36 @@ +//@version=6 +indicator("多周期 EMA", shorttitle="EMA", overlay=true) + +// 参数 +src = input.source(close, "价格源") +showEma26 = input.bool(true, "显示 EMA26?") +showEma52 = input.bool(true, "显示 EMA52?") +showEma104 = input.bool(false, "显示 EMA104?") +showEma156 = input.bool(false, "显示 EMA156?") +showEma204 = input.bool(false, "显示 EMA204?") +showDiff = input.bool(false, "显示 EMA26-EMA52 差值线?") +showCrossMarks = input.bool(false, "标记均线交叉(✕)") +// 主图 EMA +ema26 = ta.ema(src, 26) +ema52 = ta.ema(src, 52) +ema104 = ta.ema(src, 104) +ema156 = ta.ema(src, 156) +ema204 = ta.ema(src, 204) +diff = ema26 - ema52 + +plot(ema26, title="EMA 26", color=color.new(color.teal, 0), linewidth=2, display=showEma26 ? display.all : display.none) +plot(ema52, title="EMA 52", color=color.new(color.orange, 0), linewidth=2, display=showEma52 ? display.all : display.none) +plot(ema104, title="EMA 104", color=color.new(color.blue, 0), linewidth=2, display=showEma104 ? display.all : display.none) +plot(ema156, title="EMA 156", color=color.new(color.fuchsia, 0), linewidth=2, display=showEma156 ? display.all : display.none) +plot(ema204, title="EMA 204", color=color.new(color.green, 0), linewidth=2, display=showEma204 ? display.all : display.none) + +// 交叉信号(快线上穿/下穿慢线) +crossUp = ta.crossover(ema26, ema52) +crossDn = ta.crossunder(ema26, ema52) + +// 交叉点价格(取两条 EMA 的中点,视觉上就是交叉位置) +crossPrice = (ema26 + ema52) / 2.0 + +// 用 plotshape 标记交叉点(直接画在两条 EMA 的交叉处) +plotshape(showCrossMarks and crossUp ? crossPrice : na, title="EMA26 上穿 EMA52", style=shape.xcross, location=location.absolute, color=color.lime, size=size.small, text="", textcolor=color.lime) +plotshape(showCrossMarks and crossDn ? crossPrice : na, title="EMA26 下穿 EMA52", style=shape.xcross, location=location.absolute, color=color.red, size=size.small, text="", textcolor=color.red) diff --git a/ema26_ema52_diff.pine b/ema26_ema52_diff.pine new file mode 100644 index 0000000..26be3f0 --- /dev/null +++ b/ema26_ema52_diff.pine @@ -0,0 +1,39 @@ +//@version=6 +indicator("EMA26-EMA52 差值线", shorttitle="EMA26_52_DIFF", overlay=false, max_labels_count=500) + +// 参数 +src = input.source(close, "价格源") +lenFast = input.int(26, "快线 EMA 长度", minval=1) +lenSlow = input.int(52, "慢线 EMA 长度", minval=1) +showCrossMarks = input.bool(true, "标记均线交叉(✕)") + +// 计算 EMA 与差值 +emaFast = ta.ema(src, lenFast) +emaSlow = ta.ema(src, lenSlow) +diff = emaFast - emaSlow + +// 交叉信号(快线上穿/下穿慢线) +crossUp = ta.crossover(emaFast, emaSlow) +crossDn = ta.crossunder(emaFast, emaSlow) + +// 在差值线上标记交叉点(在本窗格显示) +if showCrossMarks and crossUp + label.new(bar_index, diff, "✕", xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_center, color=color.new(color.black, 100), textcolor=color.lime, size=size.small) +if showCrossMarks and crossDn + label.new(bar_index, diff, "✕", xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_center, color=color.new(color.black, 100), textcolor=color.red, size=size.small) + +// 零轴 +plot( + 0, + title = "零轴", + color = color.new(color.gray, 70), + linewidth = 1 +) + +// 差值线(类似 MACD 的 DIF 线) +plot( + diff, + title = "EMA26 - EMA52 差值", + color = diff >= 0 ? color.new(color.teal, 0) : color.new(color.red, 0), + linewidth = 2 +) \ No newline at end of file diff --git a/kdt_divergence.pine b/kdt_divergence.pine new file mode 100644 index 0000000..4da792b --- /dev/null +++ b/kdt_divergence.pine @@ -0,0 +1,542 @@ +//@version=6 +indicator( + "K线动能理论 - 背离识别" + ,shorttitle = "KDT_Divergence" + ,overlay = true + ,precision = 2 + ,max_lines_count = 500 + ,max_labels_count = 500 +) + +//============================================================================== +// 参数设置 +//============================================================================== + +// --- 均线参数 --- +group_ma = "均线参数" +ema_fast_len = input.int(24, "EMA快线周期", minval = 5, maxval = 200, group = group_ma) +ema_slow_len = input.int(52, "EMA慢线周期", minval = 10, maxval = 300, group = group_ma) + +// --- MACD 参数 --- +group_macd = "MACD参数" +fast_length = input.int(12, "MACD快线", minval = 1, maxval = 100, group = group_macd) +slow_length = input.int(26, "MACD慢线", minval = 1, maxval = 200, group = group_macd) +signal_length = input.int(9, "信号线", minval = 1, maxval = 100, group = group_macd) + +// --- 背离判定参数 --- +group_div = "背离参数" +near_zero_dea_th = input.float(0.10, "DEA零轴附近阈值", minval = 0.01, maxval = 1.00, step = 0.01, group = group_div, tooltip = "用于单位调整周期:|DEA| 小于该值视为贴近零轴") +near_zero_dif_th = input.float(0.05, "DIF零轴附近阈值", minval = 0.01, maxval = 1.00, step = 0.01, group = group_div, tooltip = "用于买卖点确认:|DIF| 小于该值视为归零轴") +min_hist_val = input.float(0.015, "量能堆最小柱高", minval = 0.001, maxval = 1.00, step = 0.001, group = group_div, tooltip = "绝对值小于该值的柱视为无效能量") +min_heap_bars = input.int(4, "量能堆最少柱数", minval = 3, maxval = 20, group = group_div) +max_heap_gap_bars = input.int(2, "连续跳空最大间隔K数", minval = 0, maxval = 20, group = group_div, tooltip = "后量能堆起始bar 与 前量能堆结束bar 的最大间隔,<= 该值视为连续跳空") +hidden_hist_th = input.float(0.01, "隐形背离柱高阈值", minval = 0.001, maxval = 0.1, step = 0.001, group = group_div, tooltip = "绝对值小于该值视为"无能量"") +div_ratio_th = input.float(0.65, "背离系数阈值", minval = 0.10, maxval = 1.00, step = 0.05, group = group_div, tooltip = "背离系数 = 当前量能堆高度 / 前一量能堆高度,小于该值才视为有效背离") +use_strict_filter = input.bool(true, "严格买卖条件(需EMA触碰+DIF归零)", group = group_div, tooltip = "勾选:买卖点必须满足EMA贴近+ DIFO 归零;取消勾选:只要有顶/底背离就给出买卖点") + +// --- 显示参数 --- +group_disp = "显示设置" +show_ema_fast = input.bool(true, "显示EMA快线", group = group_disp) +show_ema_slow = input.bool(true, "显示EMA慢线", group = group_disp) +show_div_labels = input.bool(true, "显示背离类型标注", group = group_disp) +show_bg_div_zones = input.bool(true, "高亮背离区间背景", group = group_disp) +show_buy_sell = input.bool(true, "显示买卖点信号", group = group_disp) +show_raw_div_mark = input.bool(false, "显示基础背驰信号(不要求EMA+DIF)", group = group_disp, tooltip = "用于排查:只要出现顶/底背离就打标,无需满足EMA贴近和DIF归零") +show_status_label = input.bool(true, "显示状态标签(用于排查脚本是否在运行)", group = group_disp, tooltip = "开启后在最新K线上方显示脚本状态/关键数值,确保“至少有东西显示”。可关闭以保持图表干净。") + +// --- 逻辑放宽(避免因“单位调整周期”无法识别而完全无信号) --- +group_relax = "背离识别放宽" +require_unit_cycle = input.bool(true, "周期内背离必须处于同一单位调整周期", group = group_relax, tooltip = "开启:只在同一单位调整周期内比较量能堆(更严格,可能无信号)。关闭:即使未识别单位调整周期,也允许量能堆间背离(更容易出信号)。") +require_dea_side = input.bool(true, "周期内顶/底背离需DEA同侧(顶>0/底<0)", group = group_relax, tooltip = "开启:顶背离要求DEA>0,底背离要求DEA<0;关闭:仅按量能堆方向判断。") + +//============================================================================== +// 基础指标:EMA 与 MACD +//============================================================================== + +ema_fast = ta.ema(close, ema_fast_len) +ema_slow = ta.ema(close, ema_slow_len) + +[dif, dea, macd_hist] = ta.macd(close, fast_length, slow_length, signal_length) + +is_dea_above_zero = dea > 0.0 // 上涨线段(黄线在零轴上方) +is_dea_below_zero = dea < 0.0 // 下跌线段(黄线在零轴下方) +is_dea_near_zero = math.abs(dea) < near_zero_dea_th +is_dif_near_zero = math.abs(dif) < near_zero_dif_th + +is_bull_hist = macd_hist > min_hist_val +is_bear_hist = macd_hist < -min_hist_val + +//============================================================================== +// 单位调整周期:DEA 从零轴附近出发 → 远离 → 再次回归零轴附近 +//============================================================================== + +var bool cycle_in_progress = false +var bool cycle_left_zero = false +var int cycle_id = 0 +var int cycle_start_bar = na +var float cycle_high = na +var float cycle_low = na +var float cycle_dea_max = na +var float cycle_dea_min = na +var int cycle_dir = 0 // 1 = 上涨线段内周期;-1 = 下跌线段内周期 + +// 保存最近一完整周期(用于周期间背离) +var float prev_cycle_price_ext = na +var float prev_cycle_dea_ext = na +var int prev_cycle_dir = 0 +var int prev_cycle_start_bar = na +var int prev_cycle_end_bar = na + +var float last_cycle_ratio = na // 最近一次周期间背离的背离系数(|dea2| / |dea1|) + +// 当前bar所在周期ID(未处于单位调整周期则为 na) +int cur_cycle_id = cycle_in_progress ? cycle_id : na + +// 周期间背离信号(在周期结束bar上触发) +bool inter_cycle_top_div = false +bool inter_cycle_bottom_div = false + +if not cycle_in_progress and is_dea_near_zero + // 启动新单位调整周期 + cycle_in_progress := true + cycle_left_zero := false + cycle_id += 1 + cycle_start_bar := bar_index + cycle_high := high + cycle_low := low + cycle_dea_max := dea + cycle_dea_min := dea + cycle_dir := dea >= 0.0 ? 1 : -1 +else if cycle_in_progress + // 周期运行中 + cycle_high := math.max(cycle_high, high) + cycle_low := math.min(cycle_low, low) + cycle_dea_max := math.max(cycle_dea_max, dea) + cycle_dea_min := math.min(cycle_dea_min, dea) + + if not is_dea_near_zero + cycle_left_zero := true + + // 已离开零轴后再次回归零轴附近 → 周期结束 + bool cycle_end_cond = cycle_left_zero and is_dea_near_zero and not is_dea_near_zero[1] + if cycle_end_cond + cycle_in_progress := false + + float cur_cycle_price_ext = cycle_dir == 1 ? cycle_high : cycle_low + float cur_cycle_dea_ext = cycle_dir == 1 ? cycle_dea_max : cycle_dea_min + int cur_cycle_start_bar = cycle_start_bar + int cur_cycle_end_bar = bar_index + + // 周期间背离:相邻两个同方向单位调整周期 + if prev_cycle_dir == cycle_dir and cur_cycle_dea_ext != 0.0 and prev_cycle_dea_ext != 0.0 + float cur_abs = math.abs(cur_cycle_dea_ext) + float prev_abs = math.abs(prev_cycle_dea_ext) + last_cycle_ratio := cur_abs / prev_abs + + if last_cycle_ratio < div_ratio_th + if cycle_dir == 1 and cur_cycle_price_ext > prev_cycle_price_ext and cur_cycle_dea_ext < prev_cycle_dea_ext + inter_cycle_top_div := true + if cycle_dir == -1 and cur_cycle_price_ext < prev_cycle_price_ext and cur_cycle_dea_ext > prev_cycle_dea_ext + inter_cycle_bottom_div := true + + // 更新“上一周期”记录 + prev_cycle_price_ext := cur_cycle_price_ext + prev_cycle_dea_ext := cur_cycle_dea_ext + prev_cycle_dir := cycle_dir + prev_cycle_start_bar := cur_cycle_start_bar + prev_cycle_end_bar := cur_cycle_end_bar + +//============================================================================== +// 量能堆:连续同向 MACD 柱(过滤掉绝对值过小的柱) +//============================================================================== + +var bool heap_active = false +var bool heap_is_bull = false +var int heap_start_bar = na +var int heap_end_bar = na +var int heap_bar_count = 0 +var float heap_peak = na +var float heap_price_extreme = na + +// 保存上一个有效量能堆(用于单位调整周期内背离) +var float prev_heap_peak = na +var float prev_heap_price_extreme = na +var int prev_heap_cycle_id = na +var int prev_heap_dir = 0 // 1=多头量能堆, -1=空头量能堆 +var int prev_heap_start_bar = na +var int prev_heap_end_bar = na + +// 背离强度:当前量能堆 / 上一量能堆 +var float last_heap_ratio = na +var float last_intra_ratio = na // 最近一次周期内背离的背离系数 + +// 周期内背离信号(在当前量能堆结束bar上触发) +bool intra_top_div_cont = false // 顶背离 - 连续跳空 +bool intra_top_div_discrete = false // 顶背离 - 分立跳空 +bool intra_bottom_div_cont = false // 底背离 - 连续跳空 +bool intra_bottom_div_disc = false // 底背离 - 分立跳空 + +// 量能堆推进逻辑 +bool start_new_bull_heap = is_bull_hist and (not heap_active or not heap_is_bull) +bool start_new_bear_heap = is_bear_hist and (not heap_active or heap_is_bull) +bool end_current_heap = heap_active and not (is_bull_hist or is_bear_hist) + +if start_new_bull_heap or start_new_bear_heap + // 结束旧堆并判定背离(如果上一堆存在) + if heap_active and heap_bar_count >= min_heap_bars and not na(heap_peak) + int cur_heap_dir = heap_is_bull ? 1 : -1 + int cur_heap_cid = cur_cycle_id + float cur_heap_peak = math.abs(heap_peak) + float cur_heap_price = heap_price_extreme + // 旧堆结束bar(旧堆的最后一根柱在当前bar的前一根) + heap_end_bar := bar_index - 1 + + // 周期内背离前提:同一单位调整周期内、同方向的连续量能堆 + // 这里要求: + // 1)当前量能堆与上一量能堆属于同一个单位调整周期(cur_heap_cid == prev_heap_cycle_id) + // 2)两个量能堆同方向(多头/空头一致) + // 3)上一堆和当前堆的“峰值能量”均为有效正数 + bool same_cycle = + (cur_heap_cid == prev_heap_cycle_id) or (na(cur_heap_cid) and na(prev_heap_cycle_id)) + bool cycle_ok = require_unit_cycle ? (same_cycle and not na(cur_heap_cid) and not na(prev_heap_cycle_id)) : same_cycle + bool dea_side_ok = + not require_dea_side ? true : + (cur_heap_dir == 1 ? dea > 0.0 : dea < 0.0) + + if cycle_ok and cur_heap_dir == prev_heap_dir and prev_heap_peak > 0 and cur_heap_peak > 0 and dea_side_ok + last_heap_ratio := cur_heap_peak / prev_heap_peak + + bool ratio_ok = last_heap_ratio < div_ratio_th + bool is_continuous = not na(prev_heap_end_bar) and heap_start_bar - prev_heap_end_bar <= max_heap_gap_bars + + if cur_heap_dir == 1 and cur_heap_price > prev_heap_price_extreme and ratio_ok + // 顶背离(上涨线段) + intra_top_div_cont := is_continuous + intra_top_div_discrete := not is_continuous + last_intra_ratio := last_heap_ratio + + if cur_heap_dir == -1 and cur_heap_price < prev_heap_price_extreme and ratio_ok + // 底背离(下跌线段) + intra_bottom_div_cont := is_continuous + intra_bottom_div_disc := not is_continuous + last_intra_ratio := last_heap_ratio + + // 更新上一堆信息 + prev_heap_peak := cur_heap_peak + prev_heap_price_extreme := cur_heap_price + prev_heap_cycle_id := cur_heap_cid + prev_heap_dir := cur_heap_dir + prev_heap_start_bar := heap_start_bar + prev_heap_end_bar := heap_end_bar + + // 开启新堆 + bool new_heap_is_bull = start_new_bull_heap + heap_active := true + heap_is_bull := new_heap_is_bull + heap_start_bar := bar_index + heap_end_bar := bar_index + heap_bar_count := 1 + heap_peak := math.abs(macd_hist) + // 修正:量能堆的价格极值应该与“量能堆方向”一致(多头看high,空头看low),不应使用DEA位置 + heap_price_extreme := new_heap_is_bull ? high : low + +else if heap_active and (is_bull_hist or is_bear_hist) and ((heap_is_bull and is_bull_hist) or (not heap_is_bull and is_bear_hist)) + // 量能堆内部推进 + heap_bar_count += 1 + heap_end_bar := bar_index + heap_peak := math.max(heap_peak, math.abs(macd_hist)) + heap_price_extreme := heap_is_bull ? math.max(heap_price_extreme, high) : math.min(heap_price_extreme, low) + +// 无效/反向柱导致当前堆结束,但此处不再新启一堆 +if end_current_heap + heap_active := false + +//============================================================================== +// 隐形背离:价格创新高/新低,但 MACD 柱几乎为 0(无能量) +//============================================================================== + +var float cycle_price_high = na +var float cycle_price_low = na + +if cycle_in_progress and (nz(cycle_dir) == 1) + cycle_price_high := math.max(nz(cycle_price_high, high), high) + cycle_price_low := nz(cycle_price_low, low) +else if cycle_in_progress and (nz(cycle_dir) == -1) + cycle_price_low := math.min(nz(cycle_price_low, low), low) + cycle_price_high := nz(cycle_price_high, high) +else if not cycle_in_progress + cycle_price_high := na + cycle_price_low := na + +bool hidden_top_div = false +bool hidden_bottom_div = false + +if cycle_in_progress and math.abs(macd_hist) < hidden_hist_th + if cycle_dir == 1 and not na(cycle_price_high) and high > cycle_price_high[1] + hidden_top_div := true + if cycle_dir == -1 and not na(cycle_price_low) and low < cycle_price_low[1] + hidden_bottom_div := true + +//============================================================================== +// 背离区间背景高亮(最近一次背离所覆盖的区间) +//============================================================================== + +var int top_div_zone_start_bar = na +var int top_div_zone_end_bar = na +var int bottom_div_zone_start_bar = na +var int bottom_div_zone_end_bar = na +var int hidden_div_zone_start_bar = na +var int hidden_div_zone_end_bar = na +var int inter_div_zone_start_bar = na +var int inter_div_zone_end_bar = na + +// 量能堆周期内顶/底背离区间:上一堆起点 → 当前堆终点 +if intra_top_div_cont or intra_top_div_discrete + top_div_zone_start_bar := prev_heap_start_bar + top_div_zone_end_bar := heap_end_bar + +if intra_bottom_div_cont or intra_bottom_div_disc + bottom_div_zone_start_bar := prev_heap_start_bar + bottom_div_zone_end_bar := heap_end_bar + +// 隐形背离区间:以当前bar为中心的极短区间 +if hidden_top_div or hidden_bottom_div + hidden_div_zone_start_bar := bar_index - 1 + hidden_div_zone_end_bar := bar_index + 1 + +// 周期间背离区间:前一周期起点 → 当前周期终点 +if inter_cycle_top_div or inter_cycle_bottom_div + inter_div_zone_start_bar := prev_cycle_start_bar + inter_div_zone_end_bar := bar_index + +bool in_top_div_zone = show_bg_div_zones and not na(top_div_zone_start_bar) and bar_index >= top_div_zone_start_bar and bar_index <= top_div_zone_end_bar +bool in_bottom_div_zone = show_bg_div_zones and not na(bottom_div_zone_start_bar) and bar_index >= bottom_div_zone_start_bar and bar_index <= bottom_div_zone_end_bar +bool in_hidden_div_zone = show_bg_div_zones and not na(hidden_div_zone_start_bar) and bar_index >= hidden_div_zone_start_bar and bar_index <= hidden_div_zone_end_bar +bool in_inter_div_zone = show_bg_div_zones and not na(inter_div_zone_start_bar) and bar_index >= inter_div_zone_start_bar and bar_index <= inter_div_zone_end_bar + +color bg_col = + in_top_div_zone ? color.new(color.red, 88) : + in_bottom_div_zone ? color.new(color.green, 88) : + in_inter_div_zone ? color.new(color.orange,88) : + in_hidden_div_zone ? color.new(color.purple,88) : + na + +bgcolor(bg_col) + +//============================================================================== +// 买卖点综合判定 +//============================================================================== + +bool has_bottom_div = intra_bottom_div_cont or intra_bottom_div_disc or inter_cycle_bottom_div or hidden_bottom_div +bool has_top_div = intra_top_div_cont or intra_top_div_discrete or inter_cycle_top_div or hidden_top_div + +// 价格触碰 EMA52 / EMA24 近似判定(放宽为输入参数,默认 1%) +float ema_touch_tol = 0.01 // 1% 价差容忍 + +bool touch_support = + (low <= ema_fast * (1 + ema_touch_tol) and low >= ema_fast * (1 - ema_touch_tol)) or + (low <= ema_slow * (1 + ema_touch_tol) and low >= ema_slow * (1 - ema_touch_tol)) + +bool touch_resist = + (high >= ema_fast * (1 - ema_touch_tol) and high <= ema_fast * (1 + ema_touch_tol)) or + (high >= ema_slow * (1 - ema_touch_tol) and high <= ema_slow * (1 + ema_touch_tol)) + +// 基础买/卖条件:默认只要出现顶/底背离即可(不再强制要求处于对应线段,避免漏标) +bool basic_buy_cond = has_bottom_div +bool basic_sell_cond = has_top_div + +// 严格买点:下跌线段 + 底背离 + 触碰EMA支撑 + DIF归零轴 +bool strict_buy_cond = + basic_buy_cond and + touch_support and + is_dif_near_zero + +// 严格卖点:上涨线段 + 顶背离 + 触碰EMA压力 + DIF归零轴 +bool strict_sell_cond = + basic_sell_cond and + touch_resist and + is_dif_near_zero + +// 最终买卖点:根据 use_strict_filter 决定是否启用严格过滤 +bool buy_signal = + show_buy_sell and + (use_strict_filter ? strict_buy_cond : basic_buy_cond) + +bool sell_signal = + show_buy_sell and + (use_strict_filter ? strict_sell_cond : basic_sell_cond) + +//============================================================================== +// 背离类型文本 & 背离系数 +//============================================================================== + +string heap_ratio_str = na(last_intra_ratio) ? "" : str.format("{0,number,0.00}", last_intra_ratio) +string cycle_ratio_str = na(last_cycle_ratio) ? "" : str.format("{0,number,0.00}", last_cycle_ratio) + +string intra_top_text = + intra_top_div_cont ? "连续跳空顶背离\nk=" + heap_ratio_str : + intra_top_div_discrete ? "分立跳空顶背离\nk=" + heap_ratio_str : + "" + +string intra_bottom_text = + intra_bottom_div_cont ? "连续跳空底背离\nk=" + heap_ratio_str : + intra_bottom_div_disc ? "分立跳空底背离\nk=" + heap_ratio_str : + "" + +string inter_top_text = + inter_cycle_top_div ? "周期间顶背离\nk=" + cycle_ratio_str : "" + +string inter_bottom_text = + inter_cycle_bottom_div ? "周期间底背离\nk=" + cycle_ratio_str : "" + +string hidden_top_text = + hidden_top_div ? "隐形顶背离" : "" + +string hidden_bottom_text = + hidden_bottom_div ? "隐形底背离" : "" + +//============================================================================== +// 绘图:均线、买卖点、背离标记 +//============================================================================== + +plot(show_ema_slow ? ema_slow : na, color = color.new(color.blue, 0), title = "EMA慢线", linewidth = 2) +plot(show_ema_fast ? ema_fast : na, color = color.new(color.orange, 0), title = "EMA快线", linewidth = 2) + +// 状态标签:确保脚本加载后“至少能看到一个东西”,同时便于判断为何没信号 +var label status_lbl = na +if barstate.islast + if show_status_label + if na(status_lbl) + status_lbl := label.new(bar_index, high, "") + label.set_xy(status_lbl, bar_index, high) + string s1 = "KDT_Divergence OK" + string s2 = "DEA=" + str.tostring(dea, format.mintick) + " DIF=" + str.tostring(dif, format.mintick) + string s3 = "near0(DEA)=" + str.tostring(is_dea_near_zero) + " near0(DIF)=" + str.tostring(is_dif_near_zero) + string s4 = "cycle=" + (cycle_in_progress ? str.tostring(cycle_id) : "na") + " heap=" + (heap_active ? "on" : "off") + label.set_text(status_lbl, s1 + "\n" + s2 + "\n" + s3 + "\n" + s4) + label.set_textcolor(status_lbl, color.white) + label.set_color(status_lbl, color.new(color.black, 0)) + label.set_style(status_lbl, label.style_label_down) + label.set_size(status_lbl, size.small) + else + if not na(status_lbl) + label.delete(status_lbl) + status_lbl := na + +// 买点、卖点 +plotshape( + buy_signal + ,title = "买点" + ,location = location.belowbar + ,color = color.new(color.green, 0) + ,style = shape.labelup + ,text = "买" + ,textcolor = color.white + ,size = size.normal +) + +plotshape( + sell_signal + ,title = "卖点" + ,location = location.abovebar + ,color = color.new(color.red, 0) + ,style = shape.labeldown + ,text = "卖" + ,textcolor = color.white + ,size = size.normal +) + +// 基础背驰信号(仅用于排查:不要求EMA触碰与DIF归零,也不要求DEA在线段内) +plotshape( + show_raw_div_mark and basic_buy_cond + ,title = "基础底背驰(无EMA+DIF过滤)" + ,location = location.belowbar + ,color = color.new(color.lime, 0) + ,style = shape.triangleup + ,text = "底背" + ,textcolor = color.white + ,size = size.tiny +) + +plotshape( + show_raw_div_mark and basic_sell_cond + ,title = "基础顶背驰(无EMA+DIF过滤)" + ,location = location.abovebar + ,color = color.new(color.maroon, 0) + ,style = shape.triangledown + ,text = "顶背" + ,textcolor = color.white + ,size = size.tiny +) + +// 背离类型标注(在图表上标出“连续跳空背离 / 分立跳空背离 / 周期间背离 / 隐形背离”等) +// 注意:plotshape 必须在全局作用域调用,使用 show_div_labels 作为条件的一部分 + +// 顶背离相关 +plotshape( + show_div_labels and (intra_top_div_cont or intra_top_div_discrete) + ,title = "周期内顶背离" + ,location = location.abovebar + ,color = color.new(color.red, 0) + ,style = shape.labeldown + ,text = "周期内顶背离" + ,textcolor = color.white + ,size = size.tiny +) + +plotshape( + show_div_labels and inter_cycle_top_div + ,title = "周期间顶背离" + ,location = location.abovebar + ,color = color.new(color.orange, 0) + ,style = shape.labeldown + ,text = "周期间顶背离" + ,textcolor = color.white + ,size = size.tiny +) + +plotshape( + show_div_labels and hidden_top_div + ,title = "隐形顶背离" + ,location = location.abovebar + ,color = color.new(color.purple, 0) + ,style = shape.triangledown + ,text = "隐形顶背离" + ,textcolor = color.white + ,size = size.tiny +) + +// 底背离相关 +plotshape( + show_div_labels and (intra_bottom_div_cont or intra_bottom_div_disc) + ,title = "周期内底背离" + ,location = location.belowbar + ,color = color.new(color.green, 0) + ,style = shape.labelup + ,text = "周期内底背离" + ,textcolor = color.white + ,size = size.tiny +) + +plotshape( + show_div_labels and inter_cycle_bottom_div + ,title = "周期间底背离" + ,location = location.belowbar + ,color = color.new(color.orange, 0) + ,style = shape.labelup + ,text = "周期间底背离" + ,textcolor = color.white + ,size = size.tiny +) + +plotshape( + show_div_labels and hidden_bottom_div + ,title = "隐形底背离" + ,location = location.belowbar + ,color = color.new(color.purple, 0) + ,style = shape.triangleup + ,text = "隐形底背离" + ,textcolor = color.white + ,size = size.tiny +) + diff --git a/last_divergence_simple.pine b/last_divergence_simple.pine new file mode 100644 index 0000000..3d80e5f --- /dev/null +++ b/last_divergence_simple.pine @@ -0,0 +1,145 @@ +//@version=6 +indicator("背了又背:最后一笔顶/底背离(单周期简化版)", shorttitle="LastDiv_Simple", overlay=true, max_labels_count=500) + +// 参数 +pivot_left = input.int(3, "左侧枢轴K数", minval=1) +pivot_right = input.int(3, "右侧枢轴K数", minval=1) +fastlen = input.int(12, "MACD快线") +slowlen = input.int(26, "MACD慢线") +siglen = input.int(9, "MACD信号线") +min_div_strength = input.float(0.6, "背离强度系数阈值", minval=0.1, maxval=1.0, step=0.05) +need_double_div = input.bool(true, "必须『背了又背』(同向至少两次背离)") + +// MACD +[dif, dea, hist] = ta.macd(close, fastlen, slowlen, siglen) + +// 枢轴 +ph_val = ta.pivothigh(high, pivot_left, pivot_right) +pl_val = ta.pivotlow(low, pivot_left, pivot_right) + +is_pivoth = not na(ph_val) +is_pivotl = not na(pl_val) + +ph_price = is_pivoth ? ph_val : na +pl_price = is_pivotl ? pl_val : na +ph_osc = is_pivoth ? hist[pivot_right] : na +pl_osc = is_pivotl ? hist[pivot_right] : na +ph_bar = is_pivoth ? bar_index[pivot_right] : na +pl_bar = is_pivotl ? bar_index[pivot_right] : na + +// 工具函数 +// 隐形背离定义: +// - 顶:价格创新高,但对应枢轴处没有正的能量柱(hist <= 0) +// - 底:价格创新低,但对应枢轴处没有负的能量柱(hist >= 0) +f_top_div(price1, price2, osc1, osc2) => + price2 > price1 and osc2 <= 0 + +f_bottom_div(price1, price2, osc1, osc2) => + price2 < price1 and osc2 >= 0 + +// 状态变量:记录连续同向背离 +var float last_ph_price = na +var float last_ph_osc = na +var int last_ph_bar = na + +var float last_pl_price = na +var float last_pl_osc = na +var int last_pl_bar = na + +var float prev_div_top_price = na +var float prev_div_top_osc = na +var int prev_div_top_bar = na + +var float last_div_top_price = na +var float last_div_top_osc = na +var int last_div_top_bar = na + +var float prev_div_bot_price = na +var float prev_div_bot_osc = na +var int prev_div_bot_bar = na + +var float last_div_bot_price = na +var float last_div_bot_osc = na +var int last_div_bot_bar = na + +var int top_div_count_seq = 0 +var int bot_div_count_seq = 0 + +new_top_div = false +new_bot_div = false + +// 顶背离(隐形) +if is_pivoth and not na(last_ph_price) + if f_top_div(last_ph_price, ph_price, last_ph_osc, ph_osc) + new_top_div := true + // 更新链 + prev_div_top_price := last_div_top_price + prev_div_top_osc := last_div_top_osc + prev_div_top_bar := last_div_top_bar + + last_div_top_price := ph_price + last_div_top_osc := ph_osc + last_div_top_bar := ph_bar + + top_div_count_seq += 1 + else + if ph_price > last_ph_price and ph_osc >= last_ph_osc + top_div_count_seq := 0 + +if is_pivoth + last_ph_price := ph_price + last_ph_osc := ph_osc + last_ph_bar := ph_bar + +// 底背离(隐形) +if is_pivotl and not na(last_pl_price) + if f_bottom_div(last_pl_price, pl_price, last_pl_osc, pl_osc) + new_bot_div := true + // 更新链 + prev_div_bot_price := last_div_bot_price + prev_div_bot_osc := last_div_bot_osc + prev_div_bot_bar := last_div_bot_bar + + last_div_bot_price := pl_price + last_div_bot_osc := pl_osc + last_div_bot_bar := pl_bar + + bot_div_count_seq += 1 + else + if pl_price < last_pl_price and pl_osc <= last_pl_osc + bot_div_count_seq := 0 + +if is_pivotl + last_pl_price := pl_price + last_pl_osc := pl_osc + last_pl_bar := pl_bar + +// 只在『最后一笔』背了又背时画信号 +need_count_top = need_double_div ? top_div_count_seq >= 2 : top_div_count_seq >= 1 +need_count_bot = need_double_div ? bot_div_count_seq >= 2 : bot_div_count_seq >= 1 + +final_top_div = new_top_div and need_count_top +final_bot_div = new_bot_div and need_count_bot + +plotshape( + final_bot_div + ,title="最后一笔底背离" + ,location=location.belowbar + ,style=shape.labelup + ,color=color.new(color.green, 0) + ,text="背了又背\n底终点" + ,textcolor=color.white + ,size=size.normal +) + +plotshape( + final_top_div + ,title="最后一笔顶背离" + ,location=location.abovebar + ,style=shape.labeldown + ,color=color.new(color.red, 0) + ,text="背了又背\n顶终点" + ,textcolor=color.white + ,size=size.normal +) +