神经网络在交易中的实际应用 Python (第一部分)·进阶篇
「把多周期指标一次性落盘」
这段脚本的入口 OnStart 先把当前图表 H1 周期的总 BAR 数减 1 存进 k,用作后续循环上界;外汇与贵金属的 H1 数据量随品种不同差异很大,EUUSD 在 2020–2023 三年约 1.7 万根,XAUUSD 同期约 1.9 万根,k 值直接决定文件写入耗时。 它用 iCustom 调用名为 DibMin1-1 的自定义指标取每日最低价序列,句柄 DibMin1_1Handle 若返回 INVALID_HANDLE 则整段跳过,这是 MT5 里最常见的静默失败点,开 MT5 加载前务必确认指标文件已在 MQL5/Indicators 目录。 前面截出的片段显示,内层循环把 inB[16] 到 inB[21] 连续写出后关闭 HandleInpuNet1Max,并以 Alert("Files written") 提示完成;若你改了 inB 数组长度,要同步改写入下标,否则文件会出现错位空列。
class="type">int k=iBars(NULL,PERIOD_H1)-class="num">1; class=class="str">"cmt">//------ Daily Low DibMin1_1Handle=iCustom(NULL,PERIOD_H1,"DibMin1-class="num">1",History);
◍ H1 多指标缓冲区的批量抓取
在 MT5 的 EA 初始化阶段,把自定义指标和内置振荡器一次性挂到 H1 周期上,是后续做特征矩阵的前提。下面这段代码在当期图表用 iCustom 取了两个名叫 DibMin1-1、DibMax1-1 的自定义序列,参数 History 控制回溯长度,k 则是要拷贝的柱数。 CopyBuffer 之后必须紧跟 ArraySetAsSeries(...,true),否则数组下标 0 对应的是最旧一根柱而不是当前柱,回测时信号会整体偏移一根。Stochastic 这里同时取了主线(0)和信号线(1)两个缓冲区,CCI、Momentum、RSI、MACD 则分别用 PRICE_OPEN 与 PRICE_LOW 各开一路句柄——同一周期双价格源,能看出开盘与最低价驱动的差异。 WPR 只用默认 14 周期、单一缓冲区,代码里所有句柄都基于 PERIOD_H1,意味着这套特征全在小时线生成。实盘或回测前,建议先在 MT5 终端把 k 设为 500,打印各数组第 0 位数值,确认自定义指标已编译且返回非 EMPTY_VALUE,外汇与贵金属品种波动大,指标错位可能引发高频误信号。
CopyBuffer(DibMin1_1Handle,class="num">0,class="num">0,k,DibMin1_1); ArraySetAsSeries(DibMin1_1,true); DibMax1_1Handle=iCustom(NULL,PERIOD_H1,"DibMax1-class="num">1",History); CopyBuffer(DibMax1_1Handle,class="num">0,class="num">0,k,DibMax1_1); ArraySetAsSeries(DibMax1_1,true); class="type">int Stochastic_handle=iStochastic(NULL,PERIOD_H1,class="num">5,class="num">3,class="num">3,MODE_SMA,STO_LOWHIGH); CopyBuffer(Stochastic_handle,class="num">0,class="num">0,k,Stochastic0); CopyBuffer(Stochastic_handle,class="num">1,class="num">0,k,Stochastic1); ArraySetAsSeries(Stochastic0,true); ArraySetAsSeries(Stochastic1,true); class="type">int CCI_Open_handle=iCCI(NULL,PERIOD_H1,class="num">14,PRICE_OPEN); CopyBuffer(CCI_Open_handle,class="num">0,class="num">0,k,CCI_Open); ArraySetAsSeries(CCI_Open,true); class="type">int CCI_Low_handle=iCCI(NULL,PERIOD_H1,class="num">14,PRICE_LOW); CopyBuffer(CCI_Low_handle,class="num">0,class="num">0,k,CCI_Low); ArraySetAsSeries(CCI_Low,true); class="type">int Momentum_Open_handle=iMomentum(NULL,PERIOD_H1,class="num">14,PRICE_OPEN); CopyBuffer(Momentum_Open_handle,class="num">0,class="num">0,k,Momentum_Open); ArraySetAsSeries(Momentum_Open,true); class="type">int Momentum_Low_handle=iMomentum(NULL,PERIOD_H1,class="num">14,PRICE_LOW); CopyBuffer(Momentum_Low_handle,class="num">0,class="num">0,k,Momentum_Low); ArraySetAsSeries(Momentum_Low,true); class="type">int RSI_Open_handle=iRSI(NULL,PERIOD_H1,class="num">14,PRICE_OPEN); CopyBuffer(RSI_Open_handle,class="num">0,class="num">0,k,RSI_Open); ArraySetAsSeries(RSI_Open,true); class="type">int RSI_Low_handle=iRSI(NULL,PERIOD_H1,class="num">14,PRICE_LOW); CopyBuffer(RSI_Low_handle,class="num">0,class="num">0,k,RSI_Low); ArraySetAsSeries(RSI_Low,true); class="type">int WPR_handle=iWPR(NULL,PERIOD_H1,class="num">14); CopyBuffer(WPR_handle,class="num">0,class="num">0,k,WPR); ArraySetAsSeries(WPR,true); class="type">int MACD_Open_handle=iMACD(NULL,PERIOD_H1,class="num">12,class="num">26,class="num">9,PRICE_OPEN); CopyBuffer(MACD_Open_handle,class="num">0,class="num">0,k,MACD_Open); ArraySetAsSeries(MACD_Open,true); class="type">int MACD_Low_handle=iMACD(NULL,PERIOD_H1,class="num">12,class="num">26,class="num">9,PRICE_LOW); CopyBuffer(MACD_Low_handle,class="num">0,class="num">0,k,MACD_Low); ArraySetAsSeries(MACD_Low,true);
多指标句柄与训练样本的批量落盘
在 H1 周期上同时拉取 OsMA、TriX、BearsPower、ADX 负向 DI 与 StdDev 这五类指标,并分别按 PRICE_OPEN 与 PRICE_LOW 取两套序列,是后续喂给神经网络的原始特征。OsMA 用 12/26/9 经典参数,TriX 与 ADX 取 14 周期,BearsPower 用 13,StdDev 用 20 周期 SMA——这些数字直接决定特征分布,改一个就可能让网络输入偏移。 代码里每个指标都走 i 系列函数拿句柄,再用 CopyBuffer 把 k 根柱的数据拷进数组,最后 ArraySetAsSeries(...,true) 把索引翻成时间序(0 是最新柱)。注意 ADX 取的是 buffer 2,也就是 -DI 线,不是主 ADX 值。 落盘部分用 FileOpen 开两个 CSV:Symbol()+"InputNet2OutNet1Min.csv" 与 "OutNet2Min.csv",标志位带 FILE_COMMON 说明写在终端公共目录,分号做分隔符。FileSeek 跳到 SEEK_END 意味着追加写,不会覆盖历史样本。 写之前用 if(Handle>0) 判句柄,里面 Alert 提示并开始按 H1 柱从老到新循环——这一步就是批量导出特征与标签,供离线训练。外汇与贵金属波动剧烈,这类样本集仅反映历史形态,实盘信号概率会随 Regime 变化而漂移。
class="type">int OsMA_Open_handle=iOsMA(NULL,PERIOD_H1,class="num">12,class="num">26,class="num">9,PRICE_OPEN); CopyBuffer(OsMA_Open_handle,class="num">0,class="num">0,k,OsMA_Open); ArraySetAsSeries(OsMA_Open,true); class="type">int OsMA_Low_handle=iOsMA(NULL,PERIOD_H1,class="num">12,class="num">26,class="num">9,PRICE_LOW); CopyBuffer(OsMA_Low_handle,class="num">0,class="num">0,k,OsMA_Low); ArraySetAsSeries(OsMA_Low,true); class="type">int TriX_Open_handle=iTriX(NULL,PERIOD_H1,class="num">14,PRICE_OPEN); CopyBuffer(TriX_Open_handle,class="num">0,class="num">0,k,TriX_Open); ArraySetAsSeries(TriX_Open,true); class="type">int TriX_Low_handle=iTriX(NULL,PERIOD_H1,class="num">14,PRICE_LOW); CopyBuffer(TriX_Low_handle,class="num">0,class="num">0,k,TriX_Low); ArraySetAsSeries(TriX_Low,true); class="type">int BearsPower_handle=iBearsPower(NULL,PERIOD_H1,class="num">13); CopyBuffer(BearsPower_handle,class="num">0,class="num">0,k,BearsPower); ArraySetAsSeries(BearsPower,true); class="type">int ADX_MINUSDI_handle=iADX(NULL,PERIOD_H1,class="num">14); CopyBuffer(ADX_MINUSDI_handle,class="num">2,class="num">0,k,ADX_MINUSDI); ArraySetAsSeries(ADX_MINUSDI,true); class="type">int StdDev_Open_handle=iStdDev(NULL,PERIOD_H1,class="num">20,class="num">0,MODE_SMA,PRICE_OPEN); CopyBuffer(StdDev_Open_handle,class="num">0,class="num">0,k,StdDev_Open); ArraySetAsSeries(StdDev_Open,true); class="type">int StdDev_Low_handle=iStdDev(NULL,PERIOD_H1,class="num">20,class="num">0,MODE_SMA,PRICE_LOW); CopyBuffer(StdDev_Low_handle,class="num">0,class="num">0,k,StdDev_Low); ArraySetAsSeries(StdDev_Low,true); class=class="str">"cmt">//--------------------------------------------------------------------------------------------------------------------------- HandleInputNet2OutNet1Min=FileOpen(Symbol()+"InputNet2OutNet1Min.csv",FILE_CSV|FILE_WRITE|FILE_SHARE_READ|FILE_ANSI|FILE_COMMON,";"); HandleOutNet2Min=FileOpen(Symbol()+"OutNet2Min.csv",FILE_CSV|FILE_WRITE|FILE_SHARE_READ|FILE_ANSI|FILE_COMMON,";"); FileSeek(HandleInputNet2OutNet1Min,class="num">0,SEEK_END); FileSeek(HandleOutNet2Min,class="num">0,SEEK_END); if(HandleInputNet2OutNet1Min>class="num">0) { Alert("Writing the files InputNet2OutNet1Min and OutNet2Min"); for(class="type">int i=iBars(NULL,PERIOD_H1)-class="num">1; i>=class="num">0; i--) {
「特征窗口滚动与样本落盘的细节」
这段逻辑干的事很直接:先在 H1 周期取第 i 根 K 线的时间字符串,用 Date1 承接;只有当 DateOut 与 Date 的区间把这根 K 线时间罩住时,才进入样本构造分支。也就是说,回测或实盘抓取样本时,时间过滤是第一道闸门,漏掉这一步会把范围外的棒线也写进训练集。 进入分支后有个前置条件:DibMin1_1 与 DibMax1_1 的组合必须命中两种形态之一——要么是 i 处为 -1、i+1 处翻 1 且 i 处 DibMax 为 1,要么两者同时等于 1。这相当于用摆动指标的转向标记来确认局部拐点,不是每根 K 都参与,样本稀疏但噪声偏低。 样本数组 inB 长度用到 59,前半段(索引 36–47)装的是 Low 类指标:随机指标、CCI_Low、Momentum_Low、RSI_Low 各占 2 项,WPR 取 i+1 错位值,MACD/OsMA/TriX 分别乘 10000、100000、100000 做量纲放大,BearsPower 乘 1000,ADX_MINUSDI 与 StdDev_Low 也做了万倍缩放。后半段(50–59)是 Open 类指标的同源快照。 写文件前先把 inB[0..35] 整体左移 12 位(inB[m]=inB[m+12]),等于把上一段 12 维窗口滚动覆盖,再在尾部续写新特征。最终 FileWrite 把 0–13 号共 14 个字段先落盘到 HandleInputNet2OutNet1Min。开 MT5 把这段塞进 EA 的循环里,改一下 HandleInputNet2OutNet1Min 指向的 csv 路径,就能直接看样本长什么样。外汇与贵金属波动剧烈,这类特征工程只是预处理,信号胜率仍受宏观流动性冲击,务必用小资金验证。
Date1=TimeToString(iTime(NULL,PERIOD_H1,i)); if(DateOut>=Date1 && Date<=Date1) { if(((DibMin1_1[i]==-class="num">1 && DibMin1_1[i+class="num">1]==class="num">1 && DibMax1_1[i]==class="num">1)) || (DibMin1_1[i]==class="num">1 && DibMax1_1[i]==class="num">1)) { for(class="type">int m=class="num">0; m<=class="num">35; m++) { inB[m]=inB[m+class="num">12]; } inB[class="num">36]=Stochastic0[i]; inB[class="num">37]=Stochastic1[i]; inB[class="num">38]=CCI_Low[i]; inB[class="num">39]=Momentum_Low[i]; inB[class="num">40]=RSI_Low[i];; inB[class="num">41]=WPR[i+class="num">1]; inB[class="num">42]=MACD_Low[i]*class="num">10000; inB[class="num">43]=OsMA_Low[i]*class="num">100000; inB[class="num">44]=TriX_Low[i]*class="num">100000;; inB[class="num">45]=BearsPower[i+class="num">1]*class="num">1000; inB[class="num">46]=ADX_MINUSDI[i+class="num">1]; inB[class="num">47]=StdDev_Low[i]*class="num">10000; inB[class="num">48]=Stochastic0[i]; inB[class="num">49]=Stochastic1[i]; inB[class="num">50]=CCI_Open[i]; inB[class="num">51]=Momentum_Open[i]; inB[class="num">52]=RSI_Open[i];; inB[class="num">53]=WPR[i]; inB[class="num">54]=MACD_Open[i]*class="num">10000; inB[class="num">55]=OsMA_Open[i]*class="num">100000; inB[class="num">56]=TriX_Open[i]*class="num">100000;; inB[class="num">57]=BearsPower[i]*class="num">1000; inB[class="num">58]=ADX_MINUSDI[i]; inB[class="num">59]=StdDev_Open[i]*class="num">10000; FileWrite(HandleInputNet2OutNet1Min, inB[class="num">0],inB[class="num">1],inB[class="num">2],inB[class="num">3],inB[class="num">4],inB[class="num">5],inB[class="num">6],inB[class="num">7],inB[class="num">8],inB[class="num">9],inB[class="num">10],inB[class="num">11],inB[class="num">12],inB[class="num">13],
◍ 把影线高点喂给多重指标
这段逻辑先是把 inB[14]~inB[59] 共 46 个预处理过的输入量一次性写进 Net2 的最小化输出文件,随后用日线开盘价与 H1 开盘价的差值乘 10000,把跨周期跳空压缩成整数点差落盘。 紧接着代码切到 Daily High 分支:对 H1 周期的高价序列分别挂了 CCI(14)、Momentum(14)、RSI(14)、MACD(12,26,9)、OsMA(12,26,9) 五个句柄。每个都先用 CopyBuffer 拉 k 根数据,再 ArraySetAsSeries 置为时间序列,保证索引 0 是最新一根 H1 棒。 实盘意义在于:用高价而非收盘价算这些震荡/动量指标,倾向更早发现上影线里的抛压。外汇与贵金属波动大、杠杆高,这类信号只作辅助,错了概率不低,开 MT5 把这段直接贴进 EA 初始化段就能验证指标句柄是否冲突。
inB[class="num">14],inB[class="num">15],inB[class="num">16],inB[class="num">17],inB[class="num">18],inB[class="num">19],inB[class="num">20],inB[class="num">21],inB[class="num">22],inB[class="num">23],inB[class="num">24],inB[class="num">25],inB[class="num">26], inB[class="num">27],inB[class="num">28],inB[class="num">29],inB[class="num">30],inB[class="num">31],inB[class="num">32],inB[class="num">33],inB[class="num">34],inB[class="num">35],inB[class="num">36],inB[class="num">37],inB[class="num">38],inB[class="num">39], inB[class="num">40],inB[class="num">41],inB[class="num">42],inB[class="num">43],inB[class="num">44],inB[class="num">45],inB[class="num">46],inB[class="num">47],inB[class="num">48],inB[class="num">49],inB[class="num">50],inB[class="num">51],inB[class="num">52], inB[class="num">53],inB[class="num">54],inB[class="num">55],inB[class="num">56],inB[class="num">57],inB[class="num">58],inB[class="num">59]); FileWrite(HandleOutNet2Min, (iOpen(NULL,PERIOD_D1,iBarShift(NULL,PERIOD_D1,iTime(NULL,PERIOD_H1,i)))-iOpen(NULL,PERIOD_H1,i))*class="num">10000); } } } } class=class="str">"cmt">//------ Daily High class="type">int CCI_High_handle=iCCI(NULL,PERIOD_H1,class="num">14,PRICE_HIGH); CopyBuffer(CCI_High_handle,class="num">0,class="num">0,k,CCI_High); ArraySetAsSeries(CCI_High,true); class="type">int Momentum_High_handle=iMomentum(NULL,PERIOD_H1,class="num">14,PRICE_HIGH); CopyBuffer(Momentum_High_handle,class="num">0,class="num">0,k,Momentum_High); ArraySetAsSeries(Momentum_High,true); class="type">int RSI_High_handle=iRSI(NULL,PERIOD_H1,class="num">14,PRICE_HIGH); CopyBuffer(RSI_High_handle,class="num">0,class="num">0,k,RSI_High); ArraySetAsSeries(RSI_High,true); class="type">int MACD_High_handle=iMACD(NULL,PERIOD_H1,class="num">12,class="num">26,class="num">9,PRICE_HIGH); CopyBuffer(MACD_High_handle,class="num">0,class="num">0,k,MACD_High); ArraySetAsSeries(MACD_High,true); class="type">int OsMA_High_handle=iOsMA(NULL,PERIOD_H1,class="num">12,class="num">26,class="num">9,PRICE_HIGH); CopyBuffer(OsMA_High_handle,class="num">0,class="num">0,k,OsMA_High); ArraySetAsSeries(OsMA_High,true);
把多指标快照灌进神经网络的落地写法
在 H1 周期上同时拉取 TriX(14, PRICE_HIGH)、BullsPower(13)、ADX+DI(14) 与 StdDev(20, SMA, PRICE_HIGH) 四条序列,是给二级网络喂原始特征前的固定动作。k 根柱的缓冲一次性 CopyBuffer 后必须 ArraySetAsSeries 置为时间倒序,否则后面用 i 下标对齐拐点数组时会整体错位。
| 落盘环节用 FileOpen 以 FILE_CSV | FILE_WRITE | FILE_COMMON 打开两个共享文件:Symbol()+InputNet2OutNet1Max.csv 与 OutNet2Max.csv,分隔符取分号。FileSeek 到 SEEK_END 保证追加而非覆盖,实盘跑久了不会把历史样本清掉。 |
|---|
真正写样本的触发条件是 DibMax1_1 由 -1 翻 1 且 DibMin1_1 为 1,或两者同值为 1——也就是局部顶分型确认的那根 H1 柱。此时把 inS[0..35] 整体左移 12 位,再把 Stochastic0/1、CCI_High、Momentum_High、RSI_High 填入 36~40 槽位,共 41 维特征。外汇与贵金属波动剧烈,这种样本采集仅描述历史形态,不预示后续方向,验证时请先在 MT5 策略测试器跑通再上真仓。
class="type">int TriX_High_handle=iTriX(NULL,PERIOD_H1,class="num">14,PRICE_HIGH); CopyBuffer(TriX_High_handle,class="num">0,class="num">0,k,TriX_High); ArraySetAsSeries(TriX_High,true); class="type">int BullsPower_handle=iBullsPower(NULL,PERIOD_H1,class="num">13); CopyBuffer(BullsPower_handle,class="num">0,class="num">0,k,BullsPower); ArraySetAsSeries(BullsPower,true); class="type">int ADX_PLUSDI_handle=iADX(NULL,PERIOD_H1,class="num">14); CopyBuffer(ADX_PLUSDI_handle,class="num">1,class="num">0,k,ADX_PLUSDI); ArraySetAsSeries(ADX_PLUSDI,true); class="type">int StdDev_High_handle=iStdDev(NULL,PERIOD_H1,class="num">20,class="num">0,MODE_SMA,PRICE_HIGH); CopyBuffer(StdDev_High_handle,class="num">0,class="num">0,k,StdDev_High); ArraySetAsSeries(StdDev_High,true); class=class="str">"cmt">//--------------------------------------------------------------------------------------------------------------------------- HandleInputNet2OutNet1Max=FileOpen(Symbol()+"InputNet2OutNet1Max.csv",FILE_CSV|FILE_WRITE|FILE_SHARE_READ|FILE_ANSI|FILE_COMMON,";"); HandleOutNet2Max=FileOpen(Symbol()+"OutNet2Max.csv",FILE_CSV|FILE_WRITE|FILE_SHARE_READ|FILE_ANSI|FILE_COMMON,";"); FileSeek(HandleInputNet2OutNet1Max,class="num">0,SEEK_END); FileSeek(HandleOutNet2Max,class="num">0,SEEK_END); if(HandleInputNet2OutNet1Max>class="num">0) { Alert("Writing the files InputNet2OutNet1Max and OutNet2Max"); for(class="type">int i=iBars(NULL,PERIOD_H1)-class="num">1; i>=class="num">0; i--) { Date1=TimeToString(iTime(NULL,PERIOD_H1,i)); if(DateOut>=Date1 && Date<=Date1) { if(((DibMax1_1[i]==-class="num">1 && DibMax1_1[i+class="num">1]==class="num">1 && DibMin1_1[i]==class="num">1)) || (DibMin1_1[i]==class="num">1 && DibMax1_1[i]==class="num">1)) { for(class="type">int m=class="num">0; m<=class="num">35; m++) { inS[m]=inS[m+class="num">12]; } inS[class="num">36]=Stochastic0[i]; inS[class="num">37]=Stochastic1[i]; inS[class="num">38]=CCI_High[i]; inS[class="num">39]=Momentum_High[i]; inS[class="num">40]=RSI_High[i];