From bfb9c14bd3e2d726ca55a9fa459c930b8198fe02 Mon Sep 17 00:00:00 2001 From: aaron <> Date: Sun, 24 May 2026 22:54:53 +0800 Subject: [PATCH] first commit --- AGENTS.md | 104 ++ ...5_30_144_macd_volume_radiation_strategy.md | 113 +++ docs/ema_ribbon_state_signal_indicator.md | 262 +++++ ...0_144_macd_volume_radiation_indicator.pine | 218 ++++ ...30_144_macd_volume_radiation_strategy.pine | 196 ++++ pine/ema_ribbon_state_signal_indicator.pine | 957 ++++++++++++++++++ 6 files changed, 1850 insertions(+) create mode 100644 AGENTS.md create mode 100644 docs/ema_5_15_30_144_macd_volume_radiation_strategy.md create mode 100644 docs/ema_ribbon_state_signal_indicator.md create mode 100644 pine/ema_5_15_30_144_macd_volume_radiation_indicator.pine create mode 100644 pine/ema_5_15_30_144_macd_volume_radiation_strategy.pine create mode 100644 pine/ema_ribbon_state_signal_indicator.pine diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..dd484d1 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,104 @@ +# 代理定位 + +你是一名量化交易策略专家,同时精通 TradingView Pine Script 脚本开发。你的核心职责是协助用户设计、验证、实现和迭代交易策略,用严谨的研究流程、工程化实现能力和严格的风险意识,把策略想法转化为可测试、可复盘、可持续优化的交易系统。 + +## 核心身份 + +- 你是资深量化策略研究员、系统化交易员和交易工具工程师。 +- 你精通趋势跟踪、均值回归、动量、突破、波动率、因子、市场状态识别、配对交易、统计套利、多周期共振等策略框架。 +- 你精通 TradingView Pine Script,包括指标、策略、告警、回测逻辑、防重绘、多周期数据调用、脚本性能限制和实盘信号一致性。 +- 你会把每一个策略想法都视为一个需要验证的假设,先明确规则,再进行回测、压力测试、鲁棒性检查和风险控制设计。 +- 你与用户的交流和沟通默认全部使用中文,除非用户明确要求使用其他语言。 + +## 策略能力范围 + +处理量化策略任务时,你需要重点考虑: + +- 市场结构:趋势与震荡状态、流动性、波动率聚集、交易时段、跳空、涨跌停规则和不同品种的交易约束。 +- 信号设计:入场、出场、过滤条件、确认条件、失效条件、仓位管理和组合级风险暴露。 +- 常见策略类型: + - 趋势跟踪与均线系统 + - 动量与相对强弱 + - 突破与波动率扩张 + - 均值回归与超买超卖 + - 量价确认 + - 多因子评分 + - 市场宽度与板块轮动 + - 配对交易与价差逻辑 + - 事件驱动与新闻辅助框架 +- 风险控制: + - 止损、止盈、移动止损、时间止损、波动率止损 + - 最大回撤、单日亏损上限、总敞口限制、相关性限制 + - 固定风险仓位、ATR 仓位、波动率目标仓位、保守 Kelly 思路 + - 防止过拟合、未来函数、幸存者偏差和数据窥探 + +## Pine Script 编写规范 + +编写 Pine Script 时: + +- 默认使用 Pine Script v5,除非用户明确要求其他版本。 +- 代码要清晰、模块化,并通过 `input.*` 提供可调参数。 +- 明确区分信号计算、过滤逻辑、交易执行、图形绘制和告警条件。 +- 默认避免重绘;如果策略逻辑存在重绘风险,必须明确说明。 +- 谨慎使用 `request.security()`,明确多周期调用、是否使用未来数据以及信号确认方式。 +- 编写策略脚本时,尽量设置现实的手续费、滑点、加仓规则和仓位方式。 +- 在有实际使用价值时添加 `alertcondition()` 告警条件。 +- 只在非显而易见的交易逻辑处添加简洁注释,避免无意义注释。 + +## 策略研究流程 + +开发策略时,遵循以下流程: + +1. 明确交易市场、品种、周期、方向、持仓时间和约束条件。 +2. 把策略想法转化为明确的入场、出场、过滤和风控规则。 +3. 在写代码前识别潜在失效场景。 +4. 实现清晰、可审计的策略原型。 +5. 建议检查关键回测指标:净利润、最大回撤、盈亏比、胜率、平均盈利/亏损、资金占用、交易次数、不同市场状态表现和样本外表现。 +6. 建议进行鲁棒性测试:参数敏感性、多品种测试、多周期测试、不同市场环境测试、手续费/滑点压力测试,以及必要时的交易序列重排测试。 +7. 只有当改动背后有清晰假设时,才进行策略迭代。 + +## 仓库协作规范 + +在本仓库中工作时: + +- 修改前先阅读现有文件和项目约定。 +- 保持改动聚焦,不做无关重构。 +- 不覆盖用户已有工作。 +- 优先采用简单、可审计、可复盘的实现方式,而不是聪明但脆弱的写法。 +- 如果创建脚本、文档、Notebook 或策略模块,文件命名要清晰,并说明关键假设。 +- 能运行测试或验证命令时,应尽量执行并汇报结果。 + +## 沟通风格 + +- 默认使用中文与用户交流。 +- 表达直接、分析清晰、重视实操。 +- 在给代码前或给代码同时,先说明策略逻辑。 +- 明确指出不确定性、数据限制和隐藏风险。 +- 不承诺收益,不把回测结果包装成实盘确定性。 +- 清楚区分“策略想法”“回测证据”和“可实盘部署”。 +- 当用户给出的策略想法不完整时,优先做合理假设并简要说明;必要时只问最关键的问题。 + +## 默认输出要求 + +当用户提出新策略需求时,默认提供: + +- 策略思路 +- 明确交易规则 +- 风险管理方案 +- 用户需要或语境明显要求时,提供 Pine Script 或其他实现代码 +- 回测与鲁棒性检查清单 +- 关于过拟合、交易成本和市场状态依赖的现实提醒 + +当用户要求代码审查时,优先检查: + +- 是否存在重绘 +- 是否存在未来函数或前视偏差 +- 下单逻辑是否错误 +- 回测假设是否不现实 +- 参数是否过拟合 +- 是否缺少出场或风控 +- 告警信号与策略交易是否不一致 + +## 基本原则 + +目标不是制造看起来漂亮的信号,而是构建规则明确、可测试、风险可控、弱点可见的交易系统。任何策略在投入真实资金前,都必须经过充分验证和风险评估。 diff --git a/docs/ema_5_15_30_144_macd_volume_radiation_strategy.md b/docs/ema_5_15_30_144_macd_volume_radiation_strategy.md new file mode 100644 index 0000000..4de93ab --- /dev/null +++ b/docs/ema_5_15_30_144_macd_volume_radiation_strategy.md @@ -0,0 +1,113 @@ +# EMA 5/15/30/144 MACD Volume Radiation Strategy + +## 策略定位 + +这是把“5/15/30/144 均线放射 + MACD + 量能 + 级别定义”工程化后的第一版 TradingView 策略原型。 + +核心假设: + +- 趋势启动后的有效行情,通常会出现 EMA5、EMA15、EMA30、EMA144 的顺序排列。 +- 趋势有力度时,EMA5 与 EMA144 的斜率差会明显扩大。 +- 均线之间的间距不是一次性打开,而是连续扩张,形成可量化的“放射延续”。 +- 连续阶梯 K 线用于确认价格结构已经沿趋势方向推进。 +- MACD 用于过滤动能,量能用于过滤无效扩张。 +- 15m/1H 用于定义级别,避免 5m 逆大级别硬趋势乱做。 + +## 规则翻译 + +### 1. 均线排列 + +做多: + +- EMA144 < EMA30 < EMA15 < EMA5 + +做空: + +- EMA144 > EMA30 > EMA15 > EMA5 + +### 2. 开放性夹角 + +原规则的“EMA5-EMA144 夹角 > 30 度”不能直接用图表角度计算,因为 TradingView 视觉角度会随缩放变化。 + +当前实现使用 ATR 标准化斜率角: + +- 计算 EMA5 的标准化斜率角。 +- 计算 EMA144 的标准化斜率角。 +- 要求两者夹角差达到 `EMA5-EMA144 最小夹角差`。 + +这样不同价格、不同波动率下更可比。 + +### 3. 放射性线的波长和延续性 + +当前用两部分量化: + +- 放射宽度:`abs(EMA5 - EMA144) / ATR` +- 放射延续:EMA5-15、EMA15-30、EMA30-144 三段间距连续扩大 + +参数: + +- `放射均线最小宽度 / ATR` +- `放射扩张连续 K 数` +- `放射延续最少 K 数` +- `放射最长追踪 K 数` + +这相当于把“波长”定义为均线打开的空间,把“延续性”定义为连续扩张的 K 数。 + +### 4. 连续三根 K 线阶梯 + +做多: + +- 连续 N 根收盘价上升 +- 连续 N 根低点上移 + +做空: + +- 连续 N 根收盘价下降 +- 连续 N 根高点下移 + +默认 N = 3。 + +### 5. MACD 和量能 + +MACD 默认采用“交叉 / 柱缩二选一”: + +- 做多:MACD 金叉,或者绿柱连续缩短 +- 做空:MACD 死叉,或者红柱连续缩短 + +量能默认采用放量确认: + +- 当前成交量大于成交量均线的指定倍数 + +也可以切换为缩量回踩模式或关闭。 + +### 6. 级别定义 + +默认: + +- 15m 用于过滤方向。 +- 1H 用于风险分级。 + +如果 5m 做多,但 15m 强空,则多头信号被过滤。 + +如果 5m 做多,但 1H 强空,默认不拦截,只显示 `逆1H` 风险标记。 + +如果想更保守,可以把 `级别过滤模式` 改成 `15m+1H 都过滤`。 + +## 风险管理 + +默认: + +- ATR 止损。 +- ATR 止盈。 +- 可启用 EMA30 移动止损。 +- 均线排列破坏时离场。 + +这只是第一版回测框架,不代表可以直接实盘。后续需要重点检查: + +- XAUUSD 5m 不同时段表现。 +- 伦敦盘、纽约盘、亚洲盘是否需要不同参数。 +- 夹角阈值 30 是否过高或过低。 +- 放量确认是否导致入场过晚。 +- MACD 缩量信号是否提前但假信号过多。 +- 15m/1H 过滤是否过度过滤早期反转。 + diff --git a/docs/ema_ribbon_state_signal_indicator.md b/docs/ema_ribbon_state_signal_indicator.md new file mode 100644 index 0000000..c21008f --- /dev/null +++ b/docs/ema_ribbon_state_signal_indicator.md @@ -0,0 +1,262 @@ +# EMA Ribbon State Signal 指标说明 + +## 定位 + +这是基于截图重新设计的干净版 5 分钟指标,只围绕 EMA5、EMA15、EMA30、EMA144 的状态变化给信号。 + +核心目标是抓“趋势已经形成后,价格回踩/反抽 EMA5/15/30 均线带,再顺势恢复”的右侧机会。 + +它不继承旧指标里的失效反转、PA、144 十字星等混合逻辑。当前识别两类机会: + +- 均线带启动 +- 均线带中继 + +默认主信号只做趋势形成后的回踩/反抽,主图只显示 `做多` 和 `做空`。均线带启动默认只作为辅助小箭头,用来观察趋势是否刚开始,不作为默认开仓信号。 + +当前主信号采用五层判断: + +- EMA144 判断大方向。 +- EMA5/15/30 判断均线带排列和发散。 +- 价格必须回踩/反抽到 EMA5/15/30 均线带。 +- 确认 K 必须重新顺势收回 EMA5/EMA15 附近,不能追高/追低。 +- ADX/DI 判断是否有足够趋势强度,过滤震荡假信号。 + +新增 `预启动` 信号用于满足更早的观察需求。它不是趋势已经确认的开仓信号,而是“压缩末端、方向开始倾斜、价格贴近短结构突破位”的提前预警。默认只显示 `预多` / `预空` 小标签,不改变原来的 `做多` / `做空` 主信号;如果希望把预启动也作为主信号,可以把 `预启动信号模式` 改为 `并入主信号`。 + +新增 `多周期趋势过滤` 用于避免 5m 信号逆更大级别硬趋势。默认使用 15m 和 30m 做方向过滤,1H 做风险分级: + +- 15m / 30m 如果出现强反向趋势,会拦截对应方向的 5m 预启动、启动和中继信号。 +- 1H 默认只显示 `逆1H` 风险标记,不直接拦截,避免把早期反转机会全部过滤掉。 +- 如果希望更保守,可以把 `风险周期强反向处理` 改成 `拦截强反向`。 +- 多周期评分由四项组成:价格相对 EMA144、EMA5/15/30 排列、EMA5/15 斜率、EMA144 斜率。 +- 分数达到 `多周期强趋势分数` 后视为强趋势,默认阈值是 2。 + +## 均线分工 + +- EMA144:大方向过滤线。 +- EMA5:短线动能线。 +- EMA15:趋势中轴。 +- EMA30:趋势带边界。 + +## 做多信号 + +### 预启动做多 + +用于识别趋势正式启动前的蓄势位置。它比“均线带启动做多”更早,但假信号也会更多。 + +条件: + +- 最近 EMA5/15/30 曾经压缩,并且当前均线带仍未明显发散。 +- 当前还没有进入成熟多头或空头趋势,避免在趋势中后段继续报“预启动”。 +- 压缩区必须相对干净,EMA144 和短均线不能反复穿越过多。 +- 价格贴近最近短线高点,但收盘仍与突破位保留最小差距,避免已经突破后才报预警。 +- EMA5、EMA15、EMA30 至少开始多头顺排,EMA5 和 EMA15 向上,短均线斜率数量达到当前信号风格要求。 +- 当前 K 线为阳线,实体达到最低要求。 +- DI 方向偏多。 +- 价格允许贴近 EMA144,但不能明显处在相反硬趋势中。 +- 上方供需区空间过滤仍然生效,避免直接预警到阻力位下方。 + +### 均线带启动做多 + +用于识别从震荡/纠缠进入多头趋势的早期机会。默认主信号模式下,它只作为辅助标记。 + +条件: + +- 前面一段时间 EMA5/15/30 曾经压缩。 +- 价格默认在 EMA144 上方。 +- EMA5 > EMA15 > EMA30。 +- 至少两条短均线向上,保守模式要求三条都向上。 +- 均线带开始扩大。 +- 当前 K 线收在 EMA5 上方,并且是阳线。 +- 均衡/保守模式默认要求突破最近短线高点结构。 +- 不能处在局部过高位置,减少高位追多。 + +### 均线带中继做多 + +用于识别多头趋势中的回踩后继续上涨。 + +条件: + +- 价格默认在 EMA144 上方。 +- EMA5 > EMA15 > EMA30。 +- EMA5、EMA15、EMA30 必须全部在 EMA144 上方。 +- 至少两条短均线向上。 +- 趋势状态至少维持 `趋势形成确认 K 数`。 +- 均线带必须达到最小发散宽度,且 EMA5-EMA15、EMA15-EMA30 之间要有最小间距。 +- 价格与 EMA144 至少拉开 `趋势离 EMA144 最小距离 / ATR`。 +- 最近几根 K 线回踩到 EMA5/15/30 均线带。 +- 当前 K 线重新收回 EMA5 和 EMA15 上方,并且是阳线。 +- 收盘价不能离 EMA5、EMA15 太远。 +- 确认 K 不能处在最近一段区间的高位,避免回踩后已经冲高才追多。 + +## 做空信号 + +### 预启动做空 + +用于识别趋势正式下破前的蓄势位置。 + +条件: + +- 最近 EMA5/15/30 曾经压缩,并且当前均线带仍未明显发散。 +- 当前还没有进入成熟多头或空头趋势,避免在趋势中后段继续报“预启动”。 +- 压缩区必须相对干净,EMA144 和短均线不能反复穿越过多。 +- 价格贴近最近短线低点,但收盘仍与跌破位保留最小差距,避免已经跌破后才报预警。 +- EMA5、EMA15、EMA30 至少开始空头顺排,EMA5 和 EMA15 向下,短均线斜率数量达到当前信号风格要求。 +- 当前 K 线为阴线,实体达到最低要求。 +- DI 方向偏空。 +- 价格允许贴近 EMA144,但不能明显处在相反硬趋势中。 +- 下方供需区空间过滤仍然生效,避免直接预警到支撑位上方。 + +### 均线带启动做空 + +用于识别从震荡/纠缠进入空头趋势的早期机会。默认主信号模式下,它只作为辅助标记。 + +条件: + +- 前面一段时间 EMA5/15/30 曾经压缩。 +- 价格默认在 EMA144 下方。 +- EMA5 < EMA15 < EMA30。 +- 至少两条短均线向下,保守模式要求三条都向下。 +- 均线带开始扩大。 +- 当前 K 线收在 EMA5 下方,并且是阴线。 +- 均衡/保守模式默认要求跌破最近短线低点结构。 +- 不能处在局部过低位置,减少低位追空。 + +### 均线带中继做空 + +用于识别空头趋势中的反抽后继续下跌。 + +条件: + +- 价格默认在 EMA144 下方。 +- EMA5 < EMA15 < EMA30。 +- EMA5、EMA15、EMA30 必须全部在 EMA144 下方。 +- 至少两条短均线向下。 +- 趋势状态至少维持 `趋势形成确认 K 数`。 +- 均线带必须达到最小发散宽度,且 EMA5-EMA15、EMA15-EMA30 之间要有最小间距。 +- 价格与 EMA144 至少拉开 `趋势离 EMA144 最小距离 / ATR`。 +- 最近几根 K 线反抽到 EMA5/15/30 均线带。 +- 当前 K 线重新跌回 EMA5 和 EMA15 下方,并且是阴线。 +- 收盘价不能离 EMA5、EMA15 太远。 +- 确认 K 不能处在最近一段区间的低位,避免反抽后已经杀跌才追空。 +- 不能处在局部过低位置。 + +## 参数重点 + +- `启用多周期趋势过滤` + - 默认开启。 + - 15m 和 30m 用于过滤明显逆向的 5m 信号。 + - 1H 用于风险分级,默认只标记 `逆1H`。 + - 这套逻辑不是要求所有大周期同向,因为那会让“趋势启动前信号”变成右侧追随信号。 +- `风险周期强反向处理` + - 默认 `只标记风险`。 + - 当 5m 做多但 1H 强空时,信号仍可出现,但图上会有 `逆1H` 风险标记。 + - 改为 `拦截强反向` 后,逆 1H 的信号会直接被过滤。 + - XAUUSD 5m 如果你只做顺大周期,可以用拦截模式;如果你想抓早期反转,建议先保持只标记。 + +- `主信号模式` + - 默认 `只做趋势回踩`。 + - 这种模式不把第一次突破当作正式开仓信号,而是等待趋势形成后,抓回踩/反抽确认。 + - 如果想同时显示突破启动的正式做多/做空,可以切换为 `启动+趋势回踩`。 +- `启用趋势末段风险过滤` + - 默认开启。 + - 同一段趋势只允许有限次数的主信号,默认最多 3 次。 + - 这个过滤用于处理趋势中后段继续追多/追空的风险,避免一段行情走了很久之后仍然反复给顺势开仓信号。 + - 如果你想更保守,可以把 `单段趋势最多主信号次数` 调到 2 或 3;如果想放开限制,可以调到 0。 + - `趋势最长有效 K 数` 默认是 0,表示不按持续时间过滤;如果后续发现趋势末段仍然偏多,可以设置为 40 到 80 做压力测试。 +- `启用供需区空间过滤` + - 默认开启。 + - 指标会用左侧 swing low 形成需求区,用 swing high 形成供应区。 + - 需求区用绿色半透明 box 标记,供应区用红色半透明 box 标记。 + - 在 box 最右侧中线位置会显示当前级别和测试次数,例如 `5m 需求 x2`、`5m 供应 x1`。 + - 做空前会检查下方最近需求区是否太近;做多前会检查上方最近供应区是否太近。 + - 需求区被实体收盘跌破后失效并删除;供应区被实体收盘冲破后失效并删除。 + - 每次价格重新进入供需区会记一次测试,默认测试达到 3 次后变成灰色弱化区。 + - 弱化区仍显示在图上作为结构参考,但不再作为强供需区参与信号过滤。 + - `信号到供需区最小空间 / ATR` 越大,越不容易在供需区附近追单。 + - 默认需要 6/6 摆点确认,并且确认后价格至少反应 1.2 ATR,避免把每个小波动都画成供需区。 + - 有效供需区的 box 会持续延伸到最新 K 线;失效后删除,弱化后变灰。 + - `每边最多显示供需区数量` 默认 3,控制图上保留多少个 box,避免图表太乱。 +- `显示高周期供需区 Box` + - 默认开启。 + - 当前图表是 5m 时,默认额外计算并显示 1H 和 4H 的供需区。 + - 高周期 box 使用更深的颜色和更粗的边框,标签显示在 box 右侧中线,例如 `1H 需求 x1`、`4H 供应 x2`。 + - `高周期供需区参与信号过滤` 默认开启。做空时,如果下方距离 1H/4H 需求区太近,会拦截信号;做多时,如果上方距离 1H/4H 供应区太近,会拦截信号。 + - `高周期供需区硬拦截` 默认开启。做空信号 K 如果触碰或贴近 1H/4H 需求区,会直接禁止主做空;做多信号 K 如果触碰或贴近 1H/4H 供应区,会直接禁止主做多。 + - `高周期供需区禁入缓冲 / ATR` 默认 1.2,用来定义距离高周期区多近算危险区域。 + - `触碰高周期区后禁反向 K 数` 默认 8。价格刚测试过高周期需求区后,短时间内不继续追空;刚测试过高周期供应区后,短时间内不继续追多。 + - `供需区反应拦截` 默认开启。价格打入需求区后,如果出现阳包阴、突破前高或急跌后的强阳反抽,会冻结做空一段时间;价格打入供应区后,如果出现阴包阳、跌破前低或急涨后的强阴反抽,会冻结做多一段时间。 + - 这条规则用于处理“已经打入 1H/4H 需求区并出现买盘反应,但均线仍滞后给空”的情况。 + - 因为高周期供需区需要摆点确认,box 可能会在后面回画到左侧;为了避免等待确认造成滞后,指标还会识别“急跌到短线新低后的强阳反应”,提前冻结做空。 + - 高周期供需区同样遵循失效和弱化规则:实体突破后删除,测试达到默认 3 次后变灰并不再作为强区过滤。 +- `启用 ADX 趋势过滤` + - 默认开启。 + - 用 ADX 判断趋势强度,用 DI 判断方向。 + - 做多要求 `+DI > -DI`,做空要求 `-DI > +DI`。 + - 中继信号默认要求 ADX 达到最小值。 + - 启动信号允许 ADX 连续上升替代阈值,因为趋势刚启动时 ADX 可能滞后。 +- `ADX 最小值` + - 默认 18。 + - 值越高,信号越少,但更偏趋势行情。 + - XAUUSD 5 分钟可以重点测试 18、20、22。 +- `要求均线带在 EMA144 同侧` + - 默认开启。 + - 做多不只要求价格在 EMA144 上方,还要求 EMA5/15/30 整个均线带在 EMA144 上方。 + - 做空不只要求价格在 EMA144 下方,还要求 EMA5/15/30 整个均线带在 EMA144 下方。 + - 这个过滤用来避免强多头趋势里短线回调误触发做空,或强空头趋势里短线反抽误触发做多。 +- `避开价格均线缠绕区` + - 默认开启。 + - 当价格频繁穿越 EMA5/15/30、K 线包住均线带、价格夹在均线带内部、离 EMA144 太近,或均线带本身过窄时,不给开仓信号。 + - 如果最近一段价格区间围绕 EMA144 横着走、短均线带跨在 EMA144 上,或价格反复穿越 EMA144,也会视为 144 缠绕区。 + - 这类区域看起来有很多短线突破,但大多是震荡噪音,容易来回打损。 + - `脱离缠绕后确认 K 数` 默认是 0,不额外等待;如果仍然觉得 144 附近噪音太多,可以手动调到 2 到 5。 + - 启动信号不要求完全脱离所有缠绕条件,但必须是真正“逃逸”:收盘离开均线带一段 ATR 距离、均线带已经打开到最低宽度,且如果刚从缠绕区出来,确认 K 实体要更大,并且收盘要离 EMA144 有最小距离。 + - 中继信号更严格,仍然要求不处在缠绕禁区。 +- `任意信号冷却 K 数` + - 默认 6。 + - 用来限制做多和做空之间来回切换,尤其是 EMA144 附近震荡时,避免连续出现多空互相打架的信号。 +- `信号风格` + - 激进:更早,信号更多。 + - 均衡:默认。 + - 保守:更慢,要求更强。 +- `启动前最大均线带宽 / ATR` + - 越大越容易识别启动。 + - 越小越强调从明显压缩后启动。 +- `启动突破结构回看 K 数` + - 控制启动信号是否必须突破最近高低点。 + - 默认较短,用来抓刚脱离均线带的启动,而不是等大幅破位后才提示。 +- `启动脱离均线带距离 / ATR` + - 防止刚刚探出均线带一点点就触发启动。 +- `缠绕逃逸实体 / ATR` + - 当启动发生在缠绕区附近时,要求确认 K 有更强实体。 +- `缠绕逃逸离 EMA144 / ATR` + - 防止贴着 EMA144 的假突破被误判成启动。 +- `中继回踩有效 K 数` + - 控制回踩 EMA15/30 后,几根 K 内确认仍有效。 +- `中继确认离 EMA5 最远 / ATR` + - 防止趋势已经冲太远之后才追。 +- `信号 K 必须收在均线带外` + - 默认开启。 + - 做多信号要求收盘在 EMA5/15/30 均线带上方。 + - 做空信号要求收盘在 EMA5/15/30 均线带下方。 + +## 状态看板 + +看板保持简洁,只显示交易决策最关键的信息: + +- `方向`:当前偏多、偏空、多头或空头。 +- `趋势`:趋势是否成熟,或者是否处于缠绕禁区。 +- `结构`:是否靠近本级别或高周期供需区。 +- `位置`:当前是否靠近短线区间高位/低位。 +- `信号`:当前是否出现做多、做空,或因为高周期供需区被拦截。 + +## 复盘重点 + +后续看截图时优先检查: + +- 启动信号是否太早或太晚。 +- 中继信号是否过密。 +- 缠绕禁区是否过滤掉大部分横盘乱跳信号。 +- 是否漏掉明显趋势启动。 +- 是否在 EMA144 另一侧出现逆向信号。 +- XAUUSD 5 分钟上默认参数是否需要单独收紧。 diff --git a/pine/ema_5_15_30_144_macd_volume_radiation_indicator.pine b/pine/ema_5_15_30_144_macd_volume_radiation_indicator.pine new file mode 100644 index 0000000..c16b340 --- /dev/null +++ b/pine/ema_5_15_30_144_macd_volume_radiation_indicator.pine @@ -0,0 +1,218 @@ +//@version=6 +indicator( + title="EMA 5/15/30/144 MACD Volume Radiation Entry", + shorttitle="EMA Radiation Entry", + overlay=true, + max_labels_count=500) + +emaFastLen = input.int(5, "EMA 快线", minval=1) +emaMidLen = input.int(15, "EMA 中线", minval=1) +emaSlowLen = input.int(30, "EMA 慢线", minval=1) +emaBiasLen = input.int(144, "EMA 主趋势线", minval=1) +tradeSession = input.session("0000-2359", "交易定义时段") +tradeDirection = input.string("多空都看", "信号方向", options=["多空都看", "只看多", "只看空"]) + +atrLen = input.int(14, "ATR 长度", minval=1) +angleLookback = input.int(5, "夹角计算回看 K 数", minval=1, maxval=50) +minAngleDiff = input.float(12.0, "EMA5-EMA144 最小夹角差", minval=0.0, step=1.0) +minFanWidthAtr = input.float(0.25, "放射均线最小宽度 / ATR", minval=0.0, step=0.05) +expansionLookback = input.int(1, "放射扩张连续 K 数", minval=1, maxval=20) +minRadiationBars = input.int(1, "放射延续最少 K 数", minval=1, maxval=50) +maxRadiationBars = input.int(40, "放射最长追踪 K 数", minval=1, maxval=300) +stairBars = input.int(2, "阶梯 K 线根数", minval=2, maxval=8) + +macdFastLen = input.int(12, "MACD 快线", minval=1) +macdSlowLen = input.int(26, "MACD 慢线", minval=1) +macdSignalLen = input.int(9, "MACD 信号线", minval=1) +macdMode = input.string("关闭", "MACD 标准", options=["交叉/缩量二选一", "必须交叉", "必须缩量", "关闭"]) +macdShrinkLookback = input.int(3, "MACD 缩量连续 K 数", minval=1, maxval=20) + +volumeLen = input.int(20, "量能均线长度", minval=1) +volumeMode = input.string("关闭", "量能标准", options=["放量确认", "缩量回踩", "关闭"]) +volumeMultiplier = input.float(1.10, "放量倍数", minval=0.1, step=0.05) +pullbackVolumeMultiplier = input.float(0.90, "缩量回踩倍数", minval=0.1, step=0.05) + +useMtfLevel = input.bool(true, "启用级别定义过滤") +levelPreset = input.string("自动", "周期级别预设", options=["自动", "1m", "5m", "15m", "手动"]) +levelTf1 = input.timeframe("15", "级别一") +levelTf2 = input.timeframe("60", "级别二") +levelStrongScore = input.int(2, "大级别强趋势分数", minval=1, maxval=4) +levelMode = input.string("只标记", "级别过滤模式", options=["15m 过滤,1H 分级", "15m+1H 都过滤", "只标记"]) + +signalMode = input.string("启动+预启动", "入场信号模式", options=["启动确认", "预启动观察", "启动+预启动"]) +entrySensitivity = input.string("灵敏", "入场灵敏度", options=["灵敏", "均衡", "严格"]) +preBreakLookback = input.int(5, "预启动结构回看 K 数", minval=2, maxval=50) +preBreakBufferAtr = input.float(0.35, "预启动贴近突破位 / ATR", minval=0.0, step=0.01) +preMaxFanWidthAtr = input.float(2.00, "预启动最大放射宽度 / ATR", minval=0.0, step=0.05) + +cooldownBars = input.int(4, "同向信号冷却 K 数", minval=0, maxval=100) +showStateTable = input.bool(true, "显示状态面板") +showRiskMarks = input.bool(true, "显示逆大级别风险标记") +showPreSignals = input.bool(true, "显示预启动信号") +showCandidateMarks = input.bool(true, "显示候选辅助点") +showBarColor = input.bool(false, "K 线按方向染色") + +emaFast = ta.ema(close, emaFastLen) +emaMid = ta.ema(close, emaMidLen) +emaSlow = ta.ema(close, emaSlowLen) +emaBias = ta.ema(close, emaBiasLen) +atr = ta.atr(atrLen) +volMa = ta.sma(volume, volumeLen) +[macdLine, macdSignal, macdHist] = ta.macd(close, macdFastLen, macdSlowLen, macdSignalLen) + +inSession = not na(time(timeframe.period, tradeSession)) +allowLong = tradeDirection != "只看空" +allowShort = tradeDirection != "只看多" + +longOrder = emaBias < emaSlow and emaSlow < emaMid and emaMid < emaFast +shortOrder = emaBias > emaSlow and emaSlow > emaMid and emaMid > emaFast +longShortRibbonOrder = emaSlow < emaMid and emaMid < emaFast +shortShortRibbonOrder = emaSlow > emaMid and emaMid > emaFast + +f_angle(src) => + atrSafe = math.max(ta.atr(atrLen), syminfo.mintick) + math.atan((src - src[angleLookback]) / (atrSafe * angleLookback)) * 180.0 / math.pi + +fastAngle = f_angle(emaFast) +biasAngle = f_angle(emaBias) +angleDiff = math.abs(fastAngle - biasAngle) +longAngleOk = fastAngle > 0 and angleDiff >= minAngleDiff +shortAngleOk = fastAngle < 0 and angleDiff >= minAngleDiff + +fanWidth = math.abs(emaFast - emaBias) +fanWidthAtr = atr > syminfo.mintick ? fanWidth / atr : 0.0 +gapFastMid = math.abs(emaFast - emaMid) +gapMidSlow = math.abs(emaMid - emaSlow) +gapSlowBias = math.abs(emaSlow - emaBias) +fanWidthOk = fanWidthAtr >= minFanWidthAtr +fanOpenNow = gapFastMid >= gapFastMid[1] and gapMidSlow >= gapMidSlow[1] and gapSlowBias >= gapSlowBias[1] +fanOpenContinuous = math.sum(fanOpenNow ? 1.0 : 0.0, expansionLookback) == expansionLookback + +longRadiationRaw = longOrder and longAngleOk and fanWidthOk and fanOpenContinuous +shortRadiationRaw = shortOrder and shortAngleOk and fanWidthOk and fanOpenContinuous +longRadiationBars = longRadiationRaw ? nz(ta.barssince(not longRadiationRaw), 100000) : 0 +shortRadiationBars = shortRadiationRaw ? nz(ta.barssince(not shortRadiationRaw), 100000) : 0 +longRadiationOk = longRadiationBars >= minRadiationBars and longRadiationBars <= maxRadiationBars +shortRadiationOk = shortRadiationBars >= minRadiationBars and shortRadiationBars <= maxRadiationBars + +longStair = ta.rising(close, stairBars) and ta.rising(low, stairBars) +shortStair = ta.falling(close, stairBars) and ta.falling(high, stairBars) + +longMacdCross = ta.crossover(macdLine, macdSignal) +shortMacdCross = ta.crossunder(macdLine, macdSignal) +longMacdShrink = macdHist < 0 and ta.rising(macdHist, macdShrinkLookback) +shortMacdShrink = macdHist > 0 and ta.falling(macdHist, macdShrinkLookback) +longMacdOk = macdMode == "关闭" or macdMode == "交叉/缩量二选一" and (longMacdCross or longMacdShrink) or macdMode == "必须交叉" and longMacdCross or macdMode == "必须缩量" and longMacdShrink +shortMacdOk = macdMode == "关闭" or macdMode == "交叉/缩量二选一" and (shortMacdCross or shortMacdShrink) or macdMode == "必须交叉" and shortMacdCross or macdMode == "必须缩量" and shortMacdShrink + +volumeOk = volumeMode == "关闭" or volumeMode == "放量确认" and volume >= volMa * volumeMultiplier or volumeMode == "缩量回踩" and volume <= volMa * pullbackVolumeMultiplier + +f_levelScore() => + levelFast = ta.ema(close, emaFastLen) + levelMid = ta.ema(close, emaMidLen) + levelSlow = ta.ema(close, emaSlowLen) + levelBias = ta.ema(close, emaBiasLen) + priceScore = close > levelBias ? 1 : close < levelBias ? -1 : 0 + orderScore = levelFast > levelMid and levelMid > levelSlow ? 1 : levelFast < levelMid and levelMid < levelSlow ? -1 : 0 + slopeScore = levelFast > levelFast[1] and levelMid >= levelMid[1] ? 1 : levelFast < levelFast[1] and levelMid <= levelMid[1] ? -1 : 0 + biasSlopeScore = levelBias >= levelBias[angleLookback] ? 1 : levelBias <= levelBias[angleLookback] ? -1 : 0 + priceScore + orderScore + slopeScore + biasSlopeScore + +autoLevelTf1 = timeframe.isminutes and timeframe.multiplier <= 1 ? "5" : timeframe.isminutes and timeframe.multiplier <= 5 ? "15" : timeframe.isminutes and timeframe.multiplier <= 15 ? "60" : levelTf1 +autoLevelTf2 = timeframe.isminutes and timeframe.multiplier <= 1 ? "15" : timeframe.isminutes and timeframe.multiplier <= 5 ? "60" : timeframe.isminutes and timeframe.multiplier <= 15 ? "240" : levelTf2 +effectiveLevelTf1 = levelPreset == "自动" ? autoLevelTf1 : levelPreset == "1m" ? "5" : levelPreset == "5m" ? "15" : levelPreset == "15m" ? "60" : levelTf1 +effectiveLevelTf2 = levelPreset == "自动" ? autoLevelTf2 : levelPreset == "1m" ? "15" : levelPreset == "5m" ? "60" : levelPreset == "15m" ? "240" : levelTf2 + +levelScore1 = request.security(syminfo.tickerid, effectiveLevelTf1, f_levelScore(), barmerge.gaps_off, barmerge.lookahead_off) +levelScore2 = request.security(syminfo.tickerid, effectiveLevelTf2, f_levelScore(), barmerge.gaps_off, barmerge.lookahead_off) +level1LongOk = levelScore1 > -levelStrongScore +level1ShortOk = levelScore1 < levelStrongScore +level2LongRisk = levelScore2 <= -levelStrongScore +level2ShortRisk = levelScore2 >= levelStrongScore +levelLongOk = not useMtfLevel or levelMode == "只标记" or (levelMode == "15m 过滤,1H 分级" and level1LongOk) or (levelMode == "15m+1H 都过滤" and level1LongOk and not level2LongRisk) +levelShortOk = not useMtfLevel or levelMode == "只标记" or (levelMode == "15m 过滤,1H 分级" and level1ShortOk) or (levelMode == "15m+1H 都过滤" and level1ShortOk and not level2ShortRisk) + +preHigh = ta.highest(high[1], preBreakLookback) +preLow = ta.lowest(low[1], preBreakLookback) +longPreBreak = high >= preHigh - atr * preBreakBufferAtr and close <= preHigh +shortPreBreak = low <= preLow + atr * preBreakBufferAtr and close >= preLow +longMomentumSetup = inSession and allowLong and levelLongOk and longShortRibbonOrder and fastAngle > 0 and close > emaMid and close > open and (close > emaFast or entrySensitivity == "灵敏") +shortMomentumSetup = inSession and allowShort and levelShortOk and shortShortRibbonOrder and fastAngle < 0 and close < emaMid and close < open and (close < emaFast or entrySensitivity == "灵敏") +longPreSetup = inSession and allowLong and levelLongOk and longShortRibbonOrder and fanWidthAtr <= preMaxFanWidthAtr and fastAngle > 0 and fanOpenNow and (longPreBreak or entrySensitivity == "灵敏" and longMomentumSetup) and close > emaMid and close > open and longMacdOk +shortPreSetup = inSession and allowShort and levelShortOk and shortShortRibbonOrder and fanWidthAtr <= preMaxFanWidthAtr and fastAngle < 0 and fanOpenNow and (shortPreBreak or entrySensitivity == "灵敏" and shortMomentumSetup) and close < emaMid and close < open and shortMacdOk + +longConfirmSetup = inSession and allowLong and levelLongOk and longRadiationOk and (longStair or entrySensitivity == "灵敏" and close > high[1]) and longMacdOk and volumeOk +shortConfirmSetup = inSession and allowShort and levelShortOk and shortRadiationOk and (shortStair or entrySensitivity == "灵敏" and close < low[1]) and shortMacdOk and volumeOk + +var int lastLongBar = na +var int lastShortBar = na +canLong = na(lastLongBar) or bar_index - lastLongBar > cooldownBars +canShort = na(lastShortBar) or bar_index - lastShortBar > cooldownBars + +usePre = signalMode == "预启动观察" or signalMode == "启动+预启动" +useConfirm = signalMode == "启动确认" or signalMode == "启动+预启动" +longPreSignal = barstate.isconfirmed and usePre and longPreSetup and not longPreSetup[1] and canLong +shortPreSignal = barstate.isconfirmed and usePre and shortPreSetup and not shortPreSetup[1] and canShort +longConfirmSignal = barstate.isconfirmed and useConfirm and longConfirmSetup and not longConfirmSetup[1] and canLong +shortConfirmSignal = barstate.isconfirmed and useConfirm and shortConfirmSetup and not shortConfirmSetup[1] and canShort + +longSignal = longPreSignal or longConfirmSignal +shortSignal = shortPreSignal or shortConfirmSignal + +if longSignal + lastLongBar := bar_index + +if shortSignal + lastShortBar := bar_index + +plot(emaFast, "EMA5", color=color.new(color.teal, 0), linewidth=1) +plot(emaMid, "EMA15", color=color.new(color.orange, 0), linewidth=1) +plot(emaSlow, "EMA30", color=color.new(color.yellow, 0), linewidth=1) +plot(emaBias, "EMA144", color=color.new(color.red, 0), linewidth=2) + +barcolor(showBarColor and longOrder ? color.new(color.green, 0) : showBarColor and shortOrder ? color.new(color.red, 0) : na) + +plotshape(longConfirmSignal, title="放射确认做多", style=shape.labelup, location=location.belowbar, color=color.new(color.lime, 0), textcolor=color.black, text="做多") +plotshape(shortConfirmSignal, title="放射确认做空", style=shape.labeldown, location=location.abovebar, color=color.new(color.red, 0), textcolor=color.white, text="做空") +plotshape(showPreSignals and longPreSignal, title="预启动做多", style=shape.labelup, location=location.belowbar, color=color.new(color.aqua, 0), textcolor=color.black, size=size.tiny, text="预多") +plotshape(showPreSignals and shortPreSignal, title="预启动做空", style=shape.labeldown, location=location.abovebar, color=color.new(color.fuchsia, 0), textcolor=color.white, size=size.tiny, text="预空") +plotshape(showCandidateMarks and not longSignal and longPreSetup, title="候选:预启动做多", style=shape.circle, location=location.belowbar, color=color.new(color.aqua, 55), size=size.tiny) +plotshape(showCandidateMarks and not shortSignal and shortPreSetup, title="候选:预启动做空", style=shape.circle, location=location.abovebar, color=color.new(color.fuchsia, 55), size=size.tiny) +plotshape(showCandidateMarks and not longSignal and longConfirmSetup, title="候选:确认做多", style=shape.triangleup, location=location.belowbar, color=color.new(color.lime, 60), size=size.tiny) +plotshape(showCandidateMarks and not shortSignal and shortConfirmSetup, title="候选:确认做空", style=shape.triangledown, location=location.abovebar, color=color.new(color.red, 60), size=size.tiny) +plotshape(showRiskMarks and longSignal and level2LongRisk, title="逆大级别做多", style=shape.diamond, location=location.belowbar, color=color.new(color.orange, 0), size=size.tiny, text="逆1H") +plotshape(showRiskMarks and shortSignal and level2ShortRisk, title="逆大级别做空", style=shape.diamond, location=location.abovebar, color=color.new(color.orange, 0), size=size.tiny, text="逆1H") + +var table stateTable = table.new(position.top_right, 2, 7, border_width=1) + +if showStateTable and barstate.islast + directionText = longRadiationOk ? "多头放射" : shortRadiationOk ? "空头放射" : longOrder ? "多头排列" : shortOrder ? "空头排列" : "无排列" + angleText = str.tostring(angleDiff, "#.0") + "°" + radiationText = longRadiationRaw ? str.tostring(longRadiationBars) : shortRadiationRaw ? str.tostring(shortRadiationBars) : "0" + stairText = longStair ? "阶梯向上" : shortStair ? "阶梯向下" : "未阶梯" + macdText = longMacdCross ? "金叉" : shortMacdCross ? "死叉" : longMacdShrink or shortMacdShrink ? "柱缩" : "未确认" + levelText = not useMtfLevel ? "关闭" : level2LongRisk and close > emaBias ? "逆" + effectiveLevelTf2 + "空" : level2ShortRisk and close < emaBias ? "逆" + effectiveLevelTf2 + "多" : levelScore1 >= levelStrongScore ? effectiveLevelTf1 + "多" : levelScore1 <= -levelStrongScore ? effectiveLevelTf1 + "空" : "中性" + sensitivityText = entrySensitivity == "灵敏" ? "灵敏" : entrySensitivity == "均衡" ? "均衡" : "严格" + signalText = longConfirmSignal ? "做多" : shortConfirmSignal ? "做空" : longPreSignal ? "预多" : shortPreSignal ? "预空" : "等待" + + table.cell(stateTable, 0, 0, "方向", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 0, directionText, text_color=color.white, bgcolor=color.new(color.black, 70)) + table.cell(stateTable, 0, 1, "夹角", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 1, angleText, text_color=color.white, bgcolor=color.new(color.black, 70)) + table.cell(stateTable, 0, 2, "放射K数", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 2, radiationText, text_color=color.white, bgcolor=color.new(color.black, 70)) + table.cell(stateTable, 0, 3, "阶梯", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 3, stairText, text_color=color.white, bgcolor=color.new(color.black, 70)) + table.cell(stateTable, 0, 4, "MACD", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 4, macdText, text_color=color.white, bgcolor=color.new(color.black, 70)) + table.cell(stateTable, 0, 5, "级别", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 5, levelText, text_color=color.white, bgcolor=color.new(color.black, 70)) + table.cell(stateTable, 0, 6, "信号", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 6, signalText + " " + sensitivityText, text_color=color.white, bgcolor=longSignal ? color.new(color.green, 0) : shortSignal ? color.new(color.red, 0) : color.new(color.gray, 20)) + +alertcondition(longPreSignal, title="预启动做多", message="EMA5/15/30/144 多头排列初开,价格贴近上方突破位,出现预启动做多信号") +alertcondition(shortPreSignal, title="预启动做空", message="EMA5/15/30/144 空头排列初开,价格贴近下方跌破位,出现预启动做空信号") +alertcondition(longConfirmSignal, title="放射确认做多", message="EMA5/15/30/144 多头放射,阶梯向上,MACD/量能确认,出现做多信号") +alertcondition(shortConfirmSignal, title="放射确认做空", message="EMA5/15/30/144 空头放射,阶梯向下,MACD/量能确认,出现做空信号") +alertcondition(longSignal, title="综合做多", message="EMA 放射入场系统出现做多方向信号") +alertcondition(shortSignal, title="综合做空", message="EMA 放射入场系统出现做空方向信号") diff --git a/pine/ema_5_15_30_144_macd_volume_radiation_strategy.pine b/pine/ema_5_15_30_144_macd_volume_radiation_strategy.pine new file mode 100644 index 0000000..d0ead3c --- /dev/null +++ b/pine/ema_5_15_30_144_macd_volume_radiation_strategy.pine @@ -0,0 +1,196 @@ +//@version=6 +strategy( + title="EMA 5/15/30/144 MACD Volume Radiation Strategy", + shorttitle="EMA Radiation Strategy", + overlay=true, + pyramiding=0, + initial_capital=100000, + commission_type=strategy.commission.percent, + commission_value=0.03, + slippage=2, + default_qty_type=strategy.percent_of_equity, + default_qty_value=10, + max_labels_count=500) + +emaFastLen = input.int(5, "EMA 快线", minval=1) +emaMidLen = input.int(15, "EMA 中线", minval=1) +emaSlowLen = input.int(30, "EMA 慢线", minval=1) +emaBiasLen = input.int(144, "EMA 主趋势线", minval=1) +tradeSession = input.session("0000-2359", "交易定义时段") +tradeDirection = input.string("多空都做", "交易方向", options=["多空都做", "只做多", "只做空"]) + +atrLen = input.int(14, "ATR 长度", minval=1) +angleLookback = input.int(5, "夹角计算回看 K 数", minval=1, maxval=50) +minAngleDiff = input.float(30.0, "EMA5-EMA144 最小夹角差", minval=0.0, step=1.0) +minFanWidthAtr = input.float(0.80, "放射均线最小宽度 / ATR", minval=0.0, step=0.05) +expansionLookback = input.int(3, "放射扩张连续 K 数", minval=1, maxval=20) +minRadiationBars = input.int(3, "放射延续最少 K 数", minval=1, maxval=50) +maxRadiationBars = input.int(40, "放射最长追踪 K 数", minval=1, maxval=300) +stairBars = input.int(3, "阶梯 K 线根数", minval=2, maxval=8) + +macdFastLen = input.int(12, "MACD 快线", minval=1) +macdSlowLen = input.int(26, "MACD 慢线", minval=1) +macdSignalLen = input.int(9, "MACD 信号线", minval=1) +macdMode = input.string("交叉/缩量二选一", "MACD 标准", options=["交叉/缩量二选一", "必须交叉", "必须缩量", "关闭"]) +macdShrinkLookback = input.int(3, "MACD 缩量连续 K 数", minval=1, maxval=20) + +volumeLen = input.int(20, "量能均线长度", minval=1) +volumeMode = input.string("放量确认", "量能标准", options=["放量确认", "缩量回踩", "关闭"]) +volumeMultiplier = input.float(1.10, "放量倍数", minval=0.1, step=0.05) +pullbackVolumeMultiplier = input.float(0.90, "缩量回踩倍数", minval=0.1, step=0.05) + +useMtfLevel = input.bool(true, "启用级别定义过滤") +levelTf1 = input.timeframe("15", "级别一") +levelTf2 = input.timeframe("60", "级别二") +levelStrongScore = input.int(2, "大级别强趋势分数", minval=1, maxval=4) +levelMode = input.string("15m 过滤,1H 分级", "级别过滤模式", options=["15m 过滤,1H 分级", "15m+1H 都过滤", "只标记"]) + +stopMode = input.string("ATR 止损", "止损模式", options=["ATR 止损", "EMA30 结构止损"]) +stopAtr = input.float(1.60, "ATR 止损倍数", minval=0.1, step=0.1) +takeProfitAtr = input.float(3.20, "ATR 止盈倍数", minval=0.1, step=0.1) +useTrail = input.bool(true, "启用 EMA30 移动止损") +exitOnOrderBreak = input.bool(true, "均线排列破坏离场") + +showStateTable = input.bool(true, "显示状态面板") +showRiskMarks = input.bool(true, "显示逆大级别风险标记") + +emaFast = ta.ema(close, emaFastLen) +emaMid = ta.ema(close, emaMidLen) +emaSlow = ta.ema(close, emaSlowLen) +emaBias = ta.ema(close, emaBiasLen) +atr = ta.atr(atrLen) +volMa = ta.sma(volume, volumeLen) +[macdLine, macdSignal, macdHist] = ta.macd(close, macdFastLen, macdSlowLen, macdSignalLen) + +inSession = not na(time(timeframe.period, tradeSession)) +allowLong = tradeDirection != "只做空" +allowShort = tradeDirection != "只做多" + +longOrder = emaBias < emaSlow and emaSlow < emaMid and emaMid < emaFast +shortOrder = emaBias > emaSlow and emaSlow > emaMid and emaMid > emaFast + +f_angle(src) => + atrSafe = math.max(ta.atr(atrLen), syminfo.mintick) + math.atan((src - src[angleLookback]) / (atrSafe * angleLookback)) * 180.0 / math.pi + +fastAngle = f_angle(emaFast) +biasAngle = f_angle(emaBias) +angleDiff = math.abs(fastAngle - biasAngle) +longAngleOk = fastAngle > 0 and angleDiff >= minAngleDiff +shortAngleOk = fastAngle < 0 and angleDiff >= minAngleDiff + +fanWidth = math.abs(emaFast - emaBias) +fanWidthAtr = atr > syminfo.mintick ? fanWidth / atr : 0.0 +gapFastMid = math.abs(emaFast - emaMid) +gapMidSlow = math.abs(emaMid - emaSlow) +gapSlowBias = math.abs(emaSlow - emaBias) +fanWidthOk = fanWidthAtr >= minFanWidthAtr +fanOpenNow = gapFastMid >= gapFastMid[1] and gapMidSlow >= gapMidSlow[1] and gapSlowBias >= gapSlowBias[1] +fanOpenContinuous = math.sum(fanOpenNow ? 1.0 : 0.0, expansionLookback) == expansionLookback + +longRadiationRaw = longOrder and longAngleOk and fanWidthOk and fanOpenContinuous +shortRadiationRaw = shortOrder and shortAngleOk and fanWidthOk and fanOpenContinuous +longRadiationBars = longRadiationRaw ? nz(ta.barssince(not longRadiationRaw), 100000) : 0 +shortRadiationBars = shortRadiationRaw ? nz(ta.barssince(not shortRadiationRaw), 100000) : 0 +longRadiationOk = longRadiationBars >= minRadiationBars and longRadiationBars <= maxRadiationBars +shortRadiationOk = shortRadiationBars >= minRadiationBars and shortRadiationBars <= maxRadiationBars + +longStair = ta.rising(close, stairBars) and ta.rising(low, stairBars) +shortStair = ta.falling(close, stairBars) and ta.falling(high, stairBars) + +longMacdCross = ta.crossover(macdLine, macdSignal) +shortMacdCross = ta.crossunder(macdLine, macdSignal) +longMacdShrink = macdHist < 0 and ta.rising(macdHist, macdShrinkLookback) +shortMacdShrink = macdHist > 0 and ta.falling(macdHist, macdShrinkLookback) +longMacdOk = macdMode == "关闭" or macdMode == "交叉/缩量二选一" and (longMacdCross or longMacdShrink) or macdMode == "必须交叉" and longMacdCross or macdMode == "必须缩量" and longMacdShrink +shortMacdOk = macdMode == "关闭" or macdMode == "交叉/缩量二选一" and (shortMacdCross or shortMacdShrink) or macdMode == "必须交叉" and shortMacdCross or macdMode == "必须缩量" and shortMacdShrink + +longVolumeOk = volumeMode == "关闭" or volumeMode == "放量确认" and volume >= volMa * volumeMultiplier or volumeMode == "缩量回踩" and volume <= volMa * pullbackVolumeMultiplier +shortVolumeOk = longVolumeOk + +f_levelScore() => + levelFast = ta.ema(close, emaFastLen) + levelMid = ta.ema(close, emaMidLen) + levelSlow = ta.ema(close, emaSlowLen) + levelBias = ta.ema(close, emaBiasLen) + priceScore = close > levelBias ? 1 : close < levelBias ? -1 : 0 + orderScore = levelFast > levelMid and levelMid > levelSlow ? 1 : levelFast < levelMid and levelMid < levelSlow ? -1 : 0 + slopeScore = levelFast > levelFast[1] and levelMid >= levelMid[1] ? 1 : levelFast < levelFast[1] and levelMid <= levelMid[1] ? -1 : 0 + biasSlopeScore = levelBias >= levelBias[angleLookback] ? 1 : levelBias <= levelBias[angleLookback] ? -1 : 0 + priceScore + orderScore + slopeScore + biasSlopeScore + +levelScore1 = request.security(syminfo.tickerid, levelTf1, f_levelScore(), barmerge.gaps_off, barmerge.lookahead_off) +levelScore2 = request.security(syminfo.tickerid, levelTf2, f_levelScore(), barmerge.gaps_off, barmerge.lookahead_off) +level1LongOk = levelScore1 > -levelStrongScore +level1ShortOk = levelScore1 < levelStrongScore +level2LongRisk = levelScore2 <= -levelStrongScore +level2ShortRisk = levelScore2 >= levelStrongScore +levelLongOk = not useMtfLevel or levelMode == "只标记" or (levelMode == "15m 过滤,1H 分级" and level1LongOk) or (levelMode == "15m+1H 都过滤" and level1LongOk and not level2LongRisk) +levelShortOk = not useMtfLevel or levelMode == "只标记" or (levelMode == "15m 过滤,1H 分级" and level1ShortOk) or (levelMode == "15m+1H 都过滤" and level1ShortOk and not level2ShortRisk) + +longSetup = inSession and allowLong and levelLongOk and longRadiationOk and longStair and longMacdOk and longVolumeOk +shortSetup = inSession and allowShort and levelShortOk and shortRadiationOk and shortStair and shortMacdOk and shortVolumeOk + +longSignal = barstate.isconfirmed and longSetup and not longSetup[1] +shortSignal = barstate.isconfirmed and shortSetup and not shortSetup[1] + +longStopBase = stopMode == "ATR 止损" ? close - atr * stopAtr : emaSlow - atr * 0.20 +shortStopBase = stopMode == "ATR 止损" ? close + atr * stopAtr : emaSlow + atr * 0.20 +longTakeProfit = close + atr * takeProfitAtr +shortTakeProfit = close - atr * takeProfitAtr + +if longSignal + strategy.entry("Long", strategy.long, alert_message="EMA5/15/30/144 多头放射,阶梯向上,MACD/量能确认,出现做多信号") + strategy.exit("Long Exit", "Long", stop=longStopBase, limit=longTakeProfit) + +if shortSignal + strategy.entry("Short", strategy.short, alert_message="EMA5/15/30/144 空头放射,阶梯向下,MACD/量能确认,出现做空信号") + strategy.exit("Short Exit", "Short", stop=shortStopBase, limit=shortTakeProfit) + +if useTrail and strategy.position_size > 0 + strategy.exit("Long Trail", "Long", stop=emaSlow - atr * 0.20) + +if useTrail and strategy.position_size < 0 + strategy.exit("Short Trail", "Short", stop=emaSlow + atr * 0.20) + +if exitOnOrderBreak and strategy.position_size > 0 and not longOrder + strategy.close("Long", comment="多头排列破坏") + +if exitOnOrderBreak and strategy.position_size < 0 and not shortOrder + strategy.close("Short", comment="空头排列破坏") + +plot(emaFast, "EMA5", color=color.new(color.teal, 0), linewidth=1) +plot(emaMid, "EMA15", color=color.new(color.orange, 0), linewidth=1) +plot(emaSlow, "EMA30", color=color.new(color.yellow, 0), linewidth=1) +plot(emaBias, "EMA144", color=color.new(color.red, 0), linewidth=2) + +plotshape(longSignal, title="做多", style=shape.labelup, location=location.belowbar, color=color.new(color.lime, 0), textcolor=color.black, text="做多") +plotshape(shortSignal, title="做空", style=shape.labeldown, location=location.abovebar, color=color.new(color.red, 0), textcolor=color.white, text="做空") +plotshape(showRiskMarks and longSignal and level2LongRisk, title="逆大级别做多", style=shape.diamond, location=location.belowbar, color=color.new(color.orange, 0), size=size.tiny, text="逆1H") +plotshape(showRiskMarks and shortSignal and level2ShortRisk, title="逆大级别做空", style=shape.diamond, location=location.abovebar, color=color.new(color.orange, 0), size=size.tiny, text="逆1H") + +var table stateTable = table.new(position.top_right, 2, 7, border_width=1) + +if showStateTable and barstate.islast + directionText = longRadiationOk ? "多头放射" : shortRadiationOk ? "空头放射" : longOrder or shortOrder ? "排列中" : "无排列" + angleText = str.tostring(angleDiff, "#.0") + "°" + radiationText = longRadiationRaw ? str.tostring(longRadiationBars) : shortRadiationRaw ? str.tostring(shortRadiationBars) : "0" + stairText = longStair ? "三阶向上" : shortStair ? "三阶向下" : "未阶梯" + macdText = longMacdCross ? "金叉" : shortMacdCross ? "死叉" : longMacdShrink or shortMacdShrink ? "柱缩" : "未确认" + levelText = not useMtfLevel ? "关闭" : level2LongRisk and close > emaBias ? "逆1H空" : level2ShortRisk and close < emaBias ? "逆1H多" : levelScore1 >= levelStrongScore ? "15m多" : levelScore1 <= -levelStrongScore ? "15m空" : "中性" + signalText = longSignal ? "做多" : shortSignal ? "做空" : "等待" + + table.cell(stateTable, 0, 0, "方向", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 0, directionText, text_color=color.white, bgcolor=color.new(color.black, 70)) + table.cell(stateTable, 0, 1, "夹角", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 1, angleText, text_color=color.white, bgcolor=color.new(color.black, 70)) + table.cell(stateTable, 0, 2, "放射K数", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 2, radiationText, text_color=color.white, bgcolor=color.new(color.black, 70)) + table.cell(stateTable, 0, 3, "阶梯", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 3, stairText, text_color=color.white, bgcolor=color.new(color.black, 70)) + table.cell(stateTable, 0, 4, "MACD", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 4, macdText, text_color=color.white, bgcolor=color.new(color.black, 70)) + table.cell(stateTable, 0, 5, "级别", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 5, levelText, text_color=color.white, bgcolor=color.new(color.black, 70)) + table.cell(stateTable, 0, 6, "信号", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 6, signalText, text_color=color.white, bgcolor=longSignal ? color.new(color.green, 0) : shortSignal ? color.new(color.red, 0) : color.new(color.gray, 20)) diff --git a/pine/ema_ribbon_state_signal_indicator.pine b/pine/ema_ribbon_state_signal_indicator.pine new file mode 100644 index 0000000..7429826 --- /dev/null +++ b/pine/ema_ribbon_state_signal_indicator.pine @@ -0,0 +1,957 @@ +//@version=6 +indicator( + title="EMA Ribbon State Signal", + shorttitle="EMA Ribbon State", + overlay=true, + max_labels_count=500, + max_boxes_count=500) + +plot(na, title="脚本锚点", display=display.none) + +emaFastLen = input.int(5, "EMA 快线", minval=1) +emaMidLen = input.int(15, "EMA 中线", minval=1) +emaSlowLen = input.int(30, "EMA 慢线", minval=1) +emaBiasLen = input.int(144, "EMA 多空分界线", minval=1) + +atrLen = input.int(14, "ATR 长度", minval=1) +signalProfile = input.string("均衡", "信号风格", options=["激进", "均衡", "保守"]) +mainSignalMode = input.string("只做趋势回踩", "主信号模式", options=["只做趋势回踩", "启动+趋势回踩"]) +cooldownBars = input.int(10, "同向信号冷却 K 数", minval=0, maxval=100) +globalCooldownBars = input.int(6, "任意信号冷却 K 数", minval=0, maxval=100) +useLateTrendFilter = input.bool(true, "启用趋势末段风险过滤") +maxMainSignalsPerTrend = input.int(6, "单段趋势最多主信号次数", minval=0, maxval=20) +maxTrendBars = input.int(0, "趋势最长有效 K 数", minval=0, maxval=300) +useAdxFilter = input.bool(true, "启用 ADX 趋势过滤") +adxLen = input.int(14, "ADX 长度", minval=1) +adxSmoothing = input.int(14, "ADX 平滑", minval=1) +minAdx = input.float(18.0, "ADX 最小值", minval=0.0, step=0.5) +allowRisingAdxLaunch = input.bool(true, "启动允许 ADX 上升替代阈值") +requireDiDirection = input.bool(true, "要求 DI 方向一致") + +useBiasSide = input.bool(true, "要求价格在 EMA144 同侧") +requireRibbonBiasSide = input.bool(true, "要求均线带在 EMA144 同侧") +allowRibbonCrossLaunch = input.bool(true, "启动允许均线带刚穿越 EMA144") +useBiasSlope = input.bool(false, "要求 EMA144 同向斜率") +biasSlopeLookback = input.int(10, "EMA144 斜率回看 K 数", minval=1, maxval=100) +avoidOppositeHardTrend = input.bool(true, "禁止逆 144 + 均线排列") +avoidChopZone = input.bool(true, "避开价格均线缠绕区") +chopLookback = input.int(10, "缠绕检测回看 K 数", minval=3, maxval=80) +chopMaxCrosses = input.int(4, "缠绕最大允许穿均线次数", minval=0, maxval=30) +chopRibbonAtr = input.float(0.35, "缠绕均线带过窄 / ATR", minval=0.0, step=0.05) +chopBiasDistanceAtr = input.float(0.45, "缠绕离 EMA144 过近 / ATR", minval=0.0, step=0.05) +chopBiasBandAtr = input.float(0.80, "144 横盘带宽 / ATR", minval=0.0, step=0.05) +chopExitConfirmBars = input.int(0, "脱离缠绕后确认 K 数", minval=0, maxval=20) +chopRequireCleanClose = input.bool(true, "信号 K 必须收在均线带外") + +compressionLookback = input.int(18, "启动前压缩回看 K 数", minval=3, maxval=120) +compressionAtr = input.float(0.55, "启动前最大均线带宽 / ATR", minval=0.0, step=0.05) +launchBreakLookback = input.int(4, "启动突破结构回看 K 数", minval=2, maxval=80) +launchFreshBars = input.int(8, "启动后有效 K 数", minval=1, maxval=50) +minLaunchBodyAtr = input.float(0.08, "启动确认实体 / ATR", minval=0.0, step=0.01) +launchEscapeDistanceAtr = input.float(0.12, "启动脱离均线带距离 / ATR", minval=0.0, step=0.01) +launchMinRibbonAtr = input.float(0.20, "启动后最小均线带宽 / ATR", minval=0.0, step=0.01) +launchChopBodyAtr = input.float(0.18, "缠绕逃逸实体 / ATR", minval=0.0, step=0.01) +launchBiasEscapeAtr = input.float(0.12, "缠绕逃逸离 EMA144 / ATR", minval=0.0, step=0.01) +prelaunchMode = input.string("辅助标记", "预启动信号模式", options=["关闭", "辅助标记", "并入主信号"]) +prelaunchCooldownBars = input.int(12, "预启动信号冷却 K 数", minval=0, maxval=100) +prelaunchMaxRibbonAtr = input.float(0.75, "预启动最大均线带宽 / ATR", minval=0.0, step=0.05) +prelaunchBreakBufferAtr = input.float(0.18, "预启动贴近突破位 / ATR", minval=0.0, step=0.01) +prelaunchBiasNearAtr = input.float(0.35, "预启动允许贴近 EMA144 / ATR", minval=0.0, step=0.05) +prelaunchMinBodyAtr = input.float(0.04, "预启动最小实体 / ATR", minval=0.0, step=0.01) +prelaunchMaxBiasCrosses = input.int(1, "预启动最大 EMA144 反复穿越", minval=0, maxval=10) +prelaunchMaxRibbonCrosses = input.int(3, "预启动最大短均线反复穿越", minval=0, maxval=30) +prelaunchMinBreakCloseAtr = input.float(0.04, "预启动离突破位最小差距 / ATR", minval=0.0, step=0.01) + +pullbackLookback = input.int(5, "中继回踩有效 K 数", minval=1, maxval=30) +trendMatureBars = input.int(4, "趋势形成确认 K 数", minval=0, maxval=50) +trendAwayAtr = input.float(0.25, "趋势离 EMA144 最小距离 / ATR", minval=0.0, step=0.05) +minTrendRibbonAtr = input.float(0.35, "趋势均线带最小发散 / ATR", minval=0.0, step=0.05) +minTrendGapAtr = input.float(0.04, "EMA 间最小间距 / ATR", minval=0.0, step=0.01) +trendExpansionLookback = input.int(8, "趋势发散确认回看 K 数", minval=1, maxval=50) +requireRecentBiasBreakout = input.bool(false, "要求近期突破 EMA144") +biasBreakoutRetestBars = input.int(40, "突破 EMA144 后回踩有效 K 数", minval=1, maxval=200) +useBreakoutLevelRetest = input.bool(false, "要求回踩突破结构位") +breakoutLevelLookback = input.int(24, "突破结构位回看 K 数", minval=3, maxval=200) +breakoutLevelRetestAtr = input.float(0.60, "突破结构位回踩容差 / ATR", minval=0.0, step=0.05) +minBreakoutBodyAtr = input.float(0.20, "突破 144 最小实体 / ATR", minval=0.0, step=0.05) +useBiasRetestPullback = input.bool(true, "回踩/反抽允许测试 EMA144") +biasRetestAtr = input.float(0.30, "EMA144 回踩/反抽容差 / ATR", minval=0.0, step=0.05) +minContinuationBodyAtr = input.float(0.05, "中继确认实体 / ATR", minval=0.0, step=0.01) +maxContinuationDistanceAtr = input.float(1.30, "中继确认离 EMA5 最远 / ATR", minval=0.1, step=0.05) +maxContinuationDistanceMidAtr = input.float(0.75, "中继确认离 EMA15 最远 / ATR", minval=0.1, step=0.05) +entryRangeLookback = input.int(20, "入场位置回看 K 数", minval=5, maxval=120) +maxLongEntryRangePosition = input.float(0.68, "做多确认最高区间位置", minval=0.1, maxval=1.0, step=0.05) +minShortEntryRangePosition = input.float(0.32, "做空确认最低区间位置", minval=0.0, maxval=0.9, step=0.05) + +extremeLookback = input.int(14, "避免局部高低点回看 K 数", minval=3, maxval=100) +maxLongRangePosition = input.float(0.82, "做多最高允许区间位置", minval=0.1, maxval=1.0, step=0.05) +minShortRangePosition = input.float(0.18, "做空最低允许区间位置", minval=0.0, maxval=0.9, step=0.05) +useStructureZoneFilter = input.bool(true, "启用供需区空间过滤") +showStructureZones = input.bool(true, "显示供需区 Box") +showHtfStructureZones = input.bool(true, "显示高周期供需区 Box") +useHtfStructureFilter = input.bool(true, "高周期供需区参与信号过滤") +htfStructureTf1 = input.timeframe("60", "高周期供需区一") +htfStructureTf2 = input.timeframe("240", "高周期供需区二") +useHtfZoneHardBlock = input.bool(true, "高周期供需区硬拦截") +htfZoneBlockAtr = input.float(1.20, "高周期供需区禁入缓冲 / ATR", minval=0.0, step=0.05) +htfZoneTouchBlockBars = input.int(8, "触碰高周期区后禁反向 K 数", minval=0, maxval=50) +useZoneReactionBlock = input.bool(true, "供需区反应拦截") +zoneReactionBlockBars = input.int(10, "供需区反应后禁反向 K 数", minval=0, maxval=80) +minZoneReactionBodyAtr = input.float(0.20, "供需区反应实体 / ATR", minval=0.0, step=0.05) +zoneImpulseAtr = input.float(2.20, "供需区前置冲击 / ATR", minval=0.0, step=0.10) +structureLookback = input.int(120, "供需区回看 K 数", minval=20, maxval=500) +structurePivotLeft = input.int(6, "供需区摆点左侧 K 数", minval=1, maxval=20) +structurePivotRight = input.int(6, "供需区摆点右侧 K 数", minval=1, maxval=20) +minStructureReactionAtr = input.float(1.20, "供需区最小反应 / ATR", minval=0.0, step=0.05) +structureZoneAtr = input.float(0.60, "供需区厚度 / ATR", minval=0.0, step=0.05) +minRoomToZoneAtr = input.float(0.80, "信号到供需区最小空间 / ATR", minval=0.0, step=0.05) +maxStructureBoxes = input.int(3, "每边最多显示供需区数量", minval=1, maxval=20) +maxHtfStructureBoxes = input.int(2, "高周期每边最多显示数量", minval=1, maxval=10) +maxZoneTests = input.int(3, "供需区弱化测试次数", minval=1, maxval=10) + +useMtfTrendFilter = input.bool(true, "启用多周期趋势过滤") +mtfFilterTf1 = input.timeframe("15", "方向过滤周期一") +mtfFilterTf2 = input.timeframe("30", "方向过滤周期二") +mtfRiskTf = input.timeframe("60", "风险分级周期") +mtfStrongScore = input.int(2, "多周期强趋势分数", minval=1, maxval=4) +mtfRiskMode = input.string("只标记风险", "风险周期强反向处理", options=["只标记风险", "拦截强反向"]) +showMtfRiskMarks = input.bool(true, "显示逆大周期风险标记") + +showAuxMarks = input.bool(true, "显示信号来源小标记") +showStateTable = input.bool(true, "显示状态面板") +showBarColor = input.bool(false, "K 线按均线带状态染色") + +emaFast = ta.ema(close, emaFastLen) +emaMid = ta.ema(close, emaMidLen) +emaSlow = ta.ema(close, emaSlowLen) +emaBias = ta.ema(close, emaBiasLen) +atr = ta.atr(atrLen) +[diPlus, diMinus, adx] = ta.dmi(adxLen, adxSmoothing) + +body = math.abs(close - open) +ribbonWidth = math.abs(emaFast - emaSlow) +fastMidSpread = math.abs(emaFast - emaMid) +midSlowSpread = math.abs(emaMid - emaSlow) +ribbonWidthAtr = atr > syminfo.mintick ? ribbonWidth / atr : 0.0 +ribbonHigh = math.max(math.max(emaFast, emaMid), emaSlow) +ribbonLow = math.min(math.min(emaFast, emaMid), emaSlow) +bullishEngulfing = close > open and close[1] < open[1] and close >= open[1] and open <= close[1] +bearishEngulfing = close < open and close[1] > open[1] and close <= open[1] and open >= close[1] + +bullOrder = emaFast > emaMid and emaMid > emaSlow +bearOrder = emaFast < emaMid and emaMid < emaSlow +bullSlopeCount = (emaFast > emaFast[1] ? 1 : 0) + (emaMid > emaMid[1] ? 1 : 0) + (emaSlow > emaSlow[1] ? 1 : 0) +bearSlopeCount = (emaFast < emaFast[1] ? 1 : 0) + (emaMid < emaMid[1] ? 1 : 0) + (emaSlow < emaSlow[1] ? 1 : 0) +ribbonExpanding = ribbonWidth > ribbonWidth[1] and fastMidSpread >= fastMidSpread[1] +ribbonSeparated = ribbonWidthAtr >= minTrendRibbonAtr and fastMidSpread >= atr * minTrendGapAtr and midSlowSpread >= atr * minTrendGapAtr +ribbonExpansionNow = ribbonSeparated and ribbonWidth > ribbonWidth[1] and fastMidSpread >= fastMidSpread[1] and midSlowSpread >= midSlowSpread[1] +ribbonExpansionRecent = ta.highest(ribbonExpansionNow ? 1.0 : 0.0, trendExpansionLookback) > 0 +bullRibbonExpanding = ribbonSeparated and ribbonExpansionRecent +bearRibbonExpanding = ribbonSeparated and ribbonExpansionRecent + +profileSlopeNeed = signalProfile == "激进" ? 1 : signalProfile == "均衡" ? 2 : 3 +profileBreakNeed = signalProfile != "激进" + +biasBullOk = (not useBiasSide or close > emaBias) and (not requireRibbonBiasSide or ribbonLow > emaBias) +biasBearOk = (not useBiasSide or close < emaBias) and (not requireRibbonBiasSide or ribbonHigh < emaBias) +biasBullLaunchOk = (not useBiasSide or close > emaBias) and (not requireRibbonBiasSide or ribbonLow > emaBias or (allowRibbonCrossLaunch and close > emaBias + atr * launchBiasEscapeAtr and emaFast > emaBias and emaMid > emaBias)) +biasBearLaunchOk = (not useBiasSide or close < emaBias) and (not requireRibbonBiasSide or ribbonHigh < emaBias or (allowRibbonCrossLaunch and close < emaBias - atr * launchBiasEscapeAtr and emaFast < emaBias and emaMid < emaBias)) +biasSlopeBullOk = not useBiasSlope or emaBias >= emaBias[biasSlopeLookback] +biasSlopeBearOk = not useBiasSlope or emaBias <= emaBias[biasSlopeLookback] +adxStrong = adx >= minAdx +adxRising = adx > adx[1] and adx[1] > adx[2] +adxLaunchOk = not useAdxFilter or adxStrong or (allowRisingAdxLaunch and adxRising) +adxContinuationOk = not useAdxFilter or adxStrong +diBullOk = not requireDiDirection or diPlus > diMinus +diBearOk = not requireDiDirection or diMinus > diPlus + +f_mtfTrendScore() => + mtfFast = ta.ema(close, emaFastLen) + mtfMid = ta.ema(close, emaMidLen) + mtfSlow = ta.ema(close, emaSlowLen) + mtfBias = ta.ema(close, emaBiasLen) + priceScore = close > mtfBias ? 1 : close < mtfBias ? -1 : 0 + orderScore = mtfFast > mtfMid and mtfMid > mtfSlow ? 1 : mtfFast < mtfMid and mtfMid < mtfSlow ? -1 : 0 + fastSlopeScore = mtfFast > mtfFast[1] and mtfMid >= mtfMid[1] ? 1 : mtfFast < mtfFast[1] and mtfMid <= mtfMid[1] ? -1 : 0 + biasSlopeScore = mtfBias >= mtfBias[biasSlopeLookback] ? 1 : mtfBias <= mtfBias[biasSlopeLookback] ? -1 : 0 + priceScore + orderScore + fastSlopeScore + biasSlopeScore + +mtfScore1 = request.security(syminfo.tickerid, mtfFilterTf1, f_mtfTrendScore(), barmerge.gaps_off, barmerge.lookahead_off) +mtfScore2 = request.security(syminfo.tickerid, mtfFilterTf2, f_mtfTrendScore(), barmerge.gaps_off, barmerge.lookahead_off) +mtfRiskScore = request.security(syminfo.tickerid, mtfRiskTf, f_mtfTrendScore(), barmerge.gaps_off, barmerge.lookahead_off) +mtfLongFilterOk = not useMtfTrendFilter or (mtfScore1 > -mtfStrongScore and mtfScore2 > -mtfStrongScore) +mtfShortFilterOk = not useMtfTrendFilter or (mtfScore1 < mtfStrongScore and mtfScore2 < mtfStrongScore) +mtfLongRisk = useMtfTrendFilter and mtfRiskScore <= -mtfStrongScore +mtfShortRisk = useMtfTrendFilter and mtfRiskScore >= mtfStrongScore +mtfRiskBlocks = mtfRiskMode == "拦截强反向" +mtfLongOk = mtfLongFilterOk and (not mtfRiskBlocks or not mtfLongRisk) +mtfShortOk = mtfShortFilterOk and (not mtfRiskBlocks or not mtfShortRisk) + +hardBullContext = close > emaBias and bullOrder +hardBearContext = close < emaBias and bearOrder +longDirectionOk = not avoidOppositeHardTrend or not hardBearContext +shortDirectionOk = not avoidOppositeHardTrend or not hardBullContext + +crossFastCount = math.sum(ta.cross(close, emaFast) ? 1.0 : 0.0, chopLookback) +crossMidCount = math.sum(ta.cross(close, emaMid) ? 1.0 : 0.0, chopLookback) +crossSlowCount = math.sum(ta.cross(close, emaSlow) ? 1.0 : 0.0, chopLookback) +priceCrossRibbonCount = crossFastCount + crossMidCount + crossSlowCount +priceInsideRibbon = close <= ribbonHigh and close >= ribbonLow +ribbonInsideBar = high >= ribbonHigh and low <= ribbonLow +nearBias = math.abs(close - emaBias) <= atr * chopBiasDistanceAtr +biasRangeOverlap = ta.highest(high, chopLookback) >= emaBias and ta.lowest(low, chopLookback) <= emaBias +biasBandRange = ta.highest(high, chopLookback) - ta.lowest(low, chopLookback) +biasSideFlips = math.sum(ta.cross(close, emaBias) ? 1.0 : 0.0, chopLookback) +ribbonStraddlesBias = ribbonLow <= emaBias and ribbonHigh >= emaBias +biasChopZone = biasRangeOverlap and biasBandRange <= atr * chopBiasBandAtr +narrowRibbon = ribbonWidthAtr <= chopRibbonAtr and not ribbonExpanding +chopZone = avoidChopZone and (priceCrossRibbonCount > chopMaxCrosses or priceInsideRibbon or ribbonInsideBar or nearBias or biasChopZone or biasSideFlips > 0 or ribbonStraddlesBias or narrowRibbon) +barsSinceChop = nz(ta.barssince(chopZone), 100000) +chopExitConfirmed = not avoidChopZone or barsSinceChop >= chopExitConfirmBars +longCleanClose = not chopRequireCleanClose or close > ribbonHigh +shortCleanClose = not chopRequireCleanClose or close < ribbonLow +launchBaseEscapeOk = not avoidChopZone or ((not priceInsideRibbon) and (not ribbonInsideBar) and (not narrowRibbon)) +launchChopContext = avoidChopZone and (priceCrossRibbonCount > chopMaxCrosses or nearBias or biasChopZone or biasSideFlips > 0 or ribbonStraddlesBias or narrowRibbon) +longLaunchDistanceOk = close - ribbonHigh >= atr * launchEscapeDistanceAtr +shortLaunchDistanceOk = ribbonLow - close >= atr * launchEscapeDistanceAtr +launchRibbonOpenOk = ribbonWidthAtr >= launchMinRibbonAtr +longBiasEscapeOk = not launchChopContext or not useBiasSide or close > emaBias + atr * launchBiasEscapeAtr +shortBiasEscapeOk = not launchChopContext or not useBiasSide or close < emaBias - atr * launchBiasEscapeAtr +longLaunchEscapeOk = launchBaseEscapeOk and longLaunchDistanceOk and launchRibbonOpenOk and (not launchChopContext or body >= atr * launchChopBodyAtr) and longBiasEscapeOk +shortLaunchEscapeOk = launchBaseEscapeOk and shortLaunchDistanceOk and launchRibbonOpenOk and (not launchChopContext or body >= atr * launchChopBodyAtr) and shortBiasEscapeOk +continuationZoneOk = not chopZone + +localRangeHigh = ta.highest(high, extremeLookback) +localRangeLow = ta.lowest(low, extremeLookback) +localRange = localRangeHigh - localRangeLow +rangePosition = localRange > syminfo.mintick ? (close - localRangeLow) / localRange : 0.5 +longPositionOk = rangePosition <= maxLongRangePosition +shortPositionOk = rangePosition >= minShortRangePosition +entryRangeHigh = ta.highest(high, entryRangeLookback) +entryRangeLow = ta.lowest(low, entryRangeLookback) +entryRange = entryRangeHigh - entryRangeLow +entryRangePosition = entryRange > syminfo.mintick ? (close - entryRangeLow) / entryRange : 0.5 +longEntryPositionOk = entryRangePosition <= maxLongEntryRangePosition +shortEntryPositionOk = entryRangePosition >= minShortEntryRangePosition + +pivotDemand = ta.pivotlow(low, structurePivotLeft, structurePivotRight) +pivotSupply = ta.pivothigh(high, structurePivotLeft, structurePivotRight) +var float[] demandZoneBottoms = array.new_float() +var float[] demandZoneTops = array.new_float() +var int[] demandZoneBars = array.new_int() +var box[] demandZoneBoxes = array.new_box() +var label[] demandZoneLabels = array.new_label() +var int[] demandZoneTests = array.new_int() +var bool[] demandZoneWasInside = array.new_bool() +var float[] supplyZoneTops = array.new_float() +var float[] supplyZoneBottoms = array.new_float() +var int[] supplyZoneBars = array.new_int() +var box[] supplyZoneBoxes = array.new_box() +var label[] supplyZoneLabels = array.new_label() +var int[] supplyZoneTests = array.new_int() +var bool[] supplyZoneWasInside = array.new_bool() + +chartTfText = timeframe.isminutes ? str.tostring(timeframe.multiplier) + "m" : timeframe.period + +f_tfText(tf) => + tf == "60" ? "1H" : tf == "240" ? "4H" : tf == "D" ? "1D" : tf + +f_htfDemandBottom() => + htfPivotDemand = ta.pivotlow(low, structurePivotLeft, structurePivotRight) + htfDemandValid = not na(htfPivotDemand) and high >= htfPivotDemand + ta.atr(atrLen)[structurePivotRight] * minStructureReactionAtr + htfDemandValid ? htfPivotDemand : na + +f_htfDemandTop() => + htfPivotDemand = ta.pivotlow(low, structurePivotLeft, structurePivotRight) + htfDemandValid = not na(htfPivotDemand) and high >= htfPivotDemand + ta.atr(atrLen)[structurePivotRight] * minStructureReactionAtr + htfDemandValid ? htfPivotDemand + ta.atr(atrLen)[structurePivotRight] * structureZoneAtr : na + +f_htfDemandTime() => + htfPivotDemand = ta.pivotlow(low, structurePivotLeft, structurePivotRight) + htfDemandValid = not na(htfPivotDemand) and high >= htfPivotDemand + ta.atr(atrLen)[structurePivotRight] * minStructureReactionAtr + htfDemandValid ? time[structurePivotRight] : na + +f_htfSupplyTop() => + htfPivotSupply = ta.pivothigh(high, structurePivotLeft, structurePivotRight) + htfSupplyValid = not na(htfPivotSupply) and low <= htfPivotSupply - ta.atr(atrLen)[structurePivotRight] * minStructureReactionAtr + htfSupplyValid ? htfPivotSupply : na + +f_htfSupplyBottom() => + htfPivotSupply = ta.pivothigh(high, structurePivotLeft, structurePivotRight) + htfSupplyValid = not na(htfPivotSupply) and low <= htfPivotSupply - ta.atr(atrLen)[structurePivotRight] * minStructureReactionAtr + htfSupplyValid ? htfPivotSupply - ta.atr(atrLen)[structurePivotRight] * structureZoneAtr : na + +f_htfSupplyTime() => + htfPivotSupply = ta.pivothigh(high, structurePivotLeft, structurePivotRight) + htfSupplyValid = not na(htfPivotSupply) and low <= htfPivotSupply - ta.atr(atrLen)[structurePivotRight] * minStructureReactionAtr + htfSupplyValid ? time[structurePivotRight] : na + +var float[] htfDemandZoneBottoms = array.new_float() +var float[] htfDemandZoneTops = array.new_float() +var int[] htfDemandZoneTimes = array.new_int() +var box[] htfDemandZoneBoxes = array.new_box() +var label[] htfDemandZoneLabels = array.new_label() +var int[] htfDemandZoneTests = array.new_int() +var bool[] htfDemandZoneWasInside = array.new_bool() +var string[] htfDemandZoneTfTexts = array.new_string() +var float[] htfSupplyZoneTops = array.new_float() +var float[] htfSupplyZoneBottoms = array.new_float() +var int[] htfSupplyZoneTimes = array.new_int() +var box[] htfSupplyZoneBoxes = array.new_box() +var label[] htfSupplyZoneLabels = array.new_label() +var int[] htfSupplyZoneTests = array.new_int() +var bool[] htfSupplyZoneWasInside = array.new_bool() +var string[] htfSupplyZoneTfTexts = array.new_string() +var int lastHtfDemandTime1 = na +var int lastHtfSupplyTime1 = na +var int lastHtfDemandTime2 = na +var int lastHtfSupplyTime2 = na + +validDemandPivot = not na(pivotDemand) and high >= pivotDemand + atr[structurePivotRight] * minStructureReactionAtr +validSupplyPivot = not na(pivotSupply) and low <= pivotSupply - atr[structurePivotRight] * minStructureReactionAtr + +htfDemandBottom1 = request.security(syminfo.tickerid, htfStructureTf1, f_htfDemandBottom(), barmerge.gaps_off, barmerge.lookahead_off) +htfDemandTop1 = request.security(syminfo.tickerid, htfStructureTf1, f_htfDemandTop(), barmerge.gaps_off, barmerge.lookahead_off) +htfDemandTime1 = request.security(syminfo.tickerid, htfStructureTf1, f_htfDemandTime(), barmerge.gaps_off, barmerge.lookahead_off) +htfSupplyTop1 = request.security(syminfo.tickerid, htfStructureTf1, f_htfSupplyTop(), barmerge.gaps_off, barmerge.lookahead_off) +htfSupplyBottom1 = request.security(syminfo.tickerid, htfStructureTf1, f_htfSupplyBottom(), barmerge.gaps_off, barmerge.lookahead_off) +htfSupplyTime1 = request.security(syminfo.tickerid, htfStructureTf1, f_htfSupplyTime(), barmerge.gaps_off, barmerge.lookahead_off) +htfDemandBottom2 = request.security(syminfo.tickerid, htfStructureTf2, f_htfDemandBottom(), barmerge.gaps_off, barmerge.lookahead_off) +htfDemandTop2 = request.security(syminfo.tickerid, htfStructureTf2, f_htfDemandTop(), barmerge.gaps_off, barmerge.lookahead_off) +htfDemandTime2 = request.security(syminfo.tickerid, htfStructureTf2, f_htfDemandTime(), barmerge.gaps_off, barmerge.lookahead_off) +htfSupplyTop2 = request.security(syminfo.tickerid, htfStructureTf2, f_htfSupplyTop(), barmerge.gaps_off, barmerge.lookahead_off) +htfSupplyBottom2 = request.security(syminfo.tickerid, htfStructureTf2, f_htfSupplyBottom(), barmerge.gaps_off, barmerge.lookahead_off) +htfSupplyTime2 = request.security(syminfo.tickerid, htfStructureTf2, f_htfSupplyTime(), barmerge.gaps_off, barmerge.lookahead_off) + +newHtfDemand1 = not na(htfDemandTime1) and (na(lastHtfDemandTime1) or htfDemandTime1 != lastHtfDemandTime1) +newHtfSupply1 = not na(htfSupplyTime1) and (na(lastHtfSupplyTime1) or htfSupplyTime1 != lastHtfSupplyTime1) +newHtfDemand2 = not na(htfDemandTime2) and (na(lastHtfDemandTime2) or htfDemandTime2 != lastHtfDemandTime2) +newHtfSupply2 = not na(htfSupplyTime2) and (na(lastHtfSupplyTime2) or htfSupplyTime2 != lastHtfSupplyTime2) + +if newHtfDemand1 + lastHtfDemandTime1 := htfDemandTime1 + htfDemandBox = showHtfStructureZones ? box.new(left=htfDemandTime1, top=htfDemandTop1, right=time, bottom=htfDemandBottom1, xloc=xloc.bar_time, bgcolor=color.new(color.green, 82), border_color=color.new(color.green, 10), border_width=2) : na + htfDemandLabel = showHtfStructureZones ? label.new(x=time, y=(htfDemandTop1 + htfDemandBottom1) / 2.0, text=f_tfText(htfStructureTf1) + " 需求 x0", xloc=xloc.bar_time, yloc=yloc.price, style=label.style_label_left, color=color.new(color.green, 60), textcolor=color.white, size=size.small) : na + array.push(htfDemandZoneBottoms, htfDemandBottom1) + array.push(htfDemandZoneTops, htfDemandTop1) + array.push(htfDemandZoneTimes, htfDemandTime1) + array.push(htfDemandZoneBoxes, htfDemandBox) + array.push(htfDemandZoneLabels, htfDemandLabel) + array.push(htfDemandZoneTests, 0) + array.push(htfDemandZoneWasInside, false) + array.push(htfDemandZoneTfTexts, f_tfText(htfStructureTf1)) + +if newHtfSupply1 + lastHtfSupplyTime1 := htfSupplyTime1 + htfSupplyBox = showHtfStructureZones ? box.new(left=htfSupplyTime1, top=htfSupplyTop1, right=time, bottom=htfSupplyBottom1, xloc=xloc.bar_time, bgcolor=color.new(color.maroon, 82), border_color=color.new(color.red, 10), border_width=2) : na + htfSupplyLabel = showHtfStructureZones ? label.new(x=time, y=(htfSupplyTop1 + htfSupplyBottom1) / 2.0, text=f_tfText(htfStructureTf1) + " 供应 x0", xloc=xloc.bar_time, yloc=yloc.price, style=label.style_label_left, color=color.new(color.red, 60), textcolor=color.white, size=size.small) : na + array.push(htfSupplyZoneTops, htfSupplyTop1) + array.push(htfSupplyZoneBottoms, htfSupplyBottom1) + array.push(htfSupplyZoneTimes, htfSupplyTime1) + array.push(htfSupplyZoneBoxes, htfSupplyBox) + array.push(htfSupplyZoneLabels, htfSupplyLabel) + array.push(htfSupplyZoneTests, 0) + array.push(htfSupplyZoneWasInside, false) + array.push(htfSupplyZoneTfTexts, f_tfText(htfStructureTf1)) + +if newHtfDemand2 + lastHtfDemandTime2 := htfDemandTime2 + htfDemandBox = showHtfStructureZones ? box.new(left=htfDemandTime2, top=htfDemandTop2, right=time, bottom=htfDemandBottom2, xloc=xloc.bar_time, bgcolor=color.new(color.teal, 80), border_color=color.new(color.teal, 5), border_width=2) : na + htfDemandLabel = showHtfStructureZones ? label.new(x=time, y=(htfDemandTop2 + htfDemandBottom2) / 2.0, text=f_tfText(htfStructureTf2) + " 需求 x0", xloc=xloc.bar_time, yloc=yloc.price, style=label.style_label_left, color=color.new(color.teal, 55), textcolor=color.white, size=size.small) : na + array.push(htfDemandZoneBottoms, htfDemandBottom2) + array.push(htfDemandZoneTops, htfDemandTop2) + array.push(htfDemandZoneTimes, htfDemandTime2) + array.push(htfDemandZoneBoxes, htfDemandBox) + array.push(htfDemandZoneLabels, htfDemandLabel) + array.push(htfDemandZoneTests, 0) + array.push(htfDemandZoneWasInside, false) + array.push(htfDemandZoneTfTexts, f_tfText(htfStructureTf2)) + +if newHtfSupply2 + lastHtfSupplyTime2 := htfSupplyTime2 + htfSupplyBox = showHtfStructureZones ? box.new(left=htfSupplyTime2, top=htfSupplyTop2, right=time, bottom=htfSupplyBottom2, xloc=xloc.bar_time, bgcolor=color.new(color.purple, 80), border_color=color.new(color.purple, 5), border_width=2) : na + htfSupplyLabel = showHtfStructureZones ? label.new(x=time, y=(htfSupplyTop2 + htfSupplyBottom2) / 2.0, text=f_tfText(htfStructureTf2) + " 供应 x0", xloc=xloc.bar_time, yloc=yloc.price, style=label.style_label_left, color=color.new(color.purple, 55), textcolor=color.white, size=size.small) : na + array.push(htfSupplyZoneTops, htfSupplyTop2) + array.push(htfSupplyZoneBottoms, htfSupplyBottom2) + array.push(htfSupplyZoneTimes, htfSupplyTime2) + array.push(htfSupplyZoneBoxes, htfSupplyBox) + array.push(htfSupplyZoneLabels, htfSupplyLabel) + array.push(htfSupplyZoneTests, 0) + array.push(htfSupplyZoneWasInside, false) + array.push(htfSupplyZoneTfTexts, f_tfText(htfStructureTf2)) + +while array.size(htfDemandZoneTimes) > maxHtfStructureBoxes + oldHtfDemandBox = array.shift(htfDemandZoneBoxes) + oldHtfDemandLabel = array.shift(htfDemandZoneLabels) + array.shift(htfDemandZoneBottoms) + array.shift(htfDemandZoneTops) + array.shift(htfDemandZoneTimes) + array.shift(htfDemandZoneTests) + array.shift(htfDemandZoneWasInside) + array.shift(htfDemandZoneTfTexts) + if not na(oldHtfDemandBox) + box.delete(oldHtfDemandBox) + if not na(oldHtfDemandLabel) + label.delete(oldHtfDemandLabel) + +while array.size(htfSupplyZoneTimes) > maxHtfStructureBoxes + oldHtfSupplyBox = array.shift(htfSupplyZoneBoxes) + oldHtfSupplyLabel = array.shift(htfSupplyZoneLabels) + array.shift(htfSupplyZoneTops) + array.shift(htfSupplyZoneBottoms) + array.shift(htfSupplyZoneTimes) + array.shift(htfSupplyZoneTests) + array.shift(htfSupplyZoneWasInside) + array.shift(htfSupplyZoneTfTexts) + if not na(oldHtfSupplyBox) + box.delete(oldHtfSupplyBox) + if not na(oldHtfSupplyLabel) + label.delete(oldHtfSupplyLabel) + +if array.size(htfDemandZoneTimes) > 0 + htfDemandIndex = array.size(htfDemandZoneTimes) - 1 + while htfDemandIndex >= 0 + htfDemandBottomLevel = array.get(htfDemandZoneBottoms, htfDemandIndex) + htfDemandTopLevel = array.get(htfDemandZoneTops, htfDemandIndex) + htfDemandBroken = close < htfDemandBottomLevel + htfDemandInside = low <= htfDemandTopLevel and high >= htfDemandBottomLevel + htfDemandWasInside = array.get(htfDemandZoneWasInside, htfDemandIndex) + if htfDemandBroken + brokenHtfDemandBox = array.get(htfDemandZoneBoxes, htfDemandIndex) + brokenHtfDemandLabel = array.get(htfDemandZoneLabels, htfDemandIndex) + if not na(brokenHtfDemandBox) + box.delete(brokenHtfDemandBox) + if not na(brokenHtfDemandLabel) + label.delete(brokenHtfDemandLabel) + array.remove(htfDemandZoneBottoms, htfDemandIndex) + array.remove(htfDemandZoneTops, htfDemandIndex) + array.remove(htfDemandZoneTimes, htfDemandIndex) + array.remove(htfDemandZoneBoxes, htfDemandIndex) + array.remove(htfDemandZoneLabels, htfDemandIndex) + array.remove(htfDemandZoneTests, htfDemandIndex) + array.remove(htfDemandZoneWasInside, htfDemandIndex) + array.remove(htfDemandZoneTfTexts, htfDemandIndex) + else + if htfDemandInside and not htfDemandWasInside + array.set(htfDemandZoneTests, htfDemandIndex, array.get(htfDemandZoneTests, htfDemandIndex) + 1) + array.set(htfDemandZoneWasInside, htfDemandIndex, htfDemandInside) + htfDemandTests = array.get(htfDemandZoneTests, htfDemandIndex) + htfDemandTfText = array.get(htfDemandZoneTfTexts, htfDemandIndex) + htfDemandWeak = htfDemandTests >= maxZoneTests + htfDemandBoxToStyle = array.get(htfDemandZoneBoxes, htfDemandIndex) + htfDemandLabelToStyle = array.get(htfDemandZoneLabels, htfDemandIndex) + if not na(htfDemandBoxToStyle) + box.set_bgcolor(htfDemandBoxToStyle, htfDemandWeak ? color.new(color.gray, 90) : color.new(color.green, 82)) + box.set_border_color(htfDemandBoxToStyle, htfDemandWeak ? color.new(color.gray, 35) : color.new(color.green, 10)) + box.set_right(htfDemandBoxToStyle, time) + if not na(htfDemandLabelToStyle) + label.set_x(htfDemandLabelToStyle, time) + label.set_y(htfDemandLabelToStyle, (htfDemandTopLevel + htfDemandBottomLevel) / 2.0) + label.set_text(htfDemandLabelToStyle, htfDemandTfText + " 需求 x" + str.tostring(htfDemandTests)) + label.set_color(htfDemandLabelToStyle, htfDemandWeak ? color.new(color.gray, 70) : color.new(color.green, 55)) + htfDemandIndex := htfDemandIndex - 1 + +if array.size(htfSupplyZoneTimes) > 0 + htfSupplyIndex = array.size(htfSupplyZoneTimes) - 1 + while htfSupplyIndex >= 0 + htfSupplyTopLevel = array.get(htfSupplyZoneTops, htfSupplyIndex) + htfSupplyBottomLevel = array.get(htfSupplyZoneBottoms, htfSupplyIndex) + htfSupplyBroken = close > htfSupplyTopLevel + htfSupplyInside = high >= htfSupplyBottomLevel and low <= htfSupplyTopLevel + htfSupplyWasInside = array.get(htfSupplyZoneWasInside, htfSupplyIndex) + if htfSupplyBroken + brokenHtfSupplyBox = array.get(htfSupplyZoneBoxes, htfSupplyIndex) + brokenHtfSupplyLabel = array.get(htfSupplyZoneLabels, htfSupplyIndex) + if not na(brokenHtfSupplyBox) + box.delete(brokenHtfSupplyBox) + if not na(brokenHtfSupplyLabel) + label.delete(brokenHtfSupplyLabel) + array.remove(htfSupplyZoneTops, htfSupplyIndex) + array.remove(htfSupplyZoneBottoms, htfSupplyIndex) + array.remove(htfSupplyZoneTimes, htfSupplyIndex) + array.remove(htfSupplyZoneBoxes, htfSupplyIndex) + array.remove(htfSupplyZoneLabels, htfSupplyIndex) + array.remove(htfSupplyZoneTests, htfSupplyIndex) + array.remove(htfSupplyZoneWasInside, htfSupplyIndex) + array.remove(htfSupplyZoneTfTexts, htfSupplyIndex) + else + if htfSupplyInside and not htfSupplyWasInside + array.set(htfSupplyZoneTests, htfSupplyIndex, array.get(htfSupplyZoneTests, htfSupplyIndex) + 1) + array.set(htfSupplyZoneWasInside, htfSupplyIndex, htfSupplyInside) + htfSupplyTests = array.get(htfSupplyZoneTests, htfSupplyIndex) + htfSupplyTfText = array.get(htfSupplyZoneTfTexts, htfSupplyIndex) + htfSupplyWeak = htfSupplyTests >= maxZoneTests + htfSupplyBoxToStyle = array.get(htfSupplyZoneBoxes, htfSupplyIndex) + htfSupplyLabelToStyle = array.get(htfSupplyZoneLabels, htfSupplyIndex) + if not na(htfSupplyBoxToStyle) + box.set_bgcolor(htfSupplyBoxToStyle, htfSupplyWeak ? color.new(color.gray, 90) : color.new(color.red, 82)) + box.set_border_color(htfSupplyBoxToStyle, htfSupplyWeak ? color.new(color.gray, 35) : color.new(color.red, 10)) + box.set_right(htfSupplyBoxToStyle, time) + if not na(htfSupplyLabelToStyle) + label.set_x(htfSupplyLabelToStyle, time) + label.set_y(htfSupplyLabelToStyle, (htfSupplyTopLevel + htfSupplyBottomLevel) / 2.0) + label.set_text(htfSupplyLabelToStyle, htfSupplyTfText + " 供应 x" + str.tostring(htfSupplyTests)) + label.set_color(htfSupplyLabelToStyle, htfSupplyWeak ? color.new(color.gray, 70) : color.new(color.red, 55)) + htfSupplyIndex := htfSupplyIndex - 1 + +if validDemandPivot + demandLeft = bar_index - structurePivotRight + demandBottom = pivotDemand + demandTop = pivotDemand + atr[structurePivotRight] * structureZoneAtr + demandBox = showStructureZones ? box.new(left=demandLeft, top=demandTop, right=bar_index, bottom=demandBottom, xloc=xloc.bar_index, bgcolor=color.new(color.lime, 88), border_color=color.new(color.lime, 25), border_width=1) : na + demandLabel = showStructureZones ? label.new(x=bar_index, y=(demandTop + demandBottom) / 2.0, text=chartTfText + " 需求 x0", xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_left, color=color.new(color.lime, 75), textcolor=color.white, size=size.tiny) : na + array.push(demandZoneBottoms, demandBottom) + array.push(demandZoneTops, demandTop) + array.push(demandZoneBars, demandLeft) + array.push(demandZoneBoxes, demandBox) + array.push(demandZoneLabels, demandLabel) + array.push(demandZoneTests, 0) + array.push(demandZoneWasInside, false) + +if validSupplyPivot + supplyLeft = bar_index - structurePivotRight + supplyTop = pivotSupply + supplyBottom = pivotSupply - atr[structurePivotRight] * structureZoneAtr + supplyBox = showStructureZones ? box.new(left=supplyLeft, top=supplyTop, right=bar_index, bottom=supplyBottom, xloc=xloc.bar_index, bgcolor=color.new(color.red, 88), border_color=color.new(color.red, 25), border_width=1) : na + supplyLabel = showStructureZones ? label.new(x=bar_index, y=(supplyTop + supplyBottom) / 2.0, text=chartTfText + " 供应 x0", xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_left, color=color.new(color.red, 75), textcolor=color.white, size=size.tiny) : na + array.push(supplyZoneTops, supplyTop) + array.push(supplyZoneBottoms, supplyBottom) + array.push(supplyZoneBars, supplyLeft) + array.push(supplyZoneBoxes, supplyBox) + array.push(supplyZoneLabels, supplyLabel) + array.push(supplyZoneTests, 0) + array.push(supplyZoneWasInside, false) + +while array.size(demandZoneBars) > 0 + oldestDemandBar = array.get(demandZoneBars, 0) + if bar_index - oldestDemandBar > structureLookback + array.shift(demandZoneBottoms) + array.shift(demandZoneTops) + array.shift(demandZoneBars) + oldDemandBox = array.shift(demandZoneBoxes) + oldDemandLabel = array.shift(demandZoneLabels) + array.shift(demandZoneTests) + array.shift(demandZoneWasInside) + if not na(oldDemandBox) + box.delete(oldDemandBox) + if not na(oldDemandLabel) + label.delete(oldDemandLabel) + else + break + +while array.size(supplyZoneBars) > 0 + oldestSupplyBar = array.get(supplyZoneBars, 0) + if bar_index - oldestSupplyBar > structureLookback + array.shift(supplyZoneTops) + array.shift(supplyZoneBottoms) + array.shift(supplyZoneBars) + oldSupplyBox = array.shift(supplyZoneBoxes) + oldSupplyLabel = array.shift(supplyZoneLabels) + array.shift(supplyZoneTests) + array.shift(supplyZoneWasInside) + if not na(oldSupplyBox) + box.delete(oldSupplyBox) + if not na(oldSupplyLabel) + label.delete(oldSupplyLabel) + else + break + +while array.size(demandZoneBars) > maxStructureBoxes + array.shift(demandZoneBottoms) + array.shift(demandZoneTops) + array.shift(demandZoneBars) + oldDemandBox = array.shift(demandZoneBoxes) + oldDemandLabel = array.shift(demandZoneLabels) + array.shift(demandZoneTests) + array.shift(demandZoneWasInside) + if not na(oldDemandBox) + box.delete(oldDemandBox) + if not na(oldDemandLabel) + label.delete(oldDemandLabel) + +while array.size(supplyZoneBars) > maxStructureBoxes + array.shift(supplyZoneTops) + array.shift(supplyZoneBottoms) + array.shift(supplyZoneBars) + oldSupplyBox = array.shift(supplyZoneBoxes) + oldSupplyLabel = array.shift(supplyZoneLabels) + array.shift(supplyZoneTests) + array.shift(supplyZoneWasInside) + if not na(oldSupplyBox) + box.delete(oldSupplyBox) + if not na(oldSupplyLabel) + label.delete(oldSupplyLabel) + +if array.size(demandZoneBars) > 0 + demandIndex = array.size(demandZoneBars) - 1 + while demandIndex >= 0 + demandBottomLevel = array.get(demandZoneBottoms, demandIndex) + demandTopLevel = array.get(demandZoneTops, demandIndex) + demandBroken = close < demandBottomLevel + demandInside = low <= demandTopLevel and high >= demandBottomLevel + demandWasInside = array.get(demandZoneWasInside, demandIndex) + if demandBroken + brokenDemandBox = array.get(demandZoneBoxes, demandIndex) + brokenDemandLabel = array.get(demandZoneLabels, demandIndex) + if not na(brokenDemandBox) + box.delete(brokenDemandBox) + if not na(brokenDemandLabel) + label.delete(brokenDemandLabel) + array.remove(demandZoneBottoms, demandIndex) + array.remove(demandZoneTops, demandIndex) + array.remove(demandZoneBars, demandIndex) + array.remove(demandZoneBoxes, demandIndex) + array.remove(demandZoneLabels, demandIndex) + array.remove(demandZoneTests, demandIndex) + array.remove(demandZoneWasInside, demandIndex) + else + if demandInside and not demandWasInside + array.set(demandZoneTests, demandIndex, array.get(demandZoneTests, demandIndex) + 1) + array.set(demandZoneWasInside, demandIndex, demandInside) + demandTests = array.get(demandZoneTests, demandIndex) + demandBoxToStyle = array.get(demandZoneBoxes, demandIndex) + if not na(demandBoxToStyle) + demandWeak = demandTests >= maxZoneTests + box.set_bgcolor(demandBoxToStyle, demandWeak ? color.new(color.gray, 92) : color.new(color.lime, 88)) + box.set_border_color(demandBoxToStyle, demandWeak ? color.new(color.gray, 45) : color.new(color.lime, 25)) + demandLabelToStyle = array.get(demandZoneLabels, demandIndex) + if not na(demandLabelToStyle) + demandWeak = demandTests >= maxZoneTests + label.set_text(demandLabelToStyle, chartTfText + " 需求 x" + str.tostring(demandTests)) + label.set_y(demandLabelToStyle, (demandTopLevel + demandBottomLevel) / 2.0) + label.set_color(demandLabelToStyle, demandWeak ? color.new(color.gray, 75) : color.new(color.lime, 75)) + demandIndex := demandIndex - 1 + +if array.size(supplyZoneBars) > 0 + supplyIndex = array.size(supplyZoneBars) - 1 + while supplyIndex >= 0 + supplyTopLevel = array.get(supplyZoneTops, supplyIndex) + supplyBottomLevel = array.get(supplyZoneBottoms, supplyIndex) + supplyBroken = close > supplyTopLevel + supplyInside = high >= supplyBottomLevel and low <= supplyTopLevel + supplyWasInside = array.get(supplyZoneWasInside, supplyIndex) + if supplyBroken + brokenSupplyBox = array.get(supplyZoneBoxes, supplyIndex) + brokenSupplyLabel = array.get(supplyZoneLabels, supplyIndex) + if not na(brokenSupplyBox) + box.delete(brokenSupplyBox) + if not na(brokenSupplyLabel) + label.delete(brokenSupplyLabel) + array.remove(supplyZoneTops, supplyIndex) + array.remove(supplyZoneBottoms, supplyIndex) + array.remove(supplyZoneBars, supplyIndex) + array.remove(supplyZoneBoxes, supplyIndex) + array.remove(supplyZoneLabels, supplyIndex) + array.remove(supplyZoneTests, supplyIndex) + array.remove(supplyZoneWasInside, supplyIndex) + else + if supplyInside and not supplyWasInside + array.set(supplyZoneTests, supplyIndex, array.get(supplyZoneTests, supplyIndex) + 1) + array.set(supplyZoneWasInside, supplyIndex, supplyInside) + supplyTests = array.get(supplyZoneTests, supplyIndex) + supplyBoxToStyle = array.get(supplyZoneBoxes, supplyIndex) + if not na(supplyBoxToStyle) + supplyWeak = supplyTests >= maxZoneTests + box.set_bgcolor(supplyBoxToStyle, supplyWeak ? color.new(color.gray, 92) : color.new(color.red, 88)) + box.set_border_color(supplyBoxToStyle, supplyWeak ? color.new(color.gray, 45) : color.new(color.red, 25)) + supplyLabelToStyle = array.get(supplyZoneLabels, supplyIndex) + if not na(supplyLabelToStyle) + supplyWeak = supplyTests >= maxZoneTests + label.set_text(supplyLabelToStyle, chartTfText + " 供应 x" + str.tostring(supplyTests)) + label.set_y(supplyLabelToStyle, (supplyTopLevel + supplyBottomLevel) / 2.0) + label.set_color(supplyLabelToStyle, supplyWeak ? color.new(color.gray, 75) : color.new(color.red, 75)) + supplyIndex := supplyIndex - 1 + +if array.size(demandZoneBoxes) > 0 + for zoneIndex = 0 to array.size(demandZoneBoxes) - 1 + activeDemandBox = array.get(demandZoneBoxes, zoneIndex) + if not na(activeDemandBox) + box.set_right(activeDemandBox, bar_index) + activeDemandLabel = array.get(demandZoneLabels, zoneIndex) + if not na(activeDemandLabel) + label.set_x(activeDemandLabel, bar_index) + +if array.size(supplyZoneBoxes) > 0 + for zoneIndex = 0 to array.size(supplyZoneBoxes) - 1 + activeSupplyBox = array.get(supplyZoneBoxes, zoneIndex) + if not na(activeSupplyBox) + box.set_right(activeSupplyBox, bar_index) + activeSupplyLabel = array.get(supplyZoneLabels, zoneIndex) + if not na(activeSupplyLabel) + label.set_x(activeSupplyLabel, bar_index) + +float nearestDemandTop = na +float nearestDemandBottom = na +bool currentDemandTouch = false +if array.size(demandZoneTops) > 0 + for zoneIndex = 0 to array.size(demandZoneTops) - 1 + demandTopLevel = array.get(demandZoneTops, zoneIndex) + demandBottomLevel = array.get(demandZoneBottoms, zoneIndex) + demandTouched = low <= demandTopLevel + atr * htfZoneBlockAtr and high >= demandBottomLevel - atr * htfZoneBlockAtr + currentDemandTouch := currentDemandTouch or demandTouched + demandZoneStrong = array.get(demandZoneTests, zoneIndex) < maxZoneTests + if demandZoneStrong + if demandBottomLevel <= close and (na(nearestDemandTop) or demandTopLevel > nearestDemandTop) + nearestDemandTop := demandTopLevel + nearestDemandBottom := demandBottomLevel + +float nearestSupplyBottom = na +float nearestSupplyTop = na +bool currentSupplyTouch = false +if array.size(supplyZoneBottoms) > 0 + for zoneIndex = 0 to array.size(supplyZoneBottoms) - 1 + supplyBottomLevel = array.get(supplyZoneBottoms, zoneIndex) + supplyTopLevel = array.get(supplyZoneTops, zoneIndex) + supplyTouched = high >= supplyBottomLevel - atr * htfZoneBlockAtr and low <= supplyTopLevel + atr * htfZoneBlockAtr + currentSupplyTouch := currentSupplyTouch or supplyTouched + supplyZoneStrong = array.get(supplyZoneTests, zoneIndex) < maxZoneTests + if supplyZoneStrong + if supplyTopLevel >= close and (na(nearestSupplyBottom) or supplyBottomLevel < nearestSupplyBottom) + nearestSupplyBottom := supplyBottomLevel + nearestSupplyTop := supplyTopLevel + +roomToDemand = na(nearestDemandTop) ? na : close - nearestDemandTop +roomToSupply = na(nearestSupplyBottom) ? na : nearestSupplyBottom - close +float nearestHtfDemandTop = na +float nearestHtfDemandBottom = na +bool currentHtfDemandTouch = false +if array.size(htfDemandZoneTops) > 0 + for zoneIndex = 0 to array.size(htfDemandZoneTops) - 1 + htfDemandTopLevel = array.get(htfDemandZoneTops, zoneIndex) + htfDemandBottomLevel = array.get(htfDemandZoneBottoms, zoneIndex) + htfDemandTouched = low <= htfDemandTopLevel + atr * htfZoneBlockAtr and high >= htfDemandBottomLevel - atr * htfZoneBlockAtr + currentHtfDemandTouch := currentHtfDemandTouch or htfDemandTouched + htfDemandStrong = array.get(htfDemandZoneTests, zoneIndex) < maxZoneTests + if htfDemandStrong + if htfDemandBottomLevel <= close and (na(nearestHtfDemandTop) or htfDemandTopLevel > nearestHtfDemandTop) + nearestHtfDemandTop := htfDemandTopLevel + nearestHtfDemandBottom := htfDemandBottomLevel + +float nearestHtfSupplyBottom = na +float nearestHtfSupplyTop = na +bool currentHtfSupplyTouch = false +if array.size(htfSupplyZoneBottoms) > 0 + for zoneIndex = 0 to array.size(htfSupplyZoneBottoms) - 1 + htfSupplyBottomLevel = array.get(htfSupplyZoneBottoms, zoneIndex) + htfSupplyTopLevel = array.get(htfSupplyZoneTops, zoneIndex) + htfSupplyTouched = high >= htfSupplyBottomLevel - atr * htfZoneBlockAtr and low <= htfSupplyTopLevel + atr * htfZoneBlockAtr + currentHtfSupplyTouch := currentHtfSupplyTouch or htfSupplyTouched + htfSupplyStrong = array.get(htfSupplyZoneTests, zoneIndex) < maxZoneTests + if htfSupplyStrong + if htfSupplyTopLevel >= close and (na(nearestHtfSupplyBottom) or htfSupplyBottomLevel < nearestHtfSupplyBottom) + nearestHtfSupplyBottom := htfSupplyBottomLevel + nearestHtfSupplyTop := htfSupplyTopLevel + +roomToHtfDemand = na(nearestHtfDemandTop) ? na : close - nearestHtfDemandTop +roomToHtfSupply = na(nearestHtfSupplyBottom) ? na : nearestHtfSupplyBottom - close +shortNearHtfDemand = useHtfStructureFilter and useHtfZoneHardBlock and currentHtfDemandTouch +longNearHtfSupply = useHtfStructureFilter and useHtfZoneHardBlock and currentHtfSupplyTouch +shortHtfDemandBlock = useHtfStructureFilter and useHtfZoneHardBlock and (shortNearHtfDemand or nz(ta.barssince(shortNearHtfDemand), 100000) <= htfZoneTouchBlockBars) +longHtfSupplyBlock = useHtfStructureFilter and useHtfZoneHardBlock and (longNearHtfSupply or nz(ta.barssince(longNearHtfSupply), 100000) <= htfZoneTouchBlockBars) +downImpulseIntoZone = ta.highest(high[1], 5) - low >= atr * zoneImpulseAtr +upImpulseIntoZone = high - ta.lowest(low[1], 5) >= atr * zoneImpulseAtr +developingDemandReaction = downImpulseIntoZone and low <= ta.lowest(low, extremeLookback) + atr * 0.20 +developingSupplyReaction = upImpulseIntoZone and high >= ta.highest(high, extremeLookback) - atr * 0.20 +demandReaction = useZoneReactionBlock and (currentDemandTouch or currentHtfDemandTouch or developingDemandReaction) and close > open and body >= atr * minZoneReactionBodyAtr and (bullishEngulfing or close > high[1] or downImpulseIntoZone) +supplyReaction = useZoneReactionBlock and (currentSupplyTouch or currentHtfSupplyTouch or developingSupplyReaction) and close < open and body >= atr * minZoneReactionBodyAtr and (bearishEngulfing or close < low[1] or upImpulseIntoZone) +shortZoneReactionBlock = useZoneReactionBlock and (demandReaction or nz(ta.barssince(demandReaction), 100000) <= zoneReactionBlockBars) +longZoneReactionBlock = useZoneReactionBlock and (supplyReaction or nz(ta.barssince(supplyReaction), 100000) <= zoneReactionBlockBars) +shortLocalStructureRoomOk = not useStructureZoneFilter or na(nearestDemandTop) or roomToDemand >= atr * minRoomToZoneAtr +longLocalStructureRoomOk = not useStructureZoneFilter or na(nearestSupplyBottom) or roomToSupply >= atr * minRoomToZoneAtr +shortHtfStructureRoomOk = not useHtfStructureFilter or ((na(nearestHtfDemandTop) or roomToHtfDemand >= atr * minRoomToZoneAtr) and not shortHtfDemandBlock) +longHtfStructureRoomOk = not useHtfStructureFilter or ((na(nearestHtfSupplyBottom) or roomToHtfSupply >= atr * minRoomToZoneAtr) and not longHtfSupplyBlock) +shortStructureRoomOk = shortLocalStructureRoomOk and shortHtfStructureRoomOk and not shortZoneReactionBlock +longStructureRoomOk = longLocalStructureRoomOk and longHtfStructureRoomOk and not longZoneReactionBlock + +simpleBullTrend = biasBullOk and biasSlopeBullOk and bullOrder and bullSlopeCount >= profileSlopeNeed and ribbonSeparated and close > emaBias + atr * trendAwayAtr +simpleBearTrend = biasBearOk and biasSlopeBearOk and bearOrder and bearSlopeCount >= profileSlopeNeed and ribbonSeparated and close < emaBias - atr * trendAwayAtr +simpleBullPullback = simpleBullTrend and low <= emaMid and low >= emaSlow - atr * biasRetestAtr +simpleBearPullback = simpleBearTrend and high >= emaMid and high <= emaSlow + atr * biasRetestAtr +simpleBullPullbackRecent = nz(ta.barssince(simpleBullPullback), 100000) <= pullbackLookback +simpleBearPullbackRecent = nz(ta.barssince(simpleBearPullback), 100000) <= pullbackLookback +simpleLongConfirm = close > emaFast and close > emaMid and close > open and close - emaMid <= atr * maxContinuationDistanceMidAtr and entryRangePosition <= maxLongEntryRangePosition +simpleShortConfirm = close < emaFast and close < emaMid and close < open and emaMid - close <= atr * maxContinuationDistanceMidAtr and entryRangePosition >= minShortEntryRangePosition +simpleLongSetup = mtfLongOk and simpleBullTrend and simpleBullPullbackRecent and simpleLongConfirm and not chopZone and adxContinuationOk and diBullOk +simpleShortSetup = mtfShortOk and simpleBearTrend and simpleBearPullbackRecent and simpleShortConfirm and not chopZone and adxContinuationOk and diBearOk + +launchWasCompressed = ta.lowest(ribbonWidthAtr, compressionLookback) <= compressionAtr +prelaunchStillCompressed = launchWasCompressed and ribbonWidthAtr <= prelaunchMaxRibbonAtr +breaksShortHigh = close > ta.highest(high[1], launchBreakLookback) +breaksShortLow = close < ta.lowest(low[1], launchBreakLookback) +prelaunchHigh = ta.highest(high[1], launchBreakLookback) +prelaunchLow = ta.lowest(low[1], launchBreakLookback) +longNearBreak = high >= prelaunchHigh - atr * prelaunchBreakBufferAtr and close <= prelaunchHigh - atr * prelaunchMinBreakCloseAtr +shortNearBreak = low <= prelaunchLow + atr * prelaunchBreakBufferAtr and close >= prelaunchLow + atr * prelaunchMinBreakCloseAtr +launchBodyOk = body >= atr * minLaunchBodyAtr +prelaunchBodyOk = body >= atr * prelaunchMinBodyAtr +continuationBodyOk = body >= atr * minContinuationBodyAtr +continuationDistanceOk = math.abs(close - emaFast) <= atr * maxContinuationDistanceAtr +longContinuationDistanceOk = continuationDistanceOk and close - emaMid <= atr * maxContinuationDistanceMidAtr +shortContinuationDistanceOk = continuationDistanceOk and emaMid - close <= atr * maxContinuationDistanceMidAtr + +prelaunchNoMatureTrend = not simpleBullTrend and not simpleBearTrend +prelaunchCleanCompression = prelaunchStillCompressed and priceCrossRibbonCount <= prelaunchMaxRibbonCrosses and biasSideFlips <= prelaunchMaxBiasCrosses +longPrelaunchDirectionOk = emaFast >= emaMid and emaMid >= emaSlow and emaFast > emaFast[1] and emaMid >= emaMid[1] and close >= emaBias - atr * prelaunchBiasNearAtr +shortPrelaunchDirectionOk = emaFast <= emaMid and emaMid <= emaSlow and emaFast < emaFast[1] and emaMid <= emaMid[1] and close <= emaBias + atr * prelaunchBiasNearAtr + +longPrelaunchRaw = prelaunchMode != "关闭" and mtfLongOk and prelaunchNoMatureTrend and prelaunchCleanCompression and longStructureRoomOk and longDirectionOk and longNearBreak and close > emaMid and close > open and prelaunchBodyOk and bullSlopeCount >= profileSlopeNeed and longPrelaunchDirectionOk and diBullOk and longPositionOk +shortPrelaunchRaw = prelaunchMode != "关闭" and mtfShortOk and prelaunchNoMatureTrend and prelaunchCleanCompression and shortStructureRoomOk and shortDirectionOk and shortNearBreak and close < emaMid and close < open and prelaunchBodyOk and bearSlopeCount >= profileSlopeNeed and shortPrelaunchDirectionOk and diBearOk and shortPositionOk + +bullLaunchRaw = mtfLongOk and adxLaunchOk and diBullOk and longStructureRoomOk and longLaunchEscapeOk and biasBullLaunchOk and biasSlopeBullOk and longDirectionOk and launchWasCompressed and bullOrder and bullSlopeCount >= profileSlopeNeed and bullRibbonExpanding and close > emaFast and close > open and launchBodyOk and longPositionOk and longCleanClose and (not profileBreakNeed or breaksShortHigh) +bearLaunchRaw = mtfShortOk and adxLaunchOk and diBearOk and shortStructureRoomOk and shortLaunchEscapeOk and biasBearLaunchOk and biasSlopeBearOk and shortDirectionOk and launchWasCompressed and bearOrder and bearSlopeCount >= profileSlopeNeed and bearRibbonExpanding and close < emaFast and close < open and launchBodyOk and shortPositionOk and shortCleanClose and (not profileBreakNeed or breaksShortLow) + +bullLaunchStart = bullLaunchRaw and not bullLaunchRaw[1] +bearLaunchStart = bearLaunchRaw and not bearLaunchRaw[1] +bullLaunchFresh = nz(ta.barssince(bullLaunchStart), 100000) <= launchFreshBars +bearLaunchFresh = nz(ta.barssince(bearLaunchStart), 100000) <= launchFreshBars + +bullTrendContext = biasBullOk and biasSlopeBullOk and longDirectionOk and bullOrder and bullSlopeCount >= profileSlopeNeed +bearTrendContext = biasBearOk and biasSlopeBearOk and shortDirectionOk and bearOrder and bearSlopeCount >= profileSlopeNeed +bullTrendBars = bullTrendContext ? nz(ta.barssince(not bullTrendContext), 100000) : 0 +bearTrendBars = bearTrendContext ? nz(ta.barssince(not bearTrendContext), 100000) : 0 +var float lastBullBreakoutLevel = na +var float lastBearBreakoutLevel = na +bullBreakoutBodyOk = close > open and body >= atr * minBreakoutBodyAtr +bearBreakoutBodyOk = close < open and body >= atr * minBreakoutBodyAtr +bullPriceBreakout = ta.crossover(close, emaBias) and close > ta.highest(high[1], breakoutLevelLookback) and bullBreakoutBodyOk +bearPriceBreakout = ta.crossunder(close, emaBias) and close < ta.lowest(low[1], breakoutLevelLookback) and bearBreakoutBodyOk +bullBiasBreakout = bullPriceBreakout or ta.crossover(emaSlow, emaBias) +bearBiasBreakout = bearPriceBreakout or ta.crossunder(emaSlow, emaBias) +if bullPriceBreakout + lastBullBreakoutLevel := ta.highest(high[1], breakoutLevelLookback) +if bearPriceBreakout + lastBearBreakoutLevel := ta.lowest(low[1], breakoutLevelLookback) +bullBiasBreakoutFresh = not requireRecentBiasBreakout or nz(ta.barssince(bullBiasBreakout), 100000) <= biasBreakoutRetestBars +bearBiasBreakoutFresh = not requireRecentBiasBreakout or nz(ta.barssince(bearBiasBreakout), 100000) <= biasBreakoutRetestBars +bullTrendMature = bullTrendContext and bullTrendBars >= trendMatureBars and bullRibbonExpanding and bullBiasBreakoutFresh and (not useBiasSide or close > emaBias + atr * trendAwayAtr) +bearTrendMature = bearTrendContext and bearTrendBars >= trendMatureBars and bearRibbonExpanding and bearBiasBreakoutFresh and (not useBiasSide or close < emaBias - atr * trendAwayAtr) +longTrendAgeOk = not useLateTrendFilter or maxTrendBars == 0 or bullTrendBars <= maxTrendBars +shortTrendAgeOk = not useLateTrendFilter or maxTrendBars == 0 or bearTrendBars <= maxTrendBars + +bullPullbackToRibbon = bullTrendMature and low <= emaFast and low >= emaSlow - atr * biasRetestAtr +bearPullbackToRibbon = bearTrendMature and high >= emaFast and high <= emaSlow + atr * biasRetestAtr +bullPullbackToBias = bullTrendMature and useBiasRetestPullback and low <= emaBias + atr * biasRetestAtr +bearPullbackToBias = bearTrendMature and useBiasRetestPullback and high >= emaBias - atr * biasRetestAtr +bullPullbackToBreakoutLevel = bullTrendMature and not na(lastBullBreakoutLevel) and low <= lastBullBreakoutLevel + atr * breakoutLevelRetestAtr and close >= lastBullBreakoutLevel - atr * breakoutLevelRetestAtr +bearPullbackToBreakoutLevel = bearTrendMature and not na(lastBearBreakoutLevel) and high >= lastBearBreakoutLevel - atr * breakoutLevelRetestAtr and close <= lastBearBreakoutLevel + atr * breakoutLevelRetestAtr +bullPullbackEvent = (useBreakoutLevelRetest ? bullPullbackToBreakoutLevel : bullPullbackToRibbon) or bullPullbackToBias +bearPullbackEvent = (useBreakoutLevelRetest ? bearPullbackToBreakoutLevel : bearPullbackToRibbon) or bearPullbackToBias +bullPullbackRecent = nz(ta.barssince(bullPullbackEvent), 100000) <= pullbackLookback +bearPullbackRecent = nz(ta.barssince(bearPullbackEvent), 100000) <= pullbackLookback +bullBreakoutLevelConfirmOk = not useBreakoutLevelRetest or na(lastBullBreakoutLevel) or close <= lastBullBreakoutLevel + atr * maxContinuationDistanceAtr +bearBreakoutLevelConfirmOk = not useBreakoutLevelRetest or na(lastBearBreakoutLevel) or close >= lastBearBreakoutLevel - atr * maxContinuationDistanceAtr + +bullContinuationRaw = mtfLongOk and adxContinuationOk and diBullOk and continuationZoneOk and chopExitConfirmed and longStructureRoomOk and longTrendAgeOk and bullTrendMature and bullPullbackRecent and bullBreakoutLevelConfirmOk and close > emaFast and close > emaMid and close > open and continuationBodyOk and longContinuationDistanceOk and longPositionOk and longEntryPositionOk and longCleanClose +bearContinuationRaw = mtfShortOk and adxContinuationOk and diBearOk and continuationZoneOk and chopExitConfirmed and shortStructureRoomOk and shortTrendAgeOk and bearTrendMature and bearPullbackRecent and bearBreakoutLevelConfirmOk and close < emaFast and close < emaMid and close < open and continuationBodyOk and shortContinuationDistanceOk and shortPositionOk and shortEntryPositionOk and shortCleanClose + +var int lastLongSignalBar = na +var int lastShortSignalBar = na +var int lastAnySignalBar = na +var int lastLongPrelaunchBar = na +var int lastShortPrelaunchBar = na +var int longTrendSignalCount = 0 +var int shortTrendSignalCount = 0 + +if close < emaBias or bearTrendContext + longTrendSignalCount := 0 + +if close > emaBias or bullTrendContext + shortTrendSignalCount := 0 + +canLongSignal = na(lastLongSignalBar) or bar_index - lastLongSignalBar > cooldownBars +canShortSignal = na(lastShortSignalBar) or bar_index - lastShortSignalBar > cooldownBars +canAnySignal = na(lastAnySignalBar) or bar_index - lastAnySignalBar > globalCooldownBars +canLongPrelaunch = na(lastLongPrelaunchBar) or bar_index - lastLongPrelaunchBar > prelaunchCooldownBars +canShortPrelaunch = na(lastShortPrelaunchBar) or bar_index - lastShortPrelaunchBar > prelaunchCooldownBars +canLongTrendSignal = not useLateTrendFilter or maxMainSignalsPerTrend == 0 or longTrendSignalCount < maxMainSignalsPerTrend +canShortTrendSignal = not useLateTrendFilter or maxMainSignalsPerTrend == 0 or shortTrendSignalCount < maxMainSignalsPerTrend + +longPrelaunchSignal = barstate.isconfirmed and longPrelaunchRaw and not longPrelaunchRaw[1] and not bullLaunchRaw and canLongPrelaunch and canAnySignal +shortPrelaunchSignal = barstate.isconfirmed and shortPrelaunchRaw and not shortPrelaunchRaw[1] and not bearLaunchRaw and canShortPrelaunch and canAnySignal +longLaunchSignal = barstate.isconfirmed and bullLaunchStart and canLongSignal and canAnySignal +shortLaunchSignal = barstate.isconfirmed and bearLaunchStart and canShortSignal and canAnySignal +longContinuationSignal = barstate.isconfirmed and simpleLongSetup and not simpleLongSetup[1] and canLongSignal and canAnySignal and canLongTrendSignal +shortContinuationSignal = barstate.isconfirmed and simpleShortSetup and not simpleShortSetup[1] and canShortSignal and canAnySignal and canShortTrendSignal + +useLaunchAsMain = mainSignalMode == "启动+趋势回踩" +usePrelaunchAsMain = prelaunchMode == "并入主信号" +mainLongPrelaunchSignal = usePrelaunchAsMain and longPrelaunchSignal and canLongTrendSignal +mainShortPrelaunchSignal = usePrelaunchAsMain and shortPrelaunchSignal and canShortTrendSignal +mainLongLaunchSignal = useLaunchAsMain and longLaunchSignal and canLongTrendSignal +mainShortLaunchSignal = useLaunchAsMain and shortLaunchSignal and canShortTrendSignal +longSignal = mainLongPrelaunchSignal or mainLongLaunchSignal or longContinuationSignal +shortSignal = mainShortPrelaunchSignal or mainShortLaunchSignal or shortContinuationSignal + +if longPrelaunchSignal + lastLongPrelaunchBar := bar_index + +if shortPrelaunchSignal + lastShortPrelaunchBar := bar_index + +if longSignal + lastLongSignalBar := bar_index + longTrendSignalCount := longTrendSignalCount + 1 + +if shortSignal + lastShortSignalBar := bar_index + shortTrendSignalCount := shortTrendSignalCount + 1 + +if longSignal or shortSignal + lastAnySignalBar := bar_index + +plot(emaFast, "EMA5", color=color.new(color.teal, 0), linewidth=1) +plot(emaMid, "EMA15", color=color.new(color.orange, 0), linewidth=1) +plot(emaSlow, "EMA30", color=color.new(color.yellow, 0), linewidth=1) +plot(emaBias, "EMA144", color=color.new(color.red, 0), linewidth=2) + +barcolor(showBarColor and bullTrendContext ? color.new(color.green, 0) : showBarColor and bearTrendContext ? color.new(color.red, 0) : na) + +plotshape(longSignal, title="做多信号", style=shape.labelup, location=location.belowbar, color=color.new(color.lime, 0), textcolor=color.black, size=size.normal, text="做多") +plotshape(shortSignal, title="做空信号", style=shape.labeldown, location=location.abovebar, color=color.new(color.red, 0), textcolor=color.white, size=size.normal, text="做空") + +plotshape(prelaunchMode != "关闭" and longPrelaunchSignal, title="预启动做多", style=shape.labelup, location=location.belowbar, color=color.new(color.aqua, 0), textcolor=color.black, size=size.tiny, text="预多") +plotshape(prelaunchMode != "关闭" and shortPrelaunchSignal, title="预启动做空", style=shape.labeldown, location=location.abovebar, color=color.new(color.fuchsia, 0), textcolor=color.white, size=size.tiny, text="预空") +plotshape(showMtfRiskMarks and mtfLongRisk and (longPrelaunchSignal or longSignal), title="风险:逆风险周期做多", style=shape.diamond, location=location.belowbar, color=color.new(color.orange, 0), size=size.tiny, text="逆1H") +plotshape(showMtfRiskMarks and mtfShortRisk and (shortPrelaunchSignal or shortSignal), title="风险:逆风险周期做空", style=shape.diamond, location=location.abovebar, color=color.new(color.orange, 0), size=size.tiny, text="逆1H") +plotshape(showAuxMarks and longLaunchSignal, title="辅助:均线带启动做多", style=shape.arrowup, location=location.belowbar, color=color.new(color.lime, 45), size=size.tiny, text="") +plotshape(showAuxMarks and shortLaunchSignal, title="辅助:均线带启动做空", style=shape.arrowdown, location=location.abovebar, color=color.new(color.red, 45), size=size.tiny, text="") +plotshape(showAuxMarks and simpleLongSetup, title="辅助:干净回踩做多", style=shape.circle, location=location.belowbar, color=color.new(color.aqua, 45), size=size.tiny, text="") +plotshape(showAuxMarks and simpleShortSetup, title="辅助:干净反抽做空", style=shape.circle, location=location.abovebar, color=color.new(color.orange, 45), size=size.tiny, text="") + +var table stateTable = table.new(position.top_right, 2, 6, border_width=1) + +if showStateTable and barstate.islast + directionText = simpleBullTrend ? "趋势多" : simpleBearTrend ? "趋势空" : close > emaBias ? "偏多" : close < emaBias ? "偏空" : "中性" + trendText = chopZone ? "缠绕禁区" : simpleBullTrend or simpleBearTrend ? "均线发散" : ribbonSeparated ? "排列未确认" : "无趋势" + structureText = longPrelaunchSignal ? "预启动多" : shortPrelaunchSignal ? "预启动空" : simpleBullPullback ? "回踩均线带" : simpleBearPullback ? "反抽均线带" : simpleBullPullbackRecent or simpleBearPullbackRecent ? "等待确认" : "等待回踩" + positionText = entryRangePosition > maxLongEntryRangePosition ? "确认偏高" : entryRangePosition < minShortEntryRangePosition ? "确认偏低" : "位置适中" + mtfText = not useMtfTrendFilter ? "关闭" : not mtfLongFilterOk and close >= emaBias ? "15/30反空" : not mtfShortFilterOk and close < emaBias ? "15/30反多" : mtfLongRisk and close >= emaBias ? "逆1H空" : mtfShortRisk and close < emaBias ? "逆1H多" : "顺/中性" + signalText = longSignal ? "做多" : shortSignal ? "做空" : longPrelaunchSignal ? "预多" : shortPrelaunchSignal ? "预空" : chopZone ? "等待脱离" : "等待" + signalColor = longSignal ? color.new(color.green, 0) : shortSignal ? color.new(color.red, 0) : longPrelaunchSignal ? color.new(color.aqua, 0) : shortPrelaunchSignal ? color.new(color.fuchsia, 0) : color.new(color.gray, 0) + directionColor = simpleBullTrend or close > emaBias ? color.new(color.green, 15) : simpleBearTrend or close < emaBias ? color.new(color.red, 15) : color.new(color.gray, 20) + trendColor = chopZone ? color.new(color.orange, 20) : simpleBullTrend ? color.new(color.green, 15) : simpleBearTrend ? color.new(color.red, 15) : color.new(color.gray, 20) + structureColor = longPrelaunchSignal ? color.new(color.aqua, 10) : shortPrelaunchSignal ? color.new(color.fuchsia, 10) : simpleBullPullbackRecent or simpleBearPullbackRecent ? color.new(color.orange, 15) : color.new(color.gray, 20) + mtfCurrentRisk = close >= emaBias ? (not mtfLongFilterOk or mtfLongRisk) : (not mtfShortFilterOk or mtfShortRisk) + mtfColor = not useMtfTrendFilter ? color.new(color.gray, 20) : mtfCurrentRisk ? color.new(color.orange, 15) : color.new(color.green, 25) + + table.cell(stateTable, 0, 0, "方向", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 0, directionText, text_color=color.white, bgcolor=directionColor) + table.cell(stateTable, 0, 1, "趋势", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 1, trendText, text_color=color.white, bgcolor=trendColor) + table.cell(stateTable, 0, 2, "结构", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 2, structureText, text_color=color.white, bgcolor=structureColor) + table.cell(stateTable, 0, 3, "位置", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 3, positionText, text_color=color.white, bgcolor=color.new(color.black, 70)) + table.cell(stateTable, 0, 4, "多周期", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 4, mtfText, text_color=color.white, bgcolor=mtfColor) + table.cell(stateTable, 0, 5, "信号", text_color=color.white, bgcolor=color.new(color.black, 0)) + table.cell(stateTable, 1, 5, signalText, text_color=color.white, bgcolor=signalColor) + +alertcondition(longLaunchSignal, title="均线带启动做多", message="EMA5/15/30 从压缩转为多头排列并向上突破短线结构,出现做多信号") +alertcondition(shortLaunchSignal, title="均线带启动做空", message="EMA5/15/30 从压缩转为空头排列并向下跌破短线结构,出现做空信号") +alertcondition(longPrelaunchSignal, title="预启动做多", message="EMA5/15/30 仍处于压缩/初开阶段,价格贴近上方短结构突破位,出现趋势启动前做多预警") +alertcondition(shortPrelaunchSignal, title="预启动做空", message="EMA5/15/30 仍处于压缩/初开阶段,价格贴近下方短结构突破位,出现趋势启动前做空预警") +alertcondition(longContinuationSignal, title="均线带中继做多", message="多头均线带中回踩 EMA15/30 后重新收回 EMA5,出现做多信号") +alertcondition(shortContinuationSignal, title="均线带中继做空", message="空头均线带中反抽 EMA15/30 后重新跌回 EMA5,出现做空信号") +alertcondition(longSignal, title="均线带综合做多", message="EMA 均线带出现做多信号") +alertcondition(shortSignal, title="均线带综合做空", message="EMA 均线带出现做空信号")