交易中的混沌理论(第二部分):深入探索(基础篇)
用相空间重构把价格序列摊开看
混沌理论在价格行为里的落地,第一步不是算分形维数,而是先做相空间重构。把一维的收盘价序列,延迟嵌入成多维点云,才能看出价格到底是在随机游走还是被低维吸引子牵着走。 具体做法是用时间延迟 τ 和嵌入维数 m,把原序列 x(i) 映射成向量 X(i) = [x(i), x(i+τ), …, x(i+(m-1)τ)]。MT5 里没有现成指标直接画这个,但可以用数组和循环自己算。 一个可验证的现象:EURUSD 的 H1 收盘价做 m=3、τ=1 的重构后,点云会明显沿一条带状结构聚集,而不是填满整个立方体空间——这暗示价格存在低维确定性成分,但外汇高杠杆下仍属高风险,任何结构都只是概率倾向。
class=class="str">"cmt">// 相空间重构示例:m=class="num">3, tau=class="num">1 class="type">int m = class="num">3, tau = class="num">1; class="type">class="kw">double buf[][class="num">3]; class="type">int n = Bars(_Symbol, PERIOD_H1); ArrayResize(buf, n - (m-class="num">1)*tau); for(class="type">int i = class="num">0; i < n - (m-class="num">1)*tau; i++) { buf[i][class="num">0] = iClose(_Symbol, PERIOD_H1, i); buf[i][class="num">1] = iClose(_Symbol, PERIOD_H1, i + tau); buf[i][class="num">2] = iClose(_Symbol, PERIOD_H1, i + class="num">2*tau); }
class=class="str">"cmt">// 相空间重构示例:m=class="num">3, tau=class="num">1 class="type">int m = class="num">3, tau = class="num">1; class="type">class="kw">double buf[][class="num">3]; class="type">int n = Bars(_Symbol, PERIOD_H1); ArrayResize(buf, n - (m-class="num">1)*tau); for(class="type">int i = class="num">0; i < n - (m-class="num">1)*tau; i++) { buf[i][class="num">0] = iClose(_Symbol, PERIOD_H1, i); buf[i][class="num">1] = iClose(_Symbol, PERIOD_H1, i + tau); buf[i][class="num">2] = iClose(_Symbol, PERIOD_H1, i + class="num">2*tau); }
「首篇留下的混沌分析底子」
首篇先把混沌理论在金融里的落点理清了:吸引子、分形、蝴蝶效应不是数学玩具,而是市场波动结构的一种描述方式。文中把经典混沌框架和比尔·威廉姆斯的操作法摆在一起比对,点出了科学模型与实战信号之间的断层。 李雅普诺夫指数在那篇里是核心工具,既讲了理论含义,也给了 MQL5 的实际算例。用它跑 EURUSD 的 H1 数据做趋势逆转和延续的统计,能看出指数转负区间往往对应动能衰减的概率更高。 收尾部分已经把混沌视角的交易地基打好了,下一篇直接进阶——用分形维数给市场混乱度打分,这比单看波动率更贴近价格结构的真实褶皱。外汇与贵金属杠杆高,这类指标只作概率参考,实盘前请在 MT5 用历史数据复算一遍。
◍ 用盒计数法给盘面锯齿度打分
分形维数本质是描述价格图自相似性与复杂度的标量。在外汇与贵金属这类高杠杆市场里,它直接反映图表的“锯齿性”——数值偏高往往意味着走势更混乱、随机成分更重,偏低则倾向于更平稳、可追踪的波段,但两者都不构成方向保证,杠杆品种随时可能反向跳空。 盒计数法是最易落地的估算路径:用边长 ε 的网格去覆盖最近 N 根烛形构成的价格轨迹,统计所需格子数 N(ε),再对多组 ε 取 log N(ε) 对 log ε 的斜率,其负值即为维数 D = -lim(ε→0)[log N(ε)/log(ε)]。D 的突变常出现在趋势、震荡与紊乱态的切换边界。 下面这段 MT5 指标用盒计数实时算 D,默认取 5 种盒尺寸、最小 2 最大 100、回看 1000 根 K 线。把代码丢进 MetaEditor 编译,挂上 XAUUSD 的 H1,你能立刻看到副图红线在消息面行情里冲高、在窄幅整理里压低的规律。 别把正态当圣经 维数高只说明“难预测”,不代表一定会跌或一定会继续乱;贵金属夜盘流动性薄时,维数虚高可能只是点差与影线造成的假象,验证时建议切到主流动性时段。
class="macro">#class="kw">property copyright "Copyright class="num">2024, Evgeniy Shtenco" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property strict class="macro">#class="kw">property indicator_separate_window class="macro">#class="kw">property indicator_buffers class="num">1 class="macro">#class="kw">property indicator_plots class="num">1 class="macro">#class="kw">property indicator_label1 "Fractal Dimension" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrRed class="macro">#class="kw">property indicator_style1 STYLE_SOLID class="macro">#class="kw">property indicator_width1 class="num">1 class="kw">input class="type">int InpBoxSizesCount = class="num">5; class=class="str">"cmt">// Number of box sizes class="kw">input class="type">int InpMinBoxSize = class="num">2; class=class="str">"cmt">// Minimum box size class="kw">input class="type">int InpMaxBoxSize = class="num">100; class=class="str">"cmt">// Maximum box size class="kw">input class="type">int InpDataLength = class="num">1000; class=class="str">"cmt">// Data length for calculation class="type">class="kw">double FractalDimensionBuffer[]; class="type">int OnInit() { SetIndexBuffer(class="num">0, FractalDimensionBuffer, INDICATOR_DATA); IndicatorSetInteger(INDICATOR_DIGITS, class="num">4); IndicatorSetString(INDICATOR_SHORTNAME, "Fractal Dimension"); class="kw">return(INIT_SUCCEEDED); } class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">class="kw">datetime &time[], class="kw">const class="type">class="kw">double &open[], class="kw">const class="type">class="kw">double &high[], class="kw">const class="type">class="kw">double &low[], class="kw">const class="type">class="kw">double &close[], class="kw">const class="type">long &tick_volume[], class="kw">const class="type">long &volume[], class="kw">const class="type">int &spread[]) { class="type">int start; if(prev_calculated == class="num">0)
分形维度估算的盒子计数与回归实现
这段计算逻辑用盒子计数法(box-counting)逼近价格序列的分形维度。核心在 CalculateFractalDimension:先按对数间隔生成一组 boxSize,再对每个尺度统计覆盖价格区间所需盒子数,最后对 log(1/boxSize) 与 log(盒子数) 做线性回归,斜率 a 即为分形维度估计值。 CountBoxes 只取 index 往前 InpDataLength 根 K 线,找这段窗口内的最高最低价,用 (max-min) 除以 (boxSize*_Point) 再向上取整,得到该尺度下的盒子数。注意这里除的是 _Point,意味着 boxSize 实际是按「点」为单位的整数格。 线性回归没调库,手算五个累加项:sumX、sumY、sumXY、sumX2,套最小二乘公式出斜率 a 与截距 b。若 InpBoxSizesCount 设 10、InpMinBoxSize=1、InpMaxBoxSize=100,则 boxSize 序列约从 1 到 100 按 10 的幂均匀取 10 个值,回归点也就 10 个,样本少时维度估计可能抖动明显。 外汇与贵金属杠杆高、滑点无常,这类维度指标只反映历史分形特征,对后续走势仅是概率性参考,不能直接当方向信号。
start = InpDataLength; else start = prev_calculated - class="num">1; for(class="type">int i = start; i < rates_total; i++) { FractalDimensionBuffer[i] = CalculateFractalDimension(close, i); } class="kw">return(rates_total); } class="type">class="kw">double CalculateFractalDimension(class="kw">const class="type">class="kw">double &price[], class="type">int index) { if(index < InpDataLength) class="kw">return class="num">0; class="type">class="kw">double x[]; class="type">class="kw">double y[]; ArrayResize(x, InpBoxSizesCount); ArrayResize(y, InpBoxSizesCount); for(class="type">int i = class="num">0; i < InpBoxSizesCount; i++) { class="type">int boxSize = (class="type">int)MathRound(MathPow(class="num">10, MathLog10(InpMinBoxSize) + (MathLog10(InpMaxBoxSize) - MathLog10(InpMinBoxSize)) * i / (InpBoxSizesCount - class="num">1))); x[i] = MathLog(class="num">1.0 / boxSize); y[i] = MathLog(CountBoxes(price, index, boxSize)); } class="type">class="kw">double a, b; CalculateLinearRegression(x, y, InpBoxSizesCount, a, b); class="kw">return a; class=class="str">"cmt">// The slope of the regression line is the estimate of the fractal dimension } class="type">int CountBoxes(class="kw">const class="type">class="kw">double &price[], class="type">int index, class="type">int boxSize) { class="type">class="kw">double min = price[index - InpDataLength]; class="type">class="kw">double max = min; for(class="type">int i = index - InpDataLength + class="num">1; i <= index; i++) { if(price[i] < min) min = price[i]; if(price[i] > max) max = price[i]; } class="kw">return (class="type">int)MathCeil((max - min) / (boxSize * _Point)); } class="type">void CalculateLinearRegression(class="kw">const class="type">class="kw">double &x[], class="kw">const class="type">class="kw">double &y[], class="type">int count, class="type">class="kw">double &a, class="type">class="kw">double &b) { class="type">class="kw">double sumX = class="num">0, sumY = class="num">0, sumXY = class="num">0, sumX2 = class="num">0; for(class="type">int i = class="num">0; i < count; i++) { sumX += x[i]; sumY += y[i]; sumXY += x[i] * y[i]; sumX2 += x[i] * x[i]; } a = (count * sumXY - sumX * sumY) / (count * sumX2 - sumX * sumX); b = (sumY - a * sumX) / count; }
「用递归图把价格状态重叠出来看」
递归分析把一维报价流先还原成相空间里的轨迹,再问一句:此刻的状态,过去哪一刻也长这样?把两两相似的时刻在二维网格里点出来,就得到递归图。对角线代表确定性动态,成片均匀点块是平稳期,垂直水平条带是层流,满屏没结构基本就是随机游走。 做这张图分四步:延迟嵌入重构相空间、定相似度阈值、逐对时间点填递归矩阵、最后渲染成图像。阈值松了满屏都是点,紧了只剩零星对角线——这个参数直接决定图能不能用,建议在 MT5 里从收盘价序列先试 ε=0.1×标准差起步。 量化上别只盯眼睛看,递归百分比(RP)、确定性(DET)、分层性(LAM)三个量就够了。RP 高说明市场反复踩相似状态;DET 高倾向趋势或周期可控;LAM 高则大概率进入窄幅层流。外汇与贵金属杠杆高、跳空频繁,这些指标只作辅助,不单独押方向。 本指标采用递归分析方法研究市场动态,计算递归水平、确定性与分层性三项。下一节再拆具体 MQL5 实现与策略读法。
◍ 相空间重构的三个输出线怎么读
这个指标挂在价格图下方的独立窗口,用三个缓冲区分别画出递归率(蓝)、决定性(红)和分层性(绿)。蓝线反映整体递归水平,也就是历史状态重复出现的密集度;红线衡量系统可预测性,看递归点连成对角线的比例;绿线评估分层性,即系统黏在某个状态里的倾向。 算法核心是延迟法:从一维价格序列重建相空间,在 200 根 K 线的分析窗口里逐点比对其余点的相似度。默认嵌入维度 3、时间延迟 1、阈值 10 点,这三个值直接决定窗口最少需要 3 + (3-1)*1 = 202 根 bar 才能出数。 实盘里把 InpThreshold 从 10 调到 5,绿线会明显更敏感,贵金属 5 分钟图常提前半根到一根 K 线给出状态滞留信号;但阈值过小易在震荡市产生伪分层。外汇和贵金属杠杆高、跳空频繁,这类非线性指标只作概率参考,别当方向确认。 想验证就直接把下面代码丢进 MT5 指标编辑器编译,调 InpWindowSize 到 100 看小窗口下蓝线噪声是否变大,比看说明来得快。
class="macro">#class="kw">property copyright "Copyright class="num">2024, Evgeniy Shtenco" class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#class="kw">property strict class="macro">#class="kw">property indicator_separate_window class="macro">#class="kw">property indicator_buffers class="num">3 class="macro">#class="kw">property indicator_plots class="num">3 class="macro">#class="kw">property indicator_label1 "Recurrence Rate" class="macro">#class="kw">property indicator_type1 DRAW_LINE class="macro">#class="kw">property indicator_color1 clrBlue class="macro">#class="kw">property indicator_label2 "Determinism" class="macro">#class="kw">property indicator_type2 DRAW_LINE class="macro">#class="kw">property indicator_color2 clrRed class="macro">#class="kw">property indicator_label3 "Laminarity" class="macro">#class="kw">property indicator_type3 DRAW_LINE class="macro">#class="kw">property indicator_color3 clrGreen class="kw">input class="type">int InpEmbeddingDimension = class="num">3; class=class="str">"cmt">// Embedding dimension class="kw">input class="type">int InpTimeDelay = class="num">1; class=class="str">"cmt">// Time delay class="kw">input class="type">int InpThreshold = class="num">10; class=class="str">"cmt">// Threshold(in points) class="kw">input class="type">int InpWindowSize = class="num">200; class=class="str">"cmt">// Window size class="type">class="kw">double RecurrenceRateBuffer[]; class="type">class="kw">double DeterminismBuffer[]; class="type">class="kw">double LaminarityBuffer[]; class="type">int minRequiredBars; class="type">int OnInit() { SetIndexBuffer(class="num">0, RecurrenceRateBuffer, INDICATOR_DATA); SetIndexBuffer(class="num">1, DeterminismBuffer, INDICATOR_DATA); SetIndexBuffer(class="num">2, LaminarityBuffer, INDICATOR_DATA); IndicatorSetInteger(INDICATOR_DIGITS, class="num">4); IndicatorSetString(INDICATOR_SHORTNAME, "Recurrence Analysis"); minRequiredBars = InpWindowSize + (InpEmbeddingDimension - class="num">1) * InpTimeDelay; class="kw">return(INIT_SUCCEEDED); } class="type">int OnCalculate(class="kw">const class="type">int rates_total, class="kw">const class="type">int prev_calculated, class="kw">const class="type">class="kw">datetime &time[], class="kw">const class="type">class="kw">double &open[], class="kw">const class="type">class="kw">double &high[], class="kw">const class="type">class="kw">double &low[],