Files
tradingview/kdt_divergence.pine
T
2026-03-24 16:36:47 +08:00

543 lines
24 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//@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
)