PINE 指标研究 / LUXALGO SMC
TradingView LuxAlgo SMC 指标完整指南
读图、参数、重绘与中文版
从高低点、结构突破到订单块和失衡区,结合源码与回放厘清信号时间,并讨论 A 股复权、期货主连、参数设置和中文本地化。
↓下载 LuxAlgo SMC 中文版完整源码(924 行)
很多人第一次打开 LuxAlgo SMC,看到的不是一张更清楚的图,而是一屏新的问题:BOS 和 CHoCH 有什么区别?红色、蓝色的方框是什么?为什么昨天还没有的高低点,今天却出现在过去的 K 线上?
这些问题不先弄清楚,指标画得越完整,越容易产生误解。
理解这个指标,最重要的一步,是把它看成“价格结构整理器”,而不是“机构订单探测器”,更不是自动买卖系统。 它用一套固定规则,把高低点、结构变化和关注区域画出来;至于是否交易、怎样交易,仍然需要另外的规则。
本文从读图开始,进一步解释计算机制、关键参数和信号时间,再讨论股票、国内期货中的数据问题。最后介绍本次制作的中文版:保留原有计算,翻译界面,并给订单块和失衡区补上完整说明。
图 1|中文本地化版在 TradeFlow 中的实际运行画面,取自本次录屏。K 线、价格和指标标记均来自原画面,不是生成的行情。图片用于说明功能,不构成对其中标的的交易建议。
本文的研究范围:研究对象是本次保存的 LuxAlgo SMC Pine v5 源码,不是所有 SMC/ICT 指标,也不是 LuxAlgo 的其他收费工具。算法解释以这份源码为准,原理图标明“示意”,交易行为推测不当作委托数据。原版身份与功能可对照作者发布页。源码版本、回放记录和外部资料列在文末。[1][2][12]
01|它真正解决的,是怎样整理一张价格图
不用指标时,我们通常要自己找前高前低、画结构线、标记突破,再回头观察价格从哪里出发。图看多了,不少工作其实是在重复标记同一类信息。
SMC 把其中一部分规则化。它主要提供三层信息:
这些模块来自本次源码的实际功能。核心计算使用开、高、低、收、时间、历史索引,以及由价格衍生的波幅数据;这份代码没有用成交量识别这些结构,也没有读取机构持仓、逐笔委托或真实止损分布。[2]
它的实际价值,是让“整体还在向上,但内部已经转弱”“到了关注区,但还没出现反应”这类描述,有一组可以在图上核对的对象。作者的说明也明确:这类工具应当是更大分析流程的一部分,不能保证识别或预测真实机构行为。[3]
因此,它可以服务于价格结构学习、多周期观察、日常看盘和逐根复盘。但已经有清楚的手动画线规则、或者只需要简单趋势过滤的人,未必需要把所有模块都打开。
信息多,不等于判断更好。 有用的图表,应当是你知道每条线、每个框为什么留在上面,而不是只觉得它看起来很专业。
02|高低点:价格是在抬高、降低,还是交错
先把图表简化,只看高点和低点。
如果每次回落都在更高的位置停住,随后又越过前高,已经发生的结构就像向上走台阶。反过来,反弹高点越来越低,回落又形成更低低点,就是向下的结构。高低点相互交错时,则不能只凭一个新高或新低,就把整段行情当成单边趋势。
图 2|原理示意,不是真实行情,也不是标签实际出现的顺序。先理解价格关系,再讨论程序什么时候确认。
这里的“点”是算法选中的结构点,不是每根 K 线的最高价和最低价;比较的也是同类点,高点对高点,低点对低点。[2]
高低点不是形成时就已经确认
默认大结构识别长度为 50。程序在当前 K 线上,检查 50 根以前的候选高点或低点与其后这段价格的关系,再结合内部方向状态判断是否记录新结构点。
因此,假设第 N 根最终被选为大结构高点,需要等后续确认以后,标签才会画回第 N 根附近。不是每个肉眼高点,过 50 根就一定得到标签。 50 也是 K 线数量,不是固定的日历天数。[2]
这正是历史图容易造成误解的地方:最终图把高点标在这里,不代表当时已经知道这里是高点。
还有一个命名边界:代码用严格的大小比较区分 HH/LH、LL/HL,完全同价时会落入对应的另一分支;最初还没有有效比较点时也要谨慎理解。所以标签是一套代码分类,不能在所有边界情况下都按中文字面机械解释。[2]
03|BOS 与 CHoCH:一个描述延续,一个提醒变化
BOS:结构突破
假设当前结构方向向上,价格回落后,又越过指标已经确认的结构高点。这次突破与原方向一致,通常会被归为 BOS(Break of Structure),即结构突破。
它描述的是已发生的延伸,不承诺后面一定继续上涨。即使方向一致,突破也可能发生在一段推动的后期;仅凭 BOS,仍然无法回答追入是否合适。
CHoCH:结构转变
如果原方向偏空,价格却向上突破已确认的结构高点,这次与原方向相反的突破,会被归为 CHoCH(Change of Character,结构转变)。向下的情形反过来。
它更适合被理解为:“原先的走法出现了需要重新评估的变化。”能否形成持续趋势,还要观察后续回落、再次突破与更大结构的关系。
图 3|分类依赖突破方向和此前的结构方向。CHoCH 不等于已经证明一轮完整趋势反转。
源码中,向上突破时,原方向为看跌则标 CHoCH,否则标 BOS;向下突破时,原方向为看涨则标 CHoCH,否则标 BOS。因此,初始化方向尚未明确时出现的首个 BOS,也不能过度解释成“此前已有充分建立的同向趋势”。[2]
突破哪一个高低点,才会触发?
本指标使用 close 穿越当前保存的结构位,并检查该点是否已经被突破。一次有效穿越后,该点会被标为已穿越,避免对同一个结构位反复触发同类事件。小结构还可能受到额外过滤条件影响。[2]
所以,突破肉眼前高却没出现 BOS,不一定是程序出错。那个高点可能尚未被确认,也可能不是当前保存的有效结构点。
使用 close,不等于程序只在收盘后运行
在已经结束的历史 K 线上,close 是该根收盘价;但在未收盘的实时 K 线上,它是不断变化的当前价格。
这份源码的结构判断没有统一加上 barstate.isconfirmed 限制。盘中暂时越过、后来又退回,标记就可能与最终历史结果不同。“历史收盘穿越”和“盘中暂时穿越”,必须分开。[2][4]
这不是一句“看收盘价”就能自动解决的时间问题。你需要区分自己观察的是盘中提示,还是明确等待收盘后的结果。
04|大结构与小结构:同一张图里的两种尺度
Swing 是大结构,Internal 是小结构。它们在同一张图、同一个图表周期上识别不同尺度的起伏,并不是自动把日线结构和分钟线结构叠到一起。
本次源码中,大结构识别长度可调,默认 50;小结构长度固定为 5,没有独立长度输入项。原版结构标记连线,大结构用实线,小结构用虚线。这里说的是 BOS/CHoCH 的结构连线,不是所有色块边框或相近高低点连线。[2]
图 4|小结构先跌破自己的低点,不自动等于大结构的重要低点也被破坏。示意用于解释尺度关系。
例如,大结构仍然向上,但内部出现连续的短线回落。小结构向下 CHoCH 和大结构仍偏向上,可以同时成立。
读图时,先问“大结构的重要位置有没有被破坏”,再问“小结构正在做什么”。这样,小结构提供的是局部信息更新,不必每次一转向,就推翻整个背景。
颜色只作辅助,不能代替方向和尺度
配色可以修改,平台也可能采用不同的涨跌颜色。应当先看价格突破方向、结构尺度和标记含义,最后再看颜色,不能把红线一律解释成看跌。
“结构趋势 K 线着色”也值得单独说明:本源码用小结构方向 internalTrend.bias 决定着色方向,而不是大结构方向;所用颜色又来自对应配色设置。因此,K 线颜色变化与大结构暂时不变,并不必然矛盾。[2]
05|EQH/EQL:相近高低点为什么让人想到流动性
EQL(Equal Lows):两次止跌,怎样形成下方潜在卖出?
价格两次回落到差不多的位置,都出现反弹,一部分交易者就可能把这里当成支撑。依赖这个支撑持仓的人,可能把退出条件放在低点下方:真正跌破时,原先的买入理由被破坏,止损或条件单可能带来卖出。
在允许做空的市场,还可能有交易者跟随破位卖出。因此,相近低点下方可能出现一些在条件满足后才转化为交易的卖出需求。这就是这里所说的潜在卖方流动性。
EQH(Equal Highs):相近高点上方,为什么可能出现买入?
价格两次在相近高度受阻,容易形成共同关注的阻力。做突破的人,可能等越过后买入;在双向交易市场,空头止损回补也可能发生在高点上方。两类行为都可能带来买入。
图 5|行为推演,不是订单簿测量。条件单不一定已经作为可见委托挂在交易所;空头回补的解释,也不能原样套到普通股票现货交易中。
代码能证明的是“两个确认点价格足够接近”,不能证明附近有多少订单,更不能证明价格必然来扫止损。 上面的解释是一种行为假设,而不是指标读取到的事实。
价格越过以后,可能继续走,也可能退回。真正需要观察的是后续反应,不是预设“先扫流动性,然后一定反转”。
“相近”有明确容差,并不是凭感觉
本源码比较相邻记录的同类确认点,主要条件为:[2]
两个点的价格差绝对值 < 阈值 × ATR(200)
默认阈值是 0.1,确认长度是 3。假设当时 ATR 为 2,允许的价差就是小于 0.2。这是数学示例,不是某个标的的推荐参数。
阈值调大,允许的差距更宽;调小,对价格接近程度要求更严格。0.1 不是股价的 10%,而是波幅量度的 0.1 倍。 由于比较使用严格小于,把阈值设为 0,也不是“只识别完全同价”,通常反而无法满足条件。[2]
默认需要后续约 3 根完成该尺度的点识别,之后再画相近高低点的连线和文字。因此 EQL/EQH 也存在“确认以后,画回历史位置”的问题。[2]
06|订单块 OB(Order Block):先有结构事件,再回头圈出区域
订单块容易被讲成“机构在这里留下的大单”。但这个指标没有读取机构订单,它做的是价格序列上的筛选。
顺序是:先保存有效结构点,随后发生 BOS 或 CHoCH,再从相关历史区间中选出一根 K 线,把相应价格范围画成方框。看涨订单块寻找处理后低价的最低位置,看跌订单块寻找处理后高价的最高位置。搜索区间从对应结构点开始,到突破 K 线之前结束,不包含当前突破 K 线。[2]
图 6|方框左边界可以在历史 K 线上,但它要到后面的结构事件发生后才被建立。历史位置不是实际可知时间。
所以,不能把这份代码的订单块简单等同于“上涨前最后一根阴线”或“下跌前最后一根阳线”。其他方法可以有自己的定义,但本文解释的是这个具体脚本。
为什么选中了这根 K 线,而不是旁边那根?
程序不是对未经处理的全部高低价无条件找极值。它先计算波动量度:默认使用 ATR(200),也可以选择累计平均波幅选项。
当一根 K 线的高低价差达到波动量度的两倍时,代码对筛选序列作特殊处理:将这根的高低值互换,分别送入 parsedHigh 和 parsedLow。因此,它不是“凡是长 K 线都直接删掉”的简单过滤,也没有加入机构身份判断。[2]
切换过滤方式,可能改变最终选中的区域。它属于识别规则,不只是换一个显示名字。
订单块什么时候被判为失效?
下面概括的是将订单块从活动数组中移除的条件。代码使用严格穿越,价格只等于边界并不满足对应判断。[2]
进入方框不必然等于失效,还要看越过哪条边界,以及使用哪种依据。收盘价模式也要结合前面实时 close 的含义理解,不能仅凭名称就认为盘中状态一定不变。
历史截图不是订单块的完整历史
程序会更新活动区域,绘制数量也有限。最终图不是“过去曾经出现过哪些订单块”的事件清单。只挑现在图上还留着、后来又反应漂亮的区域,会漏掉曾经出现、随后失效或不再显示的对象。
更严格地说,活动数据是否移除、绘图对象怎样复用,是两层实现;研究订单块有效性时,应记录事件过程,而不是仅从最终截图反推完整状态。
“两个 5”究竟是什么?
订单块面板里的两个 5,分别是小结构订单块、大结构订单块的显示数量上限。开关决定是否启用这一类,数量决定最多显示多少个最近保留的区域。
它们不是结构识别长度,不是五根 K 线,也不是前面“小结构固定 5”的那个参数。大结构默认识别长度 50 是另外一项。[2]
图 7|真实录屏的局部放大。中文版直接写出区域类型,不需要仅凭色块猜测。图片没有补造信号。
07|FVG(Fair Value Gap):三根 K 线之间的不重叠,不等于没有成交
看涨 FVG 的基本几何关系是:第三根最低价高于第一根最高价,两者之间形成一段不重叠区域。看跌 FVG 则反过来:第三根最高价低于第一根最低价。[2]
图 8|看涨 FVG 原理示意。不重叠的是第一根和第三根,中间那根可能已经经过这些价位。
例如,第一根最高价为 100,第三根最低价为 101.2,几何区域就是 100~101.2。不能因此断言这里完全没有成交,因为第二根 K 线的交易范围可能已经经过它。
同样,区域叫“失衡”,不代表价格必须回来,更不代表回来以后必然获得支撑。它是被规则选中的位置,不是市场必须履行的义务。
实际算法比“三根形态”多几道条件
这份代码还检查中间 K 线的收盘位置、实体变动幅度,以及新识别周期的条件。看涨情形中,中间收盘还需要高于第一根最高价;看跌情形中,则需要低于第一根最低价。[2]
自动阈值开启时,中间实体变动还要超过相应累计量度。它不是简单的“缺口宽度大于某个 ATR”。即使关闭自动阈值,也不等于只保留两条高低价比较,其余条件仍然在。
所以,肉眼看到几何空隙,指标却没画,应先查周期和过滤条件,不要直接认定程序失效。
进一步的源码补充:消失,不一定等于完全回补
早期研究中,用“价格完全穿过后删除”概括 FVG 生命周期不够细。逐项检查本次源码,会发现看涨、看跌分支有一个不能忽略的差别:[2]
这里的 top 是代码字段名,在看跌分支中不能直接当成方框几何上的最高边界。因此,不能把所有 FVG 的消失,统计成“整个缺口已经完全填满”。
这是对本次源码的补充核对,尚未据此完成跨参数、跨平台专项运行测试,不能扩展为作者所有版本都采用同一实现。中文版也没有顺便更改这段逻辑;是否调整,应作为单独版本说明和验证,而不是混在汉化里。
跨周期 FVG 必须单独检查
当 FVG 周期高于图表周期时,本源码通过 request.security() 请求数据,其中包含未偏移的当前高周期 high、low,并使用 barmerge.lookahead_on。按照 Pine 的合并规则,这种组合会让历史低周期 K 线提前拿到该高周期最终高低价,形成未来信息泄漏路径。[2][5]
这与“等几根确认枢轴,再画回历史”不是同一件事。代码与平台规则能够解释该路径,但具体影响多少标记、每组参数怎样表现,仍需要专项对照,不能因为普通结构回放正常,就认定高周期 FVG 也已验证。
研究基础功能时,可以先让 FVG 跟随图表周期,把跨周期合并问题单独拆开。这是为了减少混杂因素,不是某个周期收益更好的结论。把告警设成收盘触发,也不能自动消除高周期数据的时间问题。
08|强弱高低点、溢价折价区与周期高低点
Strong/Weak:结构角色,不是力量测量
代码主要根据大结构方向命名当前跟踪的高低点:大结构偏空时,上方被命名为 Strong High;偏多时,下方被命名为 Strong Low,另一侧使用相应的 Weak 名称。[2]
这些名称可以帮助区分当前结构中更受保护的一侧与可能继续被测试的一侧,但并没有测出真实买卖力量。强低点依然可能被跌破,弱高点也不代表一定马上突破。
Premium/Discount:区间位置,不是基本面估值
假设当前跟踪区间上界为 H,下界为 L,跨度为 H−L,本源码画出的三个色带为:[2]
图 9|假设区间为 100~110。色带按照本源码的比例绘制,不是把整个上半区和下半区全部涂色。
H=110、L=100 时,溢价色带为 109.5~110,均衡色带为 104.75~105.25,折价色带为 100~100.5。
这里的相对高低,只相对于这一段价格区间。折价区不等于低于企业价值,溢价区也不等于上涨应该结束。区间边界会更新,位置分类自然也可能改变。
日、周、月高低点:留意同周期分支
在较低图表周期上显示更高周期高低点时,源码通常读取该高周期上一根已完成 K 线的高低价,作为参考位置。
但这里需要补充早期研究中“全部都是上一周期”的概括:源码有同周期分支。例如在日线图执行日线高低点绘制时,会使用执行当时本根图表 K 线的高低价;图表周期高于目标周期时,相应级别还可能不绘制。[2]
因此,PDH/PDL 等缩写或中文版“前日高点”等名称,不应代替对图表周期和代码实际路径的核对。该显示还受执行时机控制,也不能直接理解成任意时刻持续刷新的独立当前高低价工具。
09|它到底会不会重绘?先把不同现象分开
“重绘”或“不重绘”两个词,不足以指导使用。真正重要的是:哪些信息会变,什么时候才能知道,以及最后画在什么位置。
延迟确认、普通结构事件先后顺序及回标,得到源码和本次回放记录支持。盘中变化的判断另有源码与实时执行规则依据,但实时逐笔的全部路径、高周期 FVG 的跨参数影响,并未因此完成全面验收。[2][4][5][12]
图 10|假设第 N 根最终被选中,且更晚某根才发生突破。价格出现、点被确认和后续穿越,是不同的时间。
BOS/CHoCH 不需要在突破后再等 50 根
50 控制的是大结构点的识别。点已经确认以后,后续 close 满足穿越等条件,就可以产生结构事件,不需要从突破日起重新等 50 根。[2]
但 BOS/CHoCH 文字所在的横坐标,也不一定是触发 K 线。源码把结构线从历史枢轴连到突破位置,文字放在两者的中点。复盘时,应核对线的右端和实际事件时刻,而不是把文字下方那根当成发出信号的位置。[2]
FVG 方框则可以从中间那根 K 线的时间开始绘制,不代表中间那根刚出现时,完整三根关系已经可知。图形锚点、确认时刻和交易可执行时刻,要分别记录。
重载后不一样,先查什么?
先查是否涉及未收盘值和高周期未完成数据,再查历史加载起点、数据修订、复权与连续合约拼接。更换这些输入后重新计算,得到不同标记,不应笼统归为同一种重绘问题。[2][4]
10|参数怎么设置:先分清改的是算法还是显示
不需要为股票和期货各背一套“最优数字”。先判断参数改变了什么,再决定是否符合自己的系统。
这里是按作用分类,不代表所有“显示”开关都只在绘图最后一步起作用。部分开关也控制功能是否执行。例如关闭某类订单块,与“全部照常计算、只是把框藏起来”不是完全同一件事;而 All/BOS/CHoCH 下拉筛选,主要控制相应标签展示,不能据此认定其他依赖逻辑都已关闭。[2]
先认识这些默认值
默认值来自源码,不是收益测试结论。附录另列中文版全部 55 项输入,便于逐项对照。[2]
一次只改一个参数,记录实际变化
固定数据、周期和一段行情,只改一项,观察哪些结构点改变、哪些区域改变、确认时间怎样变化。然后换到不同阶段再看,而不是只在同一段漂亮走势上调出一个“看起来最准”的结果。
长度较小可能更偏向短起伏,但不自动更有效;显示数量多只是画面信息更多,不自动意味着机会更多。
参数根据自己的交易系统、周期和习惯设置;没有明确需求,先用默认值。 同一数值在日线和分钟线上的时间跨度不同,也不应脱离图表周期讨论它“快不快”。
11|把功能串起来:背景、位置、反应、执行条件
下面是一种读图顺序,不是经过回测的交易策略。
先看背景。 当前讨论哪个周期、哪个结构尺度?大结构方向是什么?哪个位置的变化会让你重新评估它?
再看位置。 当前价格是在区间中间,还是接近已确认的订单块、失衡区、相近高低点或周期参考位?只留下与当前问题有关的位置,不必把每个框当成同等重要。
然后看新反应。 到达关注区后,是继续穿过,还是出现停止、反弹和小结构变化?“来到附近”不等于“已经触及”,“反弹一次”也不等于“已经确认新的大结构低点”。
最后才是执行条件。 触发、失效、仓位、成本、可成交性和退出,应由自己的系统另行定义。一张标得很全的图,还不是完整策略。
图 11|阅读框架示意,不是自动交易指令,也没有经过胜率或收益验证。
本次回放案例,真正说明了什么?
配套视频的同一段回放中,小结构先转强,随后又转弱;相近高点后来被突破,价格并没有因为出现 EQH 就必然下跌;高位发生小结构下破后,新的看跌订单块才被回头画出。这些记录说明的是标记出现顺序,不证明一套盈利策略。[12]
其中一次回调,低点仍在下方蓝色订单块上方,实际没有碰到。即使后来反弹,也不能写成“回踩订单块后获得支撑”。把未触及说成已触及,会让后面的效果判断失真。
这组回放案例来自配套视频的研究记录,与本文展示中文版界面的新录屏不是同一段行情,不应把后者当作前者的逐帧证据。
可以这样记录每次回放
这种记录的目的,是减少事后解释。最终图可以用来复查,但不能代替当时的信息状态。
12|股票与国内期货:先检查数据,再讨论参数
股票:除权跳空不能全部解释成卖出压力
权益分派、转增、配股等公司行为可能涉及除权除息处理。上交所交易规则对除权处理、参考价和相应涨跌幅基准有具体规定,因此这种价格断层不能全部按普通交易中的集中抛售来解释。[8]
SMC 不会自动识别每个跳空的经济原因。它收到怎样的价格序列,就按那套价格识别结构。复权与不复权图上的高低点、波幅、区域和事件,因而可能不同。[2]
做图表对照时,先统一复权方式;做严格历史研究时,还要区分“今天按当前因子调整过的历史图”和“当时可获得的数据”。这不等于使用前复权就必然构成交易前视偏差,但涉及历史价位与信号时,必须说明数据处理方法。
涨跌停、停复牌与短历史:形态相似,原因未必相同
价格因为交易限制、隔夜信息或停复牌而不连续,即使满足某个 FVG 形态,也不能只凭色块断言机构在其中建仓。形态条件与事件原因是两层问题。
可用历史很短时,还要检查 ATR(200) 等长窗口有没有有效值。界面能打开,不等于所有依赖历史的模块已经进入可比较状态。本源码没有为全部模块统一加入短历史替代机制;只看前几十根的表现,不能代表正常长历史行为。[2]
股票现货与双向期货,执行环境不同
普通股票现货的新买入持仓,有当日卖出约束,应纳入执行规则;不能看到小结构反向变化,就假定能像某些期货场景一样立即反手。具体证券是否适用当日回转例外,应按相应规则核对,不能把股票和所有 ETF 混作一类。[8]
同样,普通股票图上的 EQH 上方,不能默认存在与双向市场同等的空头止损回补结构。看跌订单块可以用于观察上方压力和持仓风险,不必被翻译成做空指令。
期货:主连是数据衔接,具体价位回实际合约核对
连续合约把不同时期的合约连接成更长的历史。新旧合约可能有价差,衔接处的跳空不一定是同一份合约在连续交易中突然跳价。
TradingView 的 B-ADJ 用于调整先前合约的数据,消除合约切换带来的价格断层。开关变化会改变历史输入,SMC 的部分结构和区域随之变化,不意味着过去行情重新走了一遍。[7]
可以把任务分开:连续图用于观察较长结构;涉及实际交易价位时,回到所交易合约核对。不要未经核对,把调整后的历史方框价格直接当成当前合约可执行的价位。
夜盘、交易日和周期聚合,都影响输入
交易日不必等于日历日。以上期能源公开交易细则为例,实行连续交易时,第一节交易时间的定义包含前一个工作日的连续交易时段。不同产品的具体时段仍应按相应交易所和品种规则核对。[9]
比较两套图表时,至少检查夜盘是否包含、交易日怎样归属、分钟周期从何时聚合、休市如何切分,以及历史起点是否一致。同样叫“60 分钟”,不保证每根 K 线包含的是同一组成交。
还有一个显示层细节:本源码部分方框和线的右延伸使用 time - time[1]。相邻两根跨午休、隔夜或节假日时,这个差值可能大于普通间隔,视觉延伸也可能受影响。这不是区域的交易意义突然变强。[2]
本次中文版没有自动加入换月重置、缺口分类、交易时段修正或所谓股票最优参数。 它们可以单独研究,但不是已完成的汉化功能。
13|中文版做了什么,没做什么
这次本地化的目标,是先让使用者读懂图和设置,不必靠背缩写猜功能。
设置名称、下拉选项、提示说明和告警文字采用中文;图表文字支持中文、英文、中文+英文。订单块写出小结构/大结构、看涨/看跌的完整名称,失衡区写出看涨或看跌。新增色块说明可以关闭,也能单独调字号。[2]
图 12|真实设置画面的局部放大。三档语言作用于图表文字,设置面板本身固定中文。
设置面板没有随语言输入整体切换,是 Pine 原生输入项的常量要求所限:这些标题、分组、提示和选项,不能使用另一个输入项的动态结果。告警条件标题与默认消息也有相应常量要求。[6][10]
原有识别逻辑、计算参数默认值、区域坐标和告警条件保留,新增的是文字呈现。静态代码对照已完成,也有用户提供的实际运行画面,但这不等于所有平台、所有参数、全部实时和回放场景都已完成兼容验收。
原作者仍是 LuxAlgo。源码保留 CC BY-NC-SA 4.0 许可;这是中文本地化版,不是作者官方中文发行版。公开再分发改版源码或用于商业场景前,应核对原许可、使用方式及平台规则,不把“公开源码”理解成无条件的任意使用许可。[11]
14|常见问题:按原因排查,比反复调参有效
为什么别人图上的标记比我多?
先查脚本版本、图表周期、历史范围和设置。大结构、小结构、高低点标签各有开关;FVG、订单块、相近高低点也各有条件。教学截图打开的功能,不一定是原始默认设置。
为什么突破前高,没有 BOS?
先核对它是否被算法确认并保存为当前有效结构点,是否已被突破过,再查小结构过滤。最后区分是历史收盘穿越,还是只有影线或盘中暂时越过。
为什么过去的高低点,现在才出现?
先查是否为延迟确认后回标,不要把最终标签位置当作它当时就存在的证据。
为什么订单块没了,或者变少了?
查看活动区域的移除条件、显示数量和功能开关。现在看不到,不等于过去从未出现;新区域替换、旧区域移除,也不能只靠最终截图复原全过程。[2]
收盘价失效模式是不是更准确?
它首先是更换失效定义。区域保留更久,不自动代表更有效。先定义怎样算反应、怎样算失败,再做不挑案例的比较,才有讨论优劣的基础。
为什么信号到了,没有告警?
alertcondition() 只提供可选条件,不会自动建立正在运行的告警。TradingView 还需要在界面中创建告警,历史上已经画出的信号不会因此自动补发。告警创建时保存脚本和输入快照,之后修改图表设置,不会自动更新那份已运行的告警。[10]
本源码的订单块告警对应区域被突破,不是“新订单块生成”或“价格刚进入区域”。不能只看到 OB 字样,就猜测提醒的是哪一件事。依赖实时 close 的条件,也应按需求设置频率;收盘触发不能解决前述高周期 FVG 的全部时间问题。[2][10]
能不能给一组股票或期货的最优参数?
没有明确的入场、退出、成本、成交约束与样本划分,“最优”就没有清楚的检验对象。本文研究功能和读图,没有完成收益优势或最优参数验证,因此不提供这样的结论。
15|最后,把“标记、解释、行动”分开
一个区域被画出来,是计算结果;推测某类交易者可能在附近行动,是交易解释;自己是否参与、承担多少风险,则是执行决定。
混在一起,就容易从一个色块直接跳到“机构在这里,所以价格必然反转”。分开以后,得到的是更清楚的问题:区域怎样形成,现在是否已知,价格到了以后发生什么,自己的执行条件是否满足。
SMC 值得保留的价值,不是让历史图看起来更像一条必然的路线,而是让当下的观察更有条理。
理解结构,尊重真实的信号出现时间,再把关注位置交给后续价格检验。这比记住一屏缩写更重要。
附录 A|中文版完整参数表
展开完整参数表 · 55 项,按设置分组
以下逐项列出本次 v2 的 55 个输入:原有 52 项,另加语言、色块说明开关、色块说明字号 3 项。表格按照设置分组排列;小结构固定识别长度 5 不是输入项,所以不重复计入。颜色透明度沿用 Pine 数值,0 为不透明,100 为完全透明。[2]
语言显示
聪明钱概念 · 基本设置
小结构 · 内部结构
大结构 · 摆动结构
订单块
等高点 / 等低点
失衡区 · 公允价值缺口
多周期高低点
溢价区 / 折价区
附录 B|源码定位与证据边界
上述算法结论只对应本次代码快照。本文没有证明某组参数最优、中文版提高胜率、色块必然对应机构订单、价格必然扫止损或回补,也没有证明所有平台和高周期场景结果一致。
参考资料
[1] LuxAlgo,Smart Money Concepts (SMC) 原版发布页,用于核对指标身份和功能范围。原版页面
[2] 本次研究保存的源代码:原版 Pine v5,848 行;中文本地化 v2,924 行。原版 SHA-256 为 d047cf91b0da2cbebeab5d5ac259797a20653383dc850a79019040cf2b00e8fd;v2 为 eb1f97c53e19480a0e0148a1f4e881b942200ec81e4867a0120d0f7851b4d87c。算法说明对应这份快照,不声称是作者未来更新的最新版本;函数定位见附录 B。
[3] LuxAlgo Library,Smart Money Concepts,作者对功能与使用边界的说明。作者说明
[4] TradingView Pine Script 文档,Repainting。文档
[5] TradingView Pine Script 文档,Other timeframes and data,尤其是 Lookahead 与高周期请求部分。文档
[6] TradingView Pine Script 文档,Inputs。文档
[7] TradingView 帮助中心,连续期货的历史回溯调整。B-ADJ 说明
[8] 上海证券交易所,《交易规则(2026 年修订)》,第 3.1.4—3.1.5 条及第 4.3 节。具体证券和交易安排以届时有效规则为准。规则与附件
[9] 上海国际能源交易中心,《交易细则》公开文本中有关第一节交易时间的定义;另参阅各品种交易时间安排。交易细则 · 交易时间
[10] TradingView Pine Script 文档,Alerts,以及告警常见问题。文档 · 常见问题
[11] Creative Commons,CC BY-NC-SA 4.0。许可说明
[12] 本项目的指标研究记录与配套视频:静态图完整解读、Replay 完整解读、组合使用与国内市场,以及中文本地化展示。本文提到的具体回放经过,只用于说明该案例,不代表普遍交易效果;原理示意与真实案例分别标明。
资料核对日期:2026 年 9 月 19 日。本文用于指标研究和使用教学,不提供具体标的买卖建议。
指标原作者:LuxAlgo。本文与中文本地化说明不代表作者官方发布或背书。
indicator('聪明钱概念 [LuxAlgo] · 中文版', 'SMC聪明钱概念', overlay = true, max_labels_count = 500, max_lines_count = 500, max_boxes_count = 500)
BULLISH_LEG = 1
BEARISH_LEG = 0
BULLISH = +1
BEARISH = -1
GREEN = #089981
RED = #F23645
BLUE = #2157f3
GRAY = #878b94
MONO_BULLISH = #b2b5be
MONO_BEARISH = #5d606b
HISTORICAL = 'Historical'
PRESENT = 'Present'
COLORED = 'Colored'
MONOCHROME = 'Monochrome'
ALL = 'All'
BOS = 'BOS'
CHOCH = 'CHoCH'
TINY = size.tiny
SMALL = size.small
NORMAL = size.normal
ATR = 'Atr'
RANGE = 'Cumulative Mean Range'
CLOSE = 'Close'
HIGHLOW = 'High/Low'
SOLID = '⎯⎯⎯'
DASHED = '----'
DOTTED = '····'
LANGUAGE_GROUP = '语言显示'
SMART_GROUP = '聪明钱概念 · 基本设置'
INTERNAL_GROUP = '小结构 · 内部结构'
SWING_GROUP = '大结构 · 摆动结构'
BLOCKS_GROUP = '订单块'
EQUAL_GROUP = '等高点 / 等低点'
GAPS_GROUP = '失衡区 · 公允价值缺口'
LEVELS_GROUP = '多周期高低点'
ZONES_GROUP = '溢价区 / 折价区'
modeTooltip = '显示历史结构,或仅保留最近的结构标记。'
styleTooltip = '设置指标配色主题。'
showTrendTooltip = '额外绘制一层按结构方向着色的K线,反映当前识别到的结构方向。'
showInternalsTooltip = '显示小结构,也就是内部结构;结构标记连线使用虚线。'
internalFilterConfluenceTooltip = '过滤部分不显著的小结构突破。'
showStructureTooltip = '显示大结构,也就是摆动结构;结构标记连线使用实线。'
showSwingsTooltip = '在图表上标注大结构高低点。右侧识别长度用于大结构识别,默认50;不是订单块显示数量。'
showHighLowSwingsTooltip = '突出显示最近的强、弱高低点。'
showInternalOrderBlocksTooltip = '显示小结构订单块。\n\n右侧显示数量:最多显示多少个最近保留的小结构订单块,默认5;不是小结构识别长度。'
showSwingOrderBlocksTooltip = '显示大结构订单块。\n\n右侧显示数量:最多显示多少个最近保留的大结构订单块,默认5;不是大结构识别长度。'
orderBlockFilterTooltip = '用于过滤高波动订单块的方法。\n\n原版建议:可用历史数据较少时,使用累计平均波幅。'
orderBlockMitigationTooltip = '选择判断订单块被突破时使用的价格:收盘价,或最高价与最低价。'
showEqualHighsLowsTooltip = '在图表上显示等高点和等低点,即价格相近的高点和低点。'
equalHighsLowsLengthTooltip = '用于确认等高点、等低点的K线数量。'
equalHighsLowsThresholdTooltip = '识别等高点、等低点时使用的容差阈值。\n\n数值越小,对价格接近程度的要求越严格,结果通常越少。'
showFairValueGapsTooltip = '在图表上显示失衡区,也称公允价值缺口。'
fairValueGapsThresholdTooltip = '自动过滤部分不显著的失衡区。'
fairValueGapsTimeframeTooltip = '用于识别失衡区的时间周期;留空时跟随图表周期。'
fairValueGapsExtendTooltip = '设置失衡区方框向右延伸的长度,沿用原版计算方式。'
showPremiumDiscountZonesTooltip = '在图表上显示溢价区、折价区和均衡区。'
labelLanguageInput = input.string('中文', '图表文字语言', group = LANGUAGE_GROUP, options = ['中文', '英文', '中文+英文'], tooltip = '切换图表标记及色块说明的语言,不改变任何计算、参数或信号条件。\n\n设置面板和告警文字固定中文;原生界面标题不能随此选项切换。')
showBoxDescriptionsInput = input.bool(true, '显示色块说明', group = LANGUAGE_GROUP, tooltip = '显示订单块、失衡区的完整名称;文字跟随上方图表语言。只改变说明文字,不改变区域及信号。')
boxDescriptionSizeChoiceInput = input.string('小', '色块说明文字大小', group = LANGUAGE_GROUP, options = ['极小', '小', '正常'])
boxDescriptionSize = boxDescriptionSizeChoiceInput == '极小' ? TINY : boxDescriptionSizeChoiceInput == '小' ? SMALL : NORMAL
modeChoiceInput = input.string('保留历史', '显示模式', group = SMART_GROUP, tooltip = modeTooltip, options = ['保留历史', '仅显示最近'])
modeInput = modeChoiceInput == '保留历史' ? HISTORICAL : PRESENT
styleChoiceInput = input.string('彩色', '配色风格', group = SMART_GROUP, tooltip = styleTooltip, options = ['彩色', '单色'])
styleInput = styleChoiceInput == '彩色' ? COLORED : MONOCHROME
showTrendInput = input(false, '结构趋势K线着色', group = SMART_GROUP, tooltip = showTrendTooltip)
showInternalsInput = input(true, '显示小结构', group = INTERNAL_GROUP, tooltip = showInternalsTooltip)
showInternalBullChoiceInput = input.string('全部', '看涨结构', group = INTERNAL_GROUP, inline = 'ibull', options = ['全部', '结构突破', '结构转变'])
showInternalBullInput = showInternalBullChoiceInput == '全部' ? ALL : showInternalBullChoiceInput == '结构突破' ? BOS : CHOCH
internalBullColorInput = input(GREEN, '', group = INTERNAL_GROUP, inline = 'ibull')
showInternalBearChoiceInput = input.string('全部', '看跌结构', group = INTERNAL_GROUP, inline = 'ibear', options = ['全部', '结构突破', '结构转变'])
showInternalBearInput = showInternalBearChoiceInput == '全部' ? ALL : showInternalBearChoiceInput == '结构突破' ? BOS : CHOCH
internalBearColorInput = input(RED, '', group = INTERNAL_GROUP, inline = 'ibear')
internalFilterConfluenceInput = input(false, '突破过滤', group = INTERNAL_GROUP, tooltip = internalFilterConfluenceTooltip)
internalStructureSizeChoice = input.string('极小', '小结构文字大小', group = INTERNAL_GROUP, options = ['极小', '小', '正常'])
internalStructureSize = internalStructureSizeChoice == '极小' ? TINY : internalStructureSizeChoice == '小' ? SMALL : NORMAL
showStructureInput = input(true, '显示大结构', group = SWING_GROUP, tooltip = showStructureTooltip)
showSwingBullChoiceInput = input.string('全部', '看涨结构', group = SWING_GROUP, inline = 'bull', options = ['全部', '结构突破', '结构转变'])
showSwingBullInput = showSwingBullChoiceInput == '全部' ? ALL : showSwingBullChoiceInput == '结构突破' ? BOS : CHOCH
swingBullColorInput = input(GREEN, '', group = SWING_GROUP, inline = 'bull')
showSwingBearChoiceInput = input.string('全部', '看跌结构', group = SWING_GROUP, inline = 'bear', options = ['全部', '结构突破', '结构转变'])
showSwingBearInput = showSwingBearChoiceInput == '全部' ? ALL : showSwingBearChoiceInput == '结构突破' ? BOS : CHOCH
swingBearColorInput = input(RED, '', group = SWING_GROUP, inline = 'bear')
swingStructureSizeChoice = input.string('小', '大结构文字大小', group = SWING_GROUP, options = ['极小', '小', '正常'])
swingStructureSize = swingStructureSizeChoice == '极小' ? TINY : swingStructureSizeChoice == '小' ? SMALL : NORMAL
showSwingsInput = input(false, '显示高低点', group = SWING_GROUP, tooltip = showSwingsTooltip, inline = 'swings')
swingsLengthInput = input.int(50, '识别长度', group = SWING_GROUP, minval = 10, inline = 'swings')
showHighLowSwingsInput = input(true, '显示强弱高低点', group = SWING_GROUP, tooltip = showHighLowSwingsTooltip)
showInternalOrderBlocksInput = input(true, '小结构订单块', group = BLOCKS_GROUP, tooltip = showInternalOrderBlocksTooltip, inline = 'iob')
internalOrderBlocksSizeInput = input.int(5, '显示数量', group = BLOCKS_GROUP, minval = 1, maxval = 20, inline = 'iob')
showSwingOrderBlocksInput = input(false, '大结构订单块', group = BLOCKS_GROUP, tooltip = showSwingOrderBlocksTooltip, inline = 'ob')
swingOrderBlocksSizeInput = input.int(5, '显示数量', group = BLOCKS_GROUP, minval = 1, maxval = 20, inline = 'ob')
orderBlockFilterChoiceInput = input.string('平均真实波幅', '订单块过滤方式', group = BLOCKS_GROUP, tooltip = orderBlockFilterTooltip, options = ['平均真实波幅', '累计平均波幅'])
orderBlockFilterInput = orderBlockFilterChoiceInput == '平均真实波幅' ? ATR : RANGE
orderBlockMitigationChoiceInput = input.string('最高价 / 最低价', '订单块突破判定依据', group = BLOCKS_GROUP, tooltip = orderBlockMitigationTooltip, options = ['收盘价', '最高价 / 最低价'])
orderBlockMitigationInput = orderBlockMitigationChoiceInput == '收盘价' ? CLOSE : HIGHLOW
internalBullishOrderBlockColor = input.color(color.new(#3179f5, 80), '小结构看涨订单块', group = BLOCKS_GROUP)
internalBearishOrderBlockColor = input.color(color.new(#f77c80, 80), '小结构看跌订单块', group = BLOCKS_GROUP)
swingBullishOrderBlockColor = input.color(color.new(#1848cc, 80), '大结构看涨订单块', group = BLOCKS_GROUP)
swingBearishOrderBlockColor = input.color(color.new(#b22833, 80), '大结构看跌订单块', group = BLOCKS_GROUP)
showEqualHighsLowsInput = input(true, '显示等高点 / 等低点', group = EQUAL_GROUP, tooltip = showEqualHighsLowsTooltip)
equalHighsLowsLengthInput = input.int(3, '确认K线数量', group = EQUAL_GROUP, tooltip = equalHighsLowsLengthTooltip, minval = 1)
equalHighsLowsThresholdInput = input.float(0.1, '价格接近阈值', group = EQUAL_GROUP, tooltip = equalHighsLowsThresholdTooltip, minval = 0, maxval = 0.5, step = 0.1)
equalHighsLowsSizeChoiceInput = input.string('极小', '文字大小', group = EQUAL_GROUP, options = ['极小', '小', '正常'])
equalHighsLowsSizeInput = equalHighsLowsSizeChoiceInput == '极小' ? TINY : equalHighsLowsSizeChoiceInput == '小' ? SMALL : NORMAL
showFairValueGapsInput = input(false, '显示失衡区', group = GAPS_GROUP, tooltip = showFairValueGapsTooltip)
fairValueGapsThresholdInput = input(true, '自动阈值', group = GAPS_GROUP, tooltip = fairValueGapsThresholdTooltip)
fairValueGapsTimeframeInput = input.timeframe('', '识别周期', group = GAPS_GROUP, tooltip = fairValueGapsTimeframeTooltip)
fairValueGapsBullColorInput = input.color(color.new(#00ff68, 70), '看涨失衡区', group = GAPS_GROUP)
fairValueGapsBearColorInput = input.color(color.new(#ff0008, 70), '看跌失衡区', group = GAPS_GROUP)
fairValueGapsExtendInput = input.int(1, '失衡区延伸长度', group = GAPS_GROUP, tooltip = fairValueGapsExtendTooltip, minval = 0)
showDailyLevelsInput = input(false, '日线高低点', group = LEVELS_GROUP, inline = 'daily')
dailyLevelsStyleInput = input.string(SOLID, '线型', group = LEVELS_GROUP, inline = 'daily', options = [SOLID,DASHED,DOTTED])
dailyLevelsColorInput = input(BLUE, '', group = LEVELS_GROUP, inline = 'daily')
showWeeklyLevelsInput = input(false, '周线高低点', group = LEVELS_GROUP, inline = 'weekly')
weeklyLevelsStyleInput = input.string(SOLID, '线型', group = LEVELS_GROUP, inline = 'weekly', options = [SOLID,DASHED,DOTTED])
weeklyLevelsColorInput = input(BLUE, '', group = LEVELS_GROUP, inline = 'weekly')
showMonthlyLevelsInput = input(false, '月线高低点', group = LEVELS_GROUP, inline = 'monthly')
monthlyLevelsStyleInput = input.string(SOLID, '线型', group = LEVELS_GROUP, inline = 'monthly', options = [SOLID,DASHED,DOTTED])
monthlyLevelsColorInput = input(BLUE, '', group = LEVELS_GROUP, inline = 'monthly')
showPremiumDiscountZonesInput = input(false, '显示溢价区 / 折价区', group = ZONES_GROUP, tooltip = showPremiumDiscountZonesTooltip)
premiumZoneColorInput = input.color(RED, '溢价区', group = ZONES_GROUP)
equilibriumZoneColorInput = input.color(GRAY, '均衡区', group = ZONES_GROUP)
discountZoneColorInput = input.color(GREEN, '折价区', group = ZONES_GROUP)
localizeText(string originalText) =>
string chineseText = switch originalText
'BOS' => '结构突破'
'CHoCH' => '结构转变'
'HH' => '更高高点'
'HL' => '更高低点'
'LH' => '更低高点'
'LL' => '更低低点'
'EQH' => '等高点'
'EQL' => '等低点'
'Strong High' => '强高点'
'Weak High' => '弱高点'
'Strong Low' => '强低点'
'Weak Low' => '弱低点'
'Premium' => '溢价区'
'Equilibrium' => '均衡区'
'Discount' => '折价区'
'PDH' => '前日高点'
'PDL' => '前日低点'
'PWH' => '前周高点'
'PWL' => '前周低点'
'PMH' => '前月高点'
'PML' => '前月低点'
'Internal Bullish OB' => '小结构看涨订单块'
'Internal Bearish OB' => '小结构看跌订单块'
'Swing Bullish OB' => '大结构看涨订单块'
'Swing Bearish OB' => '大结构看跌订单块'
'Bullish FVG' => '看涨失衡区'
'Bearish FVG' => '看跌失衡区'
=> originalText
labelLanguageInput == '英文' ? originalText : labelLanguageInput == '中文+英文' ? chineseText + ' / ' + originalText : chineseText
setBoxDescription(box targetBox, string tag, color regionColor) =>
targetBox.set_text(showBoxDescriptionsInput ? localizeText(tag) : '')
targetBox.set_text_color(color.new(regionColor, 0))
targetBox.set_text_size(boxDescriptionSize)
targetBox.set_text_halign(text.align_left)
targetBox.set_text_valign(text.align_top)
targetBox.set_text_wrap(text.wrap_none)
type alerts
bool internalBullishBOS = false
bool internalBearishBOS = false
bool internalBullishCHoCH = false
bool internalBearishCHoCH = false
bool swingBullishBOS = false
bool swingBearishBOS = false
bool swingBullishCHoCH = false
bool swingBearishCHoCH = false
bool internalBullishOrderBlock = false
bool internalBearishOrderBlock = false
bool swingBullishOrderBlock = false
bool swingBearishOrderBlock = false
bool equalHighs = false
bool equalLows = false
bool bullishFairValueGap = false
bool bearishFairValueGap = false
type trailingExtremes
float top
float bottom
int barTime
int barIndex
int lastTopTime
int lastBottomTime
type fairValueGap
float top
float bottom
int bias
box topBox
box bottomBox
type trend
int bias
type equalDisplay
line l_ine = na
label l_abel = na
type pivot
float currentLevel
float lastLevel
bool crossed
int barTime = time
int barIndex = bar_index
type orderBlock
float barHigh
float barLow
int barTime
int bias
var pivot swingHigh = pivot.new(na,na,false)
var pivot swingLow = pivot.new(na,na,false)
var pivot internalHigh = pivot.new(na,na,false)
var pivot internalLow = pivot.new(na,na,false)
var pivot equalHigh = pivot.new(na,na,false)
var pivot equalLow = pivot.new(na,na,false)
var trend swingTrend = trend.new(0)
var trend internalTrend = trend.new(0)
var equalDisplay equalHighDisplay = equalDisplay.new()
var equalDisplay equalLowDisplay = equalDisplay.new()
var array<fairValueGap> fairValueGaps = array.new<fairValueGap>()
var array<float> parsedHighs = array.new<float>()
var array<float> parsedLows = array.new<float>()
var array<float> highs = array.new<float>()
var array<float> lows = array.new<float>()
var array<int> times = array.new<int>()
var trailingExtremes trailing = trailingExtremes.new()
var array<orderBlock> swingOrderBlocks = array.new<orderBlock>()
var array<orderBlock> internalOrderBlocks = array.new<orderBlock>()
var array<box> swingOrderBlocksBoxes = array.new<box>()
var array<box> internalOrderBlocksBoxes = array.new<box>()
var swingBullishColor = styleInput == MONOCHROME ? MONO_BULLISH : swingBullColorInput
var swingBearishColor = styleInput == MONOCHROME ? MONO_BEARISH : swingBearColorInput
var fairValueGapBullishColor = styleInput == MONOCHROME ? color.new(MONO_BULLISH,70) : fairValueGapsBullColorInput
var fairValueGapBearishColor = styleInput == MONOCHROME ? color.new(MONO_BEARISH,70) : fairValueGapsBearColorInput
var premiumZoneColor = styleInput == MONOCHROME ? MONO_BEARISH : premiumZoneColorInput
var discountZoneColor = styleInput == MONOCHROME ? MONO_BULLISH : discountZoneColorInput
varip int currentBarIndex = bar_index
varip int lastBarIndex = bar_index
alerts currentAlerts = alerts.new()
var initialTime = time
if barstate.isfirst
if showSwingOrderBlocksInput
for index = 1 to swingOrderBlocksSizeInput
swingOrderBlocksBoxes.push(box.new(na,na,na,na,xloc = xloc.bar_time,extend = extend.right))
if showInternalOrderBlocksInput
for index = 1 to internalOrderBlocksSizeInput
internalOrderBlocksBoxes.push(box.new(na,na,na,na,xloc = xloc.bar_time,extend = extend.right))
bearishOrderBlockMitigationSource = orderBlockMitigationInput == CLOSE ? close : high
bullishOrderBlockMitigationSource = orderBlockMitigationInput == CLOSE ? close : low
atrMeasure = ta.atr(200)
volatilityMeasure = orderBlockFilterInput == ATR ? atrMeasure : ta.cum(ta.tr)/bar_index
highVolatilityBar = (high - low) >= (2 * volatilityMeasure)
parsedHigh = highVolatilityBar ? low : high
parsedLow = highVolatilityBar ? high : low
parsedHighs.push(parsedHigh)
parsedLows.push(parsedLow)
highs.push(high)
lows.push(low)
times.push(time)
leg(int size) =>
var leg = 0
newLegHigh = high[size] > ta.highest( size)
newLegLow = low[size] < ta.lowest( size)
if newLegHigh
leg := BEARISH_LEG
else if newLegLow
leg := BULLISH_LEG
leg
startOfNewLeg(int leg) => ta.change(leg) != 0
startOfBearishLeg(int leg) => ta.change(leg) == -1
startOfBullishLeg(int leg) => ta.change(leg) == +1
drawLabel(int labelTime, float labelPrice, string tag, color labelColor, string labelStyle) =>
var label l_abel = na
if modeInput == PRESENT
l_abel.delete()
l_abel := label.new(chart.point.new(labelTime,na,labelPrice),localizeText(tag),xloc.bar_time,color=color(na),textcolor=labelColor,style = labelStyle,size = size.small)
drawEqualHighLow(pivot p_ivot, float level, int size, bool equalHigh) =>
equalDisplay e_qualDisplay = equalHigh ? equalHighDisplay : equalLowDisplay
string tag = 'EQL'
color equalColor = swingBullishColor
string labelStyle = label.style_label_up
if equalHigh
tag := 'EQH'
equalColor := swingBearishColor
labelStyle := label.style_label_down
if modeInput == PRESENT
line.delete( e_qualDisplay.l_ine)
label.delete( e_qualDisplay.l_abel)
e_qualDisplay.l_ine := line.new(chart.point.new(p_ivot.barTime,na,p_ivot.currentLevel), chart.point.new(time[size],na,level), xloc = xloc.bar_time, color = equalColor, style = line.style_dotted)
labelPosition = math.round(0.5*(p_ivot.barIndex + bar_index - size))
e_qualDisplay.l_abel := label.new(chart.point.new(na,labelPosition,level), localizeText(tag), xloc.bar_index, color = color(na), textcolor = equalColor, style = labelStyle, size = equalHighsLowsSizeInput)
getCurrentStructure(int size,bool equalHighLow = false, bool internal = false) =>
currentLeg = leg(size)
newPivot = startOfNewLeg(currentLeg)
pivotLow = startOfBullishLeg(currentLeg)
pivotHigh = startOfBearishLeg(currentLeg)
if newPivot
if pivotLow
pivot p_ivot = equalHighLow ? equalLow : internal ? internalLow : swingLow
if equalHighLow and math.abs(p_ivot.currentLevel - low[size]) < equalHighsLowsThresholdInput * atrMeasure
drawEqualHighLow(p_ivot, low[size], size, false)
currentAlerts.equalLows := true
p_ivot.lastLevel := p_ivot.currentLevel
p_ivot.currentLevel := low[size]
p_ivot.crossed := false
p_ivot.barTime := time[size]
p_ivot.barIndex := bar_index[size]
if not equalHighLow and not internal
trailing.bottom := p_ivot.currentLevel
trailing.barTime := p_ivot.barTime
trailing.barIndex := p_ivot.barIndex
trailing.lastBottomTime := p_ivot.barTime
if showSwingsInput and not internal and not equalHighLow
drawLabel(time[size], p_ivot.currentLevel, p_ivot.currentLevel < p_ivot.lastLevel ? 'LL' : 'HL', swingBullishColor, label.style_label_up)
else
pivot p_ivot = equalHighLow ? equalHigh : internal ? internalHigh : swingHigh
if equalHighLow and math.abs(p_ivot.currentLevel - high[size]) < equalHighsLowsThresholdInput * atrMeasure
drawEqualHighLow(p_ivot,high[size],size,true)
currentAlerts.equalHighs := true
p_ivot.lastLevel := p_ivot.currentLevel
p_ivot.currentLevel := high[size]
p_ivot.crossed := false
p_ivot.barTime := time[size]
p_ivot.barIndex := bar_index[size]
if not equalHighLow and not internal
trailing.top := p_ivot.currentLevel
trailing.barTime := p_ivot.barTime
trailing.barIndex := p_ivot.barIndex
trailing.lastTopTime := p_ivot.barTime
if showSwingsInput and not internal and not equalHighLow
drawLabel(time[size], p_ivot.currentLevel, p_ivot.currentLevel > p_ivot.lastLevel ? 'HH' : 'LH', swingBearishColor, label.style_label_down)
drawStructure(pivot p_ivot, string tag, color structureColor, string lineStyle, string labelStyle, string labelSize) =>
var line l_ine = line.new(na,na,na,na,xloc = xloc.bar_time)
var label l_abel = label.new(na,na)
if modeInput == PRESENT
l_ine.delete()
l_abel.delete()
l_ine := line.new(chart.point.new(p_ivot.barTime,na,p_ivot.currentLevel), chart.point.new(time,na,p_ivot.currentLevel), xloc.bar_time, color=structureColor, style=lineStyle)
l_abel := label.new(chart.point.new(na,math.round(0.5*(p_ivot.barIndex+bar_index)),p_ivot.currentLevel), localizeText(tag), xloc.bar_index, color=color(na), textcolor=structureColor, style=labelStyle, size = labelSize)
deleteOrderBlocks(bool internal = false) =>
array<orderBlock> orderBlocks = internal ? internalOrderBlocks : swingOrderBlocks
for [index,eachOrderBlock] in orderBlocks
bool crossedOderBlock = false
if bearishOrderBlockMitigationSource > eachOrderBlock.barHigh and eachOrderBlock.bias == BEARISH
crossedOderBlock := true
if internal
currentAlerts.internalBearishOrderBlock := true
else
currentAlerts.swingBearishOrderBlock := true
else if bullishOrderBlockMitigationSource < eachOrderBlock.barLow and eachOrderBlock.bias == BULLISH
crossedOderBlock := true
if internal
currentAlerts.internalBullishOrderBlock := true
else
currentAlerts.swingBullishOrderBlock := true
if crossedOderBlock
orderBlocks.remove(index)
storeOrdeBlock(pivot p_ivot,bool internal = false,int bias) =>
if (not internal and showSwingOrderBlocksInput) or (internal and showInternalOrderBlocksInput)
array<float> a_rray = na
int parsedIndex = na
if bias == BEARISH
a_rray := parsedHighs.slice(p_ivot.barIndex,bar_index)
parsedIndex := p_ivot.barIndex + a_rray.indexof(a_rray.max())
else
a_rray := parsedLows.slice(p_ivot.barIndex,bar_index)
parsedIndex := p_ivot.barIndex + a_rray.indexof(a_rray.min())
orderBlock o_rderBlock = orderBlock.new(parsedHighs.get(parsedIndex), parsedLows.get(parsedIndex), times.get(parsedIndex),bias)
array<orderBlock> orderBlocks = internal ? internalOrderBlocks : swingOrderBlocks
if orderBlocks.size() >= 100
orderBlocks.pop()
orderBlocks.unshift(o_rderBlock)
drawOrderBlocks(bool internal = false) =>
array<orderBlock> orderBlocks = internal ? internalOrderBlocks : swingOrderBlocks
orderBlocksSize = orderBlocks.size()
if orderBlocksSize > 0
maxOrderBlocks = internal ? internalOrderBlocksSizeInput : swingOrderBlocksSizeInput
array<orderBlock> parsedOrdeBlocks = orderBlocks.slice(0, math.min(maxOrderBlocks,orderBlocksSize))
array<box> b_oxes = internal ? internalOrderBlocksBoxes : swingOrderBlocksBoxes
for [index,eachOrderBlock] in parsedOrdeBlocks
orderBlockColor = styleInput == MONOCHROME ? (eachOrderBlock.bias == BEARISH ? color.new(MONO_BEARISH,80) : color.new(MONO_BULLISH,80)) : internal ? (eachOrderBlock.bias == BEARISH ? internalBearishOrderBlockColor : internalBullishOrderBlockColor) : (eachOrderBlock.bias == BEARISH ? swingBearishOrderBlockColor : swingBullishOrderBlockColor)
box b_ox = b_oxes.get(index)
b_ox.set_top_left_point( chart.point.new(eachOrderBlock.barTime,na,eachOrderBlock.barHigh))
b_ox.set_bottom_right_point(chart.point.new(last_bar_time,na,eachOrderBlock.barLow))
b_ox.set_border_color( internal ? na : orderBlockColor)
b_ox.set_bgcolor( orderBlockColor)
string descriptionTag = internal ? (eachOrderBlock.bias == BULLISH ? 'Internal Bullish OB' : 'Internal Bearish OB') : (eachOrderBlock.bias == BULLISH ? 'Swing Bullish OB' : 'Swing Bearish OB')
setBoxDescription(b_ox, descriptionTag, orderBlockColor)
displayStructure(bool internal = false) =>
var bullishBar = true
var bearishBar = true
if internalFilterConfluenceInput
bullishBar := high - math.max(close, open) > math.min(close, open - low)
bearishBar := high - math.max(close, open) < math.min(close, open - low)
pivot p_ivot = internal ? internalHigh : swingHigh
trend t_rend = internal ? internalTrend : swingTrend
lineStyle = internal ? line.style_dashed : line.style_solid
labelSize = internal ? internalStructureSize : swingStructureSize
extraCondition = internal ? internalHigh.currentLevel != swingHigh.currentLevel and bullishBar : true
bullishColor = styleInput == MONOCHROME ? MONO_BULLISH : internal ? internalBullColorInput : swingBullColorInput
if ta.crossover(close,p_ivot.currentLevel) and not p_ivot.crossed and extraCondition
string tag = t_rend.bias == BEARISH ? CHOCH : BOS
if internal
currentAlerts.internalBullishCHoCH := tag == CHOCH
currentAlerts.internalBullishBOS := tag == BOS
else
currentAlerts.swingBullishCHoCH := tag == CHOCH
currentAlerts.swingBullishBOS := tag == BOS
p_ivot.crossed := true
t_rend.bias := BULLISH
displayCondition = internal ? showInternalsInput and (showInternalBullInput == ALL or (showInternalBullInput == BOS and tag != CHOCH) or (showInternalBullInput == CHOCH and tag == CHOCH)) : showStructureInput and (showSwingBullInput == ALL or (showSwingBullInput == BOS and tag != CHOCH) or (showSwingBullInput == CHOCH and tag == CHOCH))
if displayCondition
drawStructure(p_ivot,tag,bullishColor,lineStyle,label.style_label_down,labelSize)
if (internal and showInternalOrderBlocksInput) or (not internal and showSwingOrderBlocksInput)
storeOrdeBlock(p_ivot,internal,BULLISH)
p_ivot := internal ? internalLow : swingLow
extraCondition := internal ? internalLow.currentLevel != swingLow.currentLevel and bearishBar : true
bearishColor = styleInput == MONOCHROME ? MONO_BEARISH : internal ? internalBearColorInput : swingBearColorInput
if ta.crossunder(close,p_ivot.currentLevel) and not p_ivot.crossed and extraCondition
string tag = t_rend.bias == BULLISH ? CHOCH : BOS
if internal
currentAlerts.internalBearishCHoCH := tag == CHOCH
currentAlerts.internalBearishBOS := tag == BOS
else
currentAlerts.swingBearishCHoCH := tag == CHOCH
currentAlerts.swingBearishBOS := tag == BOS
p_ivot.crossed := true
t_rend.bias := BEARISH
displayCondition = internal ? showInternalsInput and (showInternalBearInput == ALL or (showInternalBearInput == BOS and tag != CHOCH) or (showInternalBearInput == CHOCH and tag == CHOCH)) : showStructureInput and (showSwingBearInput == ALL or (showSwingBearInput == BOS and tag != CHOCH) or (showSwingBearInput == CHOCH and tag == CHOCH))
if displayCondition
drawStructure(p_ivot,tag,bearishColor,lineStyle,label.style_label_up,labelSize)
if (internal and showInternalOrderBlocksInput) or (not internal and showSwingOrderBlocksInput)
storeOrdeBlock(p_ivot,internal,BEARISH)
fairValueGapBox(leftTime,rightTime,topPrice,bottomPrice,boxColor) => box.new(chart.point.new(leftTime,na,topPrice),chart.point.new(rightTime + fairValueGapsExtendInput * (time-time[1]),na,bottomPrice), xloc=xloc.bar_time, border_color = boxColor, bgcolor = boxColor)
deleteFairValueGaps() =>
for [index,eachFairValueGap] in fairValueGaps
if (low < eachFairValueGap.bottom and eachFairValueGap.bias == BULLISH) or (high > eachFairValueGap.top and eachFairValueGap.bias == BEARISH)
eachFairValueGap.topBox.delete()
eachFairValueGap.bottomBox.delete()
fairValueGaps.remove(index)
drawFairValueGaps() =>
[lastClose, lastOpen, lastTime, currentHigh, currentLow, currentTime, last2High, last2Low] = request.security(syminfo.tickerid, fairValueGapsTimeframeInput, [close[1], open[1], time[1], high[0], low[0], time[0], high[2], low[2]],lookahead = barmerge.lookahead_on)
barDeltaPercent = (lastClose - lastOpen) / (lastOpen * 100)
newTimeframe = timeframe.change(fairValueGapsTimeframeInput)
threshold = fairValueGapsThresholdInput ? ta.cum(math.abs(newTimeframe ? barDeltaPercent : 0)) / bar_index * 2 : 0
bullishFairValueGap = currentLow > last2High and lastClose > last2High and barDeltaPercent > threshold and newTimeframe
bearishFairValueGap = currentHigh < last2Low and lastClose < last2Low and -barDeltaPercent > threshold and newTimeframe
if bullishFairValueGap
currentAlerts.bullishFairValueGap := true
fairValueGaps.unshift(fairValueGap.new(currentLow,last2High,BULLISH,fairValueGapBox(lastTime,currentTime,currentLow,math.avg(currentLow,last2High),fairValueGapBullishColor),fairValueGapBox(lastTime,currentTime,math.avg(currentLow,last2High),last2High,fairValueGapBullishColor)))
fairValueGap describedGap = fairValueGaps.get(0)
setBoxDescription(describedGap.topBox, 'Bullish FVG', fairValueGapBullishColor)
if bearishFairValueGap
currentAlerts.bearishFairValueGap := true
fairValueGaps.unshift(fairValueGap.new(currentHigh,last2Low,BEARISH,fairValueGapBox(lastTime,currentTime,currentHigh,math.avg(currentHigh,last2Low),fairValueGapBearishColor),fairValueGapBox(lastTime,currentTime,math.avg(currentHigh,last2Low),last2Low,fairValueGapBearishColor)))
fairValueGap describedGap = fairValueGaps.get(0)
setBoxDescription(describedGap.topBox, 'Bearish FVG', fairValueGapBearishColor)
getStyle(string style) =>
switch style
SOLID => line.style_solid
DASHED => line.style_dashed
DOTTED => line.style_dotted
drawLevels(string timeframe, bool sameTimeframe, string style, color levelColor) =>
[topLevel, bottomLevel, leftTime, rightTime] = request.security(syminfo.tickerid, timeframe, [high[1], low[1], time[1], time],lookahead = barmerge.lookahead_on)
float parsedTop = sameTimeframe ? high : topLevel
float parsedBottom = sameTimeframe ? low : bottomLevel
int parsedLeftTime = sameTimeframe ? time : leftTime
int parsedRightTime = sameTimeframe ? time : rightTime
int parsedTopTime = time
int parsedBottomTime = time
if not sameTimeframe
int leftIndex = times.binary_search_rightmost(parsedLeftTime)
int rightIndex = times.binary_search_rightmost(parsedRightTime)
array<int> timeArray = times.slice(leftIndex,rightIndex)
array<float> topArray = highs.slice(leftIndex,rightIndex)
array<float> bottomArray = lows.slice(leftIndex,rightIndex)
parsedTopTime := timeArray.size() > 0 ? timeArray.get(topArray.indexof(topArray.max())) : initialTime
parsedBottomTime := timeArray.size() > 0 ? timeArray.get(bottomArray.indexof(bottomArray.min())) : initialTime
var line topLine = line.new(na, na, na, na, xloc = xloc.bar_time, color = levelColor, style = getStyle(style))
var line bottomLine = line.new(na, na, na, na, xloc = xloc.bar_time, color = levelColor, style = getStyle(style))
var label topLabel = label.new(na, na, xloc = xloc.bar_time, text = localizeText(str.format('P{0}H',timeframe)), color=color(na), textcolor = levelColor, size = size.small, style = label.style_label_left)
var label bottomLabel = label.new(na, na, xloc = xloc.bar_time, text = localizeText(str.format('P{0}L',timeframe)), color=color(na), textcolor = levelColor, size = size.small, style = label.style_label_left)
topLine.set_first_point( chart.point.new(parsedTopTime,na,parsedTop))
topLine.set_second_point( chart.point.new(last_bar_time + 20 * (time-time[1]),na,parsedTop))
topLabel.set_point( chart.point.new(last_bar_time + 20 * (time-time[1]),na,parsedTop))
bottomLine.set_first_point( chart.point.new(parsedBottomTime,na,parsedBottom))
bottomLine.set_second_point(chart.point.new(last_bar_time + 20 * (time-time[1]),na,parsedBottom))
bottomLabel.set_point( chart.point.new(last_bar_time + 20 * (time-time[1]),na,parsedBottom))
higherTimeframe(string timeframe) => timeframe.in_seconds() > timeframe.in_seconds(timeframe)
updateTrailingExtremes() =>
trailing.top := math.max(high,trailing.top)
trailing.lastTopTime := trailing.top == high ? time : trailing.lastTopTime
trailing.bottom := math.min(low,trailing.bottom)
trailing.lastBottomTime := trailing.bottom == low ? time : trailing.lastBottomTime
drawHighLowSwings() =>
var line topLine = line.new(na, na, na, na, color = swingBearishColor, xloc = xloc.bar_time)
var line bottomLine = line.new(na, na, na, na, color = swingBullishColor, xloc = xloc.bar_time)
var label topLabel = label.new(na, na, color=color(na), textcolor = swingBearishColor, xloc = xloc.bar_time, style = label.style_label_down, size = size.tiny)
var label bottomLabel = label.new(na, na, color=color(na), textcolor = swingBullishColor, xloc = xloc.bar_time, style = label.style_label_up, size = size.tiny)
rightTimeBar = last_bar_time + 20 * (time - time[1])
topLine.set_first_point( chart.point.new(trailing.lastTopTime, na, trailing.top))
topLine.set_second_point( chart.point.new(rightTimeBar, na, trailing.top))
topLabel.set_point( chart.point.new(rightTimeBar, na, trailing.top))
topLabel.set_text( localizeText(swingTrend.bias == BEARISH ? 'Strong High' : 'Weak High'))
bottomLine.set_first_point( chart.point.new(trailing.lastBottomTime, na, trailing.bottom))
bottomLine.set_second_point(chart.point.new(rightTimeBar, na, trailing.bottom))
bottomLabel.set_point( chart.point.new(rightTimeBar, na, trailing.bottom))
bottomLabel.set_text( localizeText(swingTrend.bias == BULLISH ? 'Strong Low' : 'Weak Low'))
drawZone(float labelLevel, int labelIndex, float top, float bottom, string tag, color zoneColor, string style) =>
var label l_abel = label.new(na,na,text = localizeText(tag), color=color(na),textcolor = zoneColor, style = style, size = size.small)
var box b_ox = box.new(na,na,na,na,bgcolor = color.new(zoneColor,80),border_color = color(na), xloc = xloc.bar_time)
b_ox.set_top_left_point( chart.point.new(trailing.barTime,na,top))
b_ox.set_bottom_right_point(chart.point.new(last_bar_time,na,bottom))
l_abel.set_point( chart.point.new(na,labelIndex,labelLevel))
drawPremiumDiscountZones() =>
drawZone(trailing.top, math.round(0.5*(trailing.barIndex + last_bar_index)), trailing.top, 0.95*trailing.top + 0.05*trailing.bottom, 'Premium', premiumZoneColor, label.style_label_down)
equilibriumLevel = math.avg(trailing.top, trailing.bottom)
drawZone(equilibriumLevel, last_bar_index, 0.525*trailing.top + 0.475*trailing.bottom, 0.525*trailing.bottom + 0.475*trailing.top, 'Equilibrium', equilibriumZoneColorInput, label.style_label_left)
drawZone(trailing.bottom, math.round(0.5*(trailing.barIndex + last_bar_index)), 0.95*trailing.bottom + 0.05*trailing.top, trailing.bottom, 'Discount', discountZoneColor, label.style_label_up)
parsedOpen = showTrendInput ? open : na
candleColor = internalTrend.bias == BULLISH ? swingBullishColor : swingBearishColor
plotcandle(parsedOpen,high,low,close,title = '结构趋势K线',color = candleColor, wickcolor = candleColor, bordercolor = candleColor)
if showHighLowSwingsInput or showPremiumDiscountZonesInput
updateTrailingExtremes()
if showHighLowSwingsInput
drawHighLowSwings()
if showPremiumDiscountZonesInput
drawPremiumDiscountZones()
if showFairValueGapsInput
deleteFairValueGaps()
getCurrentStructure(swingsLengthInput,false)
getCurrentStructure(5,false,true)
if showEqualHighsLowsInput
getCurrentStructure(equalHighsLowsLengthInput,true)
if showInternalsInput or showInternalOrderBlocksInput or showTrendInput
displayStructure(true)
if showStructureInput or showSwingOrderBlocksInput or showHighLowSwingsInput
displayStructure()
if showInternalOrderBlocksInput
deleteOrderBlocks(true)
if showSwingOrderBlocksInput
deleteOrderBlocks()
if showFairValueGapsInput
drawFairValueGaps()
if barstate.islastconfirmedhistory or barstate.islast
if showInternalOrderBlocksInput
drawOrderBlocks(true)
if showSwingOrderBlocksInput
drawOrderBlocks()
lastBarIndex := currentBarIndex
currentBarIndex := bar_index
newBar = currentBarIndex != lastBarIndex
if barstate.islastconfirmedhistory or (barstate.isrealtime and newBar)
if showDailyLevelsInput and not higherTimeframe('D')
drawLevels('D',timeframe.isdaily,dailyLevelsStyleInput,dailyLevelsColorInput)
if showWeeklyLevelsInput and not higherTimeframe('W')
drawLevels('W',timeframe.isweekly,weeklyLevelsStyleInput,weeklyLevelsColorInput)
if showMonthlyLevelsInput and not higherTimeframe('M')
drawLevels('M',timeframe.ismonthly,monthlyLevelsStyleInput,monthlyLevelsColorInput)
alertcondition(currentAlerts.internalBullishBOS, '小结构向上突破', '小结构出现向上结构突破')
alertcondition(currentAlerts.internalBullishCHoCH, '小结构向上转变', '小结构出现向上结构转变')
alertcondition(currentAlerts.internalBearishBOS, '小结构向下突破', '小结构出现向下结构突破')
alertcondition(currentAlerts.internalBearishCHoCH, '小结构向下转变', '小结构出现向下结构转变')
alertcondition(currentAlerts.swingBullishBOS, '大结构向上突破', '大结构出现向上结构突破')
alertcondition(currentAlerts.swingBullishCHoCH, '大结构向上转变', '大结构出现向上结构转变')
alertcondition(currentAlerts.swingBearishBOS, '大结构向下突破', '大结构出现向下结构突破')
alertcondition(currentAlerts.swingBearishCHoCH, '大结构向下转变', '大结构出现向下结构转变')
alertcondition(currentAlerts.internalBullishOrderBlock, '小结构看涨订单块被跌破', '价格跌破小结构看涨订单块')
alertcondition(currentAlerts.internalBearishOrderBlock, '小结构看跌订单块被突破', '价格向上突破小结构看跌订单块')
alertcondition(currentAlerts.swingBullishOrderBlock, '大结构看涨订单块被跌破', '价格跌破大结构看涨订单块')
alertcondition(currentAlerts.swingBearishOrderBlock, '大结构看跌订单块被突破', '价格向上突破大结构看跌订单块')
alertcondition(currentAlerts.equalHighs, '等高点', '检测到等高点')
alertcondition(currentAlerts.equalLows, '等低点', '检测到等低点')
alertcondition(currentAlerts.bullishFairValueGap, '看涨失衡区', '形成看涨失衡区')
alertcondition(currentAlerts.bearishFairValueGap, '看跌失衡区', '形成看跌失衡区')