摘要:S0000 是缠论里最基础的笔转折模型,每个笔端点触发一个信号。它的信号值里有个 occurrence(第几次)字段,原本恒定输出 1——占着百位却没传递任何信息。这次把它重构成「逆势或顺势首新记 1,顺势第 N 次创新极值记 N」的趋势编码。核心是把方向判定和计数口径收敛到一套与缠论几何自洽的设计:方向看最近标记符号、不翻转,计数只认确认笔。
引言:一个被焊死的信号位
在缠论量化里,S0000 是最朴素的信号模型——只看笔的转折。每一根笔走到尽头(顶或底),它就触发一次,配上一个买或卖的入场点类型,吐出一个整数信号值。
这个信号值的结构是固定的:
value = 方向 × (模型号×1000 + occurrence×100 + 入场点)
其中 occurrence 字面意思是「第几次」。可 S0000 的 occurrence 原本永远输出 1。一个能表达 1~99 的百位字段,被焊死成了常数。
问题在于:同样是「笔转折」,背后的含义天差地别。一根向上笔创了新高,可能正躺在上升线段里顺势而为(该关注),也可能是下降线段里的逆势反弹(该警惕)。固定 1 把这两种截然相反的信号揉成了一团。
浪费一个信号位,就是浪费一次让数据说话的机会。
第一章:顺势与逆势,一字之差
要把 occurrence 用起来,先得回答一个缠论核心问题:这个笔转折点,到底是顺势还是逆势?
- 顺势
:笔的方向,和它所在线段的方向一致。上升线段里的向上笔,下降线段里的向下笔。 - 逆势
:笔的方向,和线段方向相反。下降线段里冒出来的向上笔反弹。
这两者的交易逻辑完全相反。顺势创新高是趋势延续的强信号;逆势反弹往往是趋势末端的噪声。
我们想让 occurrence 区分它们,并进一步回答:如果是顺势,这是线段内第几次创新极值?第一次创新高和第五次创新高,强度显然不同。
于是目标清晰了:occurrence 要从「恒定 1」升级成「趋势强度的编码」。
原则:信号字段每一比特都该承载信息。一个常年恒定的位,是设计上的债。
第二章:一个字段,三种状态
最直觉的设计是:逆势记 1,顺势第 N 次创新高记 N。
但马上撞到一个编码冲突:occurrence=1 如果专留给逆势,那顺势第一次创新高就只能从 2 开始,计数得 +1 偏移。多一层偏移,解码时就得记得减回去,容易出错。
权衡之后,选择了一个更干净的口径:
| 1 | |
| N ≥ 2 |
也就是说,occurrence=1 是一个「弱信号」集合——凡是没法确证「这是顺势第 N 次(N≥2)」的,统统落到 1。只有明确数到第二次及以上的顺势创新,才给出 ≥2 的强度值。
解码端拿到 ≥2 就知道是强顺势信号;拿到 1 则需要结合其他位(方向、入场点)综合判断。语义分层清晰,也不必维护 +1 偏移。
S0000 模型号固定为 0,信号值简化为 方向 × (occ×100 + 入场点)
原则:编码设计要在「精确」与「可解码」之间找平衡。能让解码端一眼分出强弱档,就别为了纯数学美感引入偏移。
第三章:核心实现
落地拆成两个函数,各司其职:
- 第一步,判定方向
( seg_direction_at):这个笔转折点,处于向上线段还是向下线段? - 第二步,数创新极值
( calc_occurrence):如果是顺势,数线段内到这个点为止创了几次新高。
方向判定:看最近标记,不翻转
线段信号 stretch_sigs 里,1 是向上线段终点(顶),-1 是向下线段终点(底),0.5 与 -0.5 是线段中间点。方向判定的逻辑出乎意料地简短:
// 从 i 往回找最近的有意义标记,符号即方向(不翻转)for (int j = i; j >= 0; --j) {float s = stretch_sigs[j];if (s == 0.5f || s == 1.0f) return1; // 先遇到正标记 → 向上if (s == -0.5f || s == -1.0f) return -1; // 先遇到负标记 → 向下}return0;关键认知在于 ±0.5 这两个中间点:它们标记在线段的「综合确认位置」和段内创新极值的笔上,代表「这段线段的方向已经确认了」。所以线段正在形成中(中间点已出现、终点还没到)的位置,也能正确判出方向。
那为什么是「先遇到什么符号就是什么方向、绝不翻转」?因为线段方向的切换是由中间点 ±0.5 的出现来标志的,不是终点。顶 1.0 只标记极值,越过它方向并不会立刻翻转——要等到反向中间点 -0.5 出现,才确认新方向成立。
方向切换以反向中间点 -0.5 出现为标志,终点 1.0 不触发翻转
计数:只认确认笔
顺势时,先定位线段起点(前一个反向终点),从起点往后数累进创新极值:
for (int x = s + 1; x <= i; ++x) {float w = wave_sigs[x];// 确认笔(1.0)任意位置计入;候选笔(0.5)仅末点(触发点 i)计入bool eligible = (w == 1.0f) || (w == 0.5f && x == i);if (!eligible) continue;if (!started || high[x] > running) { ++cnt; running = high[x]; started = true; }}return std::min(cnt, 99);笔信号 wave_sigs 里,1.0 是已确认的笔端点,0.5 是尚未确认的候选笔端点——它可能被后续行情重组掉。所以计数口径定为:确认笔 1.0 任意位置都算;候选笔 0.5 只有触发点本身(最后一根)才算——因为 S0000 正是在这根候选笔上触发的,它必须参与计数;其余候选笔一律不算,避免把可能消失的笔算进创新次数。
这就是代码里 x == i 那个条件的用途。
原则:尊重实现里的「确认机制」(哪个标记代表方向已成立、哪根笔已确认),而非理论的抽象定义。判定和计数都建立在已确认的事实上。
第四章:边界与兜底
除了核心口径,还有几个边界处理,它们是信号系统可靠性的底线:
- 找不到线段起点
:没法计数。这时不能瞎猜「至少算第一次顺势」(那会虚高),保守回落到 occurrence=1。 - 计数封顶 99
:occurrence 占信号值的百位,超过 99 会让编码溢出, min(cnt, 99)兜底。 - 越界、无线段信息
:统统返回 1。和「弱信号集合」的口径一致。
这些边界看似琐碎,原则却一致:宁可给个保守的 1,也不要给个可能误导的精确值。
最终 occurrence 的取值口径完全收敛:
| 1 | |
| N |
总结:信号位的榨取与克制
这次改动,本质是给一个沉睡的信号位注入语义。而注入的过程揭示了一个反复出现的张力:想从信号里榨取更多信息,就必须更精确地理解领域的确认机制。
想区分顺势逆势,就得搞懂线段方向的确认靠的是中间点 ±0.5,不是终点±1.0。想数准创新次数,就得区分确认笔和候选笔,只认已确认的事实。 想用 occurrence 表达强度,就得在「精确」和「可靠」之间,选可靠的兜底。
核心原则:信号字段的每一比特都该承载信息,但承载的前提是尊重领域模型的确认机制——宁可保守,不可虚高。
附录:技术速查表
信号值编码(S0000,模型号=0):value = 方向 × (occurrence×100 + 入场点)
102 | ||||
-103 | ||||
207 |
入场点编码:2=Pin Bar,3=吞没,4=强分型,5=MA5拐头,6=量价齐升,7=MACD金叉/死叉。
S0000 的触发与编码调用:
// 在向上笔端点(pos)触发,wave_dir = +1int occurrence = SignalUtils::calc_occurrence( wave_sigs, stretch_sigs, high, low, pos, 1);inner_result[i] = encode_signal(0, occurrence, signal);方向判定要点:从触发点往前找最近标记,符号定方向、不翻转;方向切换以反向中间点 ±0.5 出现为标志。
量化实战手记
本系列记录作者用代码理解市场的真实历程——每个想法如何变成设计,每个设计如何变成可运行的系统。不谈理论,只聊实战。
夜雨聆风