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

1208 lines
61 KiB
Plaintext
Raw Permalink 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.
// 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="EMA156仅用于显示,不参与三均线排列/交叉")
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)
// 趋势/排列与交叉仅使用三条均线:EMA24, EMA52, EMA104EMA156仅显示)
// ============================================================================
// 三均线趋势排列检测
// ============================================================================
// 多头排列:EMA1 > EMA2 > EMA3(从上到下依次排列)
// 空头排列:EMA1 < EMA2 < EMA3(从下到上依次排列)
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和104
ema_golden_cross = ta.crossover(ema52, ema104) // 金叉:EMA52上穿EMA104
ema_death_cross = ta.crossunder(ema52, ema104) // 死叉:EMA52下穿EMA104
// 绘制交叉点(中枢震荡区间不显示)
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_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="死叉")
// 计算排列持续的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. 特征序列要先做包含处理(与K线包含处理同理)
// 4. 包含处理后的特征序列出现分型,线段才结束
// - 上升线段:特征序列(向下笔)出现顶分型 → 线段结束(第一种情况)
// - 下降线段:特征序列(向上笔)出现底分型 → 线段结束(第一种情况)
// 5. 第二种情况:特征序列无分型,但出现反向缺口(gap)确认反转
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_raw_highs = array.new<float>()
var float[] feat_raw_lows = array.new<float>()
var int[] feat_raw_end_bars = array.new<int>()
var float[] feat_raw_end_prices = array.new<float>()
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_merged_highs = array.new<float>()
var float[] feat_merged_lows = array.new<float>()
var int[] feat_merged_end_bars = array.new<int>()
var float[] feat_merged_end_prices = array.new<float>()
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_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_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
fm_size = array.size(feat_merged_highs)
// === 第一种情况:特征序列包含处理后出现分型 ===
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 fm_h2 > fm_h1 and fm_h2 > fm_h3
xd_broken := true
else
// 下降线段:特征序列(向上笔)出现底分型 → 线段结束
// 底分型:中间元素低点最低
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 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_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_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
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)
// ==================== 构建笔中枢(线段内部) ====================
// 缠论标准:笔中枢必须在同一条线段内部构建
// 中枢方向 = 所在线段的方向(上涨线段内 → 上涨中枢,下跌线段内 → 下跌中枢)
// 中枢由线段内任意连续3笔的价格重叠区间构成
// ZG = min(3笔高点)ZD = max(3笔低点)ZG > ZD 则构成中枢
// 中枢延伸:同一线段内后续笔与中枢[ZD, ZG]有重叠,则时间扩展,区间不变
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)
// 收集该线段范围内的笔索引
var int[] xd_bi_indices = array.new<int>()
array.clear(xd_bi_indices)
for bi_idx = 0 to bi_count - 1
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)
local_bi_count = array.size(xd_bi_indices)
if local_bi_count >= 3
j = 0
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_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))
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 // 中枢方向 = 线段方向
// 中枢延伸:同一线段内后续笔与中枢有重叠
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
// 避免与上一个中枢重叠
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 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
j += 1
bi_zs_count = array.size(bi_zs_start_bars)
// ==================== 构建线段中枢 ====================
// 上涨中枢:以下跌线段开始、下跌线段结束 (-1, 1, -1)
// 下跌中枢:以上涨线段开始、上涨线段结束 (1, -1, 1)
// ZG = min(各线段高点)ZD = max(各线段低点)ZG > ZD 则构成中枢
// 中枢延伸:后续线段与中枢有重叠,则时间扩展,区间不变
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
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) 下跌线段开始下跌线段结束
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
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)
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
// 中枢延伸:后续线段与中枢有重叠,则延伸时间范围
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
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)
i := last_ext_xd + 1
else
i += 1
else
i += 1
else
i += 1
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)
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
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)
// ==================== 绘制线段中枢 ====================
// 上涨中枢(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)
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
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)
// ==================== 买卖点(缠论标准三类买卖点 - 与笔中枢方向对应) ====================
// 笔中枢方向 = 线段方向
// 下跌中枢(-1):在中枢下方找一买(底背驰)
// 上涨中枢(1):在中枢上方找一卖(顶背驰)
// 三买三卖:离开中枢后回调/反弹不回到中枢内部
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)
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 = 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_bottom_price = na
float prev_bottom_macd = na
// 找中枢前最后一个顶分型(用于一卖背驰比较)
float prev_top_price = na
float prev_top_macd = na
for i = fx_count - 1 to 0
fx_b = array.get(fx_bars, 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_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
int leave_bar = na
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)
macd_valid = not use_macd_filter or (fx_type == 1 and fx_macd > 0) or (fx_type == -1 and fx_macd < 0)
// 一买:下跌中枢(-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
// 一卖:上涨中枢(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 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)
macd_valid = not use_macd_filter or (fx_type == 1 and fx_macd > 0) or (fx_type == -1 and fx_macd < 0)
// 三买:下跌中枢向上离开后,回调底分型不跌破中枢上沿
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 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
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)
if fx_b <= bs_bar
continue
fx_t = array.get(fx_types, i)
if na(bounce_bar) and fx_t == 1
bounce_bar := fx_b
continue
if not na(bounce_bar) and fx_t == -1
fx_p = array.get(fx_prices, i)
if fx_p > bs_price
fx_m = array.get(fx_macds, i)
macd_valid = not use_macd_filter or fx_m < 0
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
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 na(pullback_bar) and fx_t == -1
pullback_bar := fx_b
continue
if not na(pullback_bar) and fx_t == 1
fx_p = array.get(fx_prices, i)
if fx_p < bs_price
fx_m = array.get(fx_macds, i)
macd_valid = not use_macd_filter or fx_m > 0
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
// ==================== 信息面板 ====================
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)