1263 lines
63 KiB
Plaintext
1263 lines
63 KiB
Plaintext
// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
|
||
// © 缠论完整实现 - Pine Script v6
|
||
|
||
//@version=6
|
||
indicator("缠论完整版", shorttitle="缠论", overlay=true, max_lines_count=500, max_labels_count=500, max_boxes_count=500, max_bars_back=5000)
|
||
|
||
// ============================================================================
|
||
// 参数设置
|
||
// ============================================================================
|
||
show_merged_k = input.bool(true, "显示处理后K线", group="显示设置")
|
||
show_fenxing = input.bool(true, "显示分型", group="显示设置")
|
||
show_bi = input.bool(true, "显示笔", group="显示设置")
|
||
show_xianduan = input.bool(true, "显示线段", group="显示设置")
|
||
show_bi_zhongshu = input.bool(true, "显示笔中枢", group="显示设置")
|
||
show_xd_zhongshu = input.bool(true, "显示线段中枢", group="显示设置")
|
||
show_buy_sell = input.bool(true, "显示买卖点", group="显示设置")
|
||
show_ema24 = input.bool(true, "显示EMA24", group="显示设置")
|
||
show_ema52 = input.bool(true, "显示EMA52", group="显示设置")
|
||
show_ema104 = input.bool(true, "显示EMA104", group="显示设置")
|
||
show_ema156 = input.bool(true, "显示EMA156", group="显示设置")
|
||
show_ema_trend = input.bool(true, "显示均线趋势", group="显示设置")
|
||
show_tp_signals = input.bool(true, "显示止盈信号", group="显示设置")
|
||
|
||
min_bi_bars = input.int(4, "笔最小K线数", minval=3, maxval=10, group="缠论参数")
|
||
use_macd_filter = input.bool(false, "MACD过滤分型", tooltip="顶分型要求MACD>0,底分型要求MACD<0", group="缠论参数")
|
||
|
||
// EMA参数设置
|
||
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")
|
||
trend_confirm_bars = input.int(3, "趋势确认K线数", minval=1, maxval=20, group="均线参数", tooltip="排列持续N根K线才确认趋势,增大可减少震荡假信号")
|
||
|
||
color_bi_up = input.color(color.red, "上升笔颜色", group="颜色设置")
|
||
color_bi_down = input.color(color.green, "下降笔颜色", group="颜色设置")
|
||
color_xd = input.color(color.blue, "线段颜色", group="颜色设置")
|
||
color_bi_zs = input.color(color.new(color.yellow, 80), "笔中枢颜色", group="颜色设置")
|
||
color_xd_zs = input.color(color.new(color.orange, 70), "线段中枢颜色", group="颜色设置")
|
||
color_ema24 = input.color(color.yellow, "EMA24颜色", group="颜色设置")
|
||
color_ema52 = input.color(color.orange, "EMA52颜色", group="颜色设置")
|
||
color_ema104 = input.color(color.blue, "EMA104颜色", group="颜色设置")
|
||
color_ema156 = input.color(color.purple, "EMA156颜色", group="颜色设置")
|
||
|
||
// ============================================================================
|
||
// MACD计算
|
||
// ============================================================================
|
||
[macd_line, signal_line, macd_hist] = ta.macd(close, 12, 26, 9)
|
||
|
||
// ============================================================================
|
||
// EMA均线计算
|
||
// ============================================================================
|
||
ema24 = ta.ema(close, ema1_len)
|
||
ema52 = ta.ema(close, ema2_len)
|
||
ema104 = ta.ema(close, ema3_len)
|
||
ema156 = ta.ema(close, ema4_len)
|
||
|
||
// 绘制EMA均线
|
||
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)
|
||
|
||
// ============================================================================
|
||
// 四均线趋势排列检测
|
||
// ============================================================================
|
||
// 多头排列:EMA1 > EMA2 > EMA3 > EMA4(从上到下依次排列)
|
||
// 空头排列:EMA1 < EMA2 < EMA3 < EMA4(从下到上依次排列)
|
||
|
||
is_bullish_aligned = ema24 > ema52 and ema52 > ema104 and ema104 > ema156
|
||
is_bearish_aligned = ema24 < ema52 and ema52 < ema104 and ema104 < ema156
|
||
|
||
// 中枢震荡区间:均线未形成多头或空头排列(均线纠缠状态)
|
||
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
|
||
|
||
// 绘制交叉点(中枢震荡区间不显示)
|
||
plotshape(show_ema52 and show_ema156 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死叉",
|
||
style=shape.triangledown, location=location.abovebar,
|
||
color=color.red, size=size.small, text="死叉")
|
||
|
||
// 计算排列持续的K线数
|
||
var int bullish_bars = 0
|
||
var int bearish_bars = 0
|
||
|
||
if is_bullish_aligned
|
||
bullish_bars := bullish_bars + 1
|
||
else
|
||
bullish_bars := 0
|
||
|
||
if is_bearish_aligned
|
||
bearish_bars := bearish_bars + 1
|
||
else
|
||
bearish_bars := 0
|
||
|
||
// 确认的多头/空头排列(持续N根K线后确认)
|
||
is_bullish_confirmed = bullish_bars >= trend_confirm_bars
|
||
is_bearish_confirmed = bearish_bars >= trend_confirm_bars
|
||
|
||
was_bullish_confirmed = bullish_bars[1] >= trend_confirm_bars
|
||
was_bearish_confirmed = bearish_bars[1] >= trend_confirm_bars
|
||
|
||
// 多头趋势起始:刚好达到确认K线数
|
||
bullish_trend_start = bullish_bars == trend_confirm_bars
|
||
// 多头趋势结束:从已确认变为未确认
|
||
bullish_trend_end = not is_bullish_confirmed and was_bullish_confirmed
|
||
|
||
// 空头趋势起始:刚好达到确认K线数
|
||
bearish_trend_start = bearish_bars == trend_confirm_bars
|
||
// 空头趋势结束:从已确认变为未确认
|
||
bearish_trend_end = not is_bearish_confirmed and was_bearish_confirmed
|
||
|
||
// 绘制趋势起始和结束点(中枢震荡区间不显示)
|
||
plotshape(show_ema_trend and bullish_trend_start and not is_in_oscillation, title="多头趋势开始",
|
||
style=shape.diamond, location=location.belowbar,
|
||
color=color.lime, size=size.normal, text="多头↑")
|
||
plotshape(show_ema_trend and bullish_trend_end and not is_in_oscillation, title="多头趋势结束",
|
||
style=shape.xcross, location=location.abovebar,
|
||
color=color.orange, size=size.small, text="多头结束")
|
||
|
||
plotshape(show_ema_trend and bearish_trend_start and not is_in_oscillation, title="空头趋势开始",
|
||
style=shape.diamond, location=location.abovebar,
|
||
color=color.red, size=size.normal, text="空头↓")
|
||
plotshape(show_ema_trend and bearish_trend_end and not is_in_oscillation, title="空头趋势结束",
|
||
style=shape.xcross, location=location.belowbar,
|
||
color=color.orange, size=size.small, text="空头结束")
|
||
|
||
// 背景色标识当前趋势状态(只有确认后才显示,中枢震荡区间不显示)
|
||
bgcolor(show_ema_trend and is_bullish_confirmed and not is_in_oscillation ? color.new(color.lime, 90) : na, title="多头背景")
|
||
bgcolor(show_ema_trend and is_bearish_confirmed and not is_in_oscillation ? color.new(color.red, 90) : na, title="空头背景")
|
||
|
||
// ============================================================================
|
||
// 交叉 + 排列 组合信号
|
||
// ============================================================================
|
||
// 记录最近的金叉/死叉位置
|
||
var int last_golden_cross_bar = na
|
||
var int last_death_cross_bar = na
|
||
|
||
if ema_golden_cross
|
||
last_golden_cross_bar := bar_index
|
||
if ema_death_cross
|
||
last_death_cross_bar := bar_index
|
||
|
||
// 计算距离上次交叉的K线数
|
||
bars_since_golden = na(last_golden_cross_bar) ? 9999 : bar_index - last_golden_cross_bar
|
||
bars_since_death = na(last_death_cross_bar) ? 9999 : bar_index - last_death_cross_bar
|
||
|
||
// 组合信号1:金叉后N根K线内出现多头排列确认 = 强势做多
|
||
// 组合信号2:死叉后N根K线内出现空头排列确认 = 强势做空
|
||
combo_lookback = 30 // 交叉后30根K线内排列确认有效
|
||
|
||
// 强势做多:金叉后多头排列刚确认(且金叉在死叉之后)
|
||
strong_long_signal = bullish_trend_start and bars_since_golden <= combo_lookback and bars_since_golden < bars_since_death
|
||
// 强势做空:死叉后空头排列刚确认(且死叉在金叉之后)
|
||
strong_short_signal = bearish_trend_start and bars_since_death <= combo_lookback and bars_since_death < bars_since_golden
|
||
|
||
// 组合信号3:已处于多头排列中出现金叉 = 趋势加速
|
||
// 组合信号4:已处于空头排列中出现死叉 = 趋势加速
|
||
trend_accelerate_long = ema_golden_cross and is_bullish_confirmed
|
||
trend_accelerate_short = ema_death_cross and is_bearish_confirmed
|
||
|
||
// 组合信号5:已处于多头排列中出现死叉 = 趋势可能反转预警
|
||
// 组合信号6:已处于空头排列中出现金叉 = 趋势可能反转预警
|
||
trend_reverse_warning_long = ema_death_cross and is_bullish_confirmed
|
||
trend_reverse_warning_short = ema_golden_cross and is_bearish_confirmed
|
||
|
||
// 绘制组合信号(中枢震荡区间不显示)
|
||
plotshape(show_ema_trend and strong_long_signal and not is_in_oscillation, title="强势做多",
|
||
style=shape.labelup, location=location.belowbar,
|
||
color=color.new(color.lime, 0), size=size.normal, text="强多", textcolor=color.white)
|
||
plotshape(show_ema_trend and strong_short_signal and not is_in_oscillation, title="强势做空",
|
||
style=shape.labeldown, location=location.abovebar,
|
||
color=color.new(color.red, 0), size=size.normal, text="强空", textcolor=color.white)
|
||
|
||
plotshape(show_ema_trend and trend_accelerate_long and not is_in_oscillation, title="多头加速",
|
||
style=shape.triangleup, location=location.belowbar,
|
||
color=color.aqua, size=size.small, text="加速↑")
|
||
plotshape(show_ema_trend and trend_accelerate_short and not is_in_oscillation, title="空头加速",
|
||
style=shape.triangledown, location=location.abovebar,
|
||
color=color.fuchsia, size=size.small, text="加速↓")
|
||
|
||
plotshape(show_ema_trend and trend_reverse_warning_long and not is_in_oscillation, title="多头预警",
|
||
style=shape.flag, location=location.abovebar,
|
||
color=color.yellow, size=size.small, text="预警")
|
||
plotshape(show_ema_trend and trend_reverse_warning_short and not is_in_oscillation, title="空头预警",
|
||
style=shape.flag, location=location.belowbar,
|
||
color=color.yellow, size=size.small, text="预警")
|
||
|
||
// ============================================================================
|
||
// MACD背驰止盈系统
|
||
// ============================================================================
|
||
// 原理:趋势运行中,通过MACD能量柱(柱状图)的峰值变化检测动量衰减
|
||
// 以MACD零轴穿越作为"波段"分界,比较相邻两波的MACD峰值与对应价格极值:
|
||
// 背驰止盈:价格创新高/低,但MACD能量柱峰值未能同步创新高/低(经典顶/底背驰)
|
||
// 衰竭止盈:价格未能创新高/低,且MACD动量同步减弱(力量衰竭,趋势将尽)
|
||
|
||
// MACD零轴穿越检测
|
||
macd_cross_below_zero = macd_hist[1] > 0 and macd_hist <= 0 // MACD能量柱由正转负(一波上涨动能结束)
|
||
macd_cross_above_zero = macd_hist[1] < 0 and macd_hist >= 0 // MACD能量柱由负转正(一波下跌动能结束)
|
||
|
||
// --- 多头趋势止盈追踪变量 ---
|
||
var float bull_prev_wave_price = na // 上一波价格高点
|
||
var float bull_prev_wave_macd = na // 上一波MACD正柱峰值
|
||
var float bull_curr_wave_price = na // 当前波价格高点
|
||
var float bull_curr_wave_macd = 0.0 // 当前波MACD正柱峰值
|
||
var int bull_wave_count = 0 // 已完成的MACD正柱波段计数
|
||
|
||
// --- 空头趋势止盈追踪变量 ---
|
||
var float bear_prev_wave_price = na // 上一波价格低点
|
||
var float bear_prev_wave_macd = na // 上一波MACD负柱谷值
|
||
var float bear_curr_wave_price = na // 当前波价格低点
|
||
var float bear_curr_wave_macd = 0.0 // 当前波MACD负柱谷值
|
||
var int bear_wave_count = 0 // 已完成的MACD负柱波段计数
|
||
|
||
// 多头趋势开始:重置所有追踪变量
|
||
if bullish_trend_start
|
||
bull_prev_wave_price := na
|
||
bull_prev_wave_macd := na
|
||
bull_curr_wave_price := high
|
||
bull_curr_wave_macd := math.max(macd_hist, 0)
|
||
bull_wave_count := 0
|
||
|
||
// 空头趋势开始:重置所有追踪变量
|
||
if bearish_trend_start
|
||
bear_prev_wave_price := na
|
||
bear_prev_wave_macd := na
|
||
bear_curr_wave_price := low
|
||
bear_curr_wave_macd := math.min(macd_hist, 0)
|
||
bear_wave_count := 0
|
||
|
||
// === 多头趋势中的止盈检测 ===
|
||
// 在确认的多头趋势中持续追踪MACD能量柱波段
|
||
bool tp_long_divergence = false // 顶背驰止盈信号
|
||
bool tp_long_exhaustion = false // 多头力量衰竭止盈信号
|
||
|
||
if is_bullish_confirmed
|
||
// 持续追踪当前波段的价格高点
|
||
bull_curr_wave_price := math.max(nz(bull_curr_wave_price, high), high)
|
||
// 持续追踪当前波段的MACD正柱峰值(只在MACD>0时更新)
|
||
if macd_hist > 0
|
||
bull_curr_wave_macd := math.max(nz(bull_curr_wave_macd), macd_hist)
|
||
|
||
// MACD能量柱由正转负:一波上涨动能结束,检查背驰
|
||
if macd_cross_below_zero and nz(bull_curr_wave_macd) > 0
|
||
bull_wave_count += 1
|
||
|
||
// 至少第二波才能与前一波比较
|
||
if bull_wave_count >= 2 and not na(bull_prev_wave_macd) and bull_prev_wave_macd > 0
|
||
// 顶背驰:价格创新高(或持平),但MACD能量柱峰值更低
|
||
if bull_curr_wave_price >= bull_prev_wave_price and bull_curr_wave_macd < bull_prev_wave_macd
|
||
tp_long_divergence := true
|
||
// 力量衰竭:价格未创新高,MACD能量柱峰值也更低
|
||
else if bull_curr_wave_price < bull_prev_wave_price and bull_curr_wave_macd < bull_prev_wave_macd
|
||
tp_long_exhaustion := true
|
||
|
||
// 保存当前波为前一波参考
|
||
bull_prev_wave_price := bull_curr_wave_price
|
||
bull_prev_wave_macd := bull_curr_wave_macd
|
||
// 重置当前波段追踪
|
||
bull_curr_wave_price := high
|
||
bull_curr_wave_macd := 0.0
|
||
|
||
// === 空头趋势中的止盈检测 ===
|
||
// 在确认的空头趋势中持续追踪MACD能量柱波段
|
||
bool tp_short_divergence = false // 底背驰止盈信号
|
||
bool tp_short_exhaustion = false // 空头力量衰竭止盈信号
|
||
|
||
if is_bearish_confirmed
|
||
// 持续追踪当前波段的价格低点
|
||
bear_curr_wave_price := math.min(nz(bear_curr_wave_price, low), low)
|
||
// 持续追踪当前波段的MACD负柱谷值(只在MACD<0时更新)
|
||
if macd_hist < 0
|
||
bear_curr_wave_macd := math.min(nz(bear_curr_wave_macd), macd_hist)
|
||
|
||
// MACD能量柱由负转正:一波下跌动能结束,检查背驰
|
||
if macd_cross_above_zero and nz(bear_curr_wave_macd) < 0
|
||
bear_wave_count += 1
|
||
|
||
// 至少第二波才能与前一波比较
|
||
if bear_wave_count >= 2 and not na(bear_prev_wave_macd) and bear_prev_wave_macd < 0
|
||
// 底背驰:价格创新低(或持平),但MACD能量柱谷值绝对值更小(负值回升)
|
||
if bear_curr_wave_price <= bear_prev_wave_price and bear_curr_wave_macd > bear_prev_wave_macd
|
||
tp_short_divergence := true
|
||
// 力量衰竭:价格未创新低,MACD能量柱谷值也回升
|
||
else if bear_curr_wave_price > bear_prev_wave_price and bear_curr_wave_macd > bear_prev_wave_macd
|
||
tp_short_exhaustion := true
|
||
|
||
// 保存当前波为前一波参考
|
||
bear_prev_wave_price := bear_curr_wave_price
|
||
bear_prev_wave_macd := bear_curr_wave_macd
|
||
// 重置当前波段追踪
|
||
bear_curr_wave_price := low
|
||
bear_curr_wave_macd := 0.0
|
||
|
||
// 绘制止盈信号
|
||
// 多头背驰止盈(品红色标签,显眼,标在K线上方)
|
||
plotshape(show_tp_signals and show_ema_trend and tp_long_divergence, title="多头背驰止盈",
|
||
style=shape.labeldown, location=location.abovebar,
|
||
color=color.new(color.fuchsia, 0), size=size.normal, text="背驰止盈", textcolor=color.white)
|
||
// 多头衰竭止盈(橙色标签,标在K线上方)
|
||
plotshape(show_tp_signals and show_ema_trend and tp_long_exhaustion, title="多头衰竭止盈",
|
||
style=shape.labeldown, location=location.abovebar,
|
||
color=color.new(color.orange, 0), size=size.small, text="衰竭止盈", textcolor=color.white)
|
||
// 空头背驰止盈(品红色标签,显眼,标在K线下方)
|
||
plotshape(show_tp_signals and show_ema_trend and tp_short_divergence, title="空头背驰止盈",
|
||
style=shape.labelup, location=location.belowbar,
|
||
color=color.new(color.fuchsia, 0), size=size.normal, text="背驰止盈", textcolor=color.white)
|
||
// 空头衰竭止盈(橙色标签,标在K线下方)
|
||
plotshape(show_tp_signals and show_ema_trend and tp_short_exhaustion, title="空头衰竭止盈",
|
||
style=shape.labelup, location=location.belowbar,
|
||
color=color.new(color.orange, 0), size=size.small, text="衰竭止盈", textcolor=color.white)
|
||
|
||
// ============================================================================
|
||
// K线包含处理(缠论标准)
|
||
// ============================================================================
|
||
// 存储处理后的K线
|
||
var float[] mk_high = array.new<float>()
|
||
var float[] mk_low = array.new<float>()
|
||
var int[] mk_idx = array.new<int>() // 合并K线的结束bar_index
|
||
var int[] mk_start_idx = array.new<int>() // 合并K线的起始bar_index
|
||
var float[] mk_macd = array.new<float>() // 每根合并K线对应的MACD值
|
||
|
||
// 当前趋势方向(用于包含处理)
|
||
var int mk_trend = 0 // 1=向上,-1=向下
|
||
|
||
// 检测包含关系:两根K线互相包含
|
||
is_contain(h1, l1, h2, l2) =>
|
||
(h1 >= h2 and l1 <= l2) or (h2 >= h1 and l2 <= l1)
|
||
|
||
// K线包含处理逻辑
|
||
mk_size = array.size(mk_high)
|
||
|
||
if mk_size == 0
|
||
// 第一根K线
|
||
array.push(mk_high, high)
|
||
array.push(mk_low, low)
|
||
array.push(mk_idx, bar_index)
|
||
array.push(mk_start_idx, bar_index)
|
||
array.push(mk_macd, macd_hist)
|
||
else
|
||
prev_h = array.get(mk_high, mk_size - 1)
|
||
prev_l = array.get(mk_low, mk_size - 1)
|
||
prev_start = array.get(mk_start_idx, mk_size - 1)
|
||
|
||
if is_contain(prev_h, prev_l, high, low)
|
||
// 有包含关系,需要合并
|
||
// 确定处理方向
|
||
if mk_size >= 2
|
||
prev2_h = array.get(mk_high, mk_size - 2)
|
||
prev2_l = array.get(mk_low, mk_size - 2)
|
||
mk_trend := prev_h > prev2_h ? 1 : (prev_l < prev2_l ? -1 : mk_trend)
|
||
|
||
// 根据方向合并:向上取高高低低,向下取低高低低
|
||
new_h = mk_trend >= 0 ? math.max(prev_h, high) : math.min(prev_h, high)
|
||
new_l = mk_trend >= 0 ? math.max(prev_l, low) : math.min(prev_l, low)
|
||
|
||
array.set(mk_high, mk_size - 1, new_h)
|
||
array.set(mk_low, mk_size - 1, new_l)
|
||
array.set(mk_idx, mk_size - 1, bar_index)
|
||
array.set(mk_macd, mk_size - 1, macd_hist) // 更新MACD为最新值
|
||
// mk_start_idx 保持不变(保留合并起始位置)
|
||
else
|
||
// 无包含关系
|
||
// 更新趋势方向
|
||
mk_trend := high > prev_h ? 1 : -1
|
||
|
||
// 添加新K线
|
||
array.push(mk_high, high)
|
||
array.push(mk_low, low)
|
||
array.push(mk_idx, bar_index)
|
||
array.push(mk_start_idx, bar_index)
|
||
array.push(mk_macd, macd_hist)
|
||
|
||
// 限制大小
|
||
if array.size(mk_high) > 5000
|
||
array.shift(mk_high)
|
||
array.shift(mk_low)
|
||
array.shift(mk_idx)
|
||
array.shift(mk_start_idx)
|
||
array.shift(mk_macd)
|
||
|
||
// ============================================================================
|
||
// 基于处理后K线的分型识别
|
||
// ============================================================================
|
||
var int[] fx_bars = array.new<int>()
|
||
var float[] fx_prices = array.new<float>()
|
||
var int[] fx_types = array.new<int>() // 1=顶, -1=底
|
||
var float[] fx_macds = array.new<float>() // 分型对应的MACD值
|
||
|
||
// 分型识别(使用处理后的K线)
|
||
curr_mk_size = array.size(mk_high)
|
||
|
||
if curr_mk_size >= 3
|
||
// 获取最近三根处理后的K线
|
||
h1 = array.get(mk_high, curr_mk_size - 3)
|
||
h2 = array.get(mk_high, curr_mk_size - 2)
|
||
h3 = array.get(mk_high, curr_mk_size - 1)
|
||
l1 = array.get(mk_low, curr_mk_size - 3)
|
||
l2 = array.get(mk_low, curr_mk_size - 2)
|
||
l3 = array.get(mk_low, curr_mk_size - 1)
|
||
idx2 = array.get(mk_idx, curr_mk_size - 2)
|
||
|
||
// 顶分型:中间K线高点最高
|
||
is_top = h2 > h1 and h2 > h3
|
||
// 底分型:中间K线低点最低
|
||
is_bottom = l2 < l1 and l2 < l3
|
||
|
||
// MACD过滤:顶分型要求MACD>0,底分型要求MACD<0
|
||
macd_at_fx = array.get(mk_macd, curr_mk_size - 2) // 中间K线的MACD值
|
||
macd_ok = not use_macd_filter or (is_top and macd_at_fx > 0) or (is_bottom and macd_at_fx < 0)
|
||
|
||
if (is_top or is_bottom) and macd_ok
|
||
fx_arr_size = array.size(fx_bars)
|
||
fx_type = is_top ? 1 : -1
|
||
fx_price = is_top ? h2 : l2
|
||
should_add = true
|
||
|
||
if fx_arr_size > 0
|
||
last_type = array.get(fx_types, fx_arr_size - 1)
|
||
last_bar = array.get(fx_bars, fx_arr_size - 1)
|
||
last_price = array.get(fx_prices, fx_arr_size - 1)
|
||
|
||
if last_type == fx_type
|
||
// 同类型:保留更极端的(且必须满足MACD条件)
|
||
if is_top and fx_price > last_price and (not use_macd_filter or macd_at_fx > 0)
|
||
array.set(fx_bars, fx_arr_size - 1, idx2)
|
||
array.set(fx_prices, fx_arr_size - 1, fx_price)
|
||
array.set(fx_macds, fx_arr_size - 1, macd_at_fx)
|
||
else if is_bottom and fx_price < last_price and (not use_macd_filter or macd_at_fx < 0)
|
||
array.set(fx_bars, fx_arr_size - 1, idx2)
|
||
array.set(fx_prices, fx_arr_size - 1, fx_price)
|
||
array.set(fx_macds, fx_arr_size - 1, macd_at_fx)
|
||
should_add := false
|
||
else
|
||
// 不同类型:检查距离
|
||
if idx2 - last_bar < min_bi_bars
|
||
should_add := false
|
||
// 检查笔的有效性
|
||
if fx_type == 1 and fx_price <= last_price
|
||
should_add := false
|
||
if fx_type == -1 and fx_price >= last_price
|
||
should_add := false
|
||
|
||
if should_add
|
||
array.push(fx_bars, idx2)
|
||
array.push(fx_prices, fx_price)
|
||
array.push(fx_types, fx_type)
|
||
array.push(fx_macds, macd_at_fx)
|
||
|
||
if array.size(fx_bars) > 2000
|
||
array.shift(fx_bars)
|
||
array.shift(fx_prices)
|
||
array.shift(fx_types)
|
||
array.shift(fx_macds)
|
||
|
||
// ============================================================================
|
||
// 绘制
|
||
// ============================================================================
|
||
if barstate.islast
|
||
fx_count = array.size(fx_bars)
|
||
merged_k_count = array.size(mk_high)
|
||
|
||
// ==================== 绘制处理后的K线 ====================
|
||
if show_merged_k and merged_k_count > 0
|
||
for i = 0 to merged_k_count - 1
|
||
m_start = array.get(mk_start_idx, i)
|
||
m_end = array.get(mk_idx, i)
|
||
m_h = array.get(mk_high, i)
|
||
m_l = array.get(mk_low, i)
|
||
|
||
if bar_index - m_end <= 5000
|
||
// 用矩形显示合并后的K线范围
|
||
box.new(m_start, m_h, m_end, m_l, border_color=color.purple, bgcolor=color.new(color.purple, 90), border_width=1)
|
||
|
||
// ==================== 绘制分型 ====================
|
||
if show_fenxing and fx_count > 0
|
||
for i = 0 to fx_count - 1
|
||
fx_bar = array.get(fx_bars, i)
|
||
fx_price = array.get(fx_prices, i)
|
||
fx_type = array.get(fx_types, i)
|
||
|
||
if bar_index - fx_bar <= 5000
|
||
if fx_type == 1
|
||
label.new(fx_bar, fx_price, "顶",
|
||
color=color.red,
|
||
style=label.style_label_down,
|
||
textcolor=color.white,
|
||
size=size.tiny)
|
||
else
|
||
label.new(fx_bar, fx_price, "底",
|
||
color=color.green,
|
||
style=label.style_label_up,
|
||
textcolor=color.white,
|
||
size=size.tiny)
|
||
|
||
// ==================== 构建笔 ====================
|
||
var int[] bi_start_bars = array.new<int>()
|
||
var float[] bi_start_prices = array.new<float>()
|
||
var int[] bi_end_bars = array.new<int>()
|
||
var float[] bi_end_prices = array.new<float>()
|
||
var int[] bi_dirs = array.new<int>()
|
||
|
||
array.clear(bi_start_bars)
|
||
array.clear(bi_start_prices)
|
||
array.clear(bi_end_bars)
|
||
array.clear(bi_end_prices)
|
||
array.clear(bi_dirs)
|
||
|
||
if fx_count >= 2
|
||
for i = 0 to fx_count - 2
|
||
fx1_bar = array.get(fx_bars, i)
|
||
fx1_price = array.get(fx_prices, i)
|
||
fx1_type = array.get(fx_types, i)
|
||
fx2_bar = array.get(fx_bars, i + 1)
|
||
fx2_price = array.get(fx_prices, i + 1)
|
||
fx2_type = array.get(fx_types, i + 1)
|
||
|
||
if fx1_type != fx2_type
|
||
bi_dir = fx1_type == -1 ? 1 : -1
|
||
array.push(bi_start_bars, fx1_bar)
|
||
array.push(bi_start_prices, fx1_price)
|
||
array.push(bi_end_bars, fx2_bar)
|
||
array.push(bi_end_prices, fx2_price)
|
||
array.push(bi_dirs, bi_dir)
|
||
|
||
bi_count = array.size(bi_start_bars)
|
||
|
||
// ==================== 绘制笔 ====================
|
||
if show_bi and bi_count > 0
|
||
for i = 0 to bi_count - 1
|
||
s_bar = array.get(bi_start_bars, i)
|
||
s_price = array.get(bi_start_prices, i)
|
||
e_bar = array.get(bi_end_bars, i)
|
||
e_price = array.get(bi_end_prices, i)
|
||
bi_dir = array.get(bi_dirs, i)
|
||
|
||
if bar_index - s_bar <= 5000
|
||
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. 特征序列出现分型且确认,线段结束
|
||
|
||
var int[] xd_start_bars = array.new<int>()
|
||
var float[] xd_start_prices = array.new<float>()
|
||
var int[] xd_end_bars = array.new<int>()
|
||
var float[] xd_end_prices = array.new<float>()
|
||
var int[] xd_dirs = array.new<int>()
|
||
|
||
array.clear(xd_start_bars)
|
||
array.clear(xd_start_prices)
|
||
array.clear(xd_end_bars)
|
||
array.clear(xd_end_prices)
|
||
array.clear(xd_dirs)
|
||
|
||
if bi_count >= 3
|
||
// 从分型列表构建线段更直接
|
||
// 线段连接相邻的"线段端点分型"
|
||
|
||
// 特征序列存储
|
||
var float[] feat_highs = array.new<float>()
|
||
var float[] feat_lows = array.new<float>()
|
||
array.clear(feat_highs)
|
||
array.clear(feat_lows)
|
||
|
||
// 线段状态
|
||
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_high = math.max(array.get(bi_start_prices, 0), array.get(bi_end_prices, 0))
|
||
xd_low = math.min(array.get(bi_start_prices, 0), array.get(bi_end_prices, 0))
|
||
xd_high_bar = xd_dir == 1 ? array.get(bi_end_bars, 0) : array.get(bi_start_bars, 0)
|
||
xd_low_bar = xd_dir == 1 ? array.get(bi_start_bars, 0) : array.get(bi_end_bars, 0)
|
||
|
||
i = 1
|
||
while i < bi_count
|
||
curr_bi_dir = array.get(bi_dirs, i)
|
||
curr_bi_start_bar = array.get(bi_start_bars, i)
|
||
curr_bi_start_price = array.get(bi_start_prices, i)
|
||
curr_bi_end_bar = array.get(bi_end_bars, i)
|
||
curr_bi_end_price = array.get(bi_end_prices, i)
|
||
|
||
curr_bi_high = math.max(curr_bi_start_price, curr_bi_end_price)
|
||
curr_bi_low = math.min(curr_bi_start_price, curr_bi_end_price)
|
||
|
||
xd_bi_count += 1
|
||
|
||
// 更新线段极值
|
||
if curr_bi_high > xd_high
|
||
xd_high := curr_bi_high
|
||
xd_high_bar := curr_bi_dir == 1 ? curr_bi_end_bar : curr_bi_start_bar
|
||
if curr_bi_low < xd_low
|
||
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)
|
||
|
||
// 至少3笔后检查线段是否结束
|
||
if xd_bi_count >= 3
|
||
feat_size = array.size(feat_highs)
|
||
|
||
xd_broken = false
|
||
|
||
if feat_size >= 2
|
||
// 简化判断:反向笔突破前高/前低
|
||
if xd_dir == 1
|
||
// 上升线段:向下笔低点创新低
|
||
if curr_bi_dir == -1 and curr_bi_end_price < xd_start_price
|
||
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
|
||
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
|
||
|
||
array.push(xd_start_bars, xd_start_bar)
|
||
array.push(xd_start_prices, xd_start_price)
|
||
array.push(xd_end_bars, xd_end_bar)
|
||
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
|
||
xd_bi_count := 1
|
||
|
||
// 重置极值
|
||
xd_high := curr_bi_high
|
||
xd_low := curr_bi_low
|
||
xd_high_bar := curr_bi_dir == 1 ? curr_bi_end_bar : curr_bi_start_bar
|
||
xd_low_bar := curr_bi_dir == -1 ? curr_bi_end_bar : curr_bi_start_bar
|
||
|
||
// 清空特征序列
|
||
array.clear(feat_highs)
|
||
array.clear(feat_lows)
|
||
|
||
i += 1
|
||
|
||
// 最后一个线段:只有在特征序列有至少2个元素时才保存(说明有足够的反向笔)
|
||
// 未确认的线段不画出,避免误导
|
||
feat_final_size = array.size(feat_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
|
||
|
||
array.push(xd_start_bars, xd_start_bar)
|
||
array.push(xd_start_prices, xd_start_price)
|
||
array.push(xd_end_bars, xd_end_bar)
|
||
array.push(xd_end_prices, xd_end_price)
|
||
array.push(xd_dirs, xd_dir)
|
||
|
||
xd_count = array.size(xd_start_bars)
|
||
|
||
// ==================== 绘制线段 ====================
|
||
if show_xianduan and xd_count > 0
|
||
for i = 0 to xd_count - 1
|
||
s_bar = array.get(xd_start_bars, i)
|
||
s_price = array.get(xd_start_prices, i)
|
||
e_bar = array.get(xd_end_bars, i)
|
||
e_price = array.get(xd_end_prices, i)
|
||
xd_dir_draw = array.get(xd_dirs, i)
|
||
|
||
if bar_index - s_bar <= 5000
|
||
// 上涨线段(1)=绿色,下跌线段(-1)=红色
|
||
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)
|
||
|
||
// ==================== 构建笔中枢 ====================
|
||
// 笔中枢必须在同一线段内,不能跨线段
|
||
// 下跌线段中只有下跌笔中枢(上-下-上)
|
||
// 上涨线段中只有上涨笔中枢(下-上-下)
|
||
var int[] bi_zs_start_bars = array.new<int>()
|
||
var int[] bi_zs_end_bars = array.new<int>()
|
||
var float[] bi_zs_highs = array.new<float>()
|
||
var float[] bi_zs_lows = array.new<float>()
|
||
var int[] bi_zs_types = array.new<int>() // 1=上涨中枢, -1=下跌中枢
|
||
|
||
array.clear(bi_zs_start_bars)
|
||
array.clear(bi_zs_end_bars)
|
||
array.clear(bi_zs_highs)
|
||
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<int>()
|
||
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
|
||
array.push(xd_bi_indices, bi_idx)
|
||
|
||
xd_bi_count_local = array.size(xd_bi_indices)
|
||
|
||
// 在线段内寻找符合条件的三笔组合
|
||
if xd_bi_count_local >= 3
|
||
j = 0
|
||
while j <= xd_bi_count_local - 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)
|
||
|
||
// 根据线段方向确定要找的中枢类型
|
||
// 下跌线段(-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
|
||
|
||
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)
|
||
|
||
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)
|
||
|
||
zg = math.min(bi1_h, math.min(bi2_h, bi3_h))
|
||
zd = math.max(bi1_l, math.max(bi2_l, bi3_l))
|
||
|
||
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
|
||
else
|
||
j += 1
|
||
else
|
||
j += 1
|
||
|
||
bi_zs_count = array.size(bi_zs_start_bars)
|
||
|
||
// ==================== 构建线段中枢 ====================
|
||
// 跌出上一个中枢 → 新的下跌中枢以上涨线段开始 (1, -1, 1)
|
||
// 涨出上一个中枢 → 新的上涨中枢以下降线段开始 (-1, 1, -1)
|
||
var int[] xd_zs_start_bars = array.new<int>()
|
||
var int[] xd_zs_end_bars = array.new<int>()
|
||
var float[] xd_zs_highs = array.new<float>()
|
||
var float[] xd_zs_lows = array.new<float>()
|
||
var int[] xd_zs_types = array.new<int>() // 1=上涨中枢, -1=下跌中枢
|
||
|
||
array.clear(xd_zs_start_bars)
|
||
array.clear(xd_zs_end_bars)
|
||
array.clear(xd_zs_highs)
|
||
array.clear(xd_zs_lows)
|
||
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
|
||
is_up_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)
|
||
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)
|
||
xd2_e_p = array.get(xd_end_prices, i + 1)
|
||
xd3_s_p = array.get(xd_start_prices, i + 2)
|
||
xd3_e_p = array.get(xd_end_prices, i + 2)
|
||
|
||
xd1_h = math.max(xd1_s_p, xd1_e_p)
|
||
xd1_l = math.min(xd1_s_p, xd1_e_p)
|
||
xd2_h = math.max(xd2_s_p, xd2_e_p)
|
||
xd2_l = math.min(xd2_s_p, xd2_e_p)
|
||
xd3_h = math.max(xd3_s_p, xd3_e_p)
|
||
xd3_l = math.min(xd3_s_p, xd3_e_p)
|
||
|
||
zg_xd = math.min(xd1_h, math.min(xd2_h, xd3_h))
|
||
zd_xd = math.max(xd1_l, math.max(xd2_l, xd3_l))
|
||
|
||
if zg_xd > zd_xd
|
||
zs_start_xd = array.get(xd_start_bars, i)
|
||
zs_end_xd = array.get(xd_end_bars, i + 2)
|
||
xd_zs_type = is_up_xd_zs ? 1 : -1
|
||
|
||
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)
|
||
array.push(xd_zs_end_bars, zs_end_xd)
|
||
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
|
||
else
|
||
i += 1
|
||
else
|
||
i += 1
|
||
else
|
||
i += 1
|
||
|
||
xd_zs_count = array.size(xd_zs_start_bars)
|
||
|
||
// ==================== 绘制笔中枢 ====================
|
||
// 上涨中枢用绿色,下跌中枢用红色
|
||
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)
|
||
zs_end = array.get(bi_zs_end_bars, i)
|
||
zs_h = array.get(bi_zs_highs, i)
|
||
zs_l = array.get(bi_zs_lows, i)
|
||
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)
|
||
|
||
// ==================== 绘制线段中枢 ====================
|
||
// 上涨中枢用绿色,下跌中枢用红色
|
||
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)
|
||
zs_end = array.get(xd_zs_end_bars, i)
|
||
zs_h = array.get(xd_zs_highs, i)
|
||
zs_l = array.get(xd_zs_lows, i)
|
||
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)
|
||
|
||
// ==================== 买卖点(缠论标准三类买卖点) ====================
|
||
// 第一类:趋势背驰点(离开中枢后与进入中枢前的走势发生MACD背驰)
|
||
// 第二类:第一类之后回调/反弹不破第一类
|
||
// 第三类:离开中枢后回调/反弹不回到中枢内部
|
||
|
||
// 存储已标记的买卖点,避免重复
|
||
var int[] bs_bars = array.new<int>()
|
||
var int[] bs_types = array.new<int>() // 正数=买点,负数=卖点,绝对值=类型
|
||
var float[] bs_prices = array.new<float>()
|
||
|
||
array.clear(bs_bars)
|
||
array.clear(bs_types)
|
||
array.clear(bs_prices)
|
||
|
||
if show_buy_sell and bi_zs_count > 0 and fx_count >= 2
|
||
// 遍历笔中枢,寻找买卖点
|
||
for j = 0 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
|
||
|
||
for i = 0 to fx_count - 1
|
||
fx_b = array.get(fx_bars, i)
|
||
fx_p = array.get(fx_prices, i)
|
||
fx_t = array.get(fx_types, 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
|
||
|
||
// 找中枢后的极值分型(离开中枢的点)
|
||
float leave_price = na
|
||
float leave_macd = na
|
||
int leave_bar = na
|
||
bool has_left_zs = false // 是否已经离开中枢
|
||
|
||
for i = 0 to fx_count - 1
|
||
fx_bar = array.get(fx_bars, i)
|
||
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)
|
||
|
||
// ========== 第一类卖点(一卖)==========
|
||
// 上涨中枢 + 顶分型突破中枢上沿 + 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)
|
||
|
||
// 记录离开中枢的极值点(用于判断三买三卖)
|
||
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 has_left_zs and not na(leave_bar)
|
||
for i = 0 to fx_count - 1
|
||
fx_bar = array.get(fx_bars, i)
|
||
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 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 // 只找第一个三卖
|
||
|
||
// ========== 第二类买卖点 ==========
|
||
// 遍历已标记的第一类买卖点,寻找第二类
|
||
bs_count = array.size(bs_bars)
|
||
if bs_count > 0
|
||
for k = 0 to bs_count - 1
|
||
bs_bar = array.get(bs_bars, k)
|
||
bs_type = array.get(bs_types, k)
|
||
bs_price = array.get(bs_prices, k)
|
||
|
||
// 第二类买点:一买之后,反弹形成顶分型,再回落形成底分型,且不破一买低点
|
||
if bs_type == 1
|
||
// 找一买之后的顶分型(反弹高点)
|
||
int bounce_bar = na
|
||
for i = 0 to fx_count - 1
|
||
fx_b = array.get(fx_bars, i)
|
||
fx_t = array.get(fx_types, i)
|
||
if fx_b > bs_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)
|
||
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
|
||
|
||
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)
|
||
array.push(bs_bars, fx_b)
|
||
array.push(bs_types, 2)
|
||
array.push(bs_prices, fx_p)
|
||
break
|
||
|
||
// 第二类卖点:一卖之后,回调形成底分型,再上涨形成顶分型,且不过一卖高点
|
||
if bs_type == -1
|
||
// 找一卖之后的底分型(回调低点)
|
||
int pullback_bar = na
|
||
for i = 0 to fx_count - 1
|
||
fx_b = array.get(fx_bars, i)
|
||
fx_t = array.get(fx_types, i)
|
||
if fx_b > bs_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)
|
||
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
|
||
|
||
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)
|
||
array.push(bs_bars, fx_b)
|
||
array.push(bs_types, -2)
|
||
array.push(bs_prices, fx_p)
|
||
break
|
||
|
||
// ==================== 信息面板 ====================
|
||
var table info_panel = table.new(position.top_right, 2, 8,
|
||
bgcolor=color.new(color.black, 80),
|
||
border_width=1,
|
||
border_color=color.gray)
|
||
|
||
table.cell(info_panel, 0, 0, "缠论分析", text_color=color.white, text_size=size.normal)
|
||
table.cell(info_panel, 1, 0, "", text_color=color.white)
|
||
|
||
table.cell(info_panel, 0, 1, "处理后K线", text_color=color.gray, text_size=size.small)
|
||
table.cell(info_panel, 1, 1, str.tostring(merged_k_count), text_color=color.white, text_size=size.small)
|
||
|
||
table.cell(info_panel, 0, 2, "分型数量", text_color=color.gray, text_size=size.small)
|
||
table.cell(info_panel, 1, 2, str.tostring(fx_count), text_color=color.white, text_size=size.small)
|
||
|
||
table.cell(info_panel, 0, 3, "笔数量", text_color=color.gray, text_size=size.small)
|
||
table.cell(info_panel, 1, 3, str.tostring(bi_count), text_color=color.white, text_size=size.small)
|
||
|
||
table.cell(info_panel, 0, 4, "线段数量", text_color=color.gray, text_size=size.small)
|
||
table.cell(info_panel, 1, 4, str.tostring(xd_count), text_color=color.white, text_size=size.small)
|
||
|
||
table.cell(info_panel, 0, 5, "笔中枢", text_color=color.yellow, text_size=size.small)
|
||
table.cell(info_panel, 1, 5, str.tostring(bi_zs_count), text_color=color.white, text_size=size.small)
|
||
|
||
table.cell(info_panel, 0, 6, "线段中枢", text_color=color.orange, text_size=size.small)
|
||
table.cell(info_panel, 1, 6, str.tostring(xd_zs_count), text_color=color.white, text_size=size.small)
|
||
|
||
table.cell(info_panel, 0, 7, "当前K线", text_color=color.gray, text_size=size.small)
|
||
table.cell(info_panel, 1, 7, str.tostring(bar_index), text_color=color.white, text_size=size.small)
|