基于混沌理论的超买超卖分析·进阶篇
(2/3)· 当价格看似随机乱跳时,奇异吸引子与分形几何已悄悄标出回归路径
◍ 给神经网络喂料前先调好口径
把历史报价直接塞进网络,和让俄语母语者去跟只懂英语的人谈合约一样低效。训练前必须把每个特征拉到同一尺度:线性映射把价格压进 [0,1] 区间,网络才跑得稳,也才适配 Sigmoid 类的激活路径。 隐藏层里我们弃用传统 Sigmoid,换成 tanh。它的输出域是 [-1,1],中心区斜率更陡,能同时吃下正负偏离信号,对秒级切换的混沌盘面更敏感。市场状态常在瞬息间翻转,这种陡坡让权重更新更快咬住突变。 前向与反向传播构成试错闭环:前向拿当前权重出预测,反向按误差回改权重,类似幼童学步,摔几次才稳。下面这段是 MT5 里可直抄的前向推断与配套函数,注意 NormalizePrice 依赖 PriceHistory 数组,实盘前先确保它装满了滑动窗口内的真实极值。 别把正态当圣经 标准化只是统一步频,不等于消除跳空。外汇与贵金属杠杆高、跳空频繁,[0,1] 映射遇到极端影线仍可能让隐藏层瞬时饱和,回测顺滑不等于实盘安全。 振荡器才是落点:当前价除以吸引子减 1 再乘 100,得出偏离百分比。读数 +30 代表价格相对模型‘自然水平’过热,倾向回拉;-30 则过冷,概率上反弹。代码里对吸引子为 0 做了除零保护,照搬时别手删那行。
class="type">class="kw">double ForwardPass(class="type">class="kw">double &inputs[]) { class=class="str">"cmt">// Calculating outputs of the hidden layer for(class="type">int i = class="num">0; i < HiddenNeurons; i++) { class="type">class="kw">double sum = Network.hidden[i].bias; for(class="type">int j = class="num">0; j < InputNeurons; j++) { sum += inputs[j] * Network.hidden[i].weights[j]; } Network.hidden[i].output = Sigmoid(sum); } class=class="str">"cmt">// Calculating neural network output class="type">class="kw">double sum = Network.output.bias; for(class="type">int i = class="num">0; i < HiddenNeurons; i++) { sum += Network.hidden[i].output * Network.output.weights[i]; } Network.output.output = Sigmoid(sum); class="kw">return Network.output.output; } class="type">class="kw">double Tanh(class="type">class="kw">double x) { class="kw">return (MathExp(x) - MathExp(-x)) / (MathExp(x) + MathExp(-x)); } class="type">class="kw">double NormalizePrice(class="type">class="kw">double price) { class="type">class="kw">double min = ArrayMin(PriceHistory); class="type">class="kw">double max = ArrayMax(PriceHistory); class="kw">return (price - min) / (max - min); } class=class="str">"cmt">// Calculate oscillator value as ratio of current price to attractor if(AttractorBuffer[i] > class="num">0) { OscillatorBuffer[i] = (CurrentPriceBuffer[i] / AttractorBuffer[i] - class="num">1.0) * class="num">100.0; } else { OscillatorBuffer[i] = class="num">0; class=class="str">"cmt">// Division-by-zero protection }
「把旋钮拧到自己的行情里」
交易市场没有通用的指标模板,不同品种和周期自带情绪与波动节律,照搬别人的参数往往水土不服。把指标当成音频放大器,输入神经元数、隐藏层规模、学习率这些就是面板上的旋钮,想让信号更跟手就加 InputNeurons 或缩短 PredictionPeriod,噪声炸耳就把 Smoothing 打开。 PredictionPeriod 是往前看多久的“时间透镜”:在 M1 上填 5 代表预测其后 5 分钟,切到 D1 同样写 5 就是看后 5 个交易日。实操建议从 5 这样的小值起步,逐步往上推,观察曲线拐点何时开始滞后或过度拟合——这是用 MT5 跑一遍就能验证的。 李雅普诺夫指数给混沌加了把尺子。代码里用 0.0001 的微扰 epsilon 喂给两份近似输入,算两次前向预测的距离差再取对数平均,把“蝴蝶效应”量化成 -1.0 到 1.0 之间的数。该值偏正说明初始价微小变动会被放大,行情随机性更强,外汇与贵金属杠杆高,这种敏感期止损要更收紧。 分形噪声借曼德尔布罗的中点位移思路,给指标叠出自相似的市场肌理。下面这段参数声明与指数计算函数,直接贴进 MQ5 就能编译看效果。
<span class="keyword">input</span> <span class="keyword">class="type">int</span> InputNeurons = <span class="number">class="num">10</span>; <span class="comment">class=class="str">"cmt">// Number of input neurons(historical periods)</span> <span class="keyword">input</span> <span class="keyword">class="type">int</span> HiddenNeurons = <span class="number">class="num">20</span>; <span class="comment">class=class="str">"cmt">// Number of neurons in the hidden layer</span> <span class="keyword">input</span> <span class="keyword">class="type">class="kw">double</span> LearningRate = <span class="number">class="num">0.01</span>; <span class="comment">class=class="str">"cmt">// Learning rate</span> <span class="keyword">input</span> <span class="keyword">class="type">int</span> TrainBars = <span class="number">class="num">1000</span>; <span class="comment">class=class="str">"cmt">// Number of bars for training</span> <span class="keyword">input</span> <span class="keyword">class="type">int</span> PredictionPeriod = <span class="number">class="num">5</span>; <span class="comment">class=class="str">"cmt">// Prediction period(in bars)</span> <span class="keyword">input</span> <span class="keyword">class="type">bool</span> Smoothing = <span class="macro">false</span>; <span class="comment">class=class="str">"cmt">// Apply smoothing to the oscillator</span> <span class="keyword">input</span> <span class="keyword">class="type">int</span> SmoothingPeriod = <span class="number">class="num">3</span>; <span class="comment">class=class="str">"cmt">// Smoothing period</span> <span class="keyword">class="type">class="kw">double</span> CalculateLyapunovExponent(<span class="keyword">const</span> <span class="keyword">class="type">class="kw">double</span> &close[], <span class="keyword">class="type">int</span> bars) { <span class="keyword">class="type">class="kw">double</span> epsilon = <span class="number">class="num">0.0001</span>; <span class="comment">class=class="str">"cmt">// Small perturbation 微小扰动量</span> <span class="keyword">class="type">class="kw">double</span> lyapunov = <span class="number">class="num">0.0</span>; <span class="comment">class=class="str">"cmt">// 李雅普诺夫指数累加器</span> <span class="keyword">class="type">int</span> samples = <span class="functions">MathMin</span>(LyapunovPeriod, TrainBars/<span class="number">class="num">2</span>); <span class="comment">class=class="str">"cmt">// 取样数取周期与训练量一半的较小值</span> <span class="keyword">for</span>(<span class="keyword">class="type">int</span> i = <span class="number">class="num">0</span>; i < samples; i++) { <span class="keyword">class="type">int</span> startIdx = <span class="functions">MathRand</span>() % (TrainBars - InputNeurons - PredictionPeriod); <span class="comment">class=class="str">"cmt">// 随机起点避免偏采样</span> <span class="keyword">class="type">class="kw">double</span> inputs1[]; <span class="comment">class=class="str">"cmt">// 原始输入序列</span> <span class="functions">ArrayResize</span>(inputs1, InputNeurons); <span class="keyword">for</span>(<span class="keyword">class="type">int</span> j = <span class="number">class="num">0</span>; j < InputNeurons; j++) { inputs1[j] = NormalizePrice(close[bars - TrainBars + startIdx + j]); <span class="comment">class=class="str">"cmt">// 归一化历史价填入</span> } <span class="keyword">class="type">class="kw">double</span> inputs2[]; <span class="comment">class=class="str">"cmt">// 扰动输入序列</span> <span class="functions">ArrayResize</span>(inputs2, InputNeurons); <span class="functions">ArrayCopy</span>(inputs2, inputs1); inputs2[<span class="functions">MathRand</span>() % InputNeurons] += epsilon; <span class="comment">class=class="str">"cmt">// 随机某一维加微扰</span> <span class="keyword">class="type">class="kw">double</span> pred1 = ForwardPass(inputs1); <span class="comment">class=class="str">"cmt">// 原输入预测</span> <span class="keyword">class="type">class="kw">double</span> pred2 = ForwardPass(inputs2); <span class="comment">class=class="str">"cmt">// 扰动输入预测</span> <span class="keyword">class="type">class="kw">double</span> distance = <span class="functions">MathAbs</span>(pred2 - pred1); <span class="comment">class=class="str">"cmt">// 两预测间距</span> <span class="keyword">if</span>(distance > <span class="number">class="num">0</span>) { lyapunov += <span class="functions">MathLog</span>(distance / epsilon); <span class="comment">class=class="str">"cmt">// 对数发散率累加</span> } } lyapunov = lyapunov / samples; <span class="comment">class=class="str">"cmt">// 取平均</span> lyapunov = <span class="functions">MathMax</span>(-<span class="number">class="num">1.0</span>, <span class="functions">MathMin</span>(<span class="number">class="num">1.0</span>, lyapunov)); <span class="comment">class=class="str">"cmt">// 截断至[-class="num">1,class="num">1]保稳定</span> <span class="keyword">class="kw">return</span> lyapunov; } <span class="keyword">class="type">void</span> GenerateFractalNoise(<span class="keyword">class="type">int</span> size) { <span class="functions">ArrayResize</span>(FractalNoiseBuffer, size); FractalNoiseBuffer[<span class="number">class="num">0</span>] = <span class="number">class="num">0</span>; <span class="comment">class=class="str">"cmt">// 左端锚点</span> FractalNoiseBuffer[size-<span class="number">class="num">1</span>] = <span class="number">class="num">0</span>; <span class="comment">class=class="str">"cmt">// 右端锚点</span>
递归位移后做一次归一化
上面那段收尾代码干了两件事:先对分形噪声缓冲做递归中点位移,再把整条序列压进 [-1, 1] 区间。 ArrayMin 与 ArrayMax 取缓冲极值后,循环里用 2.0*(x-min)/(max-min)-1.0 把每个点线性映射。这一步必须做,否则后续叠加到价格上时会因量纲不一而失真。 FractalDimension 参数值越高,生成的噪声曲线越崎岖混沌,对外汇或贵金属这类高杠杆品种,可能更贴近极端波动段的轨迹,但实盘验证仍属高风险行为,建议在 MT5 策略测试器里先跑裸噪声曲线。
class=class="str">"cmt">// Recursive calculation of midpoints MidpointDisplacement(FractalNoiseBuffer, class="num">0, size-class="num">1, class="num">1.0, FractalDimension); class=class="str">"cmt">// Normalization class="type">class="kw">double min = ArrayMin(FractalNoiseBuffer, class="num">0, size); class="type">class="kw">double max = ArrayMax(FractalNoiseBuffer, class="num">0, size); for(class="type">int i = class="num">0; i < size; i++) { FractalNoiseBuffer[i] = class="num">2.0 * (FractalNoiseBuffer[i] - min) / (max - min) - class="num">1.0; } }
◍ 把吸引子边界当成摆锤回中信号
神经吸引子震荡指标本质上是个受混沌约束的摆锤模型。价格偏离吸引子下边界太远(边界按李雅普诺夫指数动态调整),指标触底回升,可能给出多头入场点;升破上边界后回落,则可能是空头信号。李雅普诺夫指数越低,这类信号的可靠度越高,因为市场被更强地拉向均衡。 背离在这里比经典用法多一层含义:价格创新高/低而指标未跟随,不只是趋势减弱,更可能是吸引子结构本身在变——市场趋向的均衡点挪位了。若背离同时伴随李雅普诺夫指数由正转负,往往意味着高混沌期结束、新稳定趋势正在成形。 高混沌期(高李雅普诺夫指数)里,再漂亮的突破或背离都不可靠,实盘应减仓或离场观望。外汇与贵金属杠杆高、跳空频繁,这种时期止损可能被滑点击穿。 下方是输出层权重的动量优化片段,动量项让网络逃开局部最小值: // 带动量的输出层权重更新 for(int j = 0; j < HiddenNeurons; j++) { // 计算当前梯度步长:学习率×输出误差×隐层第j神经元输出 double delta = LearningRate * Network.output.error * Network.hidden[j].output; // 动量累加:旧动量乘衰减系数 + 新梯度步长乘(1-衰减) Network.output.momentum[j] = momentum * Network.output.momentum[j] + (1.0 - momentum) * delta; // 权重沿动量方向更新 Network.output.weights[j] += Network.output.momentum[j]; } 在 MT5 里把 momentum 从 0.0 调到 0.9 逐档回测,能直观看到陷入局部最小的概率变化。
class=class="str">"cmt">// Updating the output layer weights with momentum for(class="type">int j = class="num">0; j < HiddenNeurons; j++) { class="type">class="kw">double delta = LearningRate * Network.output.error * Network.hidden[j].output; Network.output.momentum[j] = momentum * Network.output.momentum[j] + (class="num">1.0 - momentum) * delta; Network.output.weights[j] += Network.output.momentum[j]; }