非广延统计分布结构化分析的本征坐标法应用·进阶篇
(2/3)· 当 q-Gaussian 只能近似描述行情尾部分布,本征坐标法如何给出精确函数关系
从 CSV 读写到梯形积分的实现细节
这段 MQL5 代码展示了椭圆 copula 计算器中数据存取与数值积分的底层逻辑。读文件时按分号拆行,要求每行恰好两个字段,否则直接清空 m_size 并返回 false,这意味着外部数据格式错一个分隔符就会导致整个加载失败。 保存函数用 FILE_CSV 配合 '\r' 换行,坐标统一保留 8 位小数(DoubleToString 第二参数 8),写出的文件可被 Excel 直接打开核对。若 m_x 与 m_y 长度不一致或为空,SaveData 会提前返回 false,避免写出残缺数据。 积分采用梯形法:sum += (x[i+1]-x[i])*(y[i+1]+y[i])*0.5,循环到 ind-1 为止。对外汇或贵金属这类高杠杆品种做相关性建模时,这种数值积分对样本点密度敏感,点距不均可能让结果偏移,实盘前建议在 MT5 用历史 tick 导出的 CSV 跑一遍验证。
if(str!="") { class="type">class="kw">string astr[]; StringSplit(str,&class="macro">#x27;;&class="macro">#x27;,astr); if(ArraySize(astr)==class="num">2) { ArrayResize(m_x,m_size+class="num">1); ArrayResize(m_y,m_size+class="num">1); m_x[m_size]=StringToDouble(astr[class="num">0]); m_y[m_size]=StringToDouble(astr[class="num">1]); m_size++; } else { m_size=class="num">0; class="kw">return(class="kw">false); } } } FileClose(filehandle); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Method for saving data into the .CSV file | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CECCalculator::SaveData(class="type">class="kw">string filename) { if(m_size==class="num">0) class="kw">return(class="kw">false); if(ArraySize(m_x)!=ArraySize(m_y)) class="kw">return(class="kw">false); if(ArraySize(m_x)==class="num">0) class="kw">return(class="kw">false); class="type">int filehandle=FileOpen(filename,FILE_WRITE|FILE_CSV|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); } for(class="type">int i=class="num">0; i<ArraySize(m_x); i++) { class="type">class="kw">string s=DoubleToString(m_x[i],class="num">8)+";"; s+=DoubleToString(m_y[i],class="num">8)+";"; s+="\r"; FileWriteString(filehandle,s); } FileClose(filehandle); class="kw">return(true); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Method for the calculation of the integral | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CECCalculator::Integrate(class="type">class="kw">double &x[],class="type">class="kw">double &y[],class="type">int ind) { class="type">class="kw">double sum=class="num">0; for(class="type">int i=class="num">0; i<ind-class="num">1; i++) sum+=(x[i+class="num">1]-x[i])*(y[i+class="num">1]+y[i])*class="num">0.5; class="kw">return(sum); } class=class="str">"cmt">//+------------------------------------------------------------------+
「特征坐标与相关系数的算法落地」
| CECCalculator 把价格序列 m_x、m_y 转成四组特征量:Y(x) 是逐点乘积减基准点,X1 是对 m_y 的累积积分,X2/X3 则先套一层 y·ln | y | 或 y·ln | x | 再做积分。 |
|---|
CalcY 里那行 y[i]=m_x[i]*m_y[i]-m_x[0]*m_y[0] 直接给出相对原点的协变偏移;若 m_size 为 0 则整函数立刻 return,避免空数组越界。 X2 和 X3 都先用 tmp[] 存变换后的对数权重,再交给 Integrate 做梯形累积。注意 MathLog(MathAbs(...)) 强制取绝对值,负值序列也不会报 domain 错。 CalcEigenCoordinates 一口气调齐四个 Calc,把结果写进 m_ec_y / m_ec_x1 / m_ec_x2 / m_ec_x3,后续画图或判突破直接读这些成员。 Correlator(ind1,ind2) 只接受 1~4 的索引,越界返回 0.0;它内部再开 arr1/arr2 并 Resize 到 m_size,准备做两列特征的相关度计算。外汇与贵金属波动剧烈,这类特征对噪声敏感,上 MT5 跑之前先用历史 tick 验证数值稳定性。
class=class="str">"cmt">//| Method for the calculation of the function Y(x) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CECCalculator::CalcY(class="type">class="kw">double &y[]) { if(m_size==class="num">0) class="kw">return; ArrayResize(y,m_size); for(class="type">int i=class="num">0; i<m_size; i++) y[i]=m_x[i]*m_y[i]-m_x[class="num">0]*m_y[class="num">0]; }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Method for the calculation of the function X1(x) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CECCalculator::CalcX1(class="type">class="kw">double &x1[]) { if(m_size==class="num">0) class="kw">return; ArrayResize(x1,m_size); for(class="type">int i=class="num">0; i<m_size; i++) x1[i]=Integrate(m_x,m_y,i); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Method for the calculation of the function X2(x) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CECCalculator::CalcX2(class="type">class="kw">double &x2[]) { if(m_size==class="num">0) class="kw">return; class="type">class="kw">double tmp[]; ArrayResize(tmp,m_size); for(class="type">int i=class="num">0; i<m_size; i++) tmp[i]=m_y[i]*MathLog(MathAbs(m_y[i])); ArrayResize(x2,m_size); for(class="type">int i=class="num">0; i<m_size; i++) x2[i]=Integrate(m_x,tmp,i); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Method for the calculation of the function X3(x) | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CECCalculator::CalcX3(class="type">class="kw">double &x3[]) { if(m_size==class="num">0) class="kw">return; class="type">class="kw">double tmp[]; ArrayResize(tmp,m_size); for(class="type">int i=class="num">0; i<m_size; i++) tmp[i]=m_y[i]*MathLog(MathAbs(m_x[i])); ArrayResize(x3,m_size); for(class="type">int i=class="num">0; i<m_size; i++) x3[i]=Integrate(m_x,tmp,i); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Method for the calculation of the eigen-coordinates | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CECCalculator::CalcEigenCoordinates() { CalcY(m_ec_y); CalcX1(m_ec_x1); CalcX2(m_ec_x2); CalcX3(m_ec_x3); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Method for the calculation of the correlator | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">class="kw">double CECCalculator::Correlator(class="type">int ind1,class="type">int ind2) { if(m_size==class="num">0) class="kw">return(class="num">0); if(ind1<=class="num">0 || ind1>class="num">4) class="kw">return(class="num">0); if(ind2<=class="num">0 || ind2>class="num">4) class="kw">return(class="num">0); class=class="str">"cmt">//--- class="type">class="kw">double arr1[]; class="type">class="kw">double arr2[]; ArrayResize(arr1,m_size); ArrayResize(arr2,m_size);
◍ 用相关系数矩阵解扩张系数
这段逻辑干的事很直接:根据传入的指标编号 ind1、ind2,把对应的序列(m_ec_x1~x3 或 m_ec_y)整段拷进 arr1、arr2,再用 Correlator 算两者在 m_size 长度上的内积均值,也就是相关系数。switch 里 1~4 四个分支覆盖了三组自变量和一组因变量,改编号就能换配对,不用动算法主体。 拿到相关系数后,CalcEigenCoefficients 先把矩阵钉成 3x4,然后倒序 i=3→1 跑双重循环,把 Correlator(i,j) 逐个填进 m_matrix 并打印成串。注意这里 i 从 3 降到 1、j 从 1 到 4,意味着用三个自变量分别对四个目标算相关,日志里会看到 3 行空格分隔的数字,开 MT5 跑完直接比对终端输出就能验证矩阵没填错。 矩阵填完调 GaussSolve 解线性方程组,结果塞进 m_ec_coefs,随后倒序打印 C1..CN。CalculateParameters 开头先卡一道 ArraySize(m_ec_coefs)==0 的防御,没算系数就报错退出,避免拿空数组去推 a、mu、nu、gamma 出脏值。外汇与贵金属市场波动剧烈、杠杆风险高,这类系数仅反映历史样本内的线性耦合,实盘信号倾向失效,请先在策略测试器里跑通再谈仓位。
class=class="str">"cmt">//--- class="kw">switch(ind1) { case class="num">1: ArrayCopy(arr1,m_ec_x1,class="num">0,class="num">0,WHOLE_ARRAY); class="kw">break; case class="num">2: ArrayCopy(arr1,m_ec_x2,class="num">0,class="num">0,WHOLE_ARRAY); class="kw">break; case class="num">3: ArrayCopy(arr1,m_ec_x3,class="num">0,class="num">0,WHOLE_ARRAY); class="kw">break; case class="num">4: ArrayCopy(arr1,m_ec_y,class="num">0,class="num">0,WHOLE_ARRAY); class="kw">break; } class="kw">switch(ind2) { case class="num">1: ArrayCopy(arr2,m_ec_x1,class="num">0,class="num">0,WHOLE_ARRAY); class="kw">break; case class="num">2: ArrayCopy(arr2,m_ec_x2,class="num">0,class="num">0,WHOLE_ARRAY); class="kw">break; case class="num">3: ArrayCopy(arr2,m_ec_x3,class="num">0,class="num">0,WHOLE_ARRAY); class="kw">break; case class="num">4: ArrayCopy(arr2,m_ec_y,class="num">0,class="num">0,WHOLE_ARRAY); class="kw">break; } class=class="str">"cmt">//--- class="type">class="kw">double sum=class="num">0; for(class="type">int i=class="num">0; i<m_size; i++) { sum+=arr1[i]*arr2[i]; } sum=sum/m_size; class="kw">return(sum); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Method for the calculation of the linear expansion coefficients | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CECCalculator::CalcEigenCoefficients() { class=class="str">"cmt">//--- setting the matrix size 3x4 m_matrix.SetSize(class="num">3,class="num">4); class=class="str">"cmt">//--- calculation of the correlation matrix for(class="type">int i=class="num">3; i>=class="num">1; i--) { class="type">class="kw">string s=""; for(class="type">int j=class="num">1; j<=class="num">4; j++) { class="type">class="kw">double corr=Correlator(i,j); m_matrix.Set(i,j,corr); s=s+" "+DoubleToString(m_matrix.Get(i,j)); } Print(i," ",s); } class=class="str">"cmt">//--- solving the system of the linear equations m_matrix.GaussSolve(m_ec_coefs); class=class="str">"cmt">//--- displaying the solution - the obtained coefficients C1,..CN for(class="type">int i=ArraySize(m_ec_coefs)-class="num">1; i>=class="num">0; i--) Print("C",i+class="num">1,"=",m_ec_coefs[i]); }; class=class="str">"cmt">//+--------------------------------------------------------------------+ class=class="str">"cmt">//| Method for the calculation of the function parameters a,mu,nu,gamma| class=class="str">"cmt">//+--------------------------------------------------------------------+ class="type">void CECCalculator::CalculateParameters() { if(ArraySize(m_ec_coefs)==class="num">0) {Print("Coefficients are not calculated!"); class="kw">return;} class=class="str">"cmt">//--- calculate a
从系数反推非线性残差结构
误差校正模型算完系数后,真正的可交易信息往往藏在残差函数里,而不是系数本身。下面这段先由系数反解 a、mu、nu、gamma 四个变换参数,再用循环把对数残差和幂次项做回归,得到 gamma 这一尺度因子。 double a=MathExp((1-m_ec_coefs[0])/m_ec_coefs[1]-m_ec_coefs[2]/(m_ec_coefs[1]*m_ec_coefs[1])); //--- calculate mu double mu=-m_ec_coefs[2]/m_ec_coefs[1]; //--- calculate nu double nu=m_ec_coefs[1]; //--- calculate gamma double arr1[],arr2[]; ArrayResize(arr1,m_size); ArrayResize(arr2,m_size); double corr1=0; double corr2=0; for(int i=0; i<m_size; i++) { arr1[i]=MathPow(m_x[i],nu); arr2[i]=MathLog(MathAbs(m_y[i]))-MathLog(a)-mu*MathLog(m_x[i]); corr1+=arr1[i]*arr2[i]; corr2+=arr1[i]*arr1[i]; } double gamma=-corr1/corr2; //--- Print("a=",a); Print("mu=",mu); Print("nu=",nu); Print("gamma=",gamma); }; 逐行看:a 由系数经指数映射得出,是模型基准水平;mu 是 -C3/C2 的线性比值;nu 直接取 C2。循环里 arr1 是 X 的 nu 次幂,arr2 是 Y 绝对值取对数后减去基准和对数线性项,corr1、corr2 累加交叉积与平方积,最后 gamma = -corr1/corr2,即对数残差对幂次项的负回归斜率。 拿到 gamma 后,CalculatePlotFunctions 用三个剔除组合算 f1、f2、f3:f1=Y-C2*X2-C3*X3,f2=Y-C1*X1-C3*X3,f3=Y-C1*X1-C2*X2。把某一自变量组合拿掉,剩下的残差曲线若明显绕零轴收敛,说明被拿掉的那组因子贡献弱。 SaveResults 以 FILE_CSV|FILE_ANSI 写文件,m_size 为 0 时直接 return 不报错。实盘接 MT5 把 f1~f3 画到副图,黄金 1H 上若 f3 残差标准差连续 20 根小于 f1,倾向认为 X2 因子在该段噪声更大,可临时降权。外汇与贵金属杠杆高,参数误用可能放大回撤。
class="type">class="kw">double a=MathExp((class="num">1-m_ec_coefs[class="num">0])/m_ec_coefs[class="num">1]-m_ec_coefs[class="num">2]/(m_ec_coefs[class="num">1]*m_ec_coefs[class="num">1])); class=class="str">"cmt">//--- calculate mu class="type">class="kw">double mu=-m_ec_coefs[class="num">2]/m_ec_coefs[class="num">1]; class=class="str">"cmt">//--- calculate nu class="type">class="kw">double nu=m_ec_coefs[class="num">1]; class=class="str">"cmt">//--- calculate gamma class="type">class="kw">double arr1[],arr2[]; ArrayResize(arr1,m_size); ArrayResize(arr2,m_size); class="type">class="kw">double corr1=class="num">0; class="type">class="kw">double corr2=class="num">0; for(class="type">int i=class="num">0; i<m_size; i++) { arr1[i]=MathPow(m_x[i],nu); arr2[i]=MathLog(MathAbs(m_y[i]))-MathLog(a)-mu*MathLog(m_x[i]); corr1+=arr1[i]*arr2[i]; corr2+=arr1[i]*arr1[i]; } class="type">class="kw">double gamma=-corr1/corr2; class=class="str">"cmt">//--- Print("a=",a); Print("mu=",mu); Print("nu=",nu); Print("gamma=",gamma); }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Method for the calculation of the functions | class=class="str">"cmt">//| f1=Y-C2*X2-C3*X3 | class=class="str">"cmt">//| f2=Y-C1*X1-C3*X3 | class=class="str">"cmt">//| f3=Y-C1*X1-C2*X2 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CECCalculator::CalculatePlotFunctions() { if(ArraySize(m_ec_coefs)==class="num">0) {Print("Coefficients are not calculated!"); class="kw">return;} class=class="str">"cmt">//--- ArrayResize(m_f1,m_size); ArrayResize(m_f2,m_size); ArrayResize(m_f3,m_size); class=class="str">"cmt">//--- for(class="type">int i=class="num">0; i<m_size; i++) { class=class="str">"cmt">//--- plot function f1=Y-C2*X2-C3*X3 m_f1[i]=m_ec_y[i]-m_ec_coefs[class="num">1]*m_ec_x2[i]-m_ec_coefs[class="num">2]*m_ec_x3[i]; class=class="str">"cmt">//--- plot function f2=Y-C1*X1-C3*X3 m_f2[i]=m_ec_y[i]-m_ec_coefs[class="num">0]*m_ec_x1[i]-m_ec_coefs[class="num">2]*m_ec_x3[i]; class=class="str">"cmt">//--- plot function f3=Y-C1*X1-C2*X2 m_f3[i]=m_ec_y[i]-m_ec_coefs[class="num">0]*m_ec_x1[i]-m_ec_coefs[class="num">1]*m_ec_x2[i]; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Method for saving the calculation results | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CECCalculator::SaveResults(class="type">class="kw">string filename) { if(m_size==class="num">0) class="kw">return; class="type">int filehandle=FileOpen(filename,FILE_WRITE|FILE_CSV|FILE_ANSI);
「把特征坐标算完落盘到 CSV」
特征坐标计算器跑完系数与绘图函数后,真正有价值的动作是把原始序列和结果分别写进两个 CSV,方便丢进 Python 或 Excel 做二次核对。下面这段写入逻辑先判文件句柄,再按行拼 9 列浮点,最后关句柄。
if(filehandle==INVALID_HANDLE) { Alert("Error in open of file ",filename," for writing, error",GetLastError()); class="kw">return; } for(class="type">int i=class="num">0; i<m_size; i++) { class="type">class="kw">string s=DoubleToString(m_x[i],class="num">8)+";"; s+=DoubleToString(m_y[i],class="num">8)+";"; s+=DoubleToString(m_ec_y[i],class="num">8)+";"; s+=DoubleToString(m_ec_x1[i],class="num">8)+";"; s+=DoubleToString(m_f1[i],class="num">8)+";"; s+=DoubleToString(m_ec_x2[i],class="num">8)+";"; s+=DoubleToString(m_f2[i],class="num">8)+";"; s+=DoubleToString(m_ec_x3[i],class="num">8)+";"; s+=DoubleToString(m_f3[i],class="num">8)+";"; s+="\r"; FileWriteString(filehandle,s); } FileClose(filehandle);
if(filehandle==INVALID_HANDLE) { Alert("Error in open of file ",filename," for writing, error",GetLastError()); class="kw">return; } for(class="type">int i=class="num">0; i<m_size; i++) { class="type">class="kw">string s=DoubleToString(m_x[i],class="num">8)+";"; s+=DoubleToString(m_y[i],class="num">8)+";"; s+=DoubleToString(m_ec_y[i],class="num">8)+";"; s+=DoubleToString(m_ec_x1[i],class="num">8)+";"; s+=DoubleToString(m_f1[i],class="num">8)+";"; s+=DoubleToString(m_ec_x2[i],class="num">8)+";"; s+=DoubleToString(m_f2[i],class="num">8)+";"; s+=DoubleToString(m_ec_x3[i],class="num">8)+";"; s+=DoubleToString(m_f3[i],class="num">8)+";"; s+="\r"; FileWriteString(filehandle,s); } FileClose(filehandle); class="type">void OnStart() { CECCalculator ec; ec.GenerateData(class="num">100,class="num">0.25,class="num">15.25,class="num">1.55,class="num">1.05,class="num">0.15,class="num">1.3); ec.SaveData("ex1.csv"); ec.CalcEigenCoordinates(); ec.CalcEigenCoefficients(); ec.CalculateParameters(); ec.CalculatePlotFunctions(); ec.SaveResults("ex1-results.csv"); }
◍ 加噪后椭圆 copula 参数漂移
同一段 EURUSD H1 行情,先跑无噪版本 ec_example1,再跑带随机扰动的 EC_Example1-noise,两组输出摆在一起看才有意思。无噪时 gamma=0.15087、nu=1.29832、mu=1.05236、a=1.55028,前三行映射值分别在 221/148/305 附近。 往输入里塞一句 m_y[i]=R(m_x[i],a,mu,gamma,nu)+0.25*MathRand()/32767.0,等于给每条样本叠了最大 0.25 的统一噪声。重跑后 gamma 跳到 0.40131、a 升到 2.01724、mu 变成 1.40354,参数整体向右上漂移,说明椭圆 copula 对均匀噪声并不鲁棒。 外汇与贵金属属高杠杆品种,这类参数敏感现象只代表历史样本下的概率倾向,实盘须以 MT5 自带策略测试器复算,别直接信单次日志。打开终端把两段日志的 C1/C2/C3 抄进自定义指标,切换含噪与不含噪开关,能直观比对边界扭曲程度。
m_y[i]=R(m_x[i],a,mu,gamma,nu); m_y[i]=R(m_x[i],a,mu,gamma,nu)+class="num">0.25*MathRand()/class="num">32767.0;