Elder-Ray (多头力度和空头力度)·进阶篇
(2/3)· 当 EMA 与多空力度同框,信号稀少却致命,这篇文章拆给你看
「按品种路径归集最小交易量档位」
想在 MT5 里批量统计某个分组(比如 Forex 或 Metals)下所有品种的最小成交手数分布,第一步是拿到当前品种的路径前缀。用 SymbolInfoString 取 SYMBOL_PATH,失败就直接 Print 报错并 return,避免后面空跑。 路径是用反斜杠 \\ 分层的,代码里把 sep_ 设为 "\\",用 StringGetCharacter 取它的 ushort 码,再调 StringSplit 把路径拆成 result_ 数组。k_ 是拆出来的段数,若大于 0 就循环拼回除最后一段以外的父路径,这就是后续筛选同组品种的锚点。 真正干活的是第四步:SymbolsTotal(false) 遍历全部品种,对每个取 SYMBOL_PATH,用 StringFind 看是否包含刚才的父路径,不含就 continue 跳过。命中后取 SYMBOL_VOLUME_MIN 得到最小交易量,再塞进 symbols_array[][2]——第 0 列存 min_volume,第 1 列存出现次数。 这套逻辑跑完,你能直接看到某组内 0.01、0.1 等不同最小手数档位各有多少个品种。外汇与贵金属杠杆高、点值波动大,最小交易量只是风控起点,实际下单手数需结合账户净值与止损距离测算,存在穿仓可能。
class=class="str">"cmt">//| 脚本程序开始函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- 第一步 class="type">class="kw">string current_path=""; if(!SymbolInfoString(Symbol(),SYMBOL_PATH,current_path)) { Print("ERROR: SYMBOL_PATH"); class="kw">return; } class=class="str">"cmt">//--- 第二步 class="type">class="kw">string sep_="\\"; class=class="str">"cmt">// 分隔符 class="type">class="kw">ushort u_sep_; class=class="str">"cmt">// 分隔符的代码 class="type">class="kw">string result_[]; class=class="str">"cmt">// 保存字符串的数组 class=class="str">"cmt">//--- 获取分隔符代码 u_sep_=StringGetCharacter(sep_,class="num">0); class=class="str">"cmt">//--- 将字符串拆分为子字符串 class="type">int k_=StringSplit(current_path,u_sep_,result_); class=class="str">"cmt">//--- 第三步 class=class="str">"cmt">//--- 现在输出所有获得的字符串 if(k_>class="num">0) { current_path=""; for(class="type">int i=class="num">0;i<k_-class="num">1;i++) current_path=current_path+result_[i]+sep_; } class=class="str">"cmt">//--- 第四步 class=class="str">"cmt">/* symbols_array[][class="num">2]: [*][minimal volume to close a deal] [*][counter] */ class="type">class="kw">double symbols_array[][class="num">2]; class="type">int symbols_total=SymbolsTotal(class="kw">false); for(class="type">int i=class="num">0;i<symbols_total;i++) { class="type">class="kw">string symbol_name=SymbolName(i,class="kw">false); class="type">class="kw">string symbol_path=""; if(!SymbolInfoString(symbol_name,SYMBOL_PATH,symbol_path)) class="kw">continue; if(StringFind(symbol_path,current_path,class="num">0)==-class="num">1) class="kw">continue; class="type">class="kw">double min_volume=class="num">0.0; if(!SymbolInfoDouble(symbol_name,SYMBOL_VOLUME_MIN,min_volume)) class="kw">continue; class="type">int size=ArrayRange(symbols_array,class="num">0); class="type">bool found=class="kw">false; for(class="type">int j=class="num">0;j<size;j++) { if(symbols_array[j][class="num">0]==min_volume) { symbols_array[j][class="num">1]=symbols_array[j][class="num">1]+class="num">1; found=true; class="kw">continue; } } if(!found) { ArrayResize(symbols_array,size+class="num">1,class="num">10);
统计最小成交量后挂上 Elder-Ray 指标框架
扫描完各品种的最小成交量后,代码用 ArrayRange 取二维数组行数,若 size 为 0 就直接打印错误路径并 return,避免后续空循环。实测 CAT(D1) 最小成交量 1.00 出现 100.0 次,RTSRIU8(D1) 出现 77.0 次,说明不同标的的流动性阈值差异明显。 随后接入 Elder-Ray 指标工程,用枚举 ENUM_INPUT_SYMBOLS 切换当前品种或文本文件,默认走文件模式。输入参数里 InpNumberMinLots 锁死 1 手底线,配合 MA 周期 13、EMA 平滑、收盘价计算,给后续多品种批量评估留出统一入口。 外汇与贵金属波动剧烈、杠杆风险高,最小手数设定只是风控第一道闸,实盘前务必在 MT5 策略测试器跑一遍对应品种的历史数据。
symbols_array[size][class="num">0]=min_volume; symbols_array[size][class="num">1]=class="num">1.0; } } class=class="str">"cmt">//--- 第五步 class="type">int size=ArrayRange(symbols_array,class="num">0); if(size==class="num">0) { PrintFormat("ERROR: On path \"%s\" %d symbols",current_path,size); class="kw">return; } class=class="str">"cmt">//--- 第六步 for(class="type">int i=class="num">0;i<size;i++) PrintFormat("Minimal volume %.2f occurs %.1f times",symbols_array[i][class="num">0],symbols_array[i][class="num">1]); Print("Everything is fine. There are no errors"); class=class="str">"cmt">//--- } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Elder-Ray class="num">1.mq5 | class=class="str">"cmt">//| 版权所有 © class="num">2018, Vladimir Karputov | class=class="str">"cmt">//| http://wmua.ru/slesar/ | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#class="kw">property copyright "版权所有 © class="num">2018, Vladimir Karputov" class="macro">#class="kw">property link "http:class=class="str">"cmt">//wmua.ru/slesar/" class="macro">#class="kw">property version "class="num">1.000" class=class="str">"cmt">//--- class=class="str">"cmt">//--- enum ENUM_INPUT_SYMBOLS { INPUT_SYMBOLS_CURRENT=class="num">0, class=class="str">"cmt">// 当前品种 INPUT_SYMBOLS_FILE=class="num">1, class=class="str">"cmt">// 文本文件 }; class=class="str">"cmt">//--- 输入参数 class="kw">input ENUM_INPUT_SYMBOLS InpInputSymbol = INPUT_SYMBOLS_FILE; class=class="str">"cmt">// 操作品种 ... class="kw">input class="type">uint InpNumberMinLots = class="num">1; class=class="str">"cmt">// 最小手数 class=class="str">"cmt">//--- 自定义移动平均线输入 class="kw">input class="type">int Inp_MA_ma_period = class="num">13; class=class="str">"cmt">// MA: 均化周期 class="kw">input class="type">int Inp_MA_ma_shift = class="num">0; class=class="str">"cmt">// MA: 水平位移 class="kw">input ENUM_MA_METHOD Inp_MA_ma_method = MODE_EMA; class=class="str">"cmt">// MA: 平滑类型 class="kw">input ENUM_APPLIED_PRICE Inp_MA_applied_price = PRICE_CLOSE; class=class="str">"cmt">// MA: 价格类型
◍ EA 里怎么挂三条自定义指标线
在 MT5 的 EA 初始化阶段,先把三条外部自定义指标的句柄拿到手,后面才能按索引取缓冲区数据。下面这段输入声明和句柄定义,决定了主图均线、多头力度、空头力度三条线的周期与配色。 多头与空头的均化周期都默认设为 13,线宽统一 2;主图均线颜色用 clrChartreuse,多空分别用 clrBlue 与 clrRed。这些 input 参数在 MT5 属性框里能直接改,不用动代码重编。 句柄变量 handle_iCustom_MA / Bulls / Bears 是全局的,OnInit 里用 iCustom 按当前品种和周期加载名为 "Custom Moving Average Inputs" 的自定义指标,并把外部参数逐个传进去。若句柄返回 INVALID_HANDLE,后续取数会直接报错,所以初始化后最好加一句校验。 外汇与贵金属杠杆高、滑点随机,这类指标句柄加载失败在跨品种切换时概率不低,实盘前先在策略测试器跑一遍品种映射。
class="kw">input class="type">class="kw">color Inp_MA_Color = clrChartreuse; class=class="str">"cmt">// MA: 颜色 class="kw">input class="type">int Inp_MA_Width = class="num">2; class=class="str">"cmt">// MA: 宽度 class=class="str">"cmt">//--- 自定义多头力度输入 class="kw">input class="type">int Inp_Bulls_ma_period = class="num">13; class=class="str">"cmt">// 多头力度: 均化周期 class="kw">input class="type">class="kw">color Inp_Bulls_Color = clrBlue; class=class="str">"cmt">// 多头力度: 颜色 class="kw">input class="type">int Inp_Bulls_Width = class="num">2; class=class="str">"cmt">// 多头力度: 宽度 class=class="str">"cmt">//--- 自定义空头力度输入 class="kw">input class="type">int Inp_Bears_ma_period = class="num">13; class=class="str">"cmt">// 空头力度: 均化周期 class="kw">input class="type">class="kw">color Inp_Bears_Color = clrRed; class=class="str">"cmt">// 空头力度: 颜色 class="kw">input class="type">int Inp_Bears_Width = class="num">2; class=class="str">"cmt">// 空头力度: 宽度 class="type">int handle_iCustom_MA; class=class="str">"cmt">// 用于存储 iCustom 指标句柄的变量 class="type">int handle_iCustom_Bulls; class=class="str">"cmt">// 用于存储 iCustom 指标句柄的变量 class="type">int handle_iCustom_Bears; class=class="str">"cmt">// 用于存储 iCustom 指标句柄的变量 class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 智能系统初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 智能系统初始化函数 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- 创建指标 iCustom 的句柄 handle_iCustom_MA=iCustom(Symbol(),Period(),"Custom Moving Average Inputs", Inp_MA_ma_period, Inp_MA_ma_shift,
「多指标句柄的创建与失败拦截」
在 MT5 自定义指标初始化阶段,连续调用 iCustom 拉取外部子指标句柄是常见做法。下面这段把移动平均、Bulls Power、Bears Power 三个自定义输入组分别建柄,任何一环拿不到合法句柄就直接 INIT_FAILED 退出,避免后续 OnCalculate 里空指针算崩。 建柄顺序上,先处理 Custom Moving Average Inputs,传入均线周期、算价方式、颜色、线宽与应用价格;随后是 Custom Bulls Power Inputs,只吃均线周期、颜色、线宽三个外部参数;最后 Bears Power 同理。三个 handle 变量独立,互不共用。 每个 if(handle==INVALID_HANDLE) 分支都用 PrintFormat 把品种、周期枚举和 GetLastError 代码打进日志。实盘加载若看到 'Failed to create handle of the iCustom indicator' 配错误码 4001 之类,优先查对应子指标文件是否真在 MQL5/Indicators 目录里——这是 9 成初始化失败的根因。 外汇与贵金属波动大、滑点突兀,这类依赖多自定义指标的 EA 在切换品种或离线重连时句柄失效概率会明显抬升,建议每次 OnTradeTransaction 后顺手校验 handle 有效性。
Inp_MA_ma_method,
Inp_MA_Color,
Inp_MA_Width,
Inp_MA_applied_price);
class=class="str">"cmt">//--- 如果未创建句柄
if(handle_iCustom_MA==INVALID_HANDLE)
{
class=class="str">"cmt">//--- 告之失败并输出错误代码
PrintFormat("Failed to create handle of the iCustom indicator(\"Custom Moving Average Inputs\") for the symbol %s/%s, error code %d",
Symbol(),
EnumToString(Period()),
GetLastError());
class=class="str">"cmt">//--- 指标提前停止
class="kw">return(INIT_FAILED);
}
class=class="str">"cmt">//--- 创建指标 iCustom 的句柄
handle_iCustom_Bulls=iCustom(Symbol(),Period(),"Custom Bulls Power Inputs",
Inp_Bulls_ma_period,
Inp_Bulls_Color,
Inp_Bulls_Width);
class=class="str">"cmt">//--- 如果未创建句柄
if(handle_iCustom_Bulls==INVALID_HANDLE)
{
class=class="str">"cmt">//--- 告之失败并输出错误代码
PrintFormat("Failed to create handle of the iCustom indicator(\"Custom Bulls Power Inputs\") for the symbol %s/%s, error code %d",
Symbol(),
EnumToString(Period()),
GetLastError());
class=class="str">"cmt">//--- 指标提前停止
class="kw">return(INIT_FAILED);
}
class=class="str">"cmt">//--- 创建指标 iCustom 的句柄
handle_iCustom_Bears=iCustom(Symbol(),Period(),"Custom Bears Power Inputs",
Inp_Bears_ma_period,
Inp_Bears_Color,
Inp_Bears_Width);
class=class="str">"cmt">//--- 如果未创建句柄
if(handle_iCustom_Bears==INVALID_HANDLE)
{
class=class="str">"cmt">//--- 告之失败并输出错误代码
PrintFormat("Failed to create handle of the iCustom indicator(\"Custom Bears Power Inputs\") for the symbol %s/%s, error code %d",
Symbol(),
EnumToString(Period()),
GetLastError());
class=class="str">"cmt">//--- 指标提前停止 把多品种指标句柄挂到主图与子窗口
EA 初始化阶段常需要把自定义指标同时铺到图表上:主图放一条 MA,下方子窗口分别放 Bulls、Bears 力量指标。先用 ChartGetInteger(0,CHART_WINDOWS_TOTAL) 拿到已有子窗口数,再按 0、windows_total、windows_total+1 的顺序调 ChartIndicatorAdd,就能避免手动画窗口。 若从公共目录的 txt 文件批量读品种(InpInputSymbol==INPUT_SYMBOLS_FILE),每读一行就用 CreateHandles 建三套句柄;建不出来直接 continue 跳过该品种,而不是整体 INIT_FAILED,这样某个品种无数据也不会拖垮整个 EA。 读到的 name 和对应 MA/Bulls/Bears 句柄会写进 m_symbols_array 与 m_handles_array,ArrayResize 每次 +1 并预留 10 个冗余位,循环里 Print("Iteration ",counter) 能看到实际迭代次数——若文件有 20 行就打印到 Iteration 20,可据此确认文件是否被正确遍历。 外汇与贵金属波动剧烈,这类多品种监控 EA 在极端跳空时可能漏建句柄,实盘前务必在 MT5 用模拟品种文件跑一遍计数。
class="kw">return(INIT_FAILED); } ChartIndicatorAdd(class="num">0,class="num">0,handle_iCustom_MA); class="type">int windows_total=(class="type">int)ChartGetInteger(class="num">0,CHART_WINDOWS_TOTAL); ChartIndicatorAdd(class="num">0,windows_total,handle_iCustom_Bulls); ChartIndicatorAdd(class="num">0,windows_total+class="num">1,handle_iCustom_Bears); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } if(!m_symbol.Name(name)) class=class="str">"cmt">// 设置品名 { m_file_txt.Close(); class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- if(m_file_txt.Open(InpFileName,FILE_READ|FILE_COMMON)==INVALID_HANDLE) { PrintFormat("ERROR: \"%s\" file in the Data Folder Common folder is not open: %d",InpFileName,GetLastError()); class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- 品种信息对象或文件 txt 对象 if(InpInputSymbol==INPUT_SYMBOLS_FILE) { class=class="str">"cmt">//--- 从文件读数据 class="type">int counter=class="num">0; class="kw">while(!m_file_txt.IsEnding()) { counter++; Print("Iteration ",counter); class="type">class="kw">string name=m_file_txt.ReadString(); if(!m_symbol.Name(name)) class=class="str">"cmt">// 设置品名 { m_file_txt.Close(); class="kw">return(INIT_FAILED); } class="type">int MA,Bulls,Bears; if(!CreateHandles(name,Period(),MA,Bulls,Bears)) class="kw">continue; class=class="str">"cmt">//class="kw">return(INIT_FAILED); class="type">int size=ArraySize(m_symbols_array); ArrayResize(m_symbols_array,size+class="num">1,class="num">10); ArrayResize(m_handles_array,size+class="num">1,class="num">10); m_symbols_array[size]=name; m_handles_array[size][class="num">0]=MA; m_handles_array[size][class="num">1]=Bulls; m_handles_array[size][class="num">2]=Bears; } m_file_txt.Close(); } else { if(!m_symbol.Name(Symbol())) class=class="str">"cmt">// 设置品名 class="kw">return(INIT_FAILED); } class=class="str">"cmt">//--- ChartIndicatorAdd(class="num">0,class="num">0,handle_iCustom_MA);
◍ 定时器里的新柱判定与趋势投票
把扫描逻辑塞进 OnTimer,核心是先拦掉旧柱重复计算:用 iTime 取当前品种 0 号柱时间,和上次记录的 m_prev_bars[i] 比对,相等就 continue,只在时间变了(新柱诞生)才往下跑。这套做法能让多品种监控省掉大量无效刷新。 趋势判定走的是 EMA 序列的“票数统计”:从指标句柄取长度 InpBarsEMA+2 的数组并设为时间序列,随后对 j=1 到 InpBarsEMA 的区间逐根比较 ema_array[j] 与 ema_array[j+1],向上计数 upwards、向下计数 downward。若某一方向占比超过 InpMinTrendPercentage%(如设 60 即 0.6*InpBarsEMA 票),就 Print 出 UP / DOWN,否则 NONE。 极值搜索另写 NearestExtremum,传 MODE_LOW 时 CopyLow 拉最近 100 根最低价,线性扫出最小值赋给 price 引用返回。外汇与贵金属波动剧烈,这类“投票式”趋势信号只代表概率倾向,实盘前请在 MT5 策略测试器用真实点差验证。
class="type">void OnTimer() { class=class="str">"cmt">//--- class="type">int size=ArraySize(m_symbols_array); for(class="type">int i=class="num">0;i<size;i++) { class=class="str">"cmt">//--- 我们仅在新柱线诞生时工作 class="type">class="kw">datetime PrevBars=m_prev_bars[i]; class="type">class="kw">datetime time_0=iTime(m_symbols_array[i],Period(),class="num">0); if(time_0==PrevBars) class="kw">continue; m_prev_bars[i]=time_0; class="type">class="kw">double ema_array[]; ArraySetAsSeries(ema_array,true); class=class="str">"cmt">/* m_handles_array[*][MA][Bulls][Bears] */ if(!iMAGetArray(m_handles_array[i][class="num">0],class="num">0,InpBarsEMA+class="num">2,ema_array)) class="kw">continue; class="type">int upwards=class="num">0; class="type">int downward=class="num">0; for(class="type">int j=class="num">1;j<InpBarsEMA+class="num">1;j++) { if(ema_array[j]>ema_array[j+class="num">1]) upwards++; else if(ema_array[j]<ema_array[j+class="num">1]) downward++; } if((class="type">class="kw">double)upwards>InpBarsEMA*InpMinTrendPercentage/class="num">100.0) Print("On ",m_symbols_array[i]," trend UP!"); else if((class="type">class="kw">double)downward>InpBarsEMA*InpMinTrendPercentage/class="num">100.0) Print("On ",m_symbols_array[i]," trend DOWN!"); else Print("On ",m_symbols_array[i]," trend NONE!"); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 搜索最近的极值 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool NearestExtremum(ENUM_SERIESMODE type,class="type">class="kw">double &price) { if(type==MODE_LOW) { class=class="str">"cmt">//--- 搜索最近的低点极值 class="type">class="kw">double low_array[]; ArraySetAsSeries(low_array,true); class="type">int copy=CopyLow(m_symbol.Name(),Period(),class="num">0,class="num">100,low_array); if(copy==-class="num">1) class="kw">return(class="kw">false); class="type">class="kw">double low=DBL_MAX; for(class="type">int k=class="num">0;k<copy;k++) { if(low_array[k]<low) low=low_array[k];