添加新的indicator

This commit is contained in:
jackyu66git
2026-05-14 14:17:09 +08:00
parent a414c3d982
commit 35f8916669
3 changed files with 1145 additions and 0 deletions
+38
View File
@@ -0,0 +1,38 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Overview
This is a collection of TradingView Pine Script (v5/v6) indicators and strategies for technical analysis. All scripts are standalone `.pine` files with no build system, package manager, or dependencies — they are pasted directly into the TradingView Pine Editor.
## Working with Pine Script
- There is no local build, lint, or test toolchain. Pine Script can only be validated by pasting into the TradingView web editor.
- All files use `//@version=6` (or occasionally v5). Pine Script v6 is the current target.
- `indicator()` declares an indicator (overlay on chart), `strategy()` declares a backtestable strategy with order execution.
- Common overlay settings: `overlay=true`, `max_lines_count=500`, `max_labels_count=500`, `max_boxes_count=500`, `max_bars_back=5000`.
## File Organization by Category
- **缠论 (Chan Theory)**: `chan_theory*.pine` — Classic Chinese technical theory implementing 分型 (fractals), 笔 (strokes), 线段 (segments), 中枢 (pivot zones). The `chan_theory_complete.pine` is the most comprehensive version (~62KB), combining both Chan Theory and trend identification. Earlier files are iterative improvements.
- **EMA Strategies**: `ema_strategy_4lines.pine`, `ema_macd_trend_indicator.pine`, `ema_macd_trend_strategy.pine`, `ema26_ema52.pine`, `ema26_ema52_diff.pine` — EMA cross/alignment-based trading strategies with MACD confirmation.
- **Multi-Timeframe EMA52**: `多周期EMA52_Pro.pine`, `多周期MA52.pine` — Draws EMA/MA lines from higher/lower timeframes onto the current chart. `_Pro` is the latest and most feature-rich.
- **支撑压力位 (Support/Resistance)**: `支撑压力位*.pine` — Various support/resistance detection indicators (DeepSeek, QwenMax, optimized versions).
- **趋势判断 (Trend Detection)**: `趋势行情判断*.pine` — Trend condition assessment with optimized/super-optimized variants.
- **背驰/背离 (Divergence)**: `macd_divergence.pine`, `high_macd_divergence.pine`, `kdt_divergence.pine`, `last_divergence_simple.pine` — MACD and K-line momentum theory divergence detection.
- **Bollinger Bands**: `bollinger_atr_strategy.pine`, `bollinger_band_strategy_new.pine`.
- **Other**: `裸K趋势反转识别.pine` (naked candlestick pattern reversal), `close_fft_spectrum.pine` (FFT spectrum analysis), `三重滤网均线交易策略.pine` (triple-screen MA strategy), `自定义MACD柱子面积.pine` (custom MACD histogram area).
## Code Patterns
- All user-configurable parameters use `input.*()` grouped with the `group=` parameter (e.g., `group="显示设置"`).
- Display toggles follow the pattern: `showX = input.bool(true, "显示X", group="显示设置")` with conditional plotting via `plot(showX ? value : na, ...)`.
- Color configuration uses `input.color()` for user-customizable colors.
- Chinese is used for parameter labels, tooltips, and comments. Variable names are a mix of English and pinyin (e.g., `show_fenxing`, `color_bi_up`).
- `var` prefix is used for persistent state variables (e.g., `var bool cycle_in_progress = false`).
- Many scripts are iterative refinements — when making changes, prefer editing the latest/most complete version (look for `_Pro`, `_complete`, `_超级优化版`, `_优化版` suffixes).
## Multi-File Relationship
Files in the same category are typically evolutionary versions (simple → optimized → super-optimized → complete). When asked to fix or improve an indicator, identify the latest version first. The README.md and various `*.md` files contain usage instructions and comparison guides for different versions.
+696
View File
@@ -0,0 +1,696 @@
// This Pine Script™ code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © 缠论核心版 - 从 chan_theory_complete 提取:K线包含处理、分型、笔、线段、中枢 + EMA均线趋势
//@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="K线显示")
show_fenxing = input.bool(true, "显示分型", group="分型显示")
show_bi = input.bool(true, "显示笔", group="笔显示")
show_bi_zhongshu = input.bool(true, "显示笔中枢", group="笔显示")
show_xianduan = input.bool(true, "显示线段", group="线段显示")
show_xd_zhongshu = 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="均线显示")
min_bi_bars = input.int(4, "笔最小K线数", minval=3, maxval=10, group="缠论参数")
use_macd_filter = input.bool(false, "MACD过滤分型", tooltip="顶分型要求MACD>0,底分型要求MACD<0", group="缠论参数")
// EMA参数设置
ema1_len = input.int(24, "EMA1周期", minval=5, maxval=100, group="均线参数", tooltip="最快均线,默认24")
ema2_len = input.int(52, "EMA2周期", minval=10, maxval=200, group="均线参数", tooltip="次快均线,默认52")
ema3_len = input.int(104, "EMA3周期", minval=20, maxval=300, group="均线参数", tooltip="次慢均线,默认104")
ema4_len = input.int(156, "EMA4周期", minval=30, maxval=500, group="均线参数", tooltip="EMA156仅用于显示,不参与三均线排列/交叉")
trend_confirm_bars = input.int(3, "趋势确认K线数", minval=1, maxval=20, group="均线参数", tooltip="排列持续N根K线才确认趋势,增大可减少震荡假信号")
color_bi_up = input.color(color.red, "上升笔颜色", group="笔颜色")
color_bi_down = input.color(color.green, "下降笔颜色", group="笔颜色")
color_bi_zs = input.color(color.new(color.yellow, 80), "笔中枢颜色", group="笔颜色")
color_xd = input.color(color.blue, "线段颜色", 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)
// ============================================================================
// 三均线趋势排列检测
// ============================================================================
is_bullish_aligned = ema24 > ema52 and ema52 > ema104
is_bearish_aligned = ema24 < ema52 and ema52 < ema104
is_in_oscillation = not is_bullish_aligned and not is_bearish_aligned
ema_golden_cross = ta.crossover(ema52, ema104)
ema_death_cross = ta.crossunder(ema52, ema104)
plotshape(show_ema52 and show_ema104 and ema_golden_cross and not is_in_oscillation, title="EMA金叉",
style=shape.triangleup, location=location.belowbar,
color=color.lime, size=size.small, text="金叉")
plotshape(show_ema52 and show_ema104 and ema_death_cross and not is_in_oscillation, title="EMA死叉",
style=shape.triangledown, location=location.abovebar,
color=color.red, size=size.small, text="死叉")
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
is_bullish_confirmed = bullish_bars >= trend_confirm_bars
is_bearish_confirmed = bearish_bars >= trend_confirm_bars
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="空头背景")
// ============================================================================
// K线包含处理(缠论标准)
// ============================================================================
var float[] mk_high = array.new<float>()
var float[] mk_low = array.new<float>()
var int[] mk_idx = array.new<int>()
var int[] mk_start_idx = array.new<int>()
var float[] mk_macd = array.new<float>()
var int mk_trend = 0
is_contain(h1, l1, h2, l2) =>
(h1 >= h2 and l1 <= l2) or (h2 >= h1 and l2 <= l1)
mk_size = array.size(mk_high)
if mk_size == 0
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)
else
mk_trend := high > prev_h ? 1 : -1
array.push(mk_high, high)
array.push(mk_low, low)
array.push(mk_idx, bar_index)
array.push(mk_start_idx, bar_index)
array.push(mk_macd, macd_hist)
if array.size(mk_high) > 5000
array.shift(mk_high)
array.shift(mk_low)
array.shift(mk_idx)
array.shift(mk_start_idx)
array.shift(mk_macd)
// ============================================================================
// 基于处理后K线的分型识别
// ============================================================================
var int[] fx_bars = array.new<int>()
var float[] fx_prices = array.new<float>()
var int[] fx_types = array.new<int>()
var float[] fx_macds = array.new<float>()
curr_mk_size = array.size(mk_high)
if curr_mk_size >= 3
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)
is_top = h2 > h1 and h2 > h3
is_bottom = l2 < l1 and l2 < l3
macd_at_fx = array.get(mk_macd, curr_mk_size - 2)
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
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
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, "T",
color=color.new(color.red, 100),
style=label.style_none,
textcolor=color.red,
size=size.normal)
else
label.new(fx_bar, fx_price, "B",
color=color.new(color.green, 100),
style=label.style_none,
textcolor=color.green,
size=size.normal)
// ==================== 构建笔 ====================
var int[] bi_start_bars = array.new<int>()
var float[] bi_start_prices = array.new<float>()
var int[] bi_end_bars = array.new<int>()
var float[] bi_end_prices = array.new<float>()
var int[] bi_dirs = array.new<int>()
array.clear(bi_start_bars)
array.clear(bi_start_prices)
array.clear(bi_end_bars)
array.clear(bi_end_prices)
array.clear(bi_dirs)
if fx_count >= 2
for i = 0 to fx_count - 2
fx1_bar = array.get(fx_bars, i)
fx1_price = array.get(fx_prices, i)
fx1_type = array.get(fx_types, i)
fx2_bar = array.get(fx_bars, i + 1)
fx2_price = array.get(fx_prices, i + 1)
fx2_type = array.get(fx_types, i + 1)
if fx1_type != fx2_type
bi_dir = fx1_type == -1 ? 1 : -1
array.push(bi_start_bars, fx1_bar)
array.push(bi_start_prices, fx1_price)
array.push(bi_end_bars, fx2_bar)
array.push(bi_end_prices, fx2_price)
array.push(bi_dirs, bi_dir)
bi_count = array.size(bi_start_bars)
// ==================== 绘制笔 ====================
if show_bi and bi_count > 0
for i = 0 to bi_count - 1
s_bar = array.get(bi_start_bars, i)
s_price = array.get(bi_start_prices, i)
e_bar = array.get(bi_end_bars, i)
e_price = array.get(bi_end_prices, i)
bi_dir = array.get(bi_dirs, i)
if bar_index - s_bar <= 5000
bi_color = bi_dir == 1 ? color_bi_up : color_bi_down
line.new(s_bar, s_price, e_bar, e_price, color=bi_color, width=2)
// ==================== 构建线段(标准特征序列法) ====================
var int[] xd_start_bars = array.new<int>()
var float[] xd_start_prices = array.new<float>()
var int[] xd_end_bars = array.new<int>()
var float[] xd_end_prices = array.new<float>()
var int[] xd_dirs = array.new<int>()
array.clear(xd_start_bars)
array.clear(xd_start_prices)
array.clear(xd_end_bars)
array.clear(xd_end_prices)
array.clear(xd_dirs)
if bi_count >= 3
var float[] feat_raw_highs = array.new<float>()
var float[] feat_raw_lows = array.new<float>()
var int[] feat_raw_end_bars = array.new<int>()
var float[] feat_raw_end_prices = array.new<float>()
array.clear(feat_raw_highs)
array.clear(feat_raw_lows)
array.clear(feat_raw_end_bars)
array.clear(feat_raw_end_prices)
var float[] feat_merged_highs = array.new<float>()
var float[] feat_merged_lows = array.new<float>()
var int[] feat_merged_end_bars = array.new<int>()
var float[] feat_merged_end_prices = array.new<float>()
array.clear(feat_merged_highs)
array.clear(feat_merged_lows)
array.clear(feat_merged_end_bars)
array.clear(feat_merged_end_prices)
xd_start_bar = array.get(bi_start_bars, 0)
xd_start_price = array.get(bi_start_prices, 0)
xd_dir = array.get(bi_dirs, 0)
xd_bi_count = 1
xd_high = math.max(array.get(bi_start_prices, 0), array.get(bi_end_prices, 0))
xd_low = math.min(array.get(bi_start_prices, 0), array.get(bi_end_prices, 0))
xd_high_bar = xd_dir == 1 ? array.get(bi_end_bars, 0) : array.get(bi_start_bars, 0)
xd_low_bar = xd_dir == 1 ? array.get(bi_start_bars, 0) : array.get(bi_end_bars, 0)
i = 1
while i < bi_count
curr_bi_dir = array.get(bi_dirs, i)
curr_bi_start_bar = array.get(bi_start_bars, i)
curr_bi_start_price = array.get(bi_start_prices, i)
curr_bi_end_bar = array.get(bi_end_bars, i)
curr_bi_end_price = array.get(bi_end_prices, i)
curr_bi_high = math.max(curr_bi_start_price, curr_bi_end_price)
curr_bi_low = math.min(curr_bi_start_price, curr_bi_end_price)
xd_bi_count += 1
if curr_bi_high > xd_high
xd_high := curr_bi_high
xd_high_bar := curr_bi_dir == 1 ? curr_bi_end_bar : curr_bi_start_bar
if curr_bi_low < xd_low
xd_low := curr_bi_low
xd_low_bar := curr_bi_dir == -1 ? curr_bi_end_bar : curr_bi_start_bar
if curr_bi_dir != xd_dir
array.push(feat_raw_highs, curr_bi_high)
array.push(feat_raw_lows, curr_bi_low)
array.push(feat_raw_end_bars, curr_bi_end_bar)
array.push(feat_raw_end_prices, curr_bi_end_price)
// === 特征序列包含处理 ===
fm_size = array.size(feat_merged_highs)
if fm_size == 0
array.push(feat_merged_highs, curr_bi_high)
array.push(feat_merged_lows, curr_bi_low)
array.push(feat_merged_end_bars, curr_bi_end_bar)
array.push(feat_merged_end_prices, curr_bi_end_price)
else
fm_prev_h = array.get(feat_merged_highs, fm_size - 1)
fm_prev_l = array.get(feat_merged_lows, fm_size - 1)
has_contain = (fm_prev_h >= curr_bi_high and fm_prev_l <= curr_bi_low) or (curr_bi_high >= fm_prev_h and curr_bi_low <= fm_prev_l)
if has_contain
fm_trend = 0
if fm_size >= 2
fm_prev2_h = array.get(feat_merged_highs, fm_size - 2)
fm_prev2_l = array.get(feat_merged_lows, fm_size - 2)
fm_trend := fm_prev_h > fm_prev2_h ? 1 : (fm_prev_l < fm_prev2_l ? -1 : 0)
if fm_trend >= 0
array.set(feat_merged_highs, fm_size - 1, math.max(fm_prev_h, curr_bi_high))
array.set(feat_merged_lows, fm_size - 1, math.max(fm_prev_l, curr_bi_low))
else
array.set(feat_merged_highs, fm_size - 1, math.min(fm_prev_h, curr_bi_high))
array.set(feat_merged_lows, fm_size - 1, math.min(fm_prev_l, curr_bi_low))
array.set(feat_merged_end_bars, fm_size - 1, curr_bi_end_bar)
array.set(feat_merged_end_prices, fm_size - 1, curr_bi_end_price)
else
array.push(feat_merged_highs, curr_bi_high)
array.push(feat_merged_lows, curr_bi_low)
array.push(feat_merged_end_bars, curr_bi_end_bar)
array.push(feat_merged_end_prices, curr_bi_end_price)
// 至少3笔后检查线段是否结束
xd_broken = false
xd_break_bar = 0
xd_break_price = 0.0
if xd_bi_count >= 3
fm_size = array.size(feat_merged_highs)
// === 第一种情况:特征序列包含处理后出现分型 ===
if fm_size >= 3
fm_h1 = array.get(feat_merged_highs, fm_size - 3)
fm_h2 = array.get(feat_merged_highs, fm_size - 2)
fm_h3 = array.get(feat_merged_highs, fm_size - 1)
fm_l1 = array.get(feat_merged_lows, fm_size - 3)
fm_l2 = array.get(feat_merged_lows, fm_size - 2)
fm_l3 = array.get(feat_merged_lows, fm_size - 1)
if xd_dir == 1
if fm_h2 > fm_h1 and fm_h2 > fm_h3
xd_broken := true
else
if fm_l2 < fm_l1 and fm_l2 < fm_l3
xd_broken := true
// === 第二种情况:缺口确认反转 ===
if not xd_broken and fm_size >= 2
if xd_dir == 1
fm_prev_h2 = array.get(feat_merged_highs, fm_size - 2)
fm_prev_l2 = array.get(feat_merged_lows, fm_size - 2)
fm_curr_h2 = array.get(feat_merged_highs, fm_size - 1)
fm_curr_l2 = array.get(feat_merged_lows, fm_size - 1)
if fm_curr_h2 < fm_prev_l2
xd_broken := true
else
fm_prev_h2 = array.get(feat_merged_highs, fm_size - 2)
fm_prev_l2 = array.get(feat_merged_lows, fm_size - 2)
fm_curr_h2 = array.get(feat_merged_highs, fm_size - 1)
fm_curr_l2 = array.get(feat_merged_lows, fm_size - 1)
if fm_curr_l2 > fm_prev_h2
xd_broken := true
if xd_broken
xd_end_bar = xd_dir == 1 ? xd_high_bar : xd_low_bar
xd_end_price = xd_dir == 1 ? xd_high : xd_low
array.push(xd_start_bars, xd_start_bar)
array.push(xd_start_prices, xd_start_price)
array.push(xd_end_bars, xd_end_bar)
array.push(xd_end_prices, xd_end_price)
array.push(xd_dirs, xd_dir)
xd_start_bar := xd_end_bar
xd_start_price := xd_end_price
xd_dir := xd_dir == 1 ? -1 : 1
xd_bi_count := 1
xd_high := curr_bi_high
xd_low := curr_bi_low
xd_high_bar := curr_bi_dir == 1 ? curr_bi_end_bar : curr_bi_start_bar
xd_low_bar := curr_bi_dir == -1 ? curr_bi_end_bar : curr_bi_start_bar
array.clear(feat_raw_highs)
array.clear(feat_raw_lows)
array.clear(feat_raw_end_bars)
array.clear(feat_raw_end_prices)
array.clear(feat_merged_highs)
array.clear(feat_merged_lows)
array.clear(feat_merged_end_bars)
array.clear(feat_merged_end_prices)
i += 1
feat_final_size = array.size(feat_merged_highs)
if xd_bi_count >= 3 and feat_final_size >= 2
xd_end_bar = xd_dir == 1 ? xd_high_bar : xd_low_bar
xd_end_price = xd_dir == 1 ? xd_high : xd_low
array.push(xd_start_bars, xd_start_bar)
array.push(xd_start_prices, xd_start_price)
array.push(xd_end_bars, xd_end_bar)
array.push(xd_end_prices, xd_end_price)
array.push(xd_dirs, xd_dir)
xd_count = array.size(xd_start_bars)
// ==================== 绘制线段 ====================
if show_xianduan and xd_count > 0
for i = 0 to xd_count - 1
s_bar = array.get(xd_start_bars, i)
s_price = array.get(xd_start_prices, i)
e_bar = array.get(xd_end_bars, i)
e_price = array.get(xd_end_prices, i)
xd_dir_draw = array.get(xd_dirs, i)
if bar_index - s_bar <= 5000
xd_color = xd_dir_draw == 1 ? color.green : color.red
line.new(s_bar, s_price, e_bar, e_price, color=xd_color, width=4)
// ==================== 构建笔中枢(线段内部) ====================
var int[] bi_zs_start_bars = array.new<int>()
var int[] bi_zs_end_bars = array.new<int>()
var float[] bi_zs_highs = array.new<float>()
var float[] bi_zs_lows = array.new<float>()
var int[] bi_zs_types = array.new<int>()
array.clear(bi_zs_start_bars)
array.clear(bi_zs_end_bars)
array.clear(bi_zs_highs)
array.clear(bi_zs_lows)
array.clear(bi_zs_types)
if xd_count > 0 and bi_count >= 3
for xd_idx = 0 to xd_count - 1
xd_s_bar = array.get(xd_start_bars, xd_idx)
xd_e_bar = array.get(xd_end_bars, xd_idx)
xd_direction = array.get(xd_dirs, xd_idx)
var int[] xd_bi_indices = array.new<int>()
array.clear(xd_bi_indices)
for bi_idx = 0 to bi_count - 1
bi_s = array.get(bi_start_bars, bi_idx)
bi_e = array.get(bi_end_bars, bi_idx)
if bi_s >= xd_s_bar and bi_e <= xd_e_bar
array.push(xd_bi_indices, bi_idx)
local_bi_count = array.size(xd_bi_indices)
if local_bi_count >= 3
j = 0
while j <= local_bi_count - 3
idx1 = array.get(xd_bi_indices, j)
idx2 = array.get(xd_bi_indices, j + 1)
idx3 = array.get(xd_bi_indices, j + 2)
bi1_h = math.max(array.get(bi_start_prices, idx1), array.get(bi_end_prices, idx1))
bi1_l = math.min(array.get(bi_start_prices, idx1), array.get(bi_end_prices, idx1))
bi2_h = math.max(array.get(bi_start_prices, idx2), array.get(bi_end_prices, idx2))
bi2_l = math.min(array.get(bi_start_prices, idx2), array.get(bi_end_prices, idx2))
bi3_h = math.max(array.get(bi_start_prices, idx3), array.get(bi_end_prices, idx3))
bi3_l = math.min(array.get(bi_start_prices, idx3), array.get(bi_end_prices, idx3))
zg = math.min(bi1_h, math.min(bi2_h, bi3_h))
zd = math.max(bi1_l, math.max(bi2_l, bi3_l))
if zg > zd
zs_start = array.get(bi_start_bars, idx1)
zs_end = array.get(bi_end_bars, idx3)
zs_type = xd_direction
last_ext = j + 2
if j + 3 < local_bi_count
for ext_j = j + 3 to local_bi_count - 1
ext_idx = array.get(xd_bi_indices, ext_j)
ext_h = math.max(array.get(bi_start_prices, ext_idx), array.get(bi_end_prices, ext_idx))
ext_l = math.min(array.get(bi_start_prices, ext_idx), array.get(bi_end_prices, ext_idx))
if ext_l < zg and ext_h > zd
zs_end := array.get(bi_end_bars, ext_idx)
last_ext := ext_j
else
break
should_add_zs = true
zs_arr_size = array.size(bi_zs_start_bars)
if zs_arr_size > 0
last_zs_end = array.get(bi_zs_end_bars, zs_arr_size - 1)
if zs_start <= last_zs_end
should_add_zs := false
if should_add_zs
array.push(bi_zs_start_bars, zs_start)
array.push(bi_zs_end_bars, zs_end)
array.push(bi_zs_highs, zg)
array.push(bi_zs_lows, zd)
array.push(bi_zs_types, zs_type)
j := last_ext + 1
else
j += 1
else
j += 1
bi_zs_count = array.size(bi_zs_start_bars)
// ==================== 构建线段中枢 ====================
var int[] xd_zs_start_bars = array.new<int>()
var int[] xd_zs_end_bars = array.new<int>()
var float[] xd_zs_highs = array.new<float>()
var float[] xd_zs_lows = array.new<float>()
var int[] xd_zs_types = array.new<int>()
array.clear(xd_zs_start_bars)
array.clear(xd_zs_end_bars)
array.clear(xd_zs_highs)
array.clear(xd_zs_lows)
array.clear(xd_zs_types)
if xd_count >= 3
i = 0
while i <= xd_count - 3
xd1_dir = array.get(xd_dirs, i)
xd2_dir = array.get(xd_dirs, i + 1)
xd3_dir = array.get(xd_dirs, i + 2)
is_up_xd_zs = xd1_dir == -1 and xd2_dir == 1 and xd3_dir == -1
is_down_xd_zs = xd1_dir == 1 and xd2_dir == -1 and xd3_dir == 1
if is_up_xd_zs or is_down_xd_zs
xd1_s_p = array.get(xd_start_prices, i)
xd1_e_p = array.get(xd_end_prices, i)
xd2_s_p = array.get(xd_start_prices, i + 1)
xd2_e_p = array.get(xd_end_prices, i + 1)
xd3_s_p = array.get(xd_start_prices, i + 2)
xd3_e_p = array.get(xd_end_prices, i + 2)
xd1_h = math.max(xd1_s_p, xd1_e_p)
xd1_l = math.min(xd1_s_p, xd1_e_p)
xd2_h = math.max(xd2_s_p, xd2_e_p)
xd2_l = math.min(xd2_s_p, xd2_e_p)
xd3_h = math.max(xd3_s_p, xd3_e_p)
xd3_l = math.min(xd3_s_p, xd3_e_p)
zg_xd = math.min(xd1_h, math.min(xd2_h, xd3_h))
zd_xd = math.max(xd1_l, math.max(xd2_l, xd3_l))
if zg_xd > zd_xd
zs_start_xd = array.get(xd_start_bars, i)
zs_end_xd = array.get(xd_end_bars, i + 2)
xd_zs_type = is_up_xd_zs ? 1 : -1
last_ext_xd = i + 2
if i + 3 < xd_count
for ext_idx = i + 3 to xd_count - 1
ext_s_p = array.get(xd_start_prices, ext_idx)
ext_e_p = array.get(xd_end_prices, ext_idx)
ext_h = math.max(ext_s_p, ext_e_p)
ext_l = math.min(ext_s_p, ext_e_p)
if ext_l < zg_xd and ext_h > zd_xd
zs_end_xd := array.get(xd_end_bars, ext_idx)
last_ext_xd := ext_idx
else
break
should_add_xd_zs = true
xd_zs_arr_size = array.size(xd_zs_start_bars)
if xd_zs_arr_size > 0
last_xd_zs_end = array.get(xd_zs_end_bars, xd_zs_arr_size - 1)
if zs_start_xd <= last_xd_zs_end
should_add_xd_zs := false
if should_add_xd_zs
array.push(xd_zs_start_bars, zs_start_xd)
array.push(xd_zs_end_bars, zs_end_xd)
array.push(xd_zs_highs, zg_xd)
array.push(xd_zs_lows, zd_xd)
array.push(xd_zs_types, xd_zs_type)
i := last_ext_xd + 1
else
i += 1
else
i += 1
else
i += 1
xd_zs_count = array.size(xd_zs_start_bars)
// ==================== 绘制笔中枢 ====================
if show_bi_zhongshu and bi_zs_count > 0
for i = 0 to bi_zs_count - 1
zs_start = array.get(bi_zs_start_bars, i)
zs_end = array.get(bi_zs_end_bars, i)
zs_h = array.get(bi_zs_highs, i)
zs_l = array.get(bi_zs_lows, i)
zs_type = array.get(bi_zs_types, i)
if bar_index - zs_start <= 5000
zs_border = zs_type == 1 ? color.green : color.red
zs_bg = zs_type == 1 ? color.new(color.green, 85) : color.new(color.red, 85)
box.new(zs_start, zs_h, zs_end, zs_l,
border_color=zs_border, bgcolor=zs_bg, border_width=1)
// ==================== 绘制线段中枢 ====================
if show_xd_zhongshu and xd_zs_count > 0
for i = 0 to xd_zs_count - 1
zs_start = array.get(xd_zs_start_bars, i)
zs_end = array.get(xd_zs_end_bars, i)
zs_h = array.get(xd_zs_highs, i)
zs_l = array.get(xd_zs_lows, i)
xd_zs_type = array.get(xd_zs_types, i)
if bar_index - zs_start <= 5000
xd_zs_border = xd_zs_type == 1 ? color.lime : color.maroon
xd_zs_bg = xd_zs_type == 1 ? color.new(color.lime, 80) : color.new(color.maroon, 80)
box.new(zs_start, zs_h, zs_end, zs_l,
border_color=xd_zs_border, bgcolor=xd_zs_bg, border_width=2)
+411
View File
@@ -0,0 +1,411 @@
//@version=6
indicator("多周期EMA52_Pro", 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="显示")
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="显示")
// 固定 12 周期开关(本级别也会跟随对应周期开关)
showRes1 = input.bool(true, "显示 1m", group="级别开关")
showRes2 = input.bool(true, "显示 3m", group="级别开关")
showRes3 = input.bool(true, "显示 5m", group="级别开关")
showRes4 = input.bool(true, "显示 15m", group="级别开关")
showRes5 = input.bool(true, "显示 30m", group="级别开关")
showRes6 = input.bool(true, "显示 45m", group="级别开关")
showRes7 = input.bool(true, "显示 1h", group="级别开关")
showRes8 = input.bool(true, "显示 2h", group="级别开关")
showRes9 = input.bool(true, "显示 4h", group="级别开关")
showRes10 = input.bool(true, "显示 6h", group="级别开关")
showRes11 = input.bool(true, "显示 8h", group="级别开关")
showRes12 = input.bool(true, "显示 12h", 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(2, "本级别线宽", 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 / TEMAPine 无内置 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
f_showRes(string tf) => switch tf
"1" => showRes1
"3" => showRes2
"5" => showRes3
"15" => showRes4
"30" => showRes5
"45" => showRes6
"60" => showRes7
"120" => showRes8
"240" => showRes9
"360" => showRes10
"480" => showRes11
"720" => showRes12
=> true
// 颜色调色板(循环使用)
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)
// 计算可视范围边界(近 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"...
// 仅保留分钟与小时级别。
var string[] ALL_RES = array.new_string()
if barstate.isfirst and array.size(ALL_RES) == 0
// 由低到高,便于筛选
// 分钟
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")
// ======================== 周期间关系与选择 ========================
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)
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)
// ======================== 计算各周期 EMA52 ========================
// 预分配数值数组(保持固定最大长度)
var float[] htfVals = array.new_float()
var float[] ltfVals = array.new_float()
var float[] htfLabelVals = array.new_float()
var float[] ltfLabelVals = array.new_float()
float ctfVal = na
float ctfLabelVal = na
if barstate.isfirst
for _i = 0 to MAX_HTF - 1
array.push(htfVals, na)
array.push(htfLabelVals, na)
for _j = 0 to MAX_LTF - 1
array.push(ltfVals, na)
array.push(ltfLabelVals, na)
// 填充长级别数值
for i = 0 to MAX_HTF - 1
float val = na
float labelVal = na
if enableHTF and i < htfCountSelected
tfStr = array.get(htfRes, i)
if f_showRes(tfStr)
// 计算该长级别的 EMA52
val := request.security(syminfo.tickerid, tfStr, ta.ema(close, 52), barmerge.gaps_off, barmerge.lookahead_off)
// 标签不跟随虚线断续,仅跟随级别开关与可视区间
labelVal := f_gate(f_smooth(val, smoothLenHTF))
array.set(htfVals, i, val)
array.set(htfLabelVals, i, labelVal)
// 填充次级别数值
for i = 0 to MAX_LTF - 1
float val = na
float labelVal = na
if enableLTF and i < ltfCountSelected
tfStr = array.get(ltfSel, i)
if f_showRes(tfStr)
// 计算该次级别的 EMA52(低级别向上合成,注意其更频繁更新)
val := request.security(syminfo.tickerid, tfStr, ta.ema(close, 52), barmerge.gaps_off, barmerge.lookahead_off)
// 标签不跟随虚线断续,仅跟随级别开关与可视区间
labelVal := f_gate(f_smooth(val, smoothLenLTF))
array.set(ltfVals, i, val)
array.set(ltfLabelVals, i, labelVal)
// 本级别 EMA52
if enableCTF and f_showRes(curResStr)
ctfVal := ta.ema(close, 52)
ctfLabelVal := (enableCTF and f_showRes(curResStr)) ? f_gate(f_smooth(ctfVal, smoothLenCTF)) : na
// ======================== 绘制(虚线 + 标签) ========================
// 顶层固定 plot(避免在局部/循环中调用 plot)
plot((enableCTF and f_showRes(curResStr)) ? f_gate(f_smooth(ctfVal, smoothLenCTF)) : na, title='CTF EMA52', color=color.new(color.white, 0), linewidth=lineWidthCTF, style=plot.style_line)
plot((enableHTF and 0 < htfCountSelected and not na(array.get(htfVals, 0))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 0), smoothLenHTF), 0 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 1', color=f_paletteColor(0), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 1 < htfCountSelected and not na(array.get(htfVals, 1))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 1), smoothLenHTF), 1 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 2', color=f_paletteColor(1), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 2 < htfCountSelected and not na(array.get(htfVals, 2))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 2), smoothLenHTF), 2 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 3', color=f_paletteColor(2), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 3 < htfCountSelected and not na(array.get(htfVals, 3))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 3), smoothLenHTF), 3 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 4', color=f_paletteColor(3), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 4 < htfCountSelected and not na(array.get(htfVals, 4))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 4), smoothLenHTF), 4 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 5', color=f_paletteColor(4), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 5 < htfCountSelected and not na(array.get(htfVals, 5))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 5), smoothLenHTF), 5 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 6', color=f_paletteColor(5), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 6 < htfCountSelected and not na(array.get(htfVals, 6))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 6), smoothLenHTF), 6 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 7', color=f_paletteColor(6), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 7 < htfCountSelected and not na(array.get(htfVals, 7))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 7), smoothLenHTF), 7 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 8', color=f_paletteColor(7), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 8 < htfCountSelected and not na(array.get(htfVals, 8))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 8), smoothLenHTF), 8 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 9', color=f_paletteColor(8), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 9 < htfCountSelected and not na(array.get(htfVals, 9))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 9), smoothLenHTF), 9 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 10', color=f_paletteColor(9), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 10 < htfCountSelected and not na(array.get(htfVals, 10))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 10), smoothLenHTF), 10 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 11', color=f_paletteColor(10), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableHTF and 11 < htfCountSelected and not na(array.get(htfVals, 11))) ? f_gate(f_dash(f_smooth(array.get(htfVals, 11), smoothLenHTF), 11 * 2, dashLenHTF, dashGapHTF)) : na, title='HTF EMA52 12', color=f_paletteColor(11), linewidth=lineWidthHTF, style=plot.style_line)
plot((enableLTF and 0 < ltfCountSelected and not na(array.get(ltfVals, 0))) ? f_gate(f_dash(f_smooth(array.get(ltfVals, 0), smoothLenLTF), 0 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF EMA52 1', color=color.new(f_paletteColor(0), 30), linewidth=lineWidthLTF, style=plot.style_line)
plot((enableLTF and 1 < ltfCountSelected and not na(array.get(ltfVals, 1))) ? f_gate(f_dash(f_smooth(array.get(ltfVals, 1), smoothLenLTF), 1 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF EMA52 2', color=color.new(f_paletteColor(1), 30), linewidth=lineWidthLTF, style=plot.style_line)
plot((enableLTF and 2 < ltfCountSelected and not na(array.get(ltfVals, 2))) ? f_gate(f_dash(f_smooth(array.get(ltfVals, 2), smoothLenLTF), 2 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF EMA52 3', color=color.new(f_paletteColor(2), 30), linewidth=lineWidthLTF, style=plot.style_line)
plot((enableLTF and 3 < ltfCountSelected and not na(array.get(ltfVals, 3))) ? f_gate(f_dash(f_smooth(array.get(ltfVals, 3), smoothLenLTF), 3 * 3 + 1, dashLenLTF, dashGapLTF)) : na, title='LTF EMA52 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(htfLabelVals, i)
col = f_paletteColor(i)
txt = f_prettyTf(tfStr) + ' ' + str.tostring(v, format.mintick)
// 与曲线显示条件同步:平滑后且可视区间内才显示标签
if na(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(ltfLabelVals, i)
base = f_paletteColor(i)
col = color.new(base, 0)
txt = f_prettyTf(tfStr) + ' ' + str.tostring(v, format.mintick)
if na(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 and f_showRes(curResStr)
v = ctfLabelVal
col = color.new(color.white, 0)
txt = f_prettyTf(curResStr) + ' ' + str.tostring(v, format.mintick)
if na(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) 次级别(低于当前)EMA52 也计算并显示,更新频率更高,默认更淡。