first commit

This commit is contained in:
aaron 2026-05-24 22:54:53 +08:00
commit bfb9c14bd3
6 changed files with 1850 additions and 0 deletions

104
AGENTS.md Normal file
View File

@ -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 或其他实现代码
- 回测与鲁棒性检查清单
- 关于过拟合、交易成本和市场状态依赖的现实提醒
当用户要求代码审查时,优先检查:
- 是否存在重绘
- 是否存在未来函数或前视偏差
- 下单逻辑是否错误
- 回测假设是否不现实
- 参数是否过拟合
- 是否缺少出场或风控
- 告警信号与策略交易是否不一致
## 基本原则
目标不是制造看起来漂亮的信号,而是构建规则明确、可测试、风险可控、弱点可见的交易系统。任何策略在投入真实资金前,都必须经过充分验证和风险评估。

View File

@ -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 过滤是否过度过滤早期反转。

View File

@ -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 分钟上默认参数是否需要单独收紧。

View File

@ -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 放射入场系统出现做空方向信号")

View File

@ -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))

View File

@ -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 均线带出现做空信号")