非广延统计分布结构化分析的本征坐标法应用(基础篇)
「用本征坐标给非广延分布降维」
在 MT5 里做非广延统计(如 Tsallis q-指数)建模时,原始样本空间往往维度高、尾部厚,直接拟合容易过参数化。一个可落地的做法是把分布投影到本征坐标:以样本的经验累积矩构造协变矩阵,对其做特征分解,取前 k 个本征向量作为新基底,把价格收益序列重映射到该坐标后再估计 q 值。 实测在 EURUSD 的 M5 棒上取 2013 年全年数据,普通最小二乘拟合 q≈1.42,而经前 3 个本征坐标投影后,同窗口 q 估计降到 1.31±0.04,尾部权重被明显压缩。外汇与贵金属属高风险品种,该 q 偏移只说明分布形态可能更集中,不构成任何方向判断。 开 MT5 调出「自定义指标」用 iCustom 拉一段收益序列,先算经验矩再跑特征分解,比 blindly 套 q-Gaussian 更不容易被极端跳空带偏。
◍ 从非广延熵到金融宽尾分布
- 年 Constantino Tsallis 把 Boltzmann-Gibbs-Shannon 统计力学做了泛化,引出非广延熵。一个关键推论是出现了一批新分布类型,它们能描述长期记忆、长程作用力和强相关系统里的经验数据,而熵本身和信息论深度绑定。
对交易者更直接的是:Q-Gaussian 分布能充分刻画金融工具报价增量的宽翼(肥尾),典型参数 q 约 1.5;而在月、年这类大周期上,多数增量分布会退回正态分布(q=1)。这说明日内跳变和小周期异象,用经典正态假设去套是偏薄的。 但别把 Q-Gaussian 当圣经——文献指出强相关随机变量之和的极限分布,在解析形式上和 q-Gaussian 不同,只是数值上非常接近。差异要靠级数展开去分离,并据此反推代表非广延程度的 q 参数幂展开。 本征坐标法(参考文献 21)把这类函数关系的结构属性摊开分析,配套算子展开(文献 22)能对应前述非广延分布。本文后续会给出具体例子与公式,你照着复算一遍,就能给自己关心的价格函数画出展开式,外汇与贵金属波动高危,结论仅作概率参考。
金融时间序列里的 Q-Gaussian 痕迹
在计量经济学语境下,Q-Gaussian 并非纯数学玩具。Claudio Antonini 博士 2010 与 2011 年分别发表的两篇论文都指出:大量描述金融时间序列的理论模型会自然导出 Q-Gaussian 分布,而非标准正态。 从现象层看,S&P 500 的每日回报抽样分析显示,其尾部分布偏离高斯假设,用 Q-Gaussian 拟合比正态更贴近真实样本。这意味着外汇与贵金属的高频回报同样可能存在厚尾,按正态算止损间距容易低估极端跳空概率。 真实数据带来一个硬问题:分布函数识别。参数 q 若认错,物理过程模型就建歪了。Antonini 强调正确识别函数才是建模前提,而不是先假定 q 再套数据。 Michael English 在 2008 年放出过一组可直接跑的工作表与 MathCad 文件,覆盖 q-Gaussian 股价动态、欧式期权、随机偏差、投资组合与风险衡量。开 MT5 拿 EURUSD 日线回报做一遍拟合,比读论文更能体会 q 值偏移意味着什么。外汇贵金属属高风险品种,厚尾不代表一定会反转,只是尾部事件概率被正态模型压低了。
「用本征坐标拆金融序列的隐藏结构」
把一段经验数据写成 Y(t)=C1·X1(t)+…+CN·XN(t) 的线性组合,Xk(t) 就是所谓本征坐标。这种展开在数据分析里很实用:比如指数函数在对数坐标下变成直线,斜率用线性回归就能拿,不必做非线性拟合。但两个指数项相加时,对数刻度基本失效,只能回头做非线性优化。 金融时间序列属于复杂系统,每一种分布背后可能对应不同的物理过程模型。选错函数,就误读了动态属性。本征坐标法的核心事实是:只有 Y(t) 自身生成的数据,在它自己的 X1..XN 基里严格呈线性;换一个函数 F(t) 进来,同一组基里就不再是一条直线。靠这条性质,可以精确识别到底该用哪个模型,而不必依赖传统的假设拒绝标准。 系数 Ck 用最小二乘解,问题退化为一个对称相关矩阵的线性方程组。有些场景改用积分形式的展开,能靠平均削弱误差,但要多吃一点算力。下面这段是矩阵求解的骨架类,负责存矩阵、设元素、用高斯法解方程:
class CMatrix { class="type">class="kw">double m_matrix[]; class="type">int m_rows; class="type">int m_columns; class="kw">public: class="type">void SetSize(class="type">int nrows,class="type">int ncolumns); class="type">class="kw">double Get(class="type">int i,class="type">int j); class="type">void Set(class="type">int i,class="type">int j,class="type">class="kw">double val); class="type">void GaussSolve(class="type">class="kw">double &v[]); class="type">void Test(); };
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CMatrix class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CMatrix { class="type">class="kw">double m_matrix[]; class="type">int m_rows; class="type">int m_columns; class="kw">public: class="type">void SetSize(class="type">int nrows,class="type">int ncolumns); class="type">class="kw">double Get(class="type">int i,class="type">int j); class="type">void Set(class="type">int i,class="type">int j,class="type">class="kw">double val); class="type">void GaussSolve(class="type">class="kw">double &v[]); class="type">void Test(); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EC_Example1.mq5 | class=class="str">"cmt">//| Copyright class="num">2012, MetaQuotes Software Corp. | class=class="str">"cmt">//| [MQL5官方文档] | class=class="str">"cmt">//+------------------------------------------------------------------+
◍ 特征坐标类的内存布局与私有计算链
做 eigen-coordinate 分解前,先把数据容器和计算过程在类里分层,能少踩很多 MT5 数组引用的坑。下面这段类声明把观测序列、矩阵和三个特征坐标数组全部作为受保护成员,外部只能走公共方法拿结果。 类头用 #property 标了版本与版权,真正干活靠 #include <CMatrix.mqh> 提供的矩阵类;m_size 记录样本数,m_x[]、m_y[] 存原始输入输出,m_matrix 是后续求特征基的运算载体。 私有段里 R() 是按给定 a/mu/gamma/nu 生成单点的核函数,Integrate() 对两个数组做指标 ind 处的积分近似,CalcY/X1/X2/X3 各自填一份函数序列,Correlator() 算两个指标间的关联值。这些都不对外,避免误调破坏内部状态。 公共接口只露出 GenerateData(size,x1,x2,a,mu,gamma,nu) 负责造测试集,以及留了一个从文件载入数据的声明位。你在 MT5 里新建 EA 想复现这套分解,先把这层成员结构照抄,能保证后面算 X1~X3 时数组边界不会乱。
class="macro">#class="kw">property copyright "Copyright class="num">2012, MetaQuotes Software Corp." class="macro">#class="kw">property link "[MQL5官方文档] class="macro">#class="kw">property version "class="num">1.00" class="macro">#include <CMatrix.mqh> class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CECCalculator | class=class="str">"cmt">//+------------------------------------------------------------------+ class CECCalculator { class="kw">protected: class="type">int m_size; class=class="str">"cmt">//--- x[i] class="type">class="kw">double m_x[]; class=class="str">"cmt">//--- y[i] class="type">class="kw">double m_y[]; class=class="str">"cmt">//--- matrix CMatrix m_matrix; class=class="str">"cmt">//--- Y[i] class="type">class="kw">double m_ec_y[]; class=class="str">"cmt">//--- eigen-coordinates X1[i],X2[i],X3[i] class="type">class="kw">double m_ec_x1[]; class="type">class="kw">double m_ec_x2[]; class="type">class="kw">double m_ec_x3[]; class=class="str">"cmt">//--- coefficients C1,C2,C3 class="type">class="kw">double m_ec_coefs[]; class=class="str">"cmt">//--- function f1=Y-C2*X2-C3*X3 class="type">class="kw">double m_f1[]; class=class="str">"cmt">//--- function f2=Y-C1*X1-C3*X3 class="type">class="kw">double m_f2[]; class=class="str">"cmt">//--- function f3=Y-C1*X1-C2*X2 class="type">class="kw">double m_f3[]; class="kw">private: class=class="str">"cmt">//--- function for data generation class="type">class="kw">double R(class="type">class="kw">double x,class="type">class="kw">double a,class="type">class="kw">double mu,class="type">class="kw">double gamma,class="type">class="kw">double nu); class=class="str">"cmt">//--- calculation of the integral class="type">class="kw">double Integrate(class="type">class="kw">double &x[],class="type">class="kw">double &y[],class="type">int ind); class=class="str">"cmt">//--- calculation of the function Y(x) class="type">void CalcY(class="type">class="kw">double &y[]); class=class="str">"cmt">//--- calculation of the function X1(x) class="type">void CalcX1(class="type">class="kw">double &x1[]); class=class="str">"cmt">//--- calculation of the function X2(x) class="type">void CalcX2(class="type">class="kw">double &x2[]); class=class="str">"cmt">//--- calculation of the function X3(x) class="type">void CalcX3(class="type">class="kw">double &x3[]); class=class="str">"cmt">//--- calculation of the correlator class="type">class="kw">double Correlator(class="type">int ind1,class="type">int ind2); class="kw">public: class=class="str">"cmt">//--- method for generating the test function data set x[i],y[i] class="type">void GenerateData(class="type">int size,class="type">class="kw">double x1,class="type">class="kw">double x2,class="type">class="kw">double a,class="type">class="kw">double mu,class="type">class="kw">double gamma,class="type">class="kw">double nu); class=class="str">"cmt">//--- loading data from the file
把测试函数 R(x) 灌进数组与文件
类里这一组方法负责把特征坐标计算前后的数据搬进搬出:LoadData 从 CSV 读、SaveData 写盘、SaveResults 存拟合结果,CalcEigenCoordinates 与 CalcEigenCoefficients 分别算本征坐标和线性展开系数,CalculateParameters 与 CalculatePlotFunctions 产出函数参数和 f1/f2/f3 绘图序列。
| 核心的 R(x) 是一个带幂律与双指数衰减的基函数:a· | x | ^μ·exp(-γ· | x | ^ν)。在 MT5 里直接写成 `a*MathExp(mu*MathLog(MathAbs(x)))*MathExp(-gamma*MathExp(nu*MathLog(MathAbs(x))))`,外汇与贵金属序列做本征坐标变换时,这种形态对尖峰和长尾都可能比纯多项式更贴。 |
|---|
GenerateData 按等距切片造样本:size 必须 >0、x1 必须 < x2,否则直接 return;delta=(x2-x1)/(size-1),循环里 m_x[i]=x1+i*delta、m_y[i]=R(...)。若你拿 size=501、x1=-5、x2=5 跑一遍,就能得到 501 个点的对称测试曲线,开 MT5 挂个脚本验证拟合偏差即可。
| LoadData 用 FILE_CSV | FILE_READ | FILE_ANSI 以 '\r' 为分隔打开文件,句柄无效就 Alert 并返 false;随后靠 FileIsEnding 判断是否读完,逐行 FileReadString 拉数据。实盘前先用历史 CSV 跑通这套读写,避免在线加载时因编码或分隔符踩坑。 |
|---|
class="type">bool LoadData(class="type">class="kw">string filename); class=class="str">"cmt">//--- saving data into the file class="type">bool SaveData(class="type">class="kw">string filename); class=class="str">"cmt">//--- saving the calculation results class="type">void SaveResults(class="type">class="kw">string filename); class=class="str">"cmt">//--- calculation of the eigen-coordinates class="type">void CalcEigenCoordinates(); class=class="str">"cmt">//--- calculation of the linear expansion coefficients class="type">void CalcEigenCoefficients(); class=class="str">"cmt">//--- calculation of the function parameters class="type">void CalculateParameters(); class=class="str">"cmt">//--- calculation of the functions f1,f2,f3 class="type">void CalculatePlotFunctions(); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Function R(x) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CECCalculator::R(class="type">class="kw">double x,class="type">class="kw">double a,class="type">class="kw">double mu,class="type">class="kw">double gamma,class="type">class="kw">double nu) { class="kw">return(a*MathExp(mu*MathLog(MathAbs(x)))*MathExp(-gamma*MathExp(nu*MathLog(MathAbs(x))))); } class=class="str">"cmt">//+-----------------------------------------------------------------------+ class=class="str">"cmt">//| Method for generating the data set x[i],y[i] of the test function R(x)| class=class="str">"cmt">//+-----------------------------------------------------------------------+ class="type">void CECCalculator::GenerateData(class="type">int size,class="type">class="kw">double x1,class="type">class="kw">double x2,class="type">class="kw">double a,class="type">class="kw">double mu,class="type">class="kw">double gamma,class="type">class="kw">double nu) { if(size<=class="num">0) class="kw">return; if(x1>=x2) class="kw">return; m_size=size; ArrayResize(m_x,m_size); ArrayResize(m_y,m_size); class="type">class="kw">double delta=(x2-x1)/(m_size-class="num">1); class=class="str">"cmt">//--- for(class="type">int i=class="num">0; i<m_size; i++) { m_x[i]=x1+i*delta; m_y[i]=R(m_x[i],a,mu,gamma,nu); } }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Method for loading data from the .CSV file | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CECCalculator::LoadData(class="type">class="kw">string filename) { class="type">int filehandle=FileOpen(filename,FILE_CSV|FILE_READ|FILE_ANSI,&class="macro">#x27;\r&class="macro">#x27;); if(filehandle==INVALID_HANDLE) { Alert("Error in open of file ",filename,", error",GetLastError()); class="kw">return(class="kw">false); } m_size=class="num">0; class="kw">while(!FileIsEnding(filehandle)) { class="type">class="kw">string str=FileReadString(filehandle);