利用判别分析开发交易系统·进阶篇
用 EA 把 12 个指标抽成 CSV 训练集
做价格方向预测的第一步不是调模型,而是把指标和后续柱的涨跌绑成一行数据。思路很直接:取倒数第二根已完成柱的指标值,用最后一根已完成柱的价格行为当标签,这样每一行都是一个「已知指标 → 未知方向」的样本。 具体落地时写个 EA 跑数据采集。OnInit 里把 12 个指标句柄全建好,并在 Files 目录生成 MasterData.csv 的列头;OnTick 里识别新柱,把指标绝对值、与前值的差(冠 d 前缀)、信号线差以及小时数写进文件。外汇和贵金属波动剧烈,这种原始采集最好先在策略 tester 里跑,文件会落在 tester/Agent-127.0.0.1-3000/MQL5/Files 下,避免污染实盘环境。 实测采集 EURUSD H1 从 2011-08-01 到 2011-10-01 的数据,用 37 个衍生变量(12 指标绝对值+差值+时间等)就能搭出判别分析用的底表。分组变量按最后一根柱相较标签柱的变动切四档:跌超 200 点=1,跌 200 内=2,涨 200 内=3,涨超 200 点=4,Statistica 里用 =iif(v3<=-200;1;0)+iif(v3<0 and v3>-200;2;0)+iif(v3>0 and v3<200;3;0)+iif(v3>=200;4;0) 一键转好。 指标句柄建错会直接让 EA 退出,所以初始化必须判 INVALID_HANDLE。下面这段是 OnInit 的前半部分,建完句柄顺手把缓冲区设成时间倒序,方便后面按 [0] 取最新完成柱。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA交易初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 初始化指标 h_AC=iAC(Symbol(),Period()); h_BearsPower=iBearsPower(Symbol(),Period(),BearsPower_PeriodBears); h_BullsPower=iBullsPower(Symbol(),Period(),BullsPower_PeriodBulls); h_AO=iAO(Symbol(),Period()); h_CCI=iCCI(Symbol(),Period(),CCI_PeriodCCI,CCI_Applied); h_DeMarker=iDeMarker(Symbol(),Period(),DeM_PeriodDeM); h_FrAMA=iFrAMA(Symbol(),Period(),FraMA_PeriodMA,FraMA_Shift,FraMA_Applied); h_MACD=iMACD(Symbol(),Period(),MACD_PeriodFast,MACD_PeriodSlow,MACD_PeriodSignal,MACD_Applied); h_RSI=iRSI(Symbol(),Period(),RSI_PeriodRSI,RSI_Applied); h_RVI=iRVI(Symbol(),Period(),RVI_PeriodRVI); h_Stoch=iStochastic(Symbol(),Period(),Stoch_PeriodK,Stoch_PeriodD,Stoch_PeriodSlow,MODE_SMA,Stoch_Applied); h_WPR=iWPR(Symbol(),Period(),WPR_PeriodWPR); if(h_AC==INVALID_HANDLE || h_BearsPower==INVALID_HANDLE || h_BullsPower==INVALID_HANDLE || h_AO==INVALID_HANDLE || h_CCI==INVALID_HANDLE || h_DeMarker==INVALID_HANDLE || h_FrAMA==INVALID_HANDLE || h_MACD==INVALID_HANDLE || h_RSI==INVALID_HANDLE || h_RVI==INVALID_HANDLE || h_Stoch==INVALID_HANDLE || h_WPR==INVALID_HANDLE) { Print("创建指标错误"); class="kw">return(class="num">1); } ArraySetAsSeries(buf_AC,true); ArraySetAsSeries(buf_BearsPower,true); ArraySetAsSeries(buf_BullsPower,true); ArraySetAsSeries(buf_AO,true); ArraySetAsSeries(buf_CCI,true); ArraySetAsSeries(buf_DeMarker,true);
◍ 把多指标缓冲倒进 CSV 的初始化写法
在 EA 的 OnInit 阶段,先把 9 个指标缓冲区全部设成时间序列方向,这样后续用 buf[0] 取到的就是当前柱值,而不是从最早历史开始排。 代码里连续调了 ArraySetAsSeries 处理 FrAMA、MACD 快慢线、RSI、RVI 快慢线、Stoch 快慢线和 WPR,共 9 个数组;若漏掉任意一行,回测时索引错位会导致写入 CSV 的数据整体偏移一根柱。
| 随后用 FileOpen 以 FILE_ANSI | FILE_WRITE | FILE_CSV | FILE_SHARE_READ 打开 MasterData2.csv,分隔符指定为英文分号。文件句柄有效时,首行写死 41 个列名(Time、Hour、Price 加 19 个指标及其一阶差分 dXXX),方便外部 Python 或 R 直接读表。 |
|---|
句柄若等于 INVALID_HANDLE,则打印错误码并调 ExpertRemove 自卸载,避免空文件反复刷错误日志。外汇与贵金属品种波动剧烈,这种批量落盘方式仅用于本地特征采集,实盘直接跟单属高风险行为。 OnTick 里用 static datetime Prev_time 记住上一根柱时间,配合 ArraySetAsSeries(mrate,true) 把最近 K 线复制到 mrate 数组;SymbolInfoTick 取不到最新报价就 Alert 并 return,防止脏数据写进文件。
ArraySetAsSeries(buf_FrAMA,true); ArraySetAsSeries(buf_MACD_m,true); ArraySetAsSeries(buf_MACD_s,true); ArraySetAsSeries(buf_RSI,true); ArraySetAsSeries(buf_RVI_m,true); ArraySetAsSeries(buf_RVI_s,true); ArraySetAsSeries(buf_Stoch_m,true); ArraySetAsSeries(buf_Stoch_s,true); ArraySetAsSeries(buf_WPR,true); FileHandle=FileOpen("MasterData2.csv",FILE_ANSI|FILE_WRITE|FILE_CSV|FILE_SHARE_READ,&class="macro">#x27;;&class="macro">#x27;); if(FileHandle!=INVALID_HANDLE) { Print("FileOpen OK"); class=class="str">"cmt">//--- 为方便起见保存文件第一行变量的名称 FileWrite(FileHandle,"Time","Hour","Price","AC","dAC","Bears","dBears","Bulls","dBulls", "AO","dAO","CCI","dCCI","DeMarker","dDeMarker","FrAMA","dFrAMA","MACDm","dMACDm", "MACDs","dMACDs","MACDms","dMACDms","RSI","dRSI","RVIm","dRVIm","RVIs","dRVIs", "RVIms","dRVIms","Stoch_m","dStoch_m","Stoch_s","dStoch_s","Stoch_ms","dStoch_ms", "WPR","dWPR"); } else { Print("FileOpen 操作失败. Error",GetLastError()); ExpertRemove(); } class=class="str">"cmt">//--- class="kw">return(class="num">0); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| EA交易订单函数 | class=class="str">"cmt">//| 监控市场情况并在 | class=class="str">"cmt">//| 每个新柱开端时指标值保存到文件中 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">//--- 声明 class="type">class="kw">datetime 类型静态变量 class="kw">static class="type">class="kw">datetime Prev_time; class=class="str">"cmt">//--- 它将被用作保存价格, 交易量和每个柱的点差 class="type">MqlRates mrate[]; class="type">MqlTick tickdata; ArraySetAsSeries(mrate,true); class=class="str">"cmt">//--- 取得最近报价 if(!SymbolInfoTick(_Symbol,tickdata)) { Alert("更新报价错误 - 错误: ",GetLastError(),"!!"); class="kw">return; } class=class="str">"cmt">///--- 复制最近4个柱的数据
「新K线触发后的多指标快照落盘」
EA 只在出现新柱时才执行一次数据采集,避免每 tick 重复写文件。逻辑上先拿最近 4 根 K 线的报价结构体,若 CopyRates 返回小于 0 直接弹窗报警并退出,这说明当前品种或周期的历史报价可能没同步到终端。 判断新柱的方式很朴素:用静态变量 Prev_time 存上一根柱的 time,若和 mrate[0].time 相等就直接 return。一旦不相等,便把新时间写回 Prev_time,随后开始把十几个指标缓冲区的近 4 值填进数组。 下面这段是核心填充链,任一缓冲区复制失败 copy_result 都会变成 false,最后统一检查并 Print 报错退出。注意 MACD、RVI、Stoch 用了双缓冲版本(主线+信号线),其余指标都是单线。
if(CopyRates(_Symbol,_Period,class="num">0,class="num">4,mrate)<class="num">0) { Alert("复制历史报价错误 - 错误: ",GetLastError(),"!!"); class="kw">return; } class=class="str">"cmt">//--- 如果两个时间值相等, 说明有新柱 if(Prev_time==mrate[class="num">0].time) class="kw">return; class=class="str">"cmt">//--- 把时间保存到静态变量 Prev_time=mrate[class="num">0].time; class=class="str">"cmt">//--- 使用指标值填充数组 class="type">bool copy_result=true; copy_result=copy_result && FillArrayFromBuffer1(buf_AC,h_AC,class="num">4); copy_result=copy_result && FillArrayFromBuffer1(buf_BearsPower,h_BearsPower,class="num">4); copy_result=copy_result && FillArrayFromBuffer1(buf_BullsPower,h_BullsPower,class="num">4); copy_result=copy_result && FillArrayFromBuffer1(buf_AO,h_AO,class="num">4); copy_result=copy_result && FillArrayFromBuffer1(buf_CCI,h_CCI,class="num">4); copy_result=copy_result && FillArrayFromBuffer1(buf_DeMarker,h_DeMarker,class="num">4); copy_result=copy_result && FillArrayFromBuffer1(buf_FrAMA,h_FrAMA,class="num">4); copy_result=copy_result && FillArraysFromBuffers2(buf_MACD_m,buf_MACD_s,h_MACD,class="num">4); copy_result=copy_result && FillArrayFromBuffer1(buf_RSI,h_RSI,class="num">4); copy_result=copy_result && FillArraysFromBuffers2(buf_RVI_m,buf_RVI_s,h_RVI,class="num">4); copy_result=copy_result && FillArraysFromBuffers2(buf_Stoch_m,buf_Stoch_s,h_Stoch,class="num">4); copy_result=copy_result && FillArrayFromBuffer1(buf_WPR,h_WPR,class="num">4); class=class="str">"cmt">//--- 检查复制数据的正确性 if(!copy_result==true) { Print("复制数据错误"); class="kw">return; }
if(CopyRates(_Symbol,_Period,class="num">0,class="num">4,mrate)<class="num">0) { Alert("复制历史报价错误 - 错误: ",GetLastError(),"!!"); class="kw">return; } class=class="str">"cmt">//--- 如果两个时间值相等, 说明有新柱 if(Prev_time==mrate[class="num">0].time) class="kw">return; class=class="str">"cmt">//--- 把时间保存到静态变量 Prev_time=mrate[class="num">0].time; class=class="str">"cmt">//--- 使用指标值填充数组 class="type">bool copy_result=true; copy_result=copy_result && FillArrayFromBuffer1(buf_AC,h_AC,class="num">4); copy_result=copy_result && FillArrayFromBuffer1(buf_BearsPower,h_BearsPower,class="num">4); copy_result=copy_result && FillArrayFromBuffer1(buf_BullsPower,h_BullsPower,class="num">4); copy_result=copy_result && FillArrayFromBuffer1(buf_AO,h_AO,class="num">4); copy_result=copy_result && FillArrayFromBuffer1(buf_CCI,h_CCI,class="num">4); copy_result=copy_result && FillArrayFromBuffer1(buf_DeMarker,h_DeMarker,class="num">4); copy_result=copy_result && FillArrayFromBuffer1(buf_FrAMA,h_FrAMA,class="num">4); copy_result=copy_result && FillArraysFromBuffers2(buf_MACD_m,buf_MACD_s,h_MACD,class="num">4); copy_result=copy_result && FillArrayFromBuffer1(buf_RSI,h_RSI,class="num">4); copy_result=copy_result && FillArraysFromBuffers2(buf_RVI_m,buf_RVI_s,h_RVI,class="num">4); copy_result=copy_result && FillArraysFromBuffers2(buf_Stoch_m,buf_Stoch_s,h_Stoch,class="num">4); copy_result=copy_result && FillArrayFromBuffer1(buf_WPR,h_WPR,class="num">4); class=class="str">"cmt">//--- 检查复制数据的正确性 if(!copy_result==true) { Print("复制数据错误"); class="kw">return; }
把多指标状态一次性落盘
这段代码片段是 EA 在每根 K 线收尾时,把多组指标的差值快照写进 CSV 的关键动作。它一口气塞进了 MACD(快/慢线及其差值的二阶变化)、RSI 及其一阶差分、RVI(快/慢线及二阶交叉差)、Stochastic(快/慢线及二阶交叉差)、WPR 及其差分,总共 21 个浮点字段,索引统一用 [2] 和 [3],也就是倒数第二根与倒数第三根 bar 的对比。 这种写法把‘指标动能’而不是‘指标绝对值’喂给后续分析,能直接看出每个振荡器是在加速还是减速。外汇和贵金属市场的高波动下,这类二阶差分对拐点识别有帮助,但信号迟到 1~2 根 bar 是常态,概率上只是提高过滤噪声的效率。 如果 FileWrite 返回 0,说明磁盘写入失败,代码会打印错误号并调用 ExpertRemove() 自爆退出,避免脏数据持续累积。开 MT5 把这段接在你自己的 buf_ 数组后面,跑一个 EURUSD 的 M15 回测,看 CSV 里 [2]-[3] 列的符号翻转是否比价格拐点平均早一根出现。
buf_MACD_m[class="num">2],buf_MACD_m[class="num">2]-buf_MACD_m[class="num">3], buf_MACD_s[class="num">2],buf_MACD_s[class="num">2]-buf_MACD_s[class="num">3], buf_MACD_m[class="num">2]-buf_MACD_s[class="num">2],buf_MACD_m[class="num">2]-buf_MACD_s[class="num">2]-buf_MACD_m[class="num">3]+buf_MACD_s[class="num">3], buf_RSI[class="num">2],buf_RSI[class="num">2]-buf_RSI[class="num">3], buf_RVI_m[class="num">2],buf_RVI_m[class="num">2]-buf_RVI_m[class="num">3], buf_RVI_s[class="num">2],buf_RVI_s[class="num">2]-buf_RVI_s[class="num">3], buf_RVI_m[class="num">2]-buf_RVI_s[class="num">2],buf_RVI_m[class="num">2]-buf_RVI_s[class="num">2]-buf_RVI_m[class="num">3]+buf_RVI_s[class="num">3], buf_Stoch_m[class="num">2],buf_Stoch_m[class="num">2]-buf_Stoch_m[class="num">3], buf_Stoch_s[class="num">2],buf_Stoch_s[class="num">2]-buf_Stoch_s[class="num">3], buf_Stoch_m[class="num">2]-buf_Stoch_s[class="num">2],buf_Stoch_m[class="num">2]-buf_Stoch_s[class="num">2]-buf_Stoch_m[class="num">3]+buf_Stoch_s[class="num">3], buf_WPR[class="num">2],buf_WPR[class="num">2]-buf_WPR[class="num">3]); if(Result==class="num">0) { Print("FileWrite 操作错误 ",GetLastError()); ExpertRemove(); } }