diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..536eb78 --- /dev/null +++ b/CLAUDE.md @@ -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. diff --git a/chan_theory_core.pine b/chan_theory_core.pine new file mode 100644 index 0000000..2e5920f --- /dev/null +++ b/chan_theory_core.pine @@ -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() +var float[] mk_low = array.new() +var int[] mk_idx = array.new() +var int[] mk_start_idx = array.new() +var float[] mk_macd = array.new() + +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() +var float[] fx_prices = array.new() +var int[] fx_types = array.new() +var float[] fx_macds = array.new() + +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() + 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) + + // ==================== 构建线段(标准特征序列法) ==================== + 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_raw_highs = array.new() + var float[] feat_raw_lows = array.new() + var int[] feat_raw_end_bars = array.new() + var float[] feat_raw_end_prices = array.new() + 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() + var float[] feat_merged_lows = array.new() + var int[] feat_merged_end_bars = array.new() + var float[] feat_merged_end_prices = array.new() + 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() + 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() + + 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() + 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() + 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() + + 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) diff --git a/多周期EMA52_Pro.pine b/多周期EMA52_Pro.pine new file mode 100644 index 0000000..02f9bbb --- /dev/null +++ b/多周期EMA52_Pro.pine @@ -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 / 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 + +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 也计算并显示,更新频率更高,默认更淡。 + +