From c4b066ebc3bf269713114c9ee91ecd170d5b5283 Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Mon, 9 Feb 2026 18:02:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=96=B0=E7=9A=84=E6=8C=87?= =?UTF-8?q?=E6=A0=87=E5=92=8C=E5=8E=95=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 5 + chan_theory_complete.pine | 1262 +++++++++++++++++++++++++++++++++ ema_macd_trend_indicator.pine | 316 +++++++++ ema_macd_trend_strategy.pine | 319 +++++++++ 三重滤网均线交易策略.pine | 259 +++++++ 多周期MA52.pine | 388 ++++++++++ 6 files changed, 2549 insertions(+) create mode 100644 chan_theory_complete.pine create mode 100644 ema_macd_trend_indicator.pine create mode 100644 ema_macd_trend_strategy.pine create mode 100644 三重滤网均线交易策略.pine create mode 100644 多周期MA52.pine diff --git a/README.md b/README.md index ae353d3..1a8c696 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,7 @@ # tradingview TradingView scripts + +1. 使用MA160判断大的趋势,MA40判断小趋势。走势价格在MA160上方,大级别为多头趋势,下方则为空头趋势。走势价格在MA40上方,小级别走势为多头趋势,下方则为空头趋势 +2. 使用顺大逆小的趋势策略,捕捉大级别趋势行情中的小级别调整走势,即大级别为多头趋势时,小级别为空头趋势,小级别空头趋势后会继续沿着大级别运行。大级别为空头趋势时反向操作。具体方法如下: +3. 如果大级别趋势是上涨趋势,小级别下跌趋势在完成,找到macd金叉,金叉后面三根k线仍然按照金叉涨的趋势运行,并且价格在后面10根k线站稳和突破MA40,此时时出现买入信号,止损设在回调的低点,止盈参考1:2的盈亏比设置 +4. 大趋势是下跌趋势时反向操作。 \ No newline at end of file diff --git a/chan_theory_complete.pine b/chan_theory_complete.pine new file mode 100644 index 0000000..12805ea --- /dev/null +++ b/chan_theory_complete.pine @@ -0,0 +1,1262 @@ +// 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() +var float[] mk_low = array.new() +var int[] mk_idx = array.new() // 合并K线的结束bar_index +var int[] mk_start_idx = array.new() // 合并K线的起始bar_index +var float[] mk_macd = array.new() // 每根合并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() +var float[] fx_prices = array.new() +var int[] fx_types = array.new() // 1=顶, -1=底 +var float[] fx_macds = array.new() // 分型对应的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() + var float[] bi_start_prices = array.new() + var int[] bi_end_bars = array.new() + var float[] bi_end_prices = array.new() + var int[] bi_dirs = array.new() + + 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() + var float[] xd_start_prices = array.new() + var int[] xd_end_bars = array.new() + var float[] xd_end_prices = array.new() + var int[] xd_dirs = array.new() + + 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() + var float[] feat_lows = array.new() + 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() + var int[] bi_zs_end_bars = array.new() + var float[] bi_zs_highs = array.new() + var float[] bi_zs_lows = array.new() + var int[] bi_zs_types = array.new() // 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() + 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() + var int[] xd_zs_end_bars = array.new() + var float[] xd_zs_highs = array.new() + var float[] xd_zs_lows = array.new() + var int[] xd_zs_types = array.new() // 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() + var int[] bs_types = array.new() // 正数=买点,负数=卖点,绝对值=类型 + var float[] bs_prices = array.new() + + 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) diff --git a/ema_macd_trend_indicator.pine b/ema_macd_trend_indicator.pine new file mode 100644 index 0000000..3d17f4c --- /dev/null +++ b/ema_macd_trend_indicator.pine @@ -0,0 +1,316 @@ +//@version=6 +indicator(title="EMA+MACD 三态趋势判断", shorttitle="EMA+MACD趋势", overlay=true, max_labels_count=500) + +// ========================= 输入参数 ========================= +// EMA 参数 +ema1_len = input.int(9, "EMA1 快线周期", minval=1, inline="ema1") +ema2_len = input.int(21, "EMA2 中线周期", minval=1, inline="ema2") +ema3_len = input.int(55, "EMA3 慢线周期", minval=1, inline="ema3") +ema4_len = input.int(200, "EMA4 超慢线周期", minval=1, inline="ema4") +use_ema4 = input.bool(true, "启用第4条均线(EMA200)", inline="ema4") + +// MACD 参数 +macd_fast = input.int(12, "MACD快线周期", minval=1, group="MACD设置") +macd_slow = input.int(26, "MACD慢线周期", minval=1, group="MACD设置") +macd_signal = input.int(9, "MACD信号线周期", minval=1, group="MACD设置") + +// 趋势判断参数 +ema_spread_thresh = input.float(0.3, "EMA收束阈值(%)", minval=0.1, maxval=2.0, step=0.1, group="趋势判断") / 100 +macd_zero_zone = input.float(0.1, "MACD零轴区域(%)", minval=0.01, maxval=0.5, step=0.01, group="趋势判断") / 100 +confirm_bars = input.int(2, "趋势确认周期", minval=1, maxval=5, group="趋势判断") + +// 显示设置 +show_ema = input.bool(true, "显示EMA线", group="显示设置") +show_signals = input.bool(true, "显示状态切换信号", group="显示设置") +show_background = input.bool(true, "背景着色", group="显示设置") +show_info_panel = input.bool(true, "显示信息面板", group="显示设置") + +// 颜色设置 +col_uptrend = input.color(color.new(color.teal, 80), "上涨趋势颜色", group="颜色设置") +col_downtrend = input.color(color.new(color.red, 80), "下跌趋势颜色", group="颜色设置") +col_consolidation = input.color(color.new(color.gray, 85), "盘整趋势颜色", group="颜色设置") + +// ========================= 指标计算 ========================= +// EMA计算 +ema1 = ta.ema(close, ema1_len) +ema2 = ta.ema(close, ema2_len) +ema3 = ta.ema(close, ema3_len) +ema4 = use_ema4 ? ta.ema(close, ema4_len) : na + +// MACD计算 +[macdLine, signalLine, histLine] = ta.macd(close, macd_fast, macd_slow, macd_signal) +macd_normalized = macdLine / close // 归一化MACD便于比较 + +// ========================= EMA排列判断 ========================= +// 多头排列: EMA1 > EMA2 > EMA3 (> EMA4 if enabled) +bull_alignment_3 = ema1 > ema2 and ema2 > ema3 +bull_alignment = use_ema4 ? (bull_alignment_3 and ema3 > ema4) : bull_alignment_3 + +// 空头排列: EMA1 < EMA2 < EMA3 (< EMA4 if enabled) +bear_alignment_3 = ema1 < ema2 and ema2 < ema3 +bear_alignment = use_ema4 ? (bear_alignment_3 and ema3 < ema4) : bear_alignment_3 + +// EMA方向一致性(斜率判断) +ema1_rising = ema1 > ema1[1] +ema2_rising = ema2 > ema2[1] +ema3_rising = ema3 > ema3[1] +ema4_rising = use_ema4 ? ema4 > ema4[1] : true + +ema1_falling = ema1 < ema1[1] +ema2_falling = ema2 < ema2[1] +ema3_falling = ema3 < ema3[1] +ema4_falling = use_ema4 ? ema4 < ema4[1] : true + +all_rising = ema1_rising and ema2_rising and ema3_rising and (not use_ema4 or ema4_rising) +all_falling = ema1_falling and ema2_falling and ema3_falling and (not use_ema4 or ema4_falling) + +// EMA收束程度(判断盘整) +max_ema = math.max(math.max(ema1, ema2), ema3) +min_ema = math.min(math.min(ema1, ema2), ema3) +ema_spread = (max_ema - min_ema) / close +emas_converged = ema_spread < ema_spread_thresh + +// ========================= MACD状态判断 ========================= +// MACD方向 +macd_bullish = macdLine > signalLine +macd_bearish = macdLine < signalLine + +// MACD位置 +macd_above_zero = macdLine > 0 +macd_below_zero = macdLine < 0 +macd_near_zero = math.abs(macd_normalized) < macd_zero_zone + +// MACD柱状图趋势 +hist_expanding_up = histLine > histLine[1] and histLine > 0 +hist_expanding_down = histLine < histLine[1] and histLine < 0 +hist_shrinking = math.abs(histLine) < math.abs(histLine[1]) + +// MACD综合强度评分 (-2 到 +2) +macd_score = 0 +macd_score := macd_bullish ? macd_score + 1 : macd_score - 1 +macd_score := macd_above_zero ? macd_score + 1 : macd_below_zero ? macd_score - 1 : macd_score + +// ========================= 综合趋势判断 ========================= +// 上涨趋势条件: +// 1. EMA多头排列 OR (EMA方向一致向上 AND 价格在EMA上方) +// 2. MACD评分 >= 1 (至少看涨) +price_above_emas = close > ema1 and close > ema2 and close > ema3 +uptrend_ema = bull_alignment or (all_rising and price_above_emas) +uptrend_macd = macd_score >= 1 or (macd_bullish and not macd_near_zero) +uptrend_raw = uptrend_ema and uptrend_macd + +// 下跌趋势条件: +// 1. EMA空头排列 OR (EMA方向一致向下 AND 价格在EMA下方) +// 2. MACD评分 <= -1 (至少看跌) +price_below_emas = close < ema1 and close < ema2 and close < ema3 +downtrend_ema = bear_alignment or (all_falling and price_below_emas) +downtrend_macd = macd_score <= -1 or (macd_bearish and not macd_near_zero) +downtrend_raw = downtrend_ema and downtrend_macd + +// 盘整趋势条件: +// 1. EMA收束 OR 无明确排列 +// 2. MACD在零轴附近 OR 柱状图收缩 +consolidation_ema = emas_converged or (not bull_alignment and not bear_alignment) +consolidation_macd = macd_near_zero or hist_shrinking +consolidation_raw = (consolidation_ema and consolidation_macd) or (not uptrend_raw and not downtrend_raw) + +// ========================= 连续确认计数 ========================= +var int uptrend_count = 0 +var int downtrend_count = 0 +var int consolidation_count = 0 + +uptrend_count := uptrend_raw ? uptrend_count + 1 : 0 +downtrend_count := downtrend_raw ? downtrend_count + 1 : 0 +consolidation_count := consolidation_raw ? consolidation_count + 1 : 0 + +// 确认后的趋势信号 +uptrend_confirmed = uptrend_count >= confirm_bars +downtrend_confirmed = downtrend_count >= confirm_bars +consolidation_confirmed = consolidation_count >= confirm_bars + +// ========================= 状态机 ========================= +// 状态: 0=盘整, 1=上涨, -1=下跌 +var int trend_state = 0 +prev_state = nz(trend_state[1], 0) + +// 状态转换逻辑 +int new_state = prev_state + +if prev_state == 0 // 当前盘整 + if uptrend_confirmed + new_state := 1 + else if downtrend_confirmed + new_state := -1 + +else if prev_state == 1 // 当前上涨 + if downtrend_confirmed + new_state := -1 + else if consolidation_confirmed and not uptrend_raw + new_state := 0 + +else if prev_state == -1 // 当前下跌 + if uptrend_confirmed + new_state := 1 + else if consolidation_confirmed and not downtrend_raw + new_state := 0 + +// 仅在K线收盘时更新状态 +trend_state := barstate.isconfirmed ? new_state : prev_state + +// ========================= 可视化 ========================= +// 绘制EMA线 +plot(show_ema ? ema1 : na, "EMA1 快线", color=color.new(color.red, 30), linewidth=1) +plot(show_ema ? ema2 : na, "EMA2 中线", color=color.new(color.orange, 30), linewidth=1) +plot(show_ema ? ema3 : na, "EMA3 慢线", color=color.new(color.blue, 30), linewidth=2) +plot(show_ema and use_ema4 ? ema4 : na, "EMA4 超慢线", color=color.new(color.purple, 30), linewidth=2) + +// 背景着色 +bg_color = trend_state == 1 ? col_uptrend : trend_state == -1 ? col_downtrend : col_consolidation +bgcolor(show_background ? bg_color : na, title="趋势背景") + +// 状态切换信号 +entered_up = barstate.isconfirmed and trend_state == 1 and prev_state != 1 +entered_down = barstate.isconfirmed and trend_state == -1 and prev_state != -1 +entered_consolidation = barstate.isconfirmed and trend_state == 0 and prev_state != 0 +exit_up = barstate.isconfirmed and prev_state == 1 and trend_state != 1 +exit_down = barstate.isconfirmed and prev_state == -1 and trend_state != -1 + +// ========================= 类缠论笔的绘制 ========================= +// 趋势线宽度 +trend_line_width = input.int(2, "趋势笔宽度", minval=1, maxval=5, group="显示设置") + +// 上涨笔:记录起点 +var int up_start_bar = na +var float up_start_price = na + +// 下跌笔:记录起点 +var int down_start_bar = na +var float down_start_price = na + +// 上涨趋势开始 - 记录起点 +if entered_up + up_start_bar := bar_index + up_start_price := low + +// 上涨趋势结束 - 画笔(终点 = 结束时的K线) +if exit_up and show_signals and not na(up_start_bar) + line.new(up_start_bar, up_start_price, bar_index, close, color=color.teal, width=trend_line_width, style=line.style_solid) + +// 下跌趋势开始 - 记录起点 +if entered_down + down_start_bar := bar_index + down_start_price := high + +// 下跌趋势结束 - 画笔(终点 = 结束时的K线) +if exit_down and show_signals and not na(down_start_bar) + line.new(down_start_bar, down_start_price, bar_index, close, color=color.red, width=trend_line_width, style=line.style_solid) + +// 当前进行中的笔(实时显示) +var line current_up_line = na +var line current_down_line = na + +if show_signals + if not na(current_up_line) + line.delete(current_up_line) + if not na(current_down_line) + line.delete(current_down_line) + + // 上涨中 - 画到当前K线 + if trend_state == 1 and not na(up_start_bar) + current_up_line := line.new(up_start_bar, up_start_price, bar_index, close, color=color.new(color.teal, 40), width=trend_line_width, style=line.style_dotted) + + // 下跌中 - 画到当前K线 + if trend_state == -1 and not na(down_start_bar) + current_down_line := line.new(down_start_bar, down_start_price, bar_index, close, color=color.new(color.red, 40), width=trend_line_width, style=line.style_dotted) + +// ========================= 信息面板 ========================= +if show_info_panel and barstate.islast + var table info_table = table.new(position.top_right, 2, 10, bgcolor=color.new(color.black, 80), border_width=1) + + // 标题 + table.cell(info_table, 0, 0, "EMA+MACD趋势", text_color=color.white, bgcolor=color.new(color.gray, 60), text_size=size.small) + table.cell(info_table, 1, 0, "", bgcolor=color.new(color.gray, 60)) + + // 当前状态 + state_text = trend_state == 1 ? "📈 上涨趋势" : trend_state == -1 ? "📉 下跌趋势" : "📊 盘整趋势" + state_color = trend_state == 1 ? color.teal : trend_state == -1 ? color.red : color.gray + table.cell(info_table, 0, 1, "当前状态", text_color=color.white, text_size=size.small) + table.cell(info_table, 1, 1, state_text, text_color=state_color, text_size=size.small) + + // EMA排列 + ema_text = bull_alignment ? "多头排列" : bear_alignment ? "空头排列" : "无序排列" + ema_color = bull_alignment ? color.teal : bear_alignment ? color.red : color.gray + table.cell(info_table, 0, 2, "EMA排列", text_color=color.white, text_size=size.small) + table.cell(info_table, 1, 2, ema_text, text_color=ema_color, text_size=size.small) + + // EMA收束度 + spread_text = str.tostring(math.round(ema_spread * 10000) / 100, "#.##") + "%" + spread_color = emas_converged ? color.orange : color.white + table.cell(info_table, 0, 3, "EMA间距", text_color=color.white, text_size=size.small) + table.cell(info_table, 1, 3, spread_text + (emas_converged ? " 收束" : ""), text_color=spread_color, text_size=size.small) + + // EMA方向 + dir_text = all_rising ? "全部上升" : all_falling ? "全部下降" : "方向分歧" + dir_color = all_rising ? color.teal : all_falling ? color.red : color.gray + table.cell(info_table, 0, 4, "EMA方向", text_color=color.white, text_size=size.small) + table.cell(info_table, 1, 4, dir_text, text_color=dir_color, text_size=size.small) + + // MACD状态 + macd_status = macd_bullish ? "看涨" : "看跌" + macd_pos = macd_above_zero ? " (零上)" : macd_below_zero ? " (零下)" : " (零轴)" + macd_color = macd_bullish and macd_above_zero ? color.teal : macd_bearish and macd_below_zero ? color.red : color.orange + table.cell(info_table, 0, 5, "MACD状态", text_color=color.white, text_size=size.small) + table.cell(info_table, 1, 5, macd_status + macd_pos, text_color=macd_color, text_size=size.small) + + // MACD数值 + table.cell(info_table, 0, 6, "MACD值", text_color=color.white, text_size=size.small) + table.cell(info_table, 1, 6, str.tostring(macdLine, "#.####"), text_color=macdLine > 0 ? color.teal : color.red, text_size=size.small) + + // 柱状图状态 + hist_text = hist_expanding_up ? "放大(多)" : hist_expanding_down ? "放大(空)" : hist_shrinking ? "收缩" : "平稳" + hist_color = hist_expanding_up ? color.teal : hist_expanding_down ? color.red : color.gray + table.cell(info_table, 0, 7, "柱状图", text_color=color.white, text_size=size.small) + table.cell(info_table, 1, 7, hist_text, text_color=hist_color, text_size=size.small) + + // MACD综合评分 + score_text = macd_score > 0 ? "+" + str.tostring(macd_score) : str.tostring(macd_score) + score_color = macd_score > 0 ? color.teal : macd_score < 0 ? color.red : color.gray + table.cell(info_table, 0, 8, "MACD评分", text_color=color.white, text_size=size.small) + table.cell(info_table, 1, 8, score_text + " / 2", text_color=score_color, text_size=size.small) + + // 价格位置 + price_pos = price_above_emas ? "均线上方" : price_below_emas ? "均线下方" : "均线之间" + price_color = price_above_emas ? color.teal : price_below_emas ? color.red : color.gray + table.cell(info_table, 0, 9, "价格位置", text_color=color.white, text_size=size.small) + table.cell(info_table, 1, 9, price_pos, text_color=price_color, text_size=size.small) + +// ========================= 告警 ========================= +alertcondition(entered_up, title="上涨趋势开始", message="{{ticker}} {{interval}} 进入上涨趋势 价格:{{close}}") +alertcondition(entered_down, title="下跌趋势开始", message="{{ticker}} {{interval}} 进入下跌趋势 价格:{{close}}") +alertcondition(entered_consolidation, title="盘整趋势开始", message="{{ticker}} {{interval}} 进入盘整趋势 价格:{{close}}") + +// ========================= 指标说明 ========================= +// 【EMA+MACD三态趋势判断指标】 +// +// 核心逻辑: +// 1. 使用4条EMA(9/21/55/200)判断均线排列和方向 +// 2. 结合MACD的位置、方向和柱状图状态 +// 3. 综合判断市场处于三种状态之一 +// +// 📈 上涨趋势判定: +// - EMA多头排列(快>中>慢>超慢)或均线同向上升+价格在上方 +// - MACD看涨(MACD线>信号线 或 MACD在零轴上方) +// +// 📉 下跌趋势判定: +// - EMA空头排列(快<中<慢<超慢)或均线同向下降+价格在下方 +// - MACD看跌(MACD线<信号线 或 MACD在零轴下方) +// +// 📊 盘整趋势判定: +// - EMA线收束(间距小于阈值)或无明确排列 +// - MACD在零轴附近 或 柱状图收缩 +// - 不满足上涨/下跌条件时自动判定为盘整 +// +// 参数建议: +// - 短线交易:EMA 5/10/20/50, 确认周期1-2 +// - 中线交易:EMA 9/21/55/200, 确认周期2-3(默认) +// - 长线交易:EMA 20/50/100/200, 确认周期3-5 diff --git a/ema_macd_trend_strategy.pine b/ema_macd_trend_strategy.pine new file mode 100644 index 0000000..f01326b --- /dev/null +++ b/ema_macd_trend_strategy.pine @@ -0,0 +1,319 @@ +//@version=6 +strategy(title="EMA+MACD 趋势跟踪策略", shorttitle="EMA+MACD策略", overlay=true, + default_qty_type=strategy.percent_of_equity, default_qty_value=10, + initial_capital=100000, commission_type=strategy.commission.percent, commission_value=0.1, + pyramiding=0, calc_on_every_tick=false) + +// ========================= 策略参数 ========================= +// 交易方向 +trade_direction = input.string("双向交易", "交易方向", options=["只做多", "只做空", "双向交易"], group="策略设置") + +// 止损止盈 +use_sl = input.bool(true, "启用止损", group="风控设置") +sl_pct = input.float(3.0, "止损比例(%)", minval=0.5, maxval=20, step=0.5, group="风控设置") +use_tp = input.bool(true, "启用止盈", group="风控设置") +tp_pct = input.float(6.0, "止盈比例(%)", minval=1, maxval=50, step=0.5, group="风控设置") + +// 平仓逻辑:趋势结束即平仓 + +// 移动止损 +use_trailing = input.bool(false, "启用移动止损", group="风控设置") +trail_pct = input.float(2.0, "移动止损比例(%)", minval=0.5, maxval=10, step=0.5, group="风控设置") + +// ========================= EMA 参数 ========================= +ema1_len = input.int(9, "EMA1 快线周期", minval=1, group="EMA设置") +ema2_len = input.int(21, "EMA2 中线周期", minval=1, group="EMA设置") +ema3_len = input.int(55, "EMA3 慢线周期", minval=1, group="EMA设置") +ema4_len = input.int(200, "EMA4 超慢线周期", minval=1, group="EMA设置") +use_ema4 = input.bool(true, "启用第4条均线(EMA200)", group="EMA设置") + +// ========================= MACD 参数 ========================= +macd_fast = input.int(12, "MACD快线周期", minval=1, group="MACD设置") +macd_slow = input.int(26, "MACD慢线周期", minval=1, group="MACD设置") +macd_signal = input.int(9, "MACD信号线周期", minval=1, group="MACD设置") + +// ========================= 趋势判断参数 ========================= +ema_spread_thresh = input.float(0.3, "EMA收束阈值(%)", minval=0.1, maxval=2.0, step=0.1, group="趋势判断") / 100 +macd_zero_zone = input.float(0.1, "MACD零轴区域(%)", minval=0.01, maxval=0.5, step=0.01, group="趋势判断") / 100 +confirm_bars = input.int(2, "趋势确认周期", minval=1, maxval=5, group="趋势判断") + +// ========================= 显示设置 ========================= +show_ema = input.bool(true, "显示EMA线", group="显示设置") +show_background = input.bool(true, "背景着色", group="显示设置") +show_signals = input.bool(true, "显示趋势笔", group="显示设置") + +// ========================= 颜色设置 ========================= +col_uptrend = color.new(color.teal, 80) +col_downtrend = color.new(color.red, 80) +col_consolidation = color.new(color.gray, 85) + +// ========================= 指标计算 ========================= +// EMA计算 +ema1 = ta.ema(close, ema1_len) +ema2 = ta.ema(close, ema2_len) +ema3 = ta.ema(close, ema3_len) +ema4 = use_ema4 ? ta.ema(close, ema4_len) : na + +// MACD计算 +[macdLine, signalLine, histLine] = ta.macd(close, macd_fast, macd_slow, macd_signal) +macd_normalized = macdLine / close + +// ========================= EMA排列判断 ========================= +bull_alignment_3 = ema1 > ema2 and ema2 > ema3 +bull_alignment = use_ema4 ? (bull_alignment_3 and ema3 > ema4) : bull_alignment_3 + +bear_alignment_3 = ema1 < ema2 and ema2 < ema3 +bear_alignment = use_ema4 ? (bear_alignment_3 and ema3 < ema4) : bear_alignment_3 + +// EMA方向 +ema1_rising = ema1 > ema1[1] +ema2_rising = ema2 > ema2[1] +ema3_rising = ema3 > ema3[1] +ema4_rising = use_ema4 ? ema4 > ema4[1] : true + +ema1_falling = ema1 < ema1[1] +ema2_falling = ema2 < ema2[1] +ema3_falling = ema3 < ema3[1] +ema4_falling = use_ema4 ? ema4 < ema4[1] : true + +all_rising = ema1_rising and ema2_rising and ema3_rising and (not use_ema4 or ema4_rising) +all_falling = ema1_falling and ema2_falling and ema3_falling and (not use_ema4 or ema4_falling) + +// EMA收束 +max_ema = math.max(math.max(ema1, ema2), ema3) +min_ema = math.min(math.min(ema1, ema2), ema3) +ema_spread = (max_ema - min_ema) / close +emas_converged = ema_spread < ema_spread_thresh + +// ========================= MACD状态判断 ========================= +macd_bullish = macdLine > signalLine +macd_bearish = macdLine < signalLine +macd_above_zero = macdLine > 0 +macd_below_zero = macdLine < 0 +macd_near_zero = math.abs(macd_normalized) < macd_zero_zone +hist_shrinking = math.abs(histLine) < math.abs(histLine[1]) + +// MACD评分 +macd_score = 0 +macd_score := macd_bullish ? macd_score + 1 : macd_score - 1 +macd_score := macd_above_zero ? macd_score + 1 : macd_below_zero ? macd_score - 1 : macd_score + +// ========================= 综合趋势判断 ========================= +price_above_emas = close > ema1 and close > ema2 and close > ema3 +uptrend_ema = bull_alignment or (all_rising and price_above_emas) +uptrend_macd = macd_score >= 1 or (macd_bullish and not macd_near_zero) +uptrend_raw = uptrend_ema and uptrend_macd + +price_below_emas = close < ema1 and close < ema2 and close < ema3 +downtrend_ema = bear_alignment or (all_falling and price_below_emas) +downtrend_macd = macd_score <= -1 or (macd_bearish and not macd_near_zero) +downtrend_raw = downtrend_ema and downtrend_macd + +consolidation_ema = emas_converged or (not bull_alignment and not bear_alignment) +consolidation_macd = macd_near_zero or hist_shrinking +consolidation_raw = (consolidation_ema and consolidation_macd) or (not uptrend_raw and not downtrend_raw) + +// ========================= 连续确认计数 ========================= +var int uptrend_count = 0 +var int downtrend_count = 0 +var int consolidation_count = 0 + +uptrend_count := uptrend_raw ? uptrend_count + 1 : 0 +downtrend_count := downtrend_raw ? downtrend_count + 1 : 0 +consolidation_count := consolidation_raw ? consolidation_count + 1 : 0 + +uptrend_confirmed = uptrend_count >= confirm_bars +downtrend_confirmed = downtrend_count >= confirm_bars +consolidation_confirmed = consolidation_count >= confirm_bars + +// ========================= 状态机 ========================= +var int trend_state = 0 +prev_state = nz(trend_state[1], 0) + +int new_state = prev_state + +if prev_state == 0 + if uptrend_confirmed + new_state := 1 + else if downtrend_confirmed + new_state := -1 + +else if prev_state == 1 + if downtrend_confirmed + new_state := -1 + else if consolidation_confirmed and not uptrend_raw + new_state := 0 + +else if prev_state == -1 + if uptrend_confirmed + new_state := 1 + else if consolidation_confirmed and not downtrend_raw + new_state := 0 + +trend_state := barstate.isconfirmed ? new_state : prev_state + +// 状态切换信号 +entered_up = barstate.isconfirmed and trend_state == 1 and prev_state != 1 +entered_down = barstate.isconfirmed and trend_state == -1 and prev_state != -1 +entered_consolidation = barstate.isconfirmed and trend_state == 0 and prev_state != 0 +exit_up = barstate.isconfirmed and prev_state == 1 and trend_state != 1 +exit_down = barstate.isconfirmed and prev_state == -1 and trend_state != -1 + +// ========================= 交易逻辑 ========================= +// 做多条件 +can_long = trade_direction == "只做多" or trade_direction == "双向交易" +can_short = trade_direction == "只做空" or trade_direction == "双向交易" + +// 开仓信号 +long_signal = entered_up and can_long +short_signal = entered_down and can_short + +// 平仓信号:趋势结束即平仓 +close_long_signal = exit_up // 上涨趋势结束 → 平多仓 +close_short_signal = exit_down // 下跌趋势结束 → 平空仓 + +// 止损止盈价格 +long_sl = use_sl ? strategy.position_avg_price * (1 - sl_pct / 100) : na +long_tp = use_tp ? strategy.position_avg_price * (1 + tp_pct / 100) : na +short_sl = use_sl ? strategy.position_avg_price * (1 + sl_pct / 100) : na +short_tp = use_tp ? strategy.position_avg_price * (1 - tp_pct / 100) : na + +// 执行交易 +if long_signal + strategy.entry("做多", strategy.long) + +if short_signal + strategy.entry("做空", strategy.short) + +// 平仓 +if close_long_signal and strategy.position_size > 0 + strategy.close("做多", comment="趋势结束") + +if close_short_signal and strategy.position_size < 0 + strategy.close("做空", comment="趋势结束") + +// 止损止盈 +if strategy.position_size > 0 + if use_trailing + strategy.exit("多头止损止盈", "做多", stop=long_sl, limit=long_tp, trail_points=close * trail_pct / 100 / syminfo.mintick, trail_offset=close * trail_pct / 100 / syminfo.mintick) + else + strategy.exit("多头止损止盈", "做多", stop=long_sl, limit=long_tp) + +if strategy.position_size < 0 + if use_trailing + strategy.exit("空头止损止盈", "做空", stop=short_sl, limit=short_tp, trail_points=close * trail_pct / 100 / syminfo.mintick, trail_offset=close * trail_pct / 100 / syminfo.mintick) + else + strategy.exit("空头止损止盈", "做空", stop=short_sl, limit=short_tp) + +// ========================= 可视化 ========================= +// 绘制EMA线 +plot(show_ema ? ema1 : na, "EMA1 快线", color=color.new(color.red, 30), linewidth=1) +plot(show_ema ? ema2 : na, "EMA2 中线", color=color.new(color.orange, 30), linewidth=1) +plot(show_ema ? ema3 : na, "EMA3 慢线", color=color.new(color.blue, 30), linewidth=2) +plot(show_ema and use_ema4 ? ema4 : na, "EMA4 超慢线", color=color.new(color.purple, 30), linewidth=2) + +// 背景着色 +bg_color = trend_state == 1 ? col_uptrend : trend_state == -1 ? col_downtrend : col_consolidation +bgcolor(show_background ? bg_color : na, title="趋势背景") + +// ========================= 类缠论笔的绘制 ========================= +trend_line_width = input.int(2, "趋势笔宽度", minval=1, maxval=5, group="显示设置") + +var int up_start_bar = na +var float up_start_price = na + +var int down_start_bar = na +var float down_start_price = na + +// 上涨趋势开始 - 记录起点 +if entered_up + up_start_bar := bar_index + up_start_price := low + +// 上涨趋势结束 - 画笔(终点 = 结束时的K线) +if exit_up and show_signals and not na(up_start_bar) + line.new(up_start_bar, up_start_price, bar_index, close, color=color.teal, width=trend_line_width, style=line.style_solid) + +// 下跌趋势开始 - 记录起点 +if entered_down + down_start_bar := bar_index + down_start_price := high + +// 下跌趋势结束 - 画笔(终点 = 结束时的K线) +if exit_down and show_signals and not na(down_start_bar) + line.new(down_start_bar, down_start_price, bar_index, close, color=color.red, width=trend_line_width, style=line.style_solid) + +// 当前进行中的笔(实时显示) +var line current_up_line = na +var line current_down_line = na + +if show_signals + if not na(current_up_line) + line.delete(current_up_line) + if not na(current_down_line) + line.delete(current_down_line) + + // 上涨中 - 画到当前K线 + if trend_state == 1 and not na(up_start_bar) + current_up_line := line.new(up_start_bar, up_start_price, bar_index, close, color=color.new(color.teal, 40), width=trend_line_width, style=line.style_dotted) + + // 下跌中 - 画到当前K线 + if trend_state == -1 and not na(down_start_bar) + current_down_line := line.new(down_start_bar, down_start_price, bar_index, close, color=color.new(color.red, 40), width=trend_line_width, style=line.style_dotted) + +// ========================= 信息面板 ========================= +var table info_table = table.new(position.top_right, 2, 8, bgcolor=color.new(color.black, 80), border_width=1) + +if barstate.islast + table.cell(info_table, 0, 0, "EMA+MACD策略", text_color=color.white, bgcolor=color.new(color.gray, 60), text_size=size.small) + table.cell(info_table, 1, 0, "", bgcolor=color.new(color.gray, 60)) + + state_text = trend_state == 1 ? "📈 上涨" : trend_state == -1 ? "📉 下跌" : "📊 盘整" + state_color = trend_state == 1 ? color.teal : trend_state == -1 ? color.red : color.gray + table.cell(info_table, 0, 1, "趋势状态", text_color=color.white, text_size=size.small) + table.cell(info_table, 1, 1, state_text, text_color=state_color, text_size=size.small) + + pos_text = strategy.position_size > 0 ? "多头持仓" : strategy.position_size < 0 ? "空头持仓" : "空仓" + pos_color = strategy.position_size > 0 ? color.teal : strategy.position_size < 0 ? color.red : color.gray + table.cell(info_table, 0, 2, "持仓状态", text_color=color.white, text_size=size.small) + table.cell(info_table, 1, 2, pos_text, text_color=pos_color, text_size=size.small) + + if strategy.position_size != 0 + table.cell(info_table, 0, 3, "入场价", text_color=color.white, text_size=size.small) + table.cell(info_table, 1, 3, str.tostring(strategy.position_avg_price, "#.##"), text_color=color.white, text_size=size.small) + + pnl_pct = (close - strategy.position_avg_price) / strategy.position_avg_price * 100 * (strategy.position_size > 0 ? 1 : -1) + pnl_color = pnl_pct > 0 ? color.teal : color.red + table.cell(info_table, 0, 4, "浮动盈亏", text_color=color.white, text_size=size.small) + table.cell(info_table, 1, 4, str.tostring(pnl_pct, "#.##") + "%", text_color=pnl_color, text_size=size.small) + + if use_sl + sl_price = strategy.position_size > 0 ? long_sl : short_sl + table.cell(info_table, 0, 5, "止损价", text_color=color.white, text_size=size.small) + table.cell(info_table, 1, 5, str.tostring(sl_price, "#.##"), text_color=color.red, text_size=size.small) + + if use_tp + tp_price = strategy.position_size > 0 ? long_tp : short_tp + table.cell(info_table, 0, 6, "止盈价", text_color=color.white, text_size=size.small) + table.cell(info_table, 1, 6, str.tostring(tp_price, "#.##"), text_color=color.teal, text_size=size.small) + + table.cell(info_table, 0, 7, "总交易次数", text_color=color.white, text_size=size.small) + table.cell(info_table, 1, 7, str.tostring(strategy.closedtrades), text_color=color.white, text_size=size.small) + +// ========================= 策略说明 ========================= +// 【EMA+MACD 趋势跟踪策略】 +// +// 交易逻辑: +// 1. 上涨趋势确认时 → 开多仓 +// 2. 下跌趋势确认时 → 开空仓(或平多仓) +// 3. 进入盘整时 → 根据设置决定是否平仓 +// +// 风控设置: +// - 止损:默认3%,可自定义 +// - 止盈:默认6%,可自定义 +// - 移动止损:可选开启 +// +// 交易方向: +// - 只做多:仅在上涨趋势开仓 +// - 只做空:仅在下跌趋势开仓 +// - 双向交易:趋势转换时反向开仓 diff --git a/三重滤网均线交易策略.pine b/三重滤网均线交易策略.pine new file mode 100644 index 0000000..301ff65 --- /dev/null +++ b/三重滤网均线交易策略.pine @@ -0,0 +1,259 @@ +// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/ +// © 三重滤网均线交易策略 + +//@version=6 +strategy("三重滤网均线交易策略", overlay=true, default_qty_type=strategy.percent_of_equity, default_qty_value=10, initial_capital=100000) + +// ==================== 参数设置 ==================== +// 均线参数 +ema156_length = input.int(156, "大趋势均线周期 (EMA156)", minval=1, group="均线设置") +ema52_length = input.int(52, "小趋势均线周期 (EMA52)", minval=1, group="均线设置") + +// MACD参数 +macd_fast = input.int(12, "MACD快线周期", minval=1, group="MACD设置") +macd_slow = input.int(26, "MACD慢线周期", minval=1, group="MACD设置") +macd_signal = input.int(9, "MACD信号线周期", minval=1, group="MACD设置") + +// 策略参数 +confirm_bars = input.int(3, "金叉/死叉后确认K线数", minval=1, maxval=10, group="策略设置") +breakout_bars = input.int(10, "突破EMA52确认K线数", minval=1, maxval=20, group="策略设置") +risk_reward_ratio = input.float(2.0, "盈亏比", minval=1.0, maxval=5.0, step=0.5, group="策略设置") + +// 显示设置 +show_ema156 = input.bool(true, "显示EMA156", group="显示设置") +show_ema52 = input.bool(true, "显示EMA52", group="显示设置") +show_signals = input.bool(true, "显示信号标记", group="显示设置") + +// ==================== 计算均线 ==================== +ema156 = ta.ema(close, ema156_length) +ema52 = ta.ema(close, ema52_length) + +// ==================== 计算MACD ==================== +[macd_line, signal_line, hist] = ta.macd(close, macd_fast, macd_slow, macd_signal) + +// MACD金叉和死叉 +macd_golden_cross = ta.crossover(macd_line, signal_line) +macd_death_cross = ta.crossunder(macd_line, signal_line) + +// ==================== 趋势判断 ==================== +// 大趋势:价格相对于EMA156的位置 +big_trend_bullish = close > ema156 // 大级别多头趋势 +big_trend_bearish = close < ema156 // 大级别空头趋势 + +// 小趋势:价格相对于EMA52的位置 +small_trend_bullish = close > ema52 // 小级别多头趋势 +small_trend_bearish = close < ema52 // 小级别空头趋势 + +// ==================== 金叉/死叉后趋势确认 ==================== +// 记录最近的金叉/死叉位置 +var int bars_since_golden = na +var int bars_since_death = na +var float golden_cross_price = na +var float death_cross_price = na + +if macd_golden_cross + bars_since_golden := 0 + golden_cross_price := close +else if not na(bars_since_golden) + bars_since_golden := bars_since_golden + 1 + +if macd_death_cross + bars_since_death := 0 + death_cross_price := close +else if not na(bars_since_death) + bars_since_death := bars_since_death + 1 + +// 检查金叉后confirm_bars根K线是否持续上涨(收盘价逐步走高或维持) +golden_cross_confirmed = false +if not na(bars_since_golden) and bars_since_golden >= confirm_bars and bars_since_golden <= confirm_bars + 5 + // 检查金叉后的K线是否按上涨趋势运行 + trend_up_after_golden = true + for i = 1 to confirm_bars + if close[bars_since_golden - i] < golden_cross_price + trend_up_after_golden := false + break + golden_cross_confirmed := trend_up_after_golden + +// 检查死叉后confirm_bars根K线是否持续下跌 +death_cross_confirmed = false +if not na(bars_since_death) and bars_since_death >= confirm_bars and bars_since_death <= confirm_bars + 5 + trend_down_after_death = true + for i = 1 to confirm_bars + if close[bars_since_death - i] > death_cross_price + trend_down_after_death := false + break + death_cross_confirmed := trend_down_after_death + +// ==================== 价格站稳EMA52确认 ==================== +// 检查价格是否在近期突破并站稳EMA52 +price_above_ema52_stable = true +for i = 0 to math.min(breakout_bars - 1, bar_index) + if close[i] < ema52[i] + price_above_ema52_stable := false + break + +price_below_ema52_stable = true +for i = 0 to math.min(breakout_bars - 1, bar_index) + if close[i] > ema52[i] + price_below_ema52_stable := false + break + +// 检测EMA52突破 +ema52_breakout_up = ta.crossover(close, ema52) +ema52_breakout_down = ta.crossunder(close, ema52) + +// 近期是否有EMA52突破 +recent_ema52_breakout_up = false +recent_ema52_breakout_down = false +for i = 0 to breakout_bars - 1 + if ema52_breakout_up[i] + recent_ema52_breakout_up := true + if ema52_breakout_down[i] + recent_ema52_breakout_down := true + +// ==================== 计算回调低点/高点作为止损 ==================== +// 寻找最近的回调低点(用于多头止损) +var float swing_low = na +lookback_period = 20 +swing_low := ta.lowest(low, lookback_period) + +// 寻找最近的回调高点(用于空头止损) +var float swing_high = na +swing_high := ta.highest(high, lookback_period) + +// ==================== 入场条件 ==================== +// 多头入场条件: +// 1. 大级别为多头趋势(价格在EMA156上方) +// 2. 小级别刚从空头转多头(价格突破EMA52) +// 3. MACD金叉已确认(金叉后K线继续上涨) +// 4. 价格站稳EMA52 + +long_condition = big_trend_bullish and + small_trend_bullish and + (golden_cross_confirmed or (not na(bars_since_golden) and bars_since_golden <= breakout_bars)) and + (recent_ema52_breakout_up or price_above_ema52_stable) and + macd_line > signal_line + +// 空头入场条件(反向操作): +// 1. 大级别为空头趋势(价格在EMA156下方) +// 2. 小级别刚从多头转空头(价格跌破EMA52) +// 3. MACD死叉已确认 +// 4. 价格站稳EMA52下方 + +short_condition = big_trend_bearish and + small_trend_bearish and + (death_cross_confirmed or (not na(bars_since_death) and bars_since_death <= breakout_bars)) and + (recent_ema52_breakout_down or price_below_ema52_stable) and + macd_line < signal_line + +// ==================== 计算止损止盈 ==================== +// 多头止损止盈 +long_stop_loss = swing_low +long_risk = close - long_stop_loss +long_take_profit = close + long_risk * risk_reward_ratio + +// 空头止损止盈 +short_stop_loss = swing_high +short_risk = short_stop_loss - close +short_take_profit = close - short_risk * risk_reward_ratio + +// ==================== 执行交易 ==================== +// 避免重复开仓 +var bool in_long_position = false +var bool in_short_position = false + +// 更新持仓状态 +if strategy.position_size > 0 + in_long_position := true + in_short_position := false +else if strategy.position_size < 0 + in_long_position := false + in_short_position := true +else + in_long_position := false + in_short_position := false + +// 多头入场 +if long_condition and not in_long_position and long_risk > 0 + strategy.entry("多头", strategy.long) + strategy.exit("多头止盈止损", "多头", stop=long_stop_loss, limit=long_take_profit) + +// 空头入场 +if short_condition and not in_short_position and short_risk > 0 + strategy.entry("空头", strategy.short) + strategy.exit("空头止盈止损", "空头", stop=short_stop_loss, limit=short_take_profit) + +// ==================== 绘制均线 ==================== +plot(show_ema156 ? ema156 : na, "EMA156 大趋势", color=color.new(color.orange, 0), linewidth=2) +plot(show_ema52 ? ema52 : na, "EMA52 小趋势", color=color.new(color.blue, 0), linewidth=1) + +// ==================== 绘制趋势背景 ==================== +// 大趋势背景色 +bg_color = big_trend_bullish ? color.new(color.green, 93) : big_trend_bearish ? color.new(color.red, 93) : na +bgcolor(bg_color, title="大趋势背景") + +// ==================== 绘制信号标记 ==================== +// 多头信号 +plotshape(show_signals and long_condition and not in_long_position[1] ? low : na, + title="多头信号", style=shape.triangleup, location=location.belowbar, + color=color.new(color.green, 0), size=size.small, text="买入") + +// 空头信号 +plotshape(show_signals and short_condition and not in_short_position[1] ? high : na, + title="空头信号", style=shape.triangledown, location=location.abovebar, + color=color.new(color.red, 0), size=size.small, text="卖出") + +// MACD金叉标记 +plotshape(show_signals and macd_golden_cross ? low : na, + title="MACD金叉", style=shape.circle, location=location.belowbar, + color=color.new(color.lime, 0), size=size.tiny) + +// MACD死叉标记 +plotshape(show_signals and macd_death_cross ? high : na, + title="MACD死叉", style=shape.circle, location=location.abovebar, + color=color.new(color.fuchsia, 0), size=size.tiny) + +// ==================== 绘制止损止盈线 ==================== +plot(strategy.position_size > 0 ? long_stop_loss : na, "多头止损", color=color.red, style=plot.style_linebr, linewidth=1) +plot(strategy.position_size > 0 ? long_take_profit : na, "多头止盈", color=color.green, style=plot.style_linebr, linewidth=1) +plot(strategy.position_size < 0 ? short_stop_loss : na, "空头止损", color=color.red, style=plot.style_linebr, linewidth=1) +plot(strategy.position_size < 0 ? short_take_profit : na, "空头止盈", color=color.green, style=plot.style_linebr, linewidth=1) + +// ==================== 信息面板 ==================== +var table info_panel = table.new(position.top_right, 2, 8, bgcolor=color.new(color.black, 80), border_width=1) + +if barstate.islast + table.cell(info_panel, 0, 0, "指标", text_color=color.white, text_size=size.small) + table.cell(info_panel, 1, 0, "状态", text_color=color.white, text_size=size.small) + + table.cell(info_panel, 0, 1, "大趋势(EMA156)", text_color=color.white, text_size=size.small) + table.cell(info_panel, 1, 1, big_trend_bullish ? "📈 多头" : "📉 空头", + text_color=big_trend_bullish ? color.lime : color.red, text_size=size.small) + + table.cell(info_panel, 0, 2, "小趋势(EMA52)", text_color=color.white, text_size=size.small) + table.cell(info_panel, 1, 2, small_trend_bullish ? "📈 多头" : "📉 空头", + text_color=small_trend_bullish ? color.lime : color.red, text_size=size.small) + + table.cell(info_panel, 0, 3, "MACD状态", text_color=color.white, text_size=size.small) + table.cell(info_panel, 1, 3, macd_line > signal_line ? "金叉状态" : "死叉状态", + text_color=macd_line > signal_line ? color.lime : color.red, text_size=size.small) + + table.cell(info_panel, 0, 4, "持仓状态", text_color=color.white, text_size=size.small) + position_text = strategy.position_size > 0 ? "多头持仓" : strategy.position_size < 0 ? "空头持仓" : "空仓" + position_color = strategy.position_size > 0 ? color.lime : strategy.position_size < 0 ? color.red : color.gray + table.cell(info_panel, 1, 4, position_text, text_color=position_color, text_size=size.small) + + table.cell(info_panel, 0, 5, "EMA156", text_color=color.white, text_size=size.small) + table.cell(info_panel, 1, 5, str.tostring(ema156, "#.##"), text_color=color.orange, text_size=size.small) + + table.cell(info_panel, 0, 6, "EMA52", text_color=color.white, text_size=size.small) + table.cell(info_panel, 1, 6, str.tostring(ema52, "#.##"), text_color=color.blue, text_size=size.small) + + table.cell(info_panel, 0, 7, "盈亏比", text_color=color.white, text_size=size.small) + table.cell(info_panel, 1, 7, "1:" + str.tostring(risk_reward_ratio, "#.#"), text_color=color.yellow, text_size=size.small) + +// ==================== 警报条件 ==================== +alertcondition(long_condition and not in_long_position[1], title="多头入场信号", message="三重滤网策略:多头入场信号触发!大趋势多头,小趋势回调完成,MACD金叉确认") +alertcondition(short_condition and not in_short_position[1], title="空头入场信号", message="三重滤网策略:空头入场信号触发!大趋势空头,小趋势反弹完成,MACD死叉确认") +alertcondition(macd_golden_cross, title="MACD金叉", message="MACD金叉出现") +alertcondition(macd_death_cross, title="MACD死叉", message="MACD死叉出现") diff --git a/多周期MA52.pine b/多周期MA52.pine new file mode 100644 index 0000000..d43a8a5 --- /dev/null +++ b/多周期MA52.pine @@ -0,0 +1,388 @@ +//@version=6 +indicator("多周期WMA52 (HTF/LTF)", overlay=true, max_labels_count=500, max_lines_count=500) + +// ======================== 配置输入 ======================== +// 主控 +enableCTF = input.bool(true, "启用本级别", group="显示") +enableHTF = input.bool(true, "启用长级别", group="显示") +enableLTF = input.bool(true, "启用次级别", group="显示") +maxHTFInput = input.int(10, "最多显示长级别数量", minval=1, maxval=12, group="显示") +maxLTFInput = input.int(3, "最多显示次级别数量", minval=1, maxval=4, group="显示") +includeSeconds = input.bool(false, "包含秒级别(需高级套餐)", group="显示") +protectAutoscale = input.bool(true, "防拉伸: 仅绘制可视区间内", group="显示") +lookbackBars = input.int(300, "可视区间近多少根K线", minval=50, maxval=5000, group="显示") +padPercent = input.float(2.0, "可视边界留白(%)", minval=0.0, maxval=20.0, step=0.1, group="显示") + +// 视觉(虚线效果) +dashLenHTF = input.int(6, "长级别虚线段长度", minval=1, maxval=50, group="线型") +dashGapHTF = input.int(4, "长级别虚线间隔", minval=1, maxval=50, group="线型") +dashLenLTF = input.int(2, "次级别虚线段长度", minval=1, maxval=50, group="线型") +dashGapLTF = input.int(3, "次级别虚线间隔", minval=1, maxval=50, group="线型") +lineWidthHTF = input.int(1, "长级别线宽", minval=1, maxval=4, group="线型") +lineWidthLTF = input.int(1, "次级别线宽", minval=1, maxval=4, group="线型") +lineWidthCTF = input.int(1, "本级别线宽", minval=1, maxval=4, group="线型") +enableSmooth = input.bool(true, "平滑显示(EMA)", group="线型") +smoothLenHTF = input.int(3, "长级别平滑长度", minval=1, maxval=50, group="线型") +smoothLenLTF = input.int(3, "次级别平滑长度", minval=1, maxval=50, group="线型") +smoothLenCTF = input.int(3, "本级别平滑长度", minval=1, maxval=50, group="线型") +smoothMethod = input.string("ALMA", "平滑方法", options=["EMA","DEMA","TEMA","WMA","HMA","ALMA","LinReg"], group="线型") +almaOffset = input.float(0.85, "ALMA偏移(0-1)", minval=0.0, maxval=1.0, step=0.05, group="线型") +almaSigma = input.float(6.0, "ALMA Sigma", minval=0.5, maxval=10.0, step=0.5, group="线型") +labelBgAlpha = input.int(85, "标签背景透明(0-100)", minval=0, maxval=100, group="标注") +labelSize = input.string("normal", "标签字号", options=["tiny","small","normal","large","huge"], group="标注") +labelOffsetBars = input.int(2, "标签右移K线的距离(根数)", minval=0, maxval=50, group="标注") + +// 常量上限(编译期固定,用于生成固定数量的 plot 位) +const int MAX_HTF = 12 +const int MAX_LTF = 4 + +// ======================== 工具函数 ======================== +f_dash(series float v, int phase, int dashLen, int dashGap) => + // 通过丢弃部分柱来营造虚线效果 + isShow = (bar_index + phase) % (dashLen + dashGap) < dashLen + isShow ? v : na + +// 自定义 DEMA / TEMA(Pine 无内置 dema/tema) +f_dema(series float v, int len) => + ema1 = ta.ema(v, len) + ema2 = ta.ema(ema1, len) + 2.0 * ema1 - ema2 + +f_tema(series float v, int len) => + ema1 = ta.ema(v, len) + ema2 = ta.ema(ema1, len) + ema3 = ta.ema(ema2, len) + 3.0 * (ema1 - ema2) + ema3 + +// 通用平滑(仅用于显示) +f_smooth(series float v, int len) => + not enableSmooth or len <= 1 ? v : + smoothMethod == "EMA" ? ta.ema(v, len) : + smoothMethod == "DEMA" ? f_dema(v, len) : + smoothMethod == "TEMA" ? f_tema(v, len) : + smoothMethod == "WMA" ? ta.wma(v, len) : + smoothMethod == "HMA" ? ta.hma(v, len) : + smoothMethod == "ALMA" ? ta.alma(v, len, almaOffset, almaSigma) : + ta.linreg(v, len, 0) + +f_strToSize(string s) => switch s + "tiny" => size.tiny + "small" => size.small + "normal" => size.normal + "large" => size.large + => size.huge + +// 颜色调色板(循环使用) +var color[] PALETTE = array.new_color() +if barstate.isfirst and array.size(PALETTE) == 0 + array.push(PALETTE, color.new(color.teal, 0)) + array.push(PALETTE, color.new(color.orange, 0)) + array.push(PALETTE, color.new(color.fuchsia,0)) + array.push(PALETTE, color.new(color.aqua, 0)) + array.push(PALETTE, color.new(color.yellow, 0)) + array.push(PALETTE, color.new(color.purple, 0)) + array.push(PALETTE, color.new(color.lime, 0)) + array.push(PALETTE, color.new(color.red, 0)) + array.push(PALETTE, color.new(color.blue, 0)) + array.push(PALETTE, color.new(color.navy, 0)) + array.push(PALETTE, color.new(color.maroon, 0)) + array.push(PALETTE, color.new(color.silver, 0)) + +f_paletteColor(int idx) => + sz = math.max(1, array.size(PALETTE)) + array.get(PALETTE, idx % sz) + +// 是否为秒级分辨率,如 "1S","5S" +f_isSecondsTf(string tf) => + ln = str.length(tf) + ln > 0 and str.substring(tf, ln - 1, ln) == 'S' + +// 计算可视范围边界(近 N 根 K 线 + 百分比留白) +rangeHi = ta.highest(high, lookbackBars) +rangeLo = ta.lowest(low, lookbackBars) +rangeSpan = rangeHi - rangeLo +pad = rangeSpan * padPercent * 0.01 +minY = rangeLo - pad +maxY = rangeHi + pad + +// 仅在可视区间内绘制(超出返回 na,以避免影响自动缩放)。 +f_gate(series float v) => protectAutoscale ? (v >= minY and v <= maxY ? v : na) : v + +// 检查是否在可视区间内 +f_inRange(series float v) => protectAutoscale ? (v >= minY and v <= maxY) : true + +// 周期字符串美化(如 1 -> 1m, 60 -> 1h, D -> 1D, W -> 1W, 3M -> 3M) +f_prettyTf(string tf) => + s = timeframe.in_seconds(tf) + string out = tf + if not na(s) + if s < 3600 + mins = math.max(1, math.round(s / 60)) + out := str.tostring(mins) + 'm' + else if s % 3600 == 0 and s < 86400 + hrs = math.round(s / 3600) + out := str.tostring(hrs) + 'h' + else if s % 86400 == 0 and s < 7 * 86400 + days = math.round(s / 86400) + out := str.tostring(days) + 'D' + else if s % (7 * 86400) == 0 and s < 30 * 86400 + weeks = math.round(s / (7 * 86400)) + out := str.tostring(weeks) + 'W' + else + out := tf + else + // 月等返回 na 的分辨率保持原样(M/3M) + out := tf + out + +// ======================== 标准时间周期表 ======================== +// 备注:TradingView 分辨率字符串 —— 分钟: "1","3","5"...;小时: "60","120"...; +// 天: "D","2D";周: "W","2W";月: "M","3M";秒级可能为 "1S","5S" 等。 +var string[] ALL_RES = array.new_string() +if barstate.isfirst and array.size(ALL_RES) == 0 + // 由低到高,便于筛选 + // 秒级(若品种/权限不支持,请忽略,筛选时会自动排除) + array.push(ALL_RES, "1S") + array.push(ALL_RES, "5S") + array.push(ALL_RES, "15S") + array.push(ALL_RES, "30S") + // 分钟 + array.push(ALL_RES, "1") + array.push(ALL_RES, "3") + array.push(ALL_RES, "5") + array.push(ALL_RES, "15") + array.push(ALL_RES, "30") + array.push(ALL_RES, "45") + // 小时(以分钟表示) + array.push(ALL_RES, "60") + array.push(ALL_RES, "120") + array.push(ALL_RES, "240") + array.push(ALL_RES, "360") + array.push(ALL_RES, "480") + array.push(ALL_RES, "720") + // 日/周/月 + array.push(ALL_RES, "D") + array.push(ALL_RES, "2D") + array.push(ALL_RES, "3D") + array.push(ALL_RES, "W") + array.push(ALL_RES, "2W") + array.push(ALL_RES, "M") + array.push(ALL_RES, "3M") + +// ======================== 周期间关系与选择 ======================== +curResStr = timeframe.period +curResSec = timeframe.in_seconds(curResStr) + +// 构建长/次级别列表 +var string[] htfRes = array.new_string() +var string[] ltfRes = array.new_string() +array.clear(htfRes) +array.clear(ltfRes) +for i = 0 to array.size(ALL_RES) - 1 + res = array.get(ALL_RES, i) + // 非高级用户默认不包含秒级周期 + if not includeSeconds and f_isSecondsTf(res) + continue + rsec = timeframe.in_seconds(res) + // timeframe.in_seconds 不支持时返回 na,需跳过 + if na(rsec) + continue + if rsec > curResSec + array.push(htfRes, res) + else if rsec < curResSec + array.push(ltfRes, res) + +// 选取靠近当前周期的若干次级别(更有参考意义):取 ltfRes 的末尾(更接近当前) +var string[] ltfSel = array.new_string() +array.clear(ltfSel) +ltfsz = array.size(ltfRes) +if ltfsz > 0 + take = math.min(maxLTFInput, MAX_LTF, ltfsz) + for k = 0 to take - 1 + array.push(ltfSel, array.get(ltfRes, ltfsz - 1 - k)) + +// 限制长级别数量 +htfCountSelected = math.min(array.size(htfRes), maxHTFInput, MAX_HTF) +ltfCountSelected = math.min(array.size(ltfSel), MAX_LTF) + +// ======================== 计算各周期 WMA52 ======================== +// 预分配数值数组(保持固定最大长度) +var float[] htfVals = array.new_float() +var float[] ltfVals = array.new_float() +float ctfVal = na +if barstate.isfirst + for _i = 0 to MAX_HTF - 1 + array.push(htfVals, na) + for _j = 0 to MAX_LTF - 1 + array.push(ltfVals, na) + +// 填充长级别数值 +for i = 0 to MAX_HTF - 1 + float val = na + if enableHTF and i < htfCountSelected + tfStr = array.get(htfRes, i) + // 计算该长级别的 WMA52 + val := request.security(syminfo.tickerid, tfStr, ta.wma(close, 52), barmerge.gaps_off, barmerge.lookahead_off) + array.set(htfVals, i, val) + +// 填充次级别数值 +for i = 0 to MAX_LTF - 1 + float val = na + if enableLTF and i < ltfCountSelected + tfStr = array.get(ltfSel, i) + // 计算该次级别的 WMA52(低级别向上合成,注意其更频繁更新) + val := request.security(syminfo.tickerid, tfStr, ta.wma(close, 52), barmerge.gaps_off, barmerge.lookahead_off) + array.set(ltfVals, i, val) + +// 本级别 WMA52 +if enableCTF + ctfVal := ta.wma(close, 52) + +// ======================== 绘制(虚线 + 标签) ======================== +// 顶层固定 plot(避免在局部/循环中调用 plot) +plot(enableCTF ? f_gate(f_smooth(ctfVal, smoothLenCTF)) : na, title='CTF WMA52', color=color.new(color.white, 0), linewidth=lineWidthCTF, style=plot.style_line) +plot((enableHTF and 0 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 0), smoothLenHTF), 0 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 1', color=f_paletteColor(0), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 1 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 1), smoothLenHTF), 1 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 2', color=f_paletteColor(1), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 2 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 2), smoothLenHTF), 2 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 3', color=f_paletteColor(2), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 3 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 3), smoothLenHTF), 3 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 4', color=f_paletteColor(3), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 4 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 4), smoothLenHTF), 4 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 5', color=f_paletteColor(4), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 5 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 5), smoothLenHTF), 5 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 6', color=f_paletteColor(5), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 6 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 6), smoothLenHTF), 6 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 7', color=f_paletteColor(6), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 7 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 7), smoothLenHTF), 7 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 8', color=f_paletteColor(7), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 8 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 8), smoothLenHTF), 8 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 9', color=f_paletteColor(8), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 9 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 9), smoothLenHTF), 9 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 10', color=f_paletteColor(9), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 10 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 10), smoothLenHTF), 10 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 11', color=f_paletteColor(10), linewidth=lineWidthHTF, style=plot.style_line) +plot((enableHTF and 11 < htfCountSelected) ? f_gate(f_dash(f_smooth(array.get(htfVals, 11), smoothLenHTF), 11 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF WMA52 12', color=f_paletteColor(11), linewidth=lineWidthHTF, style=plot.style_line) + +plot((enableLTF and 0 < ltfCountSelected) ? f_gate(f_dash(f_smooth(array.get(ltfVals, 0), smoothLenLTF), 0 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF WMA52 1', color=color.new(f_paletteColor(0), 30), linewidth=lineWidthLTF, style=plot.style_line) +plot((enableLTF and 1 < ltfCountSelected) ? f_gate(f_dash(f_smooth(array.get(ltfVals, 1), smoothLenLTF), 1 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF WMA52 2', color=color.new(f_paletteColor(1), 30), linewidth=lineWidthLTF, style=plot.style_line) +plot((enableLTF and 2 < ltfCountSelected) ? f_gate(f_dash(f_smooth(array.get(ltfVals, 2), smoothLenLTF), 2 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF WMA52 3', color=color.new(f_paletteColor(2), 30), linewidth=lineWidthLTF, style=plot.style_line) +plot((enableLTF and 3 < ltfCountSelected) ? f_gate(f_dash(f_smooth(array.get(ltfVals, 3), smoothLenLTF), 3 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF WMA52 4', color=color.new(f_paletteColor(3), 30), linewidth=lineWidthLTF, style=plot.style_line) + +// 右侧标签(仅在最后一根柱更新,避免重复创建) +var label[] htfLabels = array.new_label() +var label[] ltfLabels = array.new_label() +var label ctfLabel = na +var string lastTicker = na +var string lastTf = na +if barstate.isfirst + for _i = 0 to MAX_HTF - 1 + array.push(htfLabels, na) + for _j = 0 to MAX_LTF - 1 + array.push(ltfLabels, na) + lastTicker := syminfo.tickerid + lastTf := timeframe.period + +// 更新/清理标签 +// 如果品种或周期变化,清理旧标签 +symbolChanged = lastTicker != syminfo.tickerid +tfChanged = lastTf != timeframe.period +if symbolChanged or tfChanged + for i = 0 to MAX_HTF - 1 + lab = array.get(htfLabels, i) + if not na(lab) + label.delete(lab) + array.set(htfLabels, i, na) + for i = 0 to MAX_LTF - 1 + lab = array.get(ltfLabels, i) + if not na(lab) + label.delete(lab) + array.set(ltfLabels, i, na) + if not na(ctfLabel) + label.delete(ctfLabel) + ctfLabel := na + lastTicker := syminfo.tickerid + lastTf := timeframe.period + +if barstate.islast + // 长级别 + for i = 0 to MAX_HTF - 1 + lab = array.get(htfLabels, i) + if enableHTF and i < htfCountSelected + tfStr = array.get(htfRes, i) + v = array.get(htfVals, i) + col = f_paletteColor(i) + txt = f_prettyTf(tfStr) + ' ' + str.tostring(v, format.mintick) + // 可视区间外则隐藏标签;区间内按真实价格绘制,确保与标尺一致 + if protectAutoscale and not f_inRange(v) + if not na(lab) + label.delete(lab) + array.set(htfLabels, i, na) + else + if na(lab) + lab := label.new(bar_index + labelOffsetBars, v, txt, xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_left, textcolor=col, color=color.new(col, labelBgAlpha), size=f_strToSize(labelSize)) + array.set(htfLabels, i, lab) + else + label.set_xy(lab, bar_index + labelOffsetBars, v) + label.set_text(lab, txt) + label.set_textcolor(lab, col) + label.set_color(lab, color.new(col, labelBgAlpha)) + label.set_style(lab, label.style_label_left) + label.set_size(lab, f_strToSize(labelSize)) + else + if not na(lab) + label.delete(lab) + array.set(htfLabels, i, na) + + // 次级别 + for i = 0 to MAX_LTF - 1 + lab = array.get(ltfLabels, i) + if enableLTF and i < ltfCountSelected + tfStr = array.get(ltfSel, i) + v = array.get(ltfVals, i) + base = f_paletteColor(i) + col = color.new(base, 0) + txt = f_prettyTf(tfStr) + ' ' + str.tostring(v, format.mintick) + if protectAutoscale and not f_inRange(v) + if not na(lab) + label.delete(lab) + array.set(ltfLabels, i, na) + else + if na(lab) + lab := label.new(bar_index + labelOffsetBars, v, txt, xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_left, textcolor=col, color=color.new(col, labelBgAlpha + 10 > 100 ? 100 : labelBgAlpha + 10), size=f_strToSize(labelSize)) + array.set(ltfLabels, i, lab) + else + label.set_xy(lab, bar_index + labelOffsetBars, v) + label.set_text(lab, txt) + label.set_textcolor(lab, col) + label.set_color(lab, color.new(col, labelBgAlpha + 10 > 100 ? 100 : labelBgAlpha + 10)) + label.set_style(lab, label.style_label_left) + label.set_size(lab, f_strToSize(labelSize)) + else + if not na(lab) + label.delete(lab) + array.set(ltfLabels, i, na) + + // 本级别 + if enableCTF + v = ctfVal + col = color.new(color.white, 0) + txt = f_prettyTf(curResStr) + ' ' + str.tostring(v, format.mintick) + if protectAutoscale and not f_inRange(v) + if not na(ctfLabel) + label.delete(ctfLabel) + ctfLabel := na + else + if na(ctfLabel) + ctfLabel := label.new(bar_index + labelOffsetBars, v, txt, xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_left, textcolor=col, color=color.new(col, labelBgAlpha), size=f_strToSize(labelSize)) + else + label.set_xy(ctfLabel, bar_index + labelOffsetBars, v) + label.set_text(ctfLabel, txt) + label.set_textcolor(ctfLabel, col) + label.set_color(ctfLabel, color.new(col, labelBgAlpha)) + label.set_style(ctfLabel, label.style_label_left) + label.set_size(ctfLabel, f_strToSize(labelSize)) + else + if not na(ctfLabel) + label.delete(ctfLabel) + ctfLabel := na + +// ======================== 备注 ======================== +// 1) 当前周期通过 timeframe.period 自动识别,并用 timeframe.in_seconds( +// ) 转换成秒以进行相对大小比较。 +// 2) 长级别/次级别集合从标准分辨率表中过滤得出,绘图数量受 MAX_* 与输入上限控制。 +// 3) 虚线效果通过在 plot 中周期性输出 na 实现,性能优于逐柱绘制 line 对象。 +// 4) 标签在最后一根柱更新,避免过量对象;颜色与曲线一致。 +// 5) 次级别(低于当前)WMA52 也计算并显示,更新频率更高,默认更淡。 + +